Formatting results tables
There are many great resources for creating summary results tables in R. If you search online you’ll find dozens of packages and tutorials showing you how to create beautiful tables. This tutorial shows just a small fraction of options.
Summary statistics
Don’t do this
skim(chipotle)
Name | chipotle |
Number of rows | 30 |
Number of columns | 7 |
_______________________ | |
Column type frequency: | |
character | 4 |
Date | 1 |
numeric | 2 |
________________________ | |
Group variables | None |
Variable type: character
skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
---|---|---|---|---|---|---|---|
order | 0 | 1 | 6 | 6 | 0 | 2 | 0 |
meat | 0 | 1 | 7 | 8 | 0 | 2 | 0 |
store | 0 | 1 | 7 | 7 | 0 | 3 | 0 |
food | 0 | 1 | 4 | 7 | 0 | 2 | 0 |
Variable type: Date
skim_variable | n_missing | complete_rate | min | max | median | n_unique |
---|---|---|---|---|---|---|
date | 0 | 1 | 2024-01-12 | 2024-02-10 | 2024-01-26 | 30 |
Variable type: numeric
skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
---|---|---|---|---|---|---|---|---|---|---|
day | 0 | 1 | 15.5 | 8.80 | 1.00 | 8.25 | 15.50 | 22.75 | 30.00 | ▇▇▇▇▇ |
weight | 0 | 1 | 810.8 | 123.37 | 510.29 | 715.82 | 793.79 | 907.18 | 1048.93 | ▁▆▇▇▂ |
skim()
is great for exploratory analysis - not for presenting results in a published document.
Do this instead
{gtsummary} has functions for calculating descriptive statistics and formatting them nicely in a table.
chipotle |>
# select columns to summarize
select(order:weight) |>
# use gtsummary::tbl_summary()
tbl_summary()
Characteristic | N = 301 |
---|---|
order | |
Online | 15 (50%) |
Person | 15 (50%) |
meat | |
Carnitas | 10 (33%) |
Chicken | 20 (67%) |
store | |
Store 1 | 10 (33%) |
Store 2 | 10 (33%) |
Store 3 | 10 (33%) |
food | |
bowl | 15 (50%) |
burrito | 15 (50%) |
weight | 794 (709, 907) |
1 n (%); Median (Q1, Q3) |
# by group
chipotle |>
select(order:weight) |>
tbl_summary(
by = order,
# include human readable labels for variables
label = list(
meat = "Meat",
store = "Store",
food = "Meal type",
weight = "Weight (grams)"
)
)
Characteristic |
Online N = 151 |
Person N = 151 |
---|---|---|
Meat | ||
Carnitas | 6 (40%) | 4 (27%) |
Chicken | 9 (60%) | 11 (73%) |
Store | ||
Store 1 | 5 (33%) | 5 (33%) |
Store 2 | 5 (33%) | 5 (33%) |
Store 3 | 5 (33%) | 5 (33%) |
Meal type | ||
bowl | 8 (53%) | 7 (47%) |
burrito | 7 (47%) | 8 (53%) |
Weight (grams) | 709 (652, 822) | 907 (794, 936) |
1 n (%); Median (Q1, Q3) |
The tbl_summary()
tutorial includes many examples of how to generate and customize summary tables.
Linear regression model
Don’t do this
Say you estimate a linear regression model to predict the weight of each order based on the order type, meat type, food type, and store. You might report the results like this:
# observed results
obs_fit <- chipotle |>
specify(weight ~ order + meat + food + store) |>
fit()
# null distribution for p-values
null_full_dist <- chipotle |>
specify(weight ~ order + meat + food + store) |>
hypothesize(null = "independence") |>
generate(reps = 1000, type = "permute") |>
fit()
p_vals <- get_p_value(null_full_dist, obs_stat = obs_fit, direction = "two-sided")
visualize(null_full_dist) +
shade_p_value(obs_stat = obs_fit, direction = "two-sided")
# bootstrap distribution for CIs
boot_full_dist <- chipotle |>
specify(weight ~ order + meat + food + store) |>
generate(reps = 1000, type = "bootstrap") |>
fit()
# get 95% confidence interval
conf_ints <- get_ci(boot_full_dist, level = 0.95, point_estimate = obs_fit)
visualize(boot_full_dist) +
shade_ci(endpoints = conf_ints)
Please don’t. {infer} has several methods for visualizing \(p\)-values and confidence intervals, but they are best used for exploratory analysis, not for reporting results.
Do this instead
Once again, {gtsummary} can be used to create regression results tables.
# fit a standard linear regression model and rely on theoretical assumptions
# for confidence intervals and p-values
linear_reg() |>
fit(weight ~ order + meat + food + store, data = chipotle) |>
# basic results table
tbl_regression()
Characteristic | Beta | 95% CI | p-value |
---|---|---|---|
order | |||
Online | — | — | |
Person | 161 | 98, 223 | <0.001 |
meat | |||
Carnitas | — | — | |
Chicken | -29 | -96, 38 | 0.4 |
food | |||
bowl | — | — | |
burrito | -82 | -145, -20 | 0.012 |
store | |||
Store 1 | — | — | |
Store 2 | -62 | -138, 14 | 0.10 |
Store 3 | -93 | -169, -18 | 0.018 |
Abbreviation: CI = Confidence Interval |
```{r}
#| tbl-cap: An example regression results table
linear_reg() |>
fit(weight ~ order + meat + food + store, data = chipotle) |>
# basic results table
tbl_regression(
# format variable labels
label = list(
order = "Order method",
meat = "Meat",
store = "Store",
food = "Meal type"
),
# round p-values to 2 significant digits
pvalue_fun = label_style_pvalue(digits = 2)
) |>
# add standard error and test statistic
modify_header(
statistic = "**Statistic**",
std.error = "**SE**"
) |>
# anything below the alpha threshold of 0.05 - format in bold
bold_p(t = 0.05) |>
# make labels bold
bold_labels() |>
# italicize levels for categorical variables
italicize_levels()
```
Characteristic | Beta | SE | Statistic | 95% CI | p-value |
---|---|---|---|---|---|
Order method | |||||
Online | — | — | — | — | |
Person | 161 | 30.2 | 5.31 | 98, 223 | <0.001 |
Meat | |||||
Carnitas | — | — | — | — | |
Chicken | -29 | 32.5 | -0.907 | -96, 38 | 0.37 |
Meal type | |||||
bowl | — | — | — | — | |
burrito | -82 | 30.2 | -2.73 | -145, -20 | 0.012 |
Store | |||||
Store 1 | — | — | — | — | |
Store 2 | -62 | 36.7 | -1.69 | -138, 14 | 0.10 |
Store 3 | -93 | 36.7 | -2.54 | -169, -18 | 0.018 |
Abbreviations: CI = Confidence Interval, SE = Standard Error |
Use the tbl-cap
code chunk option to add captions for tables produced programmatically. See the Quarto documentation for more examples of formatting tables using Quarto.
Alternatively, you can create a regression results plot that reports your estimated coefficients and CIs using a point range plot.
# visualize regression results using a coefficient plot
obs_fit |>
# join with confidence intervals
left_join(conf_ints) |>
# order the coefficients by size, pull intercept to the beginning (by convention)
mutate(term = fct_reorder(.f = term, .x = estimate) |>
fct_relevel("intercept")) |>
# draw a pointrange plot
ggplot(mapping = aes(x = estimate, y = term, xmin = lower_ci, xmax = upper_ci)) +
geom_pointrange() +
# draw a vertical line at 0
geom_vline(xintercept = 0, linetype = "dashed") +
theme_minimal()
Notice this still requires substantial cleaning to make it publication-ready (e.g. title, axis labels, human-readable labels for each coefficient, etc.).
sessioninfo::session_info()
─ Session info ───────────────────────────────────────────────────────────────
setting value
version R version 4.4.2 (2024-10-31)
os macOS Sonoma 14.6.1
system aarch64, darwin20
ui X11
language (EN)
collate en_US.UTF-8
ctype en_US.UTF-8
tz America/New_York
date 2025-04-07
pandoc 3.4 @ /usr/local/bin/ (via rmarkdown)
─ Packages ───────────────────────────────────────────────────────────────────
package * version date (UTC) lib source
archive 1.1.9 2024-09-12 [1] CRAN (R 4.4.1)
backports 1.5.0 2024-05-23 [1] CRAN (R 4.4.0)
base64enc 0.1-3 2015-07-28 [1] CRAN (R 4.3.0)
bit 4.0.5 2022-11-15 [1] CRAN (R 4.3.0)
bit64 4.0.5 2020-08-30 [1] CRAN (R 4.3.0)
broom * 1.0.6 2024-05-17 [1] CRAN (R 4.4.0)
broom.helpers 1.20.0 2025-03-06 [1] CRAN (R 4.4.1)
cards 0.5.1 2025-03-01 [1] CRAN (R 4.4.1)
class 7.3-22 2023-05-03 [1] CRAN (R 4.4.2)
cli 3.6.4 2025-02-13 [1] CRAN (R 4.4.1)
codetools 0.2-20 2024-03-31 [1] CRAN (R 4.4.2)
commonmark 1.9.1 2024-01-30 [1] CRAN (R 4.3.1)
crayon 1.5.3 2024-06-20 [1] CRAN (R 4.4.0)
data.table 1.15.4 2024-03-30 [1] CRAN (R 4.3.1)
dials * 1.3.0 2024-07-30 [1] CRAN (R 4.4.0)
DiceDesign 1.10 2023-12-07 [1] CRAN (R 4.3.1)
dichromat 2.0-0.1 2022-05-02 [1] CRAN (R 4.3.0)
digest 0.6.37 2024-08-19 [1] CRAN (R 4.4.1)
dplyr * 1.1.4 2023-11-17 [1] CRAN (R 4.3.1)
evaluate 1.0.3 2025-01-10 [1] CRAN (R 4.4.1)
farver 2.1.2 2024-05-13 [1] CRAN (R 4.3.3)
fastmap 1.2.0 2024-05-15 [1] CRAN (R 4.4.0)
forcats * 1.0.0 2023-01-29 [1] CRAN (R 4.3.0)
foreach 1.5.2 2022-02-02 [1] CRAN (R 4.3.0)
furrr 0.3.1 2022-08-15 [1] CRAN (R 4.3.0)
future 1.33.2 2024-03-26 [1] CRAN (R 4.3.1)
future.apply 1.11.2 2024-03-28 [1] CRAN (R 4.3.1)
generics 0.1.3 2022-07-05 [1] CRAN (R 4.3.0)
ggplot2 * 3.5.1 2024-04-23 [1] CRAN (R 4.3.1)
globals 0.16.3 2024-03-08 [1] CRAN (R 4.3.1)
glue 1.8.0 2024-09-30 [1] CRAN (R 4.4.1)
gower 1.0.1 2022-12-22 [1] CRAN (R 4.3.0)
GPfit 1.0-8 2019-02-08 [1] CRAN (R 4.3.0)
gt * 0.11.1 2024-10-04 [1] CRAN (R 4.4.1)
gtable 0.3.6 2024-10-25 [1] CRAN (R 4.4.1)
gtsummary * 2.1.0 2025-02-19 [1] CRAN (R 4.4.1)
hardhat 1.4.0 2024-06-02 [1] CRAN (R 4.4.0)
haven 2.5.4 2023-11-30 [1] CRAN (R 4.3.1)
here 1.0.1 2020-12-13 [1] CRAN (R 4.3.0)
hms 1.1.3 2023-03-21 [1] CRAN (R 4.3.0)
htmltools 0.5.8.1 2024-04-04 [1] CRAN (R 4.3.1)
htmlwidgets 1.6.4 2023-12-06 [1] CRAN (R 4.3.1)
infer * 1.0.7 2024-03-25 [1] CRAN (R 4.3.1)
ipred 0.9-14 2023-03-09 [1] CRAN (R 4.3.0)
iterators 1.0.14 2022-02-05 [1] CRAN (R 4.3.0)
jsonlite 1.8.9 2024-09-20 [1] CRAN (R 4.4.1)
knitr 1.49 2024-11-08 [1] CRAN (R 4.4.1)
labeling 0.4.3 2023-08-29 [1] CRAN (R 4.3.0)
labelled 2.13.0 2024-04-23 [1] CRAN (R 4.4.0)
lattice 0.22-6 2024-03-20 [1] CRAN (R 4.4.2)
lava 1.8.0 2024-03-05 [1] CRAN (R 4.3.1)
lhs 1.1.6 2022-12-17 [1] CRAN (R 4.3.0)
lifecycle 1.0.4 2023-11-07 [1] CRAN (R 4.3.1)
listenv 0.9.1 2024-01-29 [1] CRAN (R 4.3.1)
lubridate * 1.9.3 2023-09-27 [1] CRAN (R 4.3.1)
magrittr 2.0.3 2022-03-30 [1] CRAN (R 4.3.0)
markdown 1.13 2024-06-04 [1] CRAN (R 4.4.0)
MASS 7.3-61 2024-06-13 [1] CRAN (R 4.4.2)
Matrix 1.7-1 2024-10-18 [1] CRAN (R 4.4.2)
modeldata * 1.4.0 2024-06-19 [1] CRAN (R 4.4.0)
nnet 7.3-19 2023-05-03 [1] CRAN (R 4.4.2)
parallelly 1.37.1 2024-02-29 [1] CRAN (R 4.3.1)
parsnip * 1.2.1 2024-03-22 [1] CRAN (R 4.3.1)
pillar 1.10.2 2025-04-05 [1] CRAN (R 4.4.1)
pkgconfig 2.0.3 2019-09-22 [1] CRAN (R 4.3.0)
prodlim 2023.08.28 2023-08-28 [1] CRAN (R 4.3.0)
purrr * 1.0.2 2023-08-10 [1] CRAN (R 4.3.0)
R6 2.5.1 2021-08-19 [1] CRAN (R 4.3.0)
RColorBrewer 1.1-3 2022-04-03 [1] CRAN (R 4.3.0)
Rcpp 1.0.14 2025-01-12 [1] CRAN (R 4.4.1)
readr * 2.1.5 2024-01-10 [1] CRAN (R 4.3.1)
recipes * 1.0.10 2024-02-18 [1] CRAN (R 4.3.1)
repr 1.1.7 2024-03-22 [1] CRAN (R 4.4.0)
rlang 1.1.5 2025-01-17 [1] CRAN (R 4.4.1)
rmarkdown 2.29 2024-11-04 [1] CRAN (R 4.4.1)
rpart 4.1.23 2023-12-05 [1] CRAN (R 4.4.2)
rprojroot 2.0.4 2023-11-05 [1] CRAN (R 4.3.1)
rsample * 1.2.1 2024-03-25 [1] CRAN (R 4.3.1)
rstudioapi 0.17.0 2024-10-16 [1] CRAN (R 4.4.1)
sass 0.4.9 2024-03-15 [1] CRAN (R 4.3.1)
scales * 1.3.0.9000 2025-03-19 [1] Github (bensoltoff/scales@71d8f13)
sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.3.0)
skimr * 2.1.5 2022-12-23 [1] CRAN (R 4.3.0)
stringi 1.8.4 2024-05-06 [1] CRAN (R 4.3.1)
stringr * 1.5.1 2023-11-14 [1] CRAN (R 4.3.1)
survival 3.7-0 2024-06-05 [1] CRAN (R 4.4.2)
tibble * 3.2.1 2023-03-20 [1] CRAN (R 4.3.0)
tidymodels * 1.2.0 2024-03-25 [1] CRAN (R 4.3.1)
tidyr * 1.3.1 2024-01-24 [1] CRAN (R 4.3.1)
tidyselect 1.2.1 2024-03-11 [1] CRAN (R 4.3.1)
tidyverse * 2.0.0 2023-02-22 [1] CRAN (R 4.3.0)
timechange 0.3.0 2024-01-18 [1] CRAN (R 4.3.1)
timeDate 4032.109 2023-12-14 [1] CRAN (R 4.3.1)
tune * 1.2.1 2024-04-18 [1] CRAN (R 4.3.1)
tzdb 0.4.0 2023-05-12 [1] CRAN (R 4.3.0)
vctrs 0.6.5 2023-12-01 [1] CRAN (R 4.3.1)
vroom 1.6.5 2023-12-05 [1] CRAN (R 4.3.1)
withr 3.0.2 2024-10-28 [1] CRAN (R 4.4.1)
workflows * 1.1.4 2024-02-19 [1] CRAN (R 4.4.0)
workflowsets * 1.1.0 2024-03-21 [1] CRAN (R 4.3.1)
xfun 0.50.5 2025-01-15 [1] https://yihui.r-universe.dev (R 4.4.2)
xml2 1.3.6 2023-12-04 [1] CRAN (R 4.3.1)
yaml 2.3.10 2024-07-26 [1] CRAN (R 4.4.0)
yardstick * 1.3.1 2024-03-21 [1] CRAN (R 4.3.1)
[1] /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/library
──────────────────────────────────────────────────────────────────────────────