Data Summarization: Tables

Nicky Wakim

What are summary tables?

  • Summary tables are a way to present data in a structured format
  • The use is a little vague here…
    • It could mean:
      • A table for categorical data (like a frequency table)
      • A presentable table for categorical and numeric data

 

  • We will cover two functions that can help us create summary tables for categorical and numeric data:
    • tabyl() from the janitor package
    • tbl_summary() from the gtsummary package

What does tabyl() do?

  • tabyl() is a simple function that creates a frequency table for a single categorical variable
  • It returns a data frame with three columns:
    • n: the count in each category
    • percent: that count as a proportion of all rows (including missing values)
    • valid_percent: that count as a proportion of only the non-missing rows
  • Pretty simple to use and can be used to create two-by-two tables
  • Need to feed it one variable at a time

What does tabyl() output look like?

hrs_00 |>
  tabyl(degree)
                              degree   n    percent valid_percent
                 High school diploma 512 0.18768328    0.22674934
                     Master's degree 487 0.17851906    0.21567759
 Professional degree (Ph.D./M.D./JD) 194 0.07111437    0.08591674
                                None 384 0.14076246    0.17006200
                  Associate's degree 411 0.15065982    0.18201949
                                 GED 118 0.04325513    0.05225864
                   Bachelor's degree 152 0.05571848    0.06731621
                                <NA> 470 0.17228739            NA

Cross-tabulations with tabyl()

  • You can also cross-tabulate two variables by adding a second column name
    • This will give you a table of counts for each combination of the two variables
hrs_00 |>
  tabyl(degree, sex)
                              degree Female Male
                 High school diploma    281  231
                     Master's degree    286  201
 Professional degree (Ph.D./M.D./JD)    115   79
                                None    238  146
                  Associate's degree    225  186
                                 GED     60   58
                   Bachelor's degree     88   64
                                <NA>    275  195

Dressing up a tabyl() with adorn_*()

  • adorn_*() functions let you format that table for presentation
  • These are designed to be piped together, one after another
Function What it does
adorn_totals() Adds a total row and/or column (where = "row", "col", or "both")
adorn_percentages() Converts counts to proportions (denominator = "row", "col", or "all")
adorn_pct_formatting() Converts proportions to nicely formatted percents (control decimals with digits =)
adorn_ns() Adds the raw counts back onto a table of percents, e.g. "45.2% (120)"

Example: Formatting a frequency table

  • We can add totals, convert to percentages, format as percents, and add counts back to a frequency table
  • Order matters!
    • You generally want: tabulate → add totals → convert to percentages → format as percents → add counts back
hrs_00 |>
  tabyl(degree) |>
  adorn_totals(where = "row") |>
  adorn_pct_formatting(digits = 2)
1
Add a row for totals to the table. A little confusing because it totals each column, but “row” refers to the position of the total row, not what is being totaled
2
Format the percentages to 2 decimal places
                              degree    n percent valid_percent
                 High school diploma  512  18.77%        22.67%
                     Master's degree  487  17.85%        21.57%
 Professional degree (Ph.D./M.D./JD)  194   7.11%         8.59%
                                None  384  14.08%        17.01%
                  Associate's degree  411  15.07%        18.20%
                                 GED  118   4.33%         5.23%
                   Bachelor's degree  152   5.57%         6.73%
                                <NA>  470  17.23%             -
                               Total 2728 100.00%       100.00%

Example: Formatting a cross-tabulation

  • We can also add totals, convert to percentages, format as percents, and add counts back to a cross-tabulation
hrs_00 |>
  tabyl(degree, sex) |>
  adorn_totals(where = "row") |>
  adorn_percentages(denominator = "col") |>
  adorn_pct_formatting(digits = 1) |>
  adorn_ns(position = "front")
