Introduction
The default formatting of a table rarely matches a journal’s
requirements exactly. The modify_*() functions change a
table after it has been built, and this article goes through each of
them with an example.
Many of the modifiers share a columns argument. The
column names it expects are those of the table’s table_body
element (x$table_body), which
show_header_names(x) prints.
When a modifier has a rows argument, a predicate
expression selects the rows of x$table_body it applies to;
the modify_table_styling()
reference describes the expression in detail.
The examples below work on a table summarizing four variables by
treatment arm, with p-values from add_p().
x <- trial |>
tbl_summary(
by = trt,
include = c(age, grade, response, death)
) |>
add_p()
xshow_header_names() lists the columns of
x$table_body that the columns and
rows arguments can refer to. The columns marked with a
dagger are hidden; modify_column_unhide() shows them.
Hidden columns such as variable, var_type
and row_type are rarely shown in a table, but they are what
rows predicates are written in terms of, as several of the
examples below demonstrate.
show_header_names(x, show_hidden = TRUE)
#> Column Name Header level* N* n* p*
#> variable† "variable" 200
#> test_name† "test_name" 200
#> var_type† "var_type" 200
#> var_label† "var_label" 200
#> row_type† "row_type" 200
#> label "**Characteristic**" 200
#> stat_1 "**Drug A** \\nN = 95" Drug A 200 95 0.475
#> stat_2 "**Drug B** \\nN = 105" Drug B 200 105 0.525
#> estimate† "**Difference**" 200
#> parameter† "**Parameter**" 200
#> statistic† "**Statistic**" 200
#> conf.low† "**95% CI**" 200
#> conf.high† "conf.high" 200
#> p.value "**p-value**" 200
#>
#> * These values may be placed into headers with `modify_header()`, e.g. "**{level}**, N = {n}".
#> † Hidden columnsColumn formatting modifiers
-
modify_column_alignment(): update column alignment -
modify_column_hide()andmodify_column_unhide(): hide or show columns
These functions apply column-wise changes. Each takes the columns to
operate on in the columns argument. The example shows the
test statistic that add_p() computed and stored as a hidden
column, and right-aligns the p-values.
x |>
# show the test statistic
modify_column_unhide(columns = statistic) |>
modify_fmt_fun(statistic = label_style_sigfig(digits = 3)) |>
# right-align the p-value column
modify_column_alignment(columns = p.value, align = "right")Column merging modifier
-
modify_column_merge()andremove_column_merge(): merge or un-merge two or more columns
modify_column_merge() merges columns into one, with
pattern giving the arrangement in glue syntax and
rows restricting the merge to some rows. The merged text is
displayed in the first column named in the pattern:
pattern = "{statistic} (p = {p.value})" displays the result
in the statistic column.
Formatting functions are applied to each column before merging, so
the columns in a pattern keep their own formatting.
remove_column_merge() undoes a merge.
x |>
modify_column_unhide(columns = statistic) |>
modify_fmt_fun(statistic = label_style_sigfig(digits = 3)) |>
# merge the test statistic and p-value in the rows that have a p-value
modify_column_merge(pattern = "{statistic} ({p.value})", rows = !is.na(p.value)) |>
modify_header(statistic = "**Statistic (p-value)**")Indentation modifier
-
modify_indent(): update cell indentation
Indentation can be changed for entire columns, for rows, or for
single cells. columns is always required; rows
is optional and defaults to NULL, which applies the
indentation to the whole column. To change the indentation of entire
rows, pass columns = everything() together with
rows. With both columns and rows
set, only the selected cells change.
x |>
# remove the indentation of the label column
modify_indent(columns = label, indent = 0L) |>
# indent the cells of stat_1 in the level rows by 10 spaces
modify_indent(columns = stat_1, rows = row_type == "level", indent = 10L)Indentation is rendered with lt’s indent levels in the first column
and with non-breaking spaces elsewhere, so the stat_1 cells
above move by a fixed amount rather than by a multiple of the label
indent.
Cell style modifiers
-
modify_bold()andremove_bold(): add or remove bold styling -
modify_italic()andremove_italic(): add or remove italic styling
For the modify_*() functions, columns and
rows select the cells to style. The remove_*()
functions remove all bold or italic styling by default, and accept
columns and rows to narrow that down.
x |>
# bold the label column in the label rows
modify_bold(columns = label, rows = row_type == "label") |>
# italicize p-values below 0.5
modify_italic(columns = p.value, rows = p.value < 0.5)bold_labels(), bold_levels(),
italicize_labels(), italicize_levels() and
bold_p() are shortcuts for the common cases.
Value formatting modifiers
-
modify_missing_symbol(): update how missing values are shown -
modify_fmt_fun(): update the functions that format numeric columns and rows
The statistic columns (stat_1, stat_2, …)
are character columns, formatted when the table is built according to
tbl_summary(digits =). modify_fmt_fun()
applies to the numeric columns: p.value, the hidden
statistic, estimate, conf.low and
conf.high columns, and any column added with
modify_table_body().
For merged columns, formatting functions run before the merge, so
each column of a pattern is formatted on its own. A column that is the
result of merging conf.low and conf.high is
formatted by naming both columns.
modify_fmt_fun() can be called several times to format
different rows differently.
x |>
# show "n/a" instead of a blank in the p-value column of the level rows
modify_missing_symbol(symbol = "n/a", columns = p.value, rows = row_type == "level") |>
# three decimal places for the p-value of the response variable
modify_fmt_fun(
p.value = label_style_pvalue(digits = 3),
rows = variable == "response"
) |>
# two decimal places for the others
modify_fmt_fun(
p.value = label_style_pvalue(digits = 2),
rows = variable != "response"
)Header modifiers
-
modify_header(): update column headers -
modify_spanning_header()andremove_spanning_header(): update or remove spanning headers
Header text accepts a small markdown subset (**bold**,
_italic_ and line breaks) and the dynamic values
{N}, {n}, {p} and
{level}; the modify_header()
reference describes them.
x |>
# update the p-value header, remove the label header
modify_header(
p.value ~ "**P**",
label ~ ""
) |>
# add a spanning header across the statistic columns
modify_spanning_header(all_stat_cols() ~ "**Treatment Received (N = {N})**")Footnote modifiers
-
modify_footnote_body()andremove_footnote_body(): footnotes on cells of the table body -
modify_footnote_header()andremove_footnote_header(): footnotes on column headers -
modify_footnote_spanning_header()andremove_footnote_spanning_header(): footnotes on spanning headers
Each function handles the footnotes of one part of the table. Footnotes are numbered in the order they appear, from top left to bottom right, and a footnote that appears in several places is listed once.
By default a new footnote replaces any footnote already attached to
the same location. Pass replace = FALSE to keep the
existing footnotes alongside the new one. The remove_*()
functions remove all footnotes of the corresponding part by default; see
their documentation for narrower removals.
x |>
modify_spanning_header(all_stat_cols() ~ "**Treatment Received (N = {N})**") |>
# a footnote on the grade label
modify_footnote_body(
footnote = "Tumor grade was assessed _before_ treatment began",
columns = "label",
rows = variable == "grade" & row_type == "label"
) |>
# a footnote on the p-values above 0.5
modify_footnote_body(
footnote = "Reported p-value outside of the range of interest",
columns = p.value,
rows = p.value > 0.5
) |>
# a footnote on every column header, kept alongside the existing ones
modify_footnote_header(
footnote = "All subjects received treatment",
columns = everything(),
replace = FALSE
) |>
# a footnote on the spanning header
modify_footnote_spanning_header(
"Randomized Treatment",
columns = all_stat_cols()
)Source note, abbreviation and caption modifiers
-
modify_abbreviation()andremove_abbreviation(): the abbreviations note -
modify_caption(): the table caption -
modify_source_note()andremove_source_note(): source notes
Source notes are similar to footnotes but are not linked to a cell. All abbreviations are collected into a single source note, which is printed before any other source notes, and source notes are printed after the footnotes.
x |>
modify_caption("**Table 1. Baseline characteristics**") |>
modify_source_note("Results as of June 26, 2015") |>
modify_abbreviation("I = Grade 1")Footnotes vs source notes vs abbreviations
Three kinds of notes appear under a table, each with its own functions.
- Footnotes carry a marker in the table, and the marker appears again next to the note below the table: a standard footnote.
- Source notes are footnotes without a marker.
- Abbreviations are a special source note: every abbreviation that appears in a table is collected into one note at the bottom of the table.
Advanced modifiers
These functions are for developers and advanced users.
-
modify_table_body(): updatex$table_bodydirectly -
modify_table_styling(): updatex$table_stylingdirectly -
modify_post_fmt_fun(): apply a second formatting function after the primary ones
The other modifiers are friendlier ways of doing what these three do. Their reference pages describe them, and the object definition article describes the structures they work on.