# ============================================================
# 0. Setup
# ============================================================
# If needed (run once in Console):
# install.packages(c("tidyverse","broom","kableExtra"))
library(tidyverse)
library(broom)
library(kableExtra)
theme_set(theme_minimal(base_size = 12))
options(max.print = 75)BS2004 5-minute demo, publishing-grade tables in R
Goal
Turn model output into a clean table suitable for a report, without copy-paste.
1. Fit a simple model (built-in data)
We use the built-in mtcars dataset. Question: does weight and horsepower predict mpg?
data("mtcars")
d <- mtcars |> as_tibble() |> select(mpg, wt, hp)
m <- lm(mpg ~ wt + hp, data = d)
summary(m)
Call:
lm(formula = mpg ~ wt + hp, data = d)
Residuals:
Min 1Q Median 3Q Max
-3.941 -1.600 -0.182 1.050 5.854
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 37.22727 1.59879 23.285 < 2e-16 ***
wt -3.87783 0.63273 -6.129 1.12e-06 ***
hp -0.03177 0.00903 -3.519 0.00145 **
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 2.593 on 29 degrees of freedom
Multiple R-squared: 0.8268, Adjusted R-squared: 0.8148
F-statistic: 69.21 on 2 and 29 DF, p-value: 9.109e-12
2. Tidy the model output (broom)
broom::tidy() turns the model output into a data frame.
tidy(m)# A tibble: 3 × 5
term estimate std.error statistic p.value
<chr> <dbl> <dbl> <dbl> <dbl>
1 (Intercept) 37.2 1.60 23.3 2.57e-20
2 wt -3.88 0.633 -6.13 1.12e- 6
3 hp -0.0318 0.00903 -3.52 1.45e- 3
3. Make a neat table (kableExtra)
Create a table with rounding, friendly column names, and a caption.
tab <- tidy(m) |>
mutate(across(where(is.numeric), ~ round(.x, 3))) |>
rename(
Term = term,
Estimate = estimate,
`Std error` = std.error,
`t value` = statistic,
`p value` = p.value
)
kable(tab, caption = "Multiple regression: mpg as a function of weight and horsepower") |>
kable_styling(full_width = FALSE)| Term | Estimate | Std error | t value | p value |
|---|---|---|---|---|
| (Intercept) | 37.227 | 1.599 | 23.285 | 0.000 |
| wt | -3.878 | 0.633 | -6.129 | 0.000 |
| hp | -0.032 | 0.009 | -3.519 | 0.001 |
4. Add confidence intervals (often more useful than p values)
tab_ci <- tidy(m, conf.int = TRUE) |>
mutate(across(where(is.numeric), ~ round(.x, 3))) |>
rename(
Term = term,
Estimate = estimate,
`Std error` = std.error,
`t value` = statistic,
`p value` = p.value,
`CI low` = conf.low,
`CI high` = conf.high
)
kable(tab_ci, caption = "Same model with 95% confidence intervals") |>
kable_styling(full_width = FALSE)| Term | Estimate | Std error | t value | p value | CI low | CI high |
|---|---|---|---|---|---|---|
| (Intercept) | 37.227 | 1.599 | 23.285 | 0.000 | 33.957 | 40.497 |
| wt | -3.878 | 0.633 | -6.129 | 0.000 | -5.172 | -2.584 |
| hp | -0.032 | 0.009 | -3.519 | 0.001 | -0.050 | -0.013 |
5. Report model fit in a table (R squared and sample size)
fit <- glance(m) |>
transmute(
N = nobs,
`R squared` = round(r.squared, 3),
`Adj R squared` = round(adj.r.squared, 3),
AIC = round(AIC, 1),
BIC = round(BIC, 1),
`Residual SE` = round(sigma, 3)
)
kable(fit, caption = "Model fit summary") |>
kable_styling(full_width = FALSE)| N | R squared | Adj R squared | AIC | BIC | Residual SE |
|---|---|---|---|---|---|
| 32 | 0.827 | 0.815 | 156.7 | 162.5 | 2.593 |
6. Common report workflow
Typical pattern for any analysis: 1) fit model, 2) tidy() for coefficients, 3) glance() for model summary, 4) kable() for the final table.
Optional: export the table to Word-friendly format
If rendering to Word, the table above will appear in the document. If you need a standalone file export, a simple option is to write a CSV.
write_csv(tab_ci, "model_coefficients_table.csv")Reproducibility
sessionInfo()R version 4.5.0 (2025-04-11)
Platform: x86_64-apple-darwin20
Running under: macOS Sequoia 15.6.1
Matrix products: default
BLAS: /Library/Frameworks/R.framework/Versions/4.5-x86_64/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.5-x86_64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: Europe/London
tzcode source: internal
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] kableExtra_1.4.0 broom_1.0.8 lubridate_1.9.4 forcats_1.0.0
[5] stringr_1.5.2 dplyr_1.1.4 purrr_1.1.0 readr_2.1.5
[9] tidyr_1.3.1 tibble_3.3.0 ggplot2_4.0.0 tidyverse_2.0.0
loaded via a namespace (and not attached):
[1] utf8_1.2.6 generics_0.1.4 xml2_1.4.0 stringi_1.8.7
[5] hms_1.1.3 digest_0.6.37 magrittr_2.0.4 evaluate_1.0.4
[9] grid_4.5.0 timechange_0.3.0 RColorBrewer_1.1-3 fastmap_1.2.0
[13] jsonlite_2.0.0 backports_1.5.0 viridisLite_0.4.2 scales_1.4.0
[17] textshaping_1.0.1 cli_3.6.5 crayon_1.5.3 rlang_1.1.6
[21] bit64_4.6.0-1 withr_3.0.2 yaml_2.3.10 parallel_4.5.0
[25] tools_4.5.0 tzdb_0.5.0 vctrs_0.6.5 R6_2.6.1
[29] lifecycle_1.0.4 bit_4.6.0 htmlwidgets_1.6.4 vroom_1.6.5
[33] pkgconfig_2.0.3 pillar_1.11.1 gtable_0.3.6 glue_1.8.0
[37] systemfonts_1.3.1 xfun_0.53 tidyselect_1.2.1 rstudioapi_0.17.1
[41] knitr_1.50 farver_2.1.2 htmltools_0.5.8.1 rmarkdown_2.29
[45] svglite_2.2.2 compiler_4.5.0 S7_0.2.0