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

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!

Avatar
Chong H. Kim
Health Economics & Outcomes Researcher

My research interests include health economics & outcomes research (HEOR), real-world evidence/observation research, predictive modeling, and spatial statistics.

Related