Creating Publication-Ready Tables with gtsummary

advanced
tables
statistics
Learn how to create professional statistical tables for manuscripts and reports
Author

Unijos R Users Team

Published

January 15, 2025

Introduction

One of the most time-consuming tasks in research is creating well-formatted statistical tables. The gtsummary package makes this incredibly easy while producing publication-ready output.

Why gtsummary?

  • Creates APA-style tables automatically
  • Supports complex statistical models
  • Exports to Word, HTML, PDF, and LaTeX
  • Minimal code required
  • Perfect for epidemiological studies

Installation

install.packages("gtsummary")
install.packages("flextable")  # For Word export

Basic Summary Tables

Descriptive Statistics

library(gtsummary)
library(tidyverse)

# Create a summary table stratified by exposure status
outbreak_data %>%
  select(age, sex, symptoms, exposed) %>%
  tbl_summary(
    by = exposed,
    statistic = list(
      all_continuous() ~ "{mean} ({sd})",
      all_categorical() ~ "{n} ({p}%)"
    ),
    label = list(
      age ~ "Age (years)",
      sex ~ "Sex",
      symptoms ~ "Symptoms Present"
    )
  ) %>%
  add_p() %>%  # Add p-values
  add_overall() %>%  # Add overall column
  bold_labels()

Regression Tables

Logistic Regression for Outbreak Investigation

# Fit logistic regression model
model <- glm(
  case_status ~ age + sex + water_source + food_type,
  data = outbreak_data,
  family = binomial
)

# Create publication-ready table
tbl_regression(
  model,
  exponentiate = TRUE,  # Show odds ratios
  label = list(
    age ~ "Age (years)",
    sex ~ "Sex",
    water_source ~ "Water Source",
    food_type ~ "Food Type"
  )
) %>%
  add_global_p() %>%  # Add overall p-values
  bold_p(t = 0.05) %>%  # Bold significant p-values
  bold_labels()

Exporting Tables

To Microsoft Word

my_table %>%
  as_flex_table() %>%
  flextable::save_as_docx(path = "table1.docx")

To HTML

my_table %>%
  as_gt() %>%
  gt::gtsave("table1.html")

Advanced Tips

Compare Multiple Models

# Compare unadjusted vs adjusted models
tbl_merge(
  list(
    tbl_regression(unadjusted_model, exponentiate = TRUE),
    tbl_regression(adjusted_model, exponentiate = TRUE)
  ),
  tab_spanner = c("**Unadjusted**", "**Adjusted**")
)

Customize Themes

# Set a journal-specific theme
theme_gtsummary_journal("jama")

# Or create custom theme
theme_gtsummary_compact()

Practical Example: Diphtheria Outbreak

Here’s a complete example analyzing outbreak data:

# Load outbreak data
diphtheria_data <- read_csv("diphtheria_outbreak.csv")

# Create comprehensive table
diphtheria_data %>%
  select(age_group, sex, vaccination_status, outcome, district) %>%
  tbl_summary(
    by = outcome,
    statistic = list(
      all_categorical() ~ "{n} ({p}%)"
    ),
    label = list(
      age_group ~ "Age Group",
      sex ~ "Sex",
      vaccination_status ~ "Vaccination Status",
      district ~ "District"
    )
  ) %>%
  add_p(test = all_categorical() ~ "fisher.test") %>%
  add_overall() %>%
  modify_header(label = "**Characteristic**") %>%
  bold_labels() %>%
  bold_p(t = 0.05)

Resources


Join our next session to learn more advanced table techniques! RSVP on Meetup