Generating publication ready tables with {gtsummary} and {flextable}
Publication-ready tables with {gtsummary}
and {flextable}
So, I’ve been using R for a while now and I haven’t really found a good solution in terms of producing publication-ready tables that I can customize easily until recently. I thank the developers of {gtsummary}
and {flextable}
for saving me the trouble of trying to figure out how to customize the back-end of the table generated R into microsoft word document.
The purpose of this post is to just share a few tips on how I would create a publication-ready table using {gtsummary}
and {flextable}
. There are a few things that I do manually in order to get the look and feel that I want. So let’s dive in.
Generating a {gtsummary}
table
Let’s first load in the packages
library('pacman')
p_load(gtsummary, dplyr)
We’re going to follow along the vignette for {gtsummary}
and create the same table in it.
trial %>%
tbl_summary(
by = trt,
type = all_continuous() ~ "continuous2",
statistic = all_continuous() ~ c("{N_nonmiss}",
"{median} ({p25}, {p75})",
"{min}, {max}"),
missing = "no"
) %>%
add_p(pvalue_fun = ~style_pvalue(.x, digits = 2))
## Warning: The `.dots` argument of `group_by()` is deprecated as of dplyr 1.0.0.
Characteristic | Drug A, N = 98 | Drug B, N = 102 | p-value1 |
---|---|---|---|
Age | 0.72 | ||
N | 91 | 98 | |
Median (IQR) | 46 (37, 59) | 48 (39, 56) | |
Range | 6, 78 | 9, 83 | |
Marker Level (ng/mL) | 0.085 | ||
N | 92 | 98 | |
Median (IQR) | 0.84 (0.24, 1.57) | 0.52 (0.19, 1.20) | |
Range | 0.00, 3.87 | 0.00, 3.64 | |
T Stage | 0.87 | ||
T1 | 28 (29%) | 25 (25%) | |
T2 | 25 (26%) | 29 (28%) | |
T3 | 22 (22%) | 21 (21%) | |
T4 | 23 (23%) | 27 (26%) | |
Grade | 0.87 | ||
I | 35 (36%) | 33 (32%) | |
II | 32 (33%) | 36 (35%) | |
III | 31 (32%) | 33 (32%) | |
Tumor Response | 28 (29%) | 33 (34%) | 0.53 |
Patient Died | 52 (53%) | 60 (59%) | 0.41 |
Months to Death/Censor | 0.14 | ||
N | 98 | 102 | |
Median (IQR) | 23.5 (17.4, 24.0) | 21.2 (14.6, 24.0) | |
Range | 3.5, 24.0 | 5.3, 24.0 | |
1
Wilcoxon rank sum test; Pearson's Chi-squared test
|
How about that. The table looks pretty good! But let’s say I want to add a general note as well as some abbreviations and then footnotes associated with superscripts inside the table. How would I go about doing that?
The way I edit this table is basically using the {flextable}
package and adding in the elements I want manaully. Let’s go through that
p_load(flextable)
tab_fl <- trial %>%
select(age, grade, trt) %>%
tbl_summary(
by = trt,
type = all_continuous() ~ "continuous2",
digits = list(age ~ c(0,0,0,0,0,0,0,0,0)),
statistic = list(
all_continuous() ~ c("{N_nonmiss} ({p_nonmiss}%)",
"{median} ({p25}, {p75})",
"{min}, {max}",
"{N_miss} ({p_miss}%)"
),
all_categorical() ~ "{n} / {N} ({p}%)"
),
missing = "no",
label = list(age ~ "Patient Age",
grade ~ "Tumor Grade")
) %>%
add_overall() %>%
add_p(pvalue_fun = ~style_pvalue(.x, digits = 2)) %>%
modify_header(label ~ "**Variable**") %>%
modify_footnote(
list(
all_stat_cols() ~ NA,
p.value ~ NA
)
) %>%
bold_labels() %>%
# show_header_names() # to show header names
# manually add table and footnote using {flextable}
# first remove footnote from {gtsummary}
as_flex_table() %>%
# We set a footer to the first column's name but we need to modify it later
footnote(i=1,j=1,part = "header", value = as_paragraph("Note: This data is simulated."), ref_symbols = "") %>%
footnote(i=1,j=1,part = "header", value = as_paragraph("Abbreviations: IQR=Inter Quartile Range."), ref_symbols = "") %>%
compose(i=1,j=1, part='header', value = as_paragraph("Characteristics")) %>%
# add additional footnote
footnote(i=1,j=5, part="header", value = as_paragraph("Wilcoxon rank sum test; Pearson's Chi-squared test"))
tab_fl
Characteristics | Overall, N = 200 | Drug A, N = 98 | Drug B, N = 102 | p-value1 |
Patient Age | 0.72 | |||
N (% not missing) | 189 (94%) | 91 (93%) | 98 (96%) | |
Median (IQR) | 47 (38, 57) | 46 (37, 59) | 48 (39, 56) | |
Range | 6, 83 | 6, 78 | 9, 83 | |
N missing (% missing) | 11 (6%) | 7 (7%) | 4 (4%) | |
Tumor Grade | 0.87 | |||
I | 68 / 200 (34%) | 35 / 98 (36%) | 33 / 102 (32%) | |
II | 68 / 200 (34%) | 32 / 98 (33%) | 36 / 102 (35%) | |
III | 64 / 200 (32%) | 31 / 98 (32%) | 33 / 102 (32%) | |
Note: This data is simulated. | ||||
Abbreviations: IQR=Inter Quartile Range. | ||||
1Wilcoxon rank sum test; Pearson's Chi-squared test |
There’s a bunch of things going on in the code but you get the idea through the comments! The last several lines are pretty important for the look and feel of the table that you want. I’ve struggled through trying to find the right codes to adjusting the footer but there’s a lot more customization that can be added to the table. Go checkout the github repo.
The last bit of code is saving this into a word document. You need to have the {officer} package to do so.
p_load(officer)
read_docx() %>%
# Add title
body_add_par("Table 1. Demographic & Clinical Characteristics", style = "table title") %>%
# Add table to doc
body_add_flextable(value = tab_fl) %>%
# Save to location inside computer
print(doc, target = paste0("/directory/flextable_worddoc.docx"))
I hope this was helpful. Generating tables especially for clients can be somewhat tedious to code but will be absolutely worth it once you’ve been coding the tables for a while! Good luck to everyone who needs to do so!