1
Create a cross-tabulation of degree by sex
2
Add a row for totals to the table. A little confusing because it totals each column.
3
Convert the counts to percentages, using the column totals (the added row of totals) as the denominator.
4
Format the percentages to 1 decimal place
5
Add the raw counts back to the table, in front of the percentages.
                              degree         Female           Male
                 High school diploma   281  (17.9%)   231  (19.9%)
                     Master's degree   286  (18.2%)   201  (17.3%)
 Professional degree (Ph.D./M.D./JD)   115   (7.3%)    79   (6.8%)
                                None   238  (15.2%)   146  (12.6%)
                  Associate's degree   225  (14.3%)   186  (16.0%)
                                 GED    60   (3.8%)    58   (5.0%)
                   Bachelor's degree    88   (5.6%)    64   (5.5%)
                                <NA>   275  (17.5%)   195  (16.8%)
                               Total 1,568 (100.0%) 1,160 (100.0%)

What does tbl_summary() do?

  • tbl_summary() builds a publication-ready summary table for all the columns you give it in a single command
  • It automatically picks sensible statistics based on each variable’s type:
    • Numeric variables: median (interquartile range)
    • Categorical variables: count (percent)
    • Missing values are shown as their own “Unknown” row
  • We need to use select() to decide what goes into the table

 

  • Great for publications!
  • You can customize the statistics shown, the labels, and even stratify the table by a grouping variable
  • Some of the customizations are syntax heavy

What does tbl_summary() output look like?

hrs_00 |>
  select(-id, -hhid, -pn) |>
  tbl_summary()
1
Only removing this because it would add thousands of rows to this table! See what happens when you don’t remove these variables!
Characteristic N = 2,7281
Ever Had Diabetes 735 (27%)
Highest Degree Earned
    High school diploma 512 (23%)
    Master's degree 487 (22%)
    Professional degree (Ph.D./M.D./JD) 194 (8.6%)
    None 384 (17%)
    Associate's degree 411 (18%)
    GED 118 (5.2%)
    Bachelor's degree 152 (6.7%)
    Unknown 470
Respondent Birth Year 1,967 (1,958, 1,970)
Respondent Birth Month 7.0 (3.5, 10.0)
Respondent Birth Date 2,602 (-489, 3,757)
Proxy Interview Status
    Respondent 2,721 (100%)
    Proxy 7 (0.3%)
Couple Household Status
    Not a couple HH 1,093 (40%)
    Couple HH 1,635 (60%)
Sex
    Female 1,568 (57%)
    Male 1,160 (43%)
Age at Interview (Months) 671 (635, 778)
Age at Interview (Years) 55 (52, 64)
Years of Education 13.0 (12.0, 16.0)
    Unknown 507
Race/Ethnicity
    White/Caucasian 1,000 (45%)
    Black/African American 884 (39%)
    Other 358 (16%)
    Unknown 486
Current Smoker 459 (17%)
    Unknown 2
Ever Smoked 1,243 (46%)
    Unknown 1
Current Drinker 1,874 (69%)
Self-Reported Height 1.68 (1.63, 1.77)
    Unknown 55
Self-Reported Health Status
    Fair 743 (27%)
    Good 870 (32%)
    Very Good 647 (24%)
    Excellent 277 (10%)
    Poor 187 (6.9%)
    Unknown 4
Vigorous Physical Activity Frequency
    Never 1,443 (53%)
    >1 per week 586 (22%)
    1 per week 270 (9.9%)
    1-3 per week 275 (10%)
    Every day 145 (5.3%)
    Unknown 9
Ever Had High Blood Pressure 1,433 (53%)
    Unknown 8
Ever Had Cancer 271 (9.9%)
Ever Had Lung Disease 160 (5.9%)
Ever Had Heart Problems 373 (14%)
Ever Had Stroke 193 (7.1%)
Ever Had Psychiatric Problems 540 (20%)
Ever Had Sleep Disorder 534 (20%)
Ever Had Arthritis 1,006 (37%)
Total Number of Chronic Conditions
    0 635 (23%)
    1 675 (25%)
    2 651 (24%)
    3 443 (16%)
    4 227 (8.3%)
    5 71 (2.6%)
    6 23 (0.8%)
    7 3 (0.1%)
