To customize the appearance of tables, modelsummary supports four of the most popular table-making packages:

  1. gt: https://gt.rstudio.com
  2. kableExtra: http://haozhu233.github.io/kableExtra
  3. huxtable: https://hughjonesd.github.io/huxtable/
  4. flextable: https://davidgohel.github.io/flextable/

Users are encouraged to visit these websites to determine which package suits their needs best. Each of them has different strengths and weaknesses. For instance, gt allows seamless integration with the RStudio IDE, but kableExtra’s LaTeX (and PDF) output is far more mature.

To create customized tables, the analyst begins by calling modelsummary(models) to create a summary table. Then, she post-processes the table by applying functions from one of the packages listed above. It is often convenient to use the %>% operator to do this.

To illustrate, we download data from the Rdatasets repository and we estimate 5 models:

library(modelsummary)

url <- 'https://vincentarelbundock.github.io/Rdatasets/csv/HistData/Guerry.csv'
dat <- read.csv(url)

models <- list()
models[['OLS 1']] <- lm(Donations ~ Literacy, data = dat)
models[['Poisson 1']] <- glm(Donations ~ Literacy + Clergy, family = poisson, data = dat)
models[['OLS 2']] <- lm(Crime_pers ~ Literacy, data = dat)
models[['Poisson 2']] <- glm(Crime_pers ~ Literacy + Clergy, family = poisson, data = dat)
models[['OLS 3']] <- lm(Crime_prop ~ Literacy + Clergy, data = dat)

In the rest of this vignette, we will customize tables using tools supplied by the gt, kableExtra, flextable, and huxtable packages. In each case, the pattern will be similar. First, we create a table by calling modelsummary and by specifying the output format with the output parameter. Then, we will use functions from the four packages to customize the appearance of our tables.

gt

To illustrate how to customize tables using the gt package we will use the following functions from the gt package:

  • tab_spanner creates labels to group columns.
  • tab_footnote adds a footnote and a matching marking in a specific cell.
  • tab_style can modify the text and color of rows, columns, or cells.

To produce a “cleaner” look, we will also use modelsummary’s stars, coef_map, gof_omit, and title arguments.

Note that in order to access gt functions, we must first load the library.

library(gt)
#> 
#> Attaching package: 'gt'
#> The following object is masked from 'package:modelsummary':
#> 
#>     escape_latex

# build table with `modelsummary` 
cm <- c( '(Intercept)' = 'Constant', 'Literacy' = 'Literacy (%)', 'Clergy' = 'Priests/capita')
cap <- 'A modelsummary table customized with gt'

tab <- modelsummary(models, 
                output = "gt",
                coef_map = cm, stars = TRUE, 
                title = cap, gof_omit = 'IC|Log|Adj') 
#> Warning: In version 0.8.0 of the `modelsummary` package, the default significance markers produced by the `stars=TRUE` argument were changed to be consistent with R's defaults.
#> This warning is displayed once per session.

# customize table with `gt`

tab %>%

    # column labels
    tab_spanner(label = 'Donations', columns = 2:3) %>%
    tab_spanner(label = 'Crimes (persons)', columns = 4:5) %>%
    tab_spanner(label = 'Crimes (property)', columns = 6) %>%

    # footnote
    tab_footnote(footnote = md("A very **important** variable."),
                 locations = cells_body(rows = 3, columns = 1)) %>%

    # text and background color
    tab_style(style = cell_text(color = 'red'),
              locations = cells_body(rows = 3)) %>%
    tab_style(style = cell_fill(color = 'lightblue'),
              locations = cells_body(rows = 5))
A modelsummary table customized with gt
Donations Crimes (persons) Crimes (property)
OLS 1 Poisson 1 OLS 2 Poisson 2 OLS 3
Constant 8759.068*** 8.986*** 20357.309*** 9.708*** 11243.544***
(1559.363) (0.004) (2020.980) (0.003) (1011.240)
Literacy (%)1 -42.886 -0.006*** -15.358 0.000*** -68.507***
(36.362) (0.000) (47.127) (0.000) (18.029)
Priests/capita 0.002*** 0.004*** -16.376
(0.000) (0.000) (12.522)
Num.Obs. 86 86 86 86 86
R2 0.016 0.001 0.152
F 1.391 4170.610 0.106 7905.811 7.441
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

1 A very important variable.

The gt website offers many more examples. The possibilities are endless. For instance, gt allows you to embed images in your tables using the text_transform and local_image functions:

