Skip to contents

To customize the appearance of tables, modelsummary supports five 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/
  5. DT: https://rstudio.github.io/DT

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, na.strings = "")

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, huxtable, and DT 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 five 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)

# 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') 

# 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
RMSE 5753.14 5727.27 7456.23 7233.22 2793.43
+ p &lt; 0.1, * p &lt; 0.05, ** p &lt; 0.01, *** p &lt; 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
RMSE 5753.14 5727.27 7456.23 7233.22 2793.43
+ p &lt; 0.1, * p &lt; 0.05, ** p &lt; 0.01, *** p &lt; 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
RMSE 5753.14 5727.27 7456.23 7233.22 2793.43
+ 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()
A modelsummary table customized with flextable

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

RMSE

5753.14

5727.27

7456.23

7233.22

2793.43

+ p &lt; 0.1, * p &lt; 0.05, ** p &lt; 0.01, *** p &lt; 0.001

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
RMSE 5753.14 5727.27 7456.23 7233.22 2793.43
+ p &lt; 0.1, * p &lt; 0.05, ** p &lt; 0.01, *** p &lt; 0.001

DT

The DT package is an interface to the JavasScript DataTables library. We can create a DT table by using the output argument, and pass arguments to modelsummary() which will be passed forward to the DT::datatable to customize the output of the table. For example, let’s use the fixest package to estimate multiple linear regression models, and the DT package to create a table with a “frozen” first column:

library(fixest)

models <- feols(c(mpg, hp) ~ csw0(cyl, drat, am, vs, wt, carb, qsec, gear), data = mtcars)

modelsummary(
    models,
    output = "DT",
    extensions = "FixedColumns",
    height = 50,
    options = list(pageLength = 50,
                   scrollX = TRUE,
                   fixedColumns = list(leftColumns = 1)))

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")
(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
RMSE 3.02
url <- 'https://vincentarelbundock.github.io/Rdatasets/csv/palmerpenguins/penguins.csv'
penguins <- read.csv(url, na.strings = "")

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

Restore default theme:

options("modelsummary_theme_gt" = NULL)

Themes: Data Frame

A particularly flexible strategy is to apply a theme to the dataframe output format. To illustrate, recall that setting output="dataframe" produces a data frame with a lot of extraneous meta information. To produce a nice table, we have to process that output a bit:

mod <- lm(mpg ~ hp + drat, mtcars)

modelsummary(mod, output = "dataframe")
part term statistic (1)
estimates (Intercept) estimate 10.790
estimates (Intercept) std.error (5.078)
estimates hp estimate -0.052
estimates hp std.error (0.009)
estimates drat estimate 4.698
estimates drat std.error (1.192)
gof Num.Obs. 32
gof R2 0.741
gof R2 Adj. 0.723
gof AIC 169.5
gof BIC 175.4
gof Log.Lik. -80.752
gof F 41.522
gof RMSE 3.02

modelsummary supports the DT table-making package out of the box. But for the sake of illustration, imagine we want to create a table using the DT package with specific customization and options, in a repeatable fashion. To do this, we can create a theming function:

library(DT)

theme_df <- function(tab) {
    out <- tab
    out$term[out$statistic == "modelsummary_tmp2"] <- " "
    out$part <- out$statistic <- NULL
    colnames(out)[1] <- " "
    datatable(out, rownames = FALSE,
              options = list(pageLength = 30))
}

options("modelsummary_theme_dataframe" = theme_df)
modelsummary(mod, output = "dataframe")

Restore default theme:

options("modelsummary_theme_dataframe" = NULL)

Variable labels

Some packages like haven can assign attributes to the columns of a dataset for use as labels. Most of the functions in modelsummary can display these labels automatically. For example:

library(haven)
dat <- mtcars
dat$am <- haven::labelled(dat$am, label = "Transmission")
dat$mpg <- haven::labelled(dat$mpg, label = "Miles per Gallon")
mod <- lm(hp ~ mpg + am, dat = dat)

modelsummary(mod, coef_rename = TRUE)
(1)
(Intercept) 352.312
(27.226)
Miles per Gallon -11.200
(1.494)
Transmission 47.725
(18.048)
Num.Obs. 32
R2 0.680
R2 Adj. 0.658
AIC 331.9
BIC 337.8
Log.Lik. -161.971
F 30.766
RMSE 38.19

datasummary_skim(dat[, c("mpg", "am", "drat")])
Unique (#) Missing (%) Mean SD Min Median Max
Miles per Gallon 25 0 20.1 6.0 10.4 19.2 33.9
Transmission 2 0 0.4 0.5 0.0 0.0 1.0
drat 22 0 3.6 0.5 2.8 3.7 4.9

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