CES-D Depression Score
    0 993 (36%)
    1 657 (24%)
    2 346 (13%)
    3 200 (7.4%)
    4 152 (5.6%)
    5 122 (4.5%)
    6 105 (3.9%)
    7 93 (3.4%)
    8 53 (1.9%)
    Unknown 7
Total Household Income 51,462 (19,852, 116,500)
1 n (%); Median (Q1, Q3)

Customizing tbl_summary()

  • statistic = lets you change which statistics are shown, using { } to insert stat names like {mean}, {sd}, {median}, {n}, {p} (percent)
  • label = lets you rename variables to something more readable
hrs_00 |>
  select(age_yr, sex, diab) |>
  tbl_summary(
    statistic = age_yr ~ "{mean} ({sd})",
    label = age_yr ~ "Age (years)"
  )
1
Show mean (sd) for age_yr instead of the default median (IQR)
2
Give age_yr a nicer display name
Characteristic N = 2,7281
Age (years) 59 (9)
Sex
    Female 1,568 (57%)
    Male 1,160 (43%)
Ever Had Diabetes 735 (27%)
1 Mean (SD); n (%)

Presentation-ready tables with gt()

  • gt() turns any data frame or table (including a tabyl() output) into a polished, presentation-ready table
  • It’s a general-purpose “make this table look nice” tool
  • You can further customize titles, column labels, and colors using additional gt functions (like tab_header() or fmt_percent())
Original table from tabyl()
hrs_00 |>
  tabyl(degree)
                              degree   n    percent valid_percent
                 High school diploma 512 0.18768328    0.22674934
                     Master's degree 487 0.17851906    0.21567759
 Professional degree (Ph.D./M.D./JD) 194 0.07111437    0.08591674
                                None 384 0.14076246    0.17006200
                  Associate's degree 411 0.15065982    0.18201949
                                 GED 118 0.04325513    0.05225864
                   Bachelor's degree 152 0.05571848    0.06731621
                                <NA> 470 0.17228739            NA
Using gt() to make it look nice
hrs_00 |>
  tabyl(degree) |>
  gt() |>
  tab_header(title = "Highest Degree Earned")
Highest Degree Earned
degree n percent valid_percent
High school diploma 512 0.18768328 0.22674934
Master's degree 487 0.17851906 0.21567759
Professional degree (Ph.D./M.D./JD) 194 0.07111437 0.08591674
None 384 0.14076246 0.17006200
Associate's degree 411 0.15065982 0.18201949
GED 118 0.04325513 0.05225864
Bachelor's degree 152 0.05571848 0.06731621
NA 470 0.17228739 NA

We can change the font size for tbl_summary()

hrs_00 |>
  select(age_yr, sex, diab) |>
  tbl_summary(
    statistic = age_yr ~ "{mean} ({sd})",          
    label = age_yr ~ "Age (years)"                 
  ) |>
  as_gt() |>
  tab_options(table.font.size = px(50))
1
Convert the tbl_summary() output to a gt table so we can customize it. Need to use as_gt() instead of gt() because tbl_summary() already creates a table.
2
Change the font size to 50 pixels. Be careful: font size is relative to your document. 50px is probably too large for your standard html’s.
Characteristic N = 2,7281
Age (years) 59 (9)
Sex
    Female 1,568 (57%)
    Male 1,160 (43%)
Ever Had Diabetes 735 (27%)
1 Mean (SD); n (%)

Wrap-up

  • We covered a few functions for summarizing data in R:
    • tabyl() from the janitor package
      • For categorical data
      • Can be used for one-way or two-way tables
    • tbl_summary() from the gtsummary package
      • For categorical and numeric data
      • Good for publications!
      • Can be customized to show different statistics, labels, and stratifications

Resources