f <- function(x) web_image(url = "https://user-images.githubusercontent.com/987057/82732352-b9aabf00-9cda-11ea-92a6-26750cf097d0.png", height = 80)

tab %>% 
    text_transform(locations = cells_body(columns = 2:6, rows = 1), fn = f)
A modelsummary table customized with gt
OLS 1 Poisson 1 OLS 2 Poisson 2 OLS 3
Constant
(1559.363) (0.004) (2020.980) (0.003) (1011.240)
Literacy (%) -42.886 -0.006*** -15.358 0.000*** -68.507***
(36.362) (0.000) (47.127) (0.000) (18.029)
Priests/capita 0.002*** 0.004*** -16.376
(0.000) (0.000) (12.522)
Num.Obs. 86 86 86 86 86
R2 0.016 0.001 0.152
F 1.391 4170.610 0.106 7905.811 7.441
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

kableExtra

We will now illustrate how to customize tables using functions from the kableExtra package:

  • add_header_above creates labels to group columns.
  • add_footnote adds a footnote and a matching marking in a specific cell.
  • row_spec can modify the text and color of rows, columns, or cells.

We use the same code as above, but specify output='kableExtra' in the modelsummary() call:

library(kableExtra)

# build table with `modelsummary` 
cm <- c( '(Intercept)' = 'Constant', 'Literacy' = 'Literacy (%)', 'Clergy' = 'Priests/capita')
cap <- 'A modelsummary table customized with kableExtra'

tab <- modelsummary(models, output = 'kableExtra',
                coef_map = cm, stars = TRUE, 
                title = cap, gof_omit = 'IC|Log|Adj') 

# customize table with `kableExtra`
tab %>%
    
    # column labels
    add_header_above(c(" " = 1, "Donations" = 2, "Crimes (person)" = 2, "Crimes (property)" = 1)) %>%
   
    # text and background color
    row_spec(3, color = 'red') %>%
    row_spec(5, background = 'lightblue')
A modelsummary table customized with kableExtra
Donations
Crimes (person)
Crimes (property)
OLS 1 Poisson 1 OLS 2 Poisson 2 OLS 3
Constant 8759.068*** 8.986*** 20357.309*** 9.708*** 11243.544***
(1559.363) (0.004) (2020.980) (0.003) (1011.240)
Literacy (%) −42.886 −0.006*** −15.358 0.000*** −68.507***
(36.362) (0.000) (47.127) (0.000) (18.029)
Priests/capita 0.002*** 0.004*** −16.376
(0.000) (0.000) (12.522)
Num.Obs. 86 86 86 86 86
R2 0.016 0.001 0.152
F 1.391 4170.610 0.106 7905.811 7.441
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

These kableExtra functions can be used to produce LaTeX / PDF tables such as this one:

flextable

We will now illustrate how to customize tables using functions from the flextable package:

  • color to modify the color of the text
  • bg to modify the color of the background
  • autofit sets column width to sensible values.

We use the same code as above, but specify output='flextable' in the modelsummary() call:

library(flextable)
#> 
#> Attaching package: 'flextable'
#> The following objects are masked from 'package:kableExtra':
#> 
#>     as_image, footnote

# build table with `modelsummary` 
cm <- c( '(Intercept)' = 'Constant', 'Literacy' = 'Literacy (%)', 'Clergy' = 'Priests/capita')
cap <- 'A modelsummary table customized with flextable'

tab <- modelsummary(models, output = 'flextable',
                coef_map = cm, stars = TRUE, 
                title = cap, gof_omit = 'IC|Log|Adj') 

# customize table with `flextable`
tab %>%
   
    # text and background color
    color(3, color = 'red') %>%
    bg(5, bg = 'lightblue') %>%
  
    # column widths
    autofit()

huxtable

We will now illustrate how to customize tables using functions from the huxtable package:

  • set_text_color to change the color of some entries

We use the same code as above, but specify output='huxtable' in the modelsummary() call:

library(huxtable)

# build table with `modelsummary` 
cm <- c( '(Intercept)' = 'Constant', 'Literacy' = 'Literacy (%)', 'Clergy' = 'Priests/capita')
cap <- 'A modelsummary table customized with huxtable'

tab <- modelsummary(models, output = 'huxtable',
                coef_map = cm, stars = TRUE, 
                title = cap, gof_omit = 'IC|Log|Adj') 

# customize table with `huxtable`
tab %>%
   
    # text color
    set_text_color(row = 4, col = 1:ncol(.), value = 'red')
A modelsummary table customized with huxtable
OLS 1 Poisson 1 OLS 2 Poisson 2 OLS 3
Constant 8759.068*** 8.986*** 20357.309*** 9.708*** 11243.544***
(1559.363) (0.004) (2020.980) (0.003) (1011.240)
Literacy (%) -42.886 -0.006*** -15.358 0.000*** -68.507***
(36.362) (0.000) (47.127) (0.000) (18.029)
Priests/capita 0.002*** 0.004*** -16.376
(0.000) (0.000) (12.522)
Num.Obs. 86 86 86 86 86
R2 0.016 0.001 0.152
F 1.391 4170.610 0.106 7905.811 7.441
+ p < 0.1, * p < 0.05, ** p < 0.01, *** p < 0.001

Themes

If you want to apply the same post-processing functions to your tables, you can use modelsummary’s theming functionality. To do so, we first create a function to post-process a table. This function must accept a table as its first argument, and include the ellipsis (...). Optionally, the theming function can also accept an hrule argument which is a vector of row positions where we insert horizontal rule, and an output_format which allows output format-specific customization. For inspiration, you may want to consult the default modelsummary themes in the themes.R file of the Github repository.

Once the theming function is created, we assign it to a global option called modelsummary_theme_kableExtra, modelsummary_theme_gt, modelsummary_theme_flextable, or modelsummary_theme_huxtable. For example, if you want to add row striping to all your gt tables:

library(gt)

# The ... ellipsis is required!
custom_theme <- function(x, ...) {
    x %>% gt::opt_row_striping(row_striping = TRUE)
}
options("modelsummary_theme_gt" = custom_theme)

mod <- lm(mpg ~ hp + drat, mtcars)
modelsummary(mod, output = "gt")
Model 1
(Intercept) 10.790
(5.078)
hp -0.052
(0.009)
drat 4.698
(1.192)
Num.Obs. 32
R2 0.741
R2 Adj. 0.723
AIC 169.5
BIC 175.4
Log.Lik. -80.752
F 41.522
url <- 'https://vincentarelbundock.github.io/Rdatasets/csv/palmerpenguins/penguins.csv'
penguins <- read.csv(url)

datasummary_crosstab(island ~ sex * species, output = "gt", data = penguins)
island female male NA All
Adelie Chinstrap Gentoo Adelie Chinstrap Gentoo Adelie Chinstrap Gentoo
Biscoe N 22 0 58 22 0 61 0 0 5 168
% row 13.1 0.0 34.5 13.1 0.0 36.3 0.0 0.0 3.0 100.0
Dream N 27 34 0 28 34 0 1 0 0 124
% row 21.8 27.4 0.0 22.6 27.4 0.0 0.8 0.0 0.0 100.0
Torgersen N 24 0 0 23 0 0 5 0 0 52
% row 46.2 0.0 0.0 44.2 0.0 0.0 9.6 0.0 0.0 100.0
All N 73 34 58 73 34 61 6 0 5 344
% row 21.2 9.9 16.9 21.2 9.9 17.7 1.7 0.0 1.5 100.0

Warning: Saving to file

When users supply a file name to the output argument, the table is written immediately to file. This means that users cannot post-process and customize the resulting table using functions from gt, kableExtra, huxtable, or flextable. When users specify a filename in the output argument, the modelsummary() call should be the final one in the chain.

This is OK:

modelsummary(models, output = 'table.html')

This is not OK:

modelsummary(models, output = 'table.html') %>%
    tab_spanner(label = 'Literacy', columns = c('OLS 1', 'Poisson 1'))

To save a customized table, you should apply all the customization functions you need before saving it using gt::gtsave, kableExtra::save_kable, or the appropriate helper function from the package that you are using to customize your table.

For example, to add color column spanners with the gt package:

library(gt)

tab <- modelsummary(models, output = "gt") %>%
  tab_spanner(label = 'Donations', columns = 2:3) %>%
  tab_spanner(label = 'Crimes (persons)', columns = 4:5) %>%
  tab_spanner(label = 'Crimes (property)', columns = 6)

gt::gtsave(tab, filename = "table.html")

The procedure is slightly different with kableExtra, because this package needs to know the final output format immediately when the table is created. For instance, if we want to produce, customize, and save a LaTeX table:

library(kableExtra)

tab <- modelsummary(models, output = 'latex') %>%
  add_header_above(c(" " = 1, "Donations" = 2, "Crimes (person)" = 2, "Crimes (property)" = 1))

kableExtra::save_kable(tab, file = "table.tex")