Skip to contents

ltsummary builds the summary tables that clinical and epidemiological papers open with: descriptive statistics by treatment arm, with p-values, overall columns and footnotes. It follows the interface of gtsummary, so tbl_summary(trial, by = trt) |> add_p() |> bold_labels() works the way you expect, and it renders the result with lt, Yihui Xie’s lightweight grammar of tables.

The point is the footprint. Installing ltsummary pulls in two packages beyond base R: lt and xfun. Installing gtsummary pulls in 62, because it renders through gt, which needs sass, V8 and htmlwidgets. In a validated computing environment every package in that tree is a risk assessment to write and an upgrade to keep under change control, so the difference outlasts install time. Every statistic in ltsummary is computed in base R and tested against the matching stats:: call or hand arithmetic executed in the test suite, and parity with gtsummary 2.6.1 is verified cell for cell whenever gtsummary and broom are installed. The validation article collects the evidence for a package risk assessment.

ltsummary is an independent re-implementation of the interface designed by Daniel D. Sjoberg and the gtsummary authors. Function names, arguments, defaults and output strings follow gtsummary deliberately, and so do the wording of the reference pages and the structure of the articles, so that gtsummary’s documentation applies here too. ltsummary is not affiliated with or endorsed by the gtsummary project.

Installation

ltsummary is not on CRAN yet. Install it from GitHub with pak or remotes; R 4.1 or later is required, because every example uses the native pipe.

# install.packages("pak")
pak::pak("tgerke/ltsummary")

# or
# install.packages("remotes")
remotes::install_github("tgerke/ltsummary")

The package is marked experimental because functions are still being added. The functions that exist mirror gtsummary 2.6.1, and their signatures are not expected to move.

Example

library(ltsummary)

tbl <- trial |>
  tbl_summary(by = trt, include = c(age, marker, grade, response)) |>
  add_overall() |>
  add_p() |>
  bold_labels() |>
  modify_caption("**Table 1. Patient characteristics**")

In a knitr, Quarto or litedown document the table is rendered directly; the image above is for GitHub, which does not run JavaScript. The object behind the table is a data frame of formatted cells plus a list of styling instructions, so any cell can be pulled into the text of a report:

inline_text(tbl, variable = age, column = "Drug A")
#> [1] "59 (51, 66)"
inline_text(tbl, variable = grade, level = "II", column = "Drug B")
#> [1] "43 (41%)"
inline_text(tbl, variable = age, column = p.value)
#> [1] "p=0.043"

Regression models get the same treatment: tbl_regression() summarizes a fitted lm, glm or coxph model, and tbl_uvregression() fits and stacks one model per variable.

tbl2 <- glm(response ~ age + grade + trt, trial, family = binomial) |>
  tbl_regression(exponentiate = TRUE) |>
  add_global_p() |>
  bold_labels()

as_lt() returns the lt object itself, so the lt verbs keep working after the ltsummary ones:

as_lt(tbl) |>
  lt::lt_width("70%") |>
  lt::lt_export("table1.pdf")

What is implemented

The package covers descriptive, continuous, cross, survival, hierarchical and regression tables, and the functions that combine them:

That is the whole gtsummary 2.6.1 surface except the survey and ARD families and a few functions that need packages outside base R; the migration article lists them.

Differences from gtsummary

Function names, arguments, defaults and output strings follow gtsummary 2.6.1. Functions that gtsummary has deprecated (modify_footnote(), modify_column_indent(), tbl_split(), continuous_summary()) are not mirrored. Beyond that:

  • as_lt() replaces as_gt(), and there are no converters for flextable, huxtable or kable.
  • Header and footnote text supports a markdown subset (**bold**, _italic_, line breaks) instead of full markdown. Pass HTML with text_interpret = "html" for anything else.
  • Column selection uses a base R implementation of the common tidyselect forms (c(age, grade), -trt, starts_with("a"), all_continuous()) rather than tidyselect itself.
  • The theme functions carry the ltsummary name: set_gtsummary_theme() is set_ltsummary_theme(), theme_gtsummary_compact() is theme_ltsummary_compact(), and so on. The element names inside a theme are gtsummary’s, so a theme list written for gtsummary carries over. A few defaults can also be set with options; the tbl_summary() tutorial lists them, and a theme outranks them.

Design notes

Decisions that are not obvious from the code (why base R, how the object model mirrors gtsummary, the testing policy, which gtsummary version is the reference) are recorded in decisions/.