Introduction
This article follows gtsummary’s article of the same name.
tbl_regression() takes a fitted regression model and
returns a table of its results that is ready for publication: one row
per term, with the estimate, its confidence interval and a p-value, and
a reference row for each categorical variable. Like
tbl_summary(), it has sensible defaults and every part of
the table can be changed afterwards.
Behind the scenes, tbl_regression() tidies
lm(), glm() and survival::coxph()
models in base R. Other model classes are tidied with
broom::tidy() when broom is installed, or with a function
you pass in tidy_fun; the supported models section says what that
covers.
Example data set
The examples use the trial data set that ships with the
package: 200 patients who received one of two chemotherapies, with tumor
response and death as the outcomes. Each variable has a
label attribute, which the tables use as the variable
label; a variable without one is labelled with its name.
| Variable | Class | Label |
|---|---|---|
trt |
character | Chemotherapy Treatment |
age |
numeric | Age |
marker |
numeric | Marker Level (ng/mL) |
stage |
factor | T Stage |
grade |
factor | Grade |
response |
integer | Tumor Response |
death |
integer | Patient Died |
ttdeath |
numeric | Months to Death/Censor |
Basic usage
Start with a logistic regression model for tumor response, with age and stage as the predictors.
m1 <- glm(response ~ age + stage, trial, family = binomial)
summary(m1)$coefficients
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -0.838671134 1.00695498 -0.83287848 0.40491327
#> age -0.001505552 0.01544471 -0.09748011 0.92234513
#> stageT2 0.029387942 0.46434355 0.06328922 0.94953620
#> stageT3 -0.263766551 0.48759757 -0.54095132 0.58854114
#> stageT4 -1.017557569 0.54665522 -1.86142476 0.06268422One call turns the model into a table.
tbl_regression(m1, exponentiate = TRUE)Note the defaults, each of which can be changed:
- The model is a logistic regression and the coefficients are exponentiated, so the header says OR and the abbreviation is explained below the table.
- Variable types are detected: the categorical variable gets a label row, one row per level and a reference row for the reference level.
- The estimates and confidence limits are rounded with
style_ratio(), the p-values withstyle_pvalue(). - The label attributes of the data are printed, and the levels are indented.
Customizing the output
There are four ways to customize a regression table:
- The arguments of
tbl_regression(). - The
add_*()functions, which add columns and statistics. - The ltsummary functions that modify the appearance of the table.
- The functions of the lt package, applied after
as_lt().
tbl_regression() arguments
| Argument | Description |
|---|---|
label |
variable labels printed in the table |
exponentiate |
exponentiate the coefficients and their confidence limits |
include |
variables to include in the table |
show_single_row |
dichotomous variables to print on a single row instead of three |
conf.level |
confidence level of the interval |
intercept |
whether to include the intercept |
estimate_fun |
function that rounds and formats the estimates |
pvalue_fun |
function that rounds and formats the p-values |
tidy_fun |
function that tidies the model, for classes without a built-in tidier |
add_estimate_to_reference_rows |
whether to show 0 (or 1, when exponentiated) on the reference rows |
conf.int |
whether to show the confidence interval |
An example that uses several of them:
lm(marker ~ age + trt + grade, trial) |>
tbl_regression(
label = list(grade = "Tumor grade", marker = "Marker"),
show_single_row = trt,
conf.level = 0.9,
estimate_fun = label_style_sigfig(digits = 3)
)Functions that add information
| Function | Description |
|---|---|
add_global_p() |
one p-value per variable, from a type III test of all its coefficients |
add_n() |
number of observations in the model, or at each level |
add_nevent() |
number of events, for logistic, Poisson and Cox models |
add_glance_table() |
model statistics such as R², AIC and the log-likelihood as rows at the bottom |
add_glance_source_note() |
the same statistics as a source note |
add_q() |
q-values adjusted for multiple comparisons |
tbl_regression(m1, exponentiate = TRUE) |>
add_global_p() |>
add_nevent(location = "level") |>
add_glance_source_note(include = c(nobs, AIC))add_global_p() replaces the p-values of the individual
levels with one test per variable, the same test
car::Anova(type = "III") reports, so a categorical variable
is judged as a whole. Pass keep = TRUE to keep the level
p-values next to it.
Functions that format the table
The modifier functions of tbl_summary() work on
regression tables too; the modifier
functions article covers them one by one.
| Function | Description |
|---|---|
modify_header() |
update column headers |
modify_footnote_header() |
update column header footnotes |
modify_footnote_body() |
update table body footnotes |
modify_spanning_header() |
update spanning headers |
modify_caption() |
update the table caption |
bold_labels() |
bold variable labels |
bold_levels() |
bold variable levels |
italicize_labels() |
italicize variable labels |
italicize_levels() |
italicize variable levels |
bold_p() |
bold significant p-values |
sort_p() |
sort the variables by p-value |
filter_p() |
keep the variables below a p-value threshold |
remove_row_type() |
remove reference rows, header rows or levels |
lt functions
The table is rendered by the lt package. as_lt()
converts it, and the lt functions apply from there.
tbl_regression(m1, exponentiate = TRUE) |>
as_lt() |>
lt::lt_note("Data are simulated")Example
A table with odds ratios, global p-values, p-values rounded to two decimal places and bold below 0.10, bold labels and italic levels:
tbl_regression(m1, exponentiate = TRUE, pvalue_fun = label_style_pvalue(digits = 2)) |>
add_global_p() |>
bold_p(t = 0.10) |>
bold_labels() |>
italicize_levels()Univariable regression
tbl_uvregression() fits one model per variable and
stacks the results. It is a wrapper around
tbl_regression(), so it takes nearly the same arguments,
and its result can be modified in the same ways. With y,
every variable in include is the predictor of its own
model; with x, every variable is the outcome.
trial |>
tbl_uvregression(
method = glm,
y = response,
include = c(age, grade),
method.args = list(family = binomial),
exponentiate = TRUE,
pvalue_fun = label_style_pvalue(digits = 2)
) |>
add_global_p() |>
add_nevent() |>
add_q() |>
bold_p() |>
bold_p(t = 0.10, q = TRUE) |>
bold_labels()The formula argument adjusts every model for the same
covariates, e.g. formula = "{y} ~ {x} + age".
Quoting the table in text
inline_text() returns a term’s estimate, interval and
p-value as a string, so a sentence in a Quarto or R Markdown document
updates with the data. The default pattern is
"{estimate} ({conf.level*100}% CI {conf.low}, {conf.high}; {p.value})";
the columns of the row are available for other patterns.
tbl <- tbl_regression(m1, exponentiate = TRUE)
inline_text(tbl, variable = age)
#> [1] "1.00 (95% CI 0.97, 1.03; p>0.9)"
inline_text(tbl, variable = stage, level = "T4", pattern = "OR {estimate}, {p.value}")
#> [1] "OR 0.36, p=0.063"What the tidier does
tbl_regression() reads everything it needs from the
fitted model: the coefficients from summary(), the
intervals from confint() (see the reference page for which
interval each model class gets), and the mapping from coefficients to
variables from the model matrix. The rules for the less common
cases:
- A factor, character or logical variable gets a label row, a
reference row and one row per other level. The reference level is the
first level for the default treatment contrasts, the last level for
contr.SASandcontr.sum; Helmert, polynomial and custom contrasts have no reference level, so those variables show their coefficient names. - Interaction terms are labelled by their parts,
Age * Drug B. - A term built from a function, such as
log(ttdeath)orI(age^2), keeps the label attribute of its variable. A term that spans several columns, such assplines::ns(age, 3), gets a label row and one row per column. - The N on label rows is the number of observations in the model; on level rows it is the number of observations at that level.
lm(marker ~ age * trt + log(ttdeath), trial) |>
tbl_regression() |>
add_n(location = c("label", "level"))Supported models
lm(), glm() (with every family) and
survival::coxph() are tidied in base R and tested against
gtsummary. Any other model class works through
broom::tidy() when broom is installed, or through a
function passed in tidy_fun that returns a data frame with
a term column and the estimate,
std.error, statistic, p.value,
conf.low and conf.high columns. For those
classes the variable metadata comes from the model matrix when the class
provides one (model.matrix() and model.frame()
methods), and each coefficient is otherwise shown on its own row.