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, PDF and typst (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, one page sized to the table |
| Paginated PDF file |
lt::lt_export() with
crop = FALSE
|
full output on letter-size pages, header and footnotes repeated |
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.
The JavaScript and CSS that lt needs are emitted once per document, with the first table.
Word, PDF and typst 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, LaTeX or typst 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 option is what makes the table appear at all in these formats.
Without it the chunk emits lt’s JavaScript specification, Pandoc drops
that from Word, PDF and typst output, and the table is missing from the
document with no warning from Quarto or knitr. A
format: typst document renders with the same option and
needs no LaTeX installation.
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.
PDF and PNG are rendered by a headless Chromium-based browser, which lt
finds on its own.
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)A PDF comes in two shapes. The default, crop = TRUE, is
a single page sized to the table, for placing the table into another
document as a figure. lt 0.4 measures that page a fraction of a pixel
too short when the table has two rows of footnotes, as a summary table
with add_p() does, and the browser then adds a second page
holding a repeated header and the footnotes. lt’s development version
fixes this (yihui/lt#6,
for lt 0.5); with lt 0.4 from CRAN, check the page count of a cropped
export.
With crop = FALSE the table is printed on letter-size
pages, and the column header and the footnotes repeat on every page.
With xfun 0.60 from CRAN the browser also prints its own header and
footer (the date, the page title and the path of a temporary file); the
flag below turns that off, and xfun’s development version passes it by
default (yihui/xfun#130, for
xfun 0.61). Keep "default" in args: it stands
for the headless flags, and without it the browser opens the file in a
window and writes no PDF.
lt::lt_export(as_lt(tbl), "table1.pdf", crop = FALSE,
args = c("default", "--no-pdf-header-footer"))The exported page uses the system font. A stylesheet changes it, and
for crop = FALSE an @page rule in the same
stylesheet sets the page size and margins. With lt 0.4, give
as_lt() an absolute path: a relative one is written into
the page as a link that does not resolve from the temporary directory
the browser renders in. lt’s development version resolves relative paths
at export time (yihui/lt#8).
# submission.css:
# @page { size: letter; margin: 0.375in 0.375in 0.375in 0.75in; }
# body { font-family: "Times New Roman", Times, serif; }
# td, th { font-size: 10pt; }
lt::lt_export(as_lt(tbl, css = normalizePath("submission.css")), "table1.pdf",
crop = FALSE, args = c("default", "--no-pdf-header-footer"))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.