Skip to contents

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()

x

show_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 columns

Column formatting modifiers

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() 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

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

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

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

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

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

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.

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.