{gtsummary}Make an easy Table 1
{gtsummary}?gtsummary::tbl_summary()| Characteristic | Male N = 6,4031 |
Female N = 6,2831 |
|---|---|---|
| race_eth_cat | ||
| Hispanic | 1,000 (16%) | 1,002 (16%) |
| Black | 1,613 (25%) | 1,561 (25%) |
| Non-Black, Non-Hispanic | 3,790 (59%) | 3,720 (59%) |
| region_cat | ||
| Northeast | 1,296 (21%) | 1,254 (20%) |
| North Central | 1,488 (24%) | 1,446 (23%) |
| South | 2,251 (36%) | 2,317 (38%) |
| West | 1,253 (20%) | 1,142 (19%) |
| Unknown | 115 | 124 |
| eyesight_cat | ||
| Excellent | 1,582 (38%) | 1,334 (31%) |
| Very good | 1,470 (35%) | 1,500 (35%) |
| Good | 792 (19%) | 1,002 (23%) |
| Fair | 267 (6.4%) | 365 (8.5%) |
| Poor | 47 (1.1%) | 85 (2.0%) |
| Unknown | 2,245 | 1,997 |
| glasses | 1,566 (38%) | 2,328 (54%) |
| Unknown | 2,241 | 1,995 |
| age_bir | 25 (21, 29) | 22 (19, 27) |
| Unknown | 3,652 | 3,091 |
| 1 n (%); Median (Q1, Q3) | ||
| Characteristic | Male N = 6,4031 |
Female N = 6,2831 |
|---|---|---|
| region_cat | ||
| Northeast | 1,296 (21%) | 1,254 (20%) |
| North Central | 1,488 (24%) | 1,446 (23%) |
| South | 2,251 (36%) | 2,317 (38%) |
| West | 1,253 (20%) | 1,142 (19%) |
| Unknown | 115 | 124 |
| race_eth_cat | ||
| Hispanic | 1,000 (16%) | 1,002 (16%) |
| Black | 1,613 (25%) | 1,561 (25%) |
| Non-Black, Non-Hispanic | 3,790 (59%) | 3,720 (59%) |
| eyesight_cat | ||
| Excellent | 1,582 (38%) | 1,334 (31%) |
| Very good | 1,470 (35%) | 1,500 (35%) |
| Good | 792 (19%) | 1,002 (23%) |
| Fair | 267 (6.4%) | 365 (8.5%) |
| Poor | 47 (1.1%) | 85 (2.0%) |
| Unknown | 2,245 | 1,997 |
| glasses | 1,566 (38%) | 2,328 (54%) |
| Unknown | 2,241 | 1,995 |
| age_bir | 25 (21, 29) | 22 (19, 27) |
| Unknown | 3,652 | 3,091 |
| 1 n (%); Median (Q1, Q3) | ||
| Characteristic | Male N = 6,4031 |
Female N = 6,2831 |
|---|---|---|
| Race/ethnicity | ||
| Hispanic | 1,000 (16%) | 1,002 (16%) |
| Black | 1,613 (25%) | 1,561 (25%) |
| Non-Black, Non-Hispanic | 3,790 (59%) | 3,720 (59%) |
| Region | ||
| Northeast | 1,296 (21%) | 1,254 (20%) |
| North Central | 1,488 (24%) | 1,446 (23%) |
| South | 2,251 (36%) | 2,317 (38%) |
| West | 1,253 (20%) | 1,142 (19%) |
| Missing | 115 | 124 |
| Eyesight | ||
| Excellent | 1,582 (38%) | 1,334 (31%) |
| Very good | 1,470 (35%) | 1,500 (35%) |
| Good | 792 (19%) | 1,002 (23%) |
| Fair | 267 (6.4%) | 365 (8.5%) |
| Poor | 47 (1.1%) | 85 (2.0%) |
| Missing | 2,245 | 1,997 |
| Wears glasses | 1,566 (38%) | 2,328 (54%) |
| Missing | 2,241 | 1,995 |
| Age at first birth | 25 (21, 29) | 22 (19, 27) |
| Missing | 3,652 | 3,091 |
| 1 n (%); Median (Q1, Q3) | ||
tbl_summary(
nlsy,
by = sex_cat,
include = c(
sex_cat, race_eth_cat,
eyesight_cat, glasses,
age_bir),
label = list(
race_eth_cat ~
"Race/ethnicity",
eyesight_cat ~ "Eyesight",
glasses ~ "Wears glasses",
age_bir ~ "Age at first birth"
),
missing_text = "Missing") |>
add_p(test = list(
all_continuous() ~ "t.test",
all_categorical() ~
"chisq.test")) |>
add_overall(
col_label = "**Total**") |>
bold_labels() |>
remove_footnote_header() |>
modify_header(
label = "**Variable**",
p.value = "**P**") |>
modify_caption(
"**Participant characteristics**")| Variable | Total | Male N = 6,403 |
Female N = 6,283 |
P |
|---|---|---|---|---|
| Race/ethnicity | 0.8 | |||
| Hispanic | 2,002 (16%) | 1,000 (16%) | 1,002 (16%) | |
| Black | 3,174 (25%) | 1,613 (25%) | 1,561 (25%) | |
| Non-Black, Non-Hispanic | 7,510 (59%) | 3,790 (59%) | 3,720 (59%) | |
| Eyesight | <0.001 | |||
| Excellent | 2,916 (35%) | 1,582 (38%) | 1,334 (31%) | |
| Very good | 2,970 (35%) | 1,470 (35%) | 1,500 (35%) | |
| Good | 1,794 (21%) | 792 (19%) | 1,002 (23%) | |
| Fair | 632 (7.5%) | 267 (6.4%) | 365 (8.5%) | |
| Poor | 132 (1.6%) | 47 (1.1%) | 85 (2.0%) | |
| Missing | 4,242 | 2,245 | 1,997 | |
| Wears glasses | 3,894 (46%) | 1,566 (38%) | 2,328 (54%) | <0.001 |
| Missing | 4,236 | 2,241 | 1,995 | |
| Age at first birth | 23 (20, 28) | 25 (21, 29) | 22 (19, 27) | <0.001 |
| Missing | 6,743 | 3,652 | 3,091 |
That last example did two different kinds of customization:
Some things are arguments inside tbl_summary(). Others are separate functions we piped to – and those can have arguments of their own.
x |> f() is just another way of writing f(x) (or x %>% f() if you’re used to the {magrittr} pipe). So these are equivalent:
Every one of those {gtsummary} functions takes a table and returns a table. That’s why you can keep stacking them:
tbl_summary() – decisions about what the table is made of: which variables, which grouping, which statistics, what to call them. You need the data to answer these.A rough test: if you can’t answer the question without looking at the data, it’s probably an argument. If you’re adding to or restyling a table you already have, look for a function. Sometimes it’s arbitrary, though, and there’s no way to know without looking at the documentation!
Once you know you want a function, the prefix tells you where to look:
| prefix | what it does | examples |
|---|---|---|
add_ |
adds a row or column | add_p(), add_overall(), add_n() |
modify_ |
changes labels, headers, captions | modify_header(), modify_caption() |
bold_ / italicize_ |
styling | bold_labels(), italicize_levels() |
remove_ |
takes something away | remove_footnote_header() |
as_ |
converts to another format | as_flex_table(), as_gt() |
Tip
In RStudio, type gtsummary:: and hit Tab to scroll the whole list. For arguments, ?tbl_summary.
We saw include =, by =, label =, missing_text = in the example
statistic =:
list(all_continuous() ~ "{median} ({p25}, {p75})", all_categorical() ~ "{n} ({p}%)"){n} (frequency), {N} (denominator), {p} formatted percentage{median}, {mean}, {sd}, {var}, {min}, {max}, {sum}, {p##} (any percentile), or any function {foo}list(age ~ "min = {min}; max = {max}")digits =:
digits = everything() ~ style_sigfigstatistic = list(age ~ "min = {min}; max = {max}", year_of_birth = "{median} ({p25}, {p75})") :digits = list(age ~ c(1, 1) year_of_birth ~ c(0, 0, 0))type =:
type = list(``varname``~ "categorical")value = list(varname ~ "level to show")missing =:
add_overall(): In a stratified table, add a column for all strata combinedbold_labels(): Bold the variable names (also bold_levels(), italicize_levels())add_p(): Add a p-value (required by some journals 🤷♀️)remove_footnote_header(): Remove the default footnotes (see also modify_footnote_header() and modify_footnote_body() to add your own)modify_header(): Change the header columnmodify_caption(): Add a table caption{gtsummary} changed a lot in version 2.0, so you might get the old syntax when you ask AI.
The one you’re most likely to see:
Tip
It still runs, and the deprecation warning is easy to miss. When a suggestion doesn’t match the package documentation, trust the documentation.
tbl_summary()Incredibly customizeable
You can view it in the browser and copy/paste into Word
Everything else we’re doing this week is about not redoing work by hand when the data changes. Tables are no different:
Re-run the analysis, re-run this line, and the Word file is up to date.
Tip
Best of all is to put the table directly in a quarto document, which we’ll do tomorrow – then there’s no separate file to keep in sync at all.
Open the script with some examples.
Install {gtsummary} and run the examples.
We’ll come back together here to discuss before you’re on your own.
3-7. You’re on your own! Work with your neighbors, and we’ll come back together to go over these.
Extra time? Start a table using the data you want to use for your final project! Make sure you switch to that R project!