Skip to contents

ltsummary tables are rendered by the lt package, which builds the HTML table in the browser from a small JSON specification. That makes HTML output the native format, and it is the format this website uses. Other formats are reached through lt’s static rendering or by exporting the table to a file. This article says what works where, and how.

Output types

Format How Result
HTML (Quarto, R Markdown, litedown) print the table full output
GitHub Flavored Markdown print the table with the static option set full output
Word and PDF (Quarto) print the table with the static option set native table; indentation and bold cells are lost
Word and PDF (R Markdown) export to PNG and include the image image of the full table
PNG, PDF and HTML files lt::lt_export() full output

HTML

Printing a table in a chunk is enough. The package registers methods for knitr::knit_print() and for litedown’s xfun::record_print(), so the table renders in Quarto, R Markdown and litedown documents without any extra code.

```{r}
library(ltsummary)
tbl_summary(trial, by = trt, include = c(age, grade)) |> add_p()
```

The JavaScript and CSS that lt needs are emitted once per document, with the first table.

Word and PDF with Quarto

lt can bake a static <table> instead of the JavaScript specification. Set the lt.lt_static option at the top of the document and print tables as usual; Quarto then converts the HTML table into a native Word or LaTeX table.

```{r}
#| include: false
options(lt.lt_static = list(fragment = TRUE, css = FALSE))
library(ltsummary)
```

```{r}
tbl_summary(trial, by = trt, include = c(age, grade)) |> add_p() |> bold_labels()
```

Baking the table needs Node.js or a Chromium-based browser on the machine that renders the document; lt finds either on its own. The conversion keeps the headers, the values and the footnotes, and drops the inline styles, so the indentation of the levels and bold or italic cells do not survive. This is the same limitation that gtsummary documents for its kable output.

The same option makes the table render in GitHub Flavored Markdown, for example in a README built from an R Markdown file, because GitHub displays raw HTML tables.

Word and PDF with R Markdown

Pandoc drops raw HTML when it writes Word or LaTeX, so in R Markdown the static option is not enough. Export the table to an image and include it:

```{r, echo = FALSE}
tbl <- tbl_summary(trial, by = trt, include = c(age, grade)) |> add_p()
lt::lt_export(as_lt(tbl), "table1.png")
knitr::include_graphics("table1.png")
```

The image is cropped to the table and keeps every formatting feature. lt_export() renders through a headless Chromium browser; cropping PNG output also needs the magick package.

Alternatively, switch to Quarto for the document: the Quarto route above gives a real table that Word users can edit.

Images and files

lt::lt_export() writes a table to disk. The file extension chooses the format: .html for a standalone web page, .pdf for a vector PDF, and anything else for a PNG.

tbl <- tbl_summary(trial, by = trt, include = c(age, grade)) |> add_p()

lt::lt_export(as_lt(tbl), "table1.html")
lt::lt_export(as_lt(tbl), "table1.pdf")
lt::lt_export(as_lt(tbl), "table1.png", width = 500)

The README of the package is built this way: the table is exported to PNG and the image is included, which is why it shows on GitHub.

Plain text

as.data.frame() returns the formatted cells as a data frame, with the header labels as column names. Print it, or pass it to knitr::kable(), when a plain table is all that is needed. The data.frame print engine does the same at the console.

tbl <- tbl_summary(trial, by = trt, include = c(age, grade)) |> add_p()
as.data.frame(tbl)
#>   **Characteristic** **Drug A**  \nN = 95 **Drug B**  \nN = 105 **p-value**
#> 1                Age          59 (51, 66)           62 (54, 71)       0.043
#> 2            Unknown                    4                     7        <NA>
#> 3              Grade                 <NA>                  <NA>         0.3
#> 4                  I             25 (26%)              37 (35%)        <NA>
#> 5                 II             40 (42%)              43 (41%)        <NA>
#> 6                III             30 (32%)              25 (24%)        <NA>

The header labels carry markdown (**Drug A**) and line breaks, which kable() does not interpret, and empty cells are NA. Tidy both before passing the data frame on:

df <- as.data.frame(tbl)
names(df) <- gsub("  \n", ", ", gsub("**", "", names(df), fixed = TRUE))
df[is.na(df)] <- ""
knitr::kable(df)
Characteristic Drug A, N = 95 Drug B, N = 105 p-value
Age 59 (51, 66) 62 (54, 71) 0.043
Unknown 4 7
Grade 0.3
I 25 (26%) 37 (35%)
II 40 (42%) 43 (41%)
III 30 (32%) 25 (24%)

Tips

When printing tables in a loop, use print() in a chunk with results = "asis":

```{r loop_print, results = "asis"}
for (v in c("age", "grade")) {
  tbl <- tbl_summary(trial, by = trt, include = all_of(v))
  print(tbl)
}
```

If print(tbl) does not render, try knitr::knit_print(tbl) or cat(knitr::knit_print(tbl)).

inline_text() quotes the table in the text; the inline_text() tutorial shows how.