A Gamechanger of Creating Tables in R: tinytable

R
Quarto
LaTeX
Author

Kazuharu Yanagimoto

Published

April 29, 2024

kableExtra or gt? No, tinytable!

In recent years, kableExtra and gt have been popular packages for creating tables in R. I have used kableExtra for tables in a (LaTeX compiled) paper, and gt for tables in a (HTML compiled) slides, and I taught it in my R workshop. However, I found a new package called tinytable that is more flexible and easier to use than the other two packages. In this post, I will introduce how to use tinytable to create tables for LaTeX documents as an updated section of my workshop.

What is tinytable?

The tinytable is a small (zero-dependencies, only uses baseR!) but powerful package that provides a simple and flexible way to create tables in R. This package is developed by Vincent Arel-Bundock, the maintainer of the modelsummary package, and is designed to work seamlessly with modelsummary.

An Example of Creating Tables with tinytable

library(dplyr)
library(tidyr)
library(tinytable)

I use the Madrid traffic accident dataset, which is from my workshop. You can find the downloading and cleaning codes in my blog repository.

dir_post <- here::here("blog/2024/04/29/")
data <- nanoparquet::read_parquet(file.path(dir_post, "data", "cleaned.parquet")) |>
  mutate(is_died = injury8 == "Died within 24 hours",
         is_hospitalized = injury8 %in% c("Hospitalization after 24 hours",
                                          "Hospitalization within 24 hours",
                                          "Died within 24 hours"))

I will create a table that shows the number of accidents.

tab_count <- data |>
  filter(!is.na(weather), !is.na(gender)) |>
  summarize(n = n(), .by = c(year, gender, weather)) |>  
  pivot_wider(names_from = c(gender, year), values_from = n) |>
  arrange(weather) |>
  select(weather, starts_with("Men"), starts_with("Women"))

tab_count
# A tibble: 6 × 11
  weather   Men_2019 Men_2020 Men_2021 Men_2022 Men_2023 Women_2019 Women_2020
  <fct>        <int>    <int>    <int>    <int>    <int>      <int>      <int>
1 sunny        24399    14969    19208    20679    22451      11971       6958
2 cloud         1159     1190     1325     2082     2011        555        554
3 soft rain     2126     1198     1281     1930     1224       1068        542
4 hard rain      386      202      386      527      317        222         96
5 snow             2        2      124        5       NA         NA         NA
6 hail            11        5        6        4        3          3          3
# ℹ 3 more variables: Women_2021 <int>, Women_2022 <int>, Women_2023 <int>

You can create a table by using the tt() function.

tt_count <- tab_count |>
  `colnames<-`(c("", rep(2019:2023, 2))) |>
  tt() |>
  group_tt(i = list("Good Weather" = 1, "Bad Weather" = 3),
           j = list("Men" = 2:6, "Women" = 7:11)) |>
  style_tt(i = c(1, 4), bold = TRUE) |>
  format_tt(replace = "-")

tt_count |>
  theme_tt("tabular") |>
  save_tt(file.path(dir_post, "tex", "table_count.tex"),
          overwrite = TRUE)

To format the table as an table in a paper,

  • group_tt() groups the rows and columns as multirow and multicolumn in \(\LaTeX\).
  • style_tt() styles the rows as bold, italic, etc.
  • format_tt() formats the cells as numeric, percentage, etc. replace argument replaces the NA cells with the specified character.
  • You cannot change the column names in the tt() function, so you need to use colnames<-(), related to the issue #194
  • To save the table as a plain table (without \begin{table} and \end{table}), use theme_tt("tabular").

Number of Persons Involved in Traffic Accidents

A trick to insert a LaTeX table in a Quarto document

In the previous section, I inserted a \(\LaTeX\) table by SVG format. Actually, I converted a tinytable object to a svg figure by the following two steps:

1. Save the tinytable object as a PDF file.

tt_count |>
  save_tt(file.path(dir_post, "img", "table_count.pdf"),
          overwrite = TRUE)

tinytable::save_tt() is a powerful funtion that can save the tinytable object as a PDF file. If the file extension is .pdf, the function compile it as a single PDF file by tinytex package.

2. Convert the PDF file to a SVG file.

```{bash}
#!/bin/bash
pdf2svg img/table_count.pdf img/table_count.svg
```

pdf2svg is a command line tool that converts a PDF file to a SVG file. And importantly, knitr can run the bash script in a code chunk.

Modelsummary

library(modelsummary)
library(fixest)

Since tinytable is designed to work seamlessly with modelsummary, you can create a table of regression results by using modelsummary and tinytable.

setFixest_fml(..ctrl = ~ type_person + positive_alcohol + positive_drug |
                          age_c + gender)
models <- list(
    "(1)" = feglm(xpd(is_hospitalized ~ ..ctrl),
                  family = binomial(logit), data = data),
    "(2)" = feglm(xpd(is_hospitalized ~ ..ctrl + type_vehicle),
                  family = binomial(logit), data = data),
    "(3)" = feglm(xpd(is_hospitalized ~ ..ctrl + type_vehicle + weather),
                  family = binomial(logit), data = data),
    "(4)" = feglm(xpd(is_died ~ ..ctrl),
                  family = binomial(logit), data = data),
    "(5)" = feglm(xpd(is_died ~ ..ctrl + type_vehicle),
                  family = binomial(logit), data = data),
    "(6)" = feglm(xpd(is_died ~ ..ctrl + type_vehicle + weather),
                  family = binomial(logit), data = data)
)

modelsummary(models)
(1) (2) (3) (4) (5) (6)
type_personPassenger 0.026 0.501 0.475 -1.697 -1.445 -1.440
(0.100) (0.069) (0.068) (0.616) (0.631) (0.631)
type_personPedestrian 2.117 2.381 2.298 2.190 2.326 2.327
(0.114) (0.061) (0.058) (0.260) (0.248) (0.246)
positive_alcoholTRUE 0.007 0.355 0.381 -13.685 -13.421 -13.460
(0.072) (0.081) (0.081) (0.059) (0.055) (0.054)
Num.Obs. 197477 197369 176133 118462 116344 112659
R2 0.056 0.170 0.163 0.103 0.142 0.145
R2 Adj. 0.056 0.169 0.162 0.087 0.116 0.116
R2 Within 0.048 0.055 0.053 0.070 0.070 0.071
R2 Within Adj. 0.048 0.055 0.052 0.067 0.067 0.068
AIC 82581.4 72694.8 70519.4 2029.6 1960.3 1940.0
BIC 82795.5 73204.5 71063.6 2213.6 2240.5 2248.2
RMSE 0.23 0.22 0.24 0.03 0.04 0.04
Std.Errors by: age_c by: age_c by: age_c by: age_c by: age_c by: age_c
FE: age_c X X X X X X
FE: gender X X X X X X
FE: type_vehicle X X X X
FE: weather X X

To polish the table for a paper,

cm  <-  c(
    "type_personPassenger" = "Passenger",
    "type_personPedestrian" = "Pedestrian",
    "positive_alcoholTRUE" = "Positive Alcohol"
)

gm <- tibble(
    raw = c("nobs", "FE: age_c", "FE: gender",
            "FE: type_vehicle", "FE: weather"),
    clean = c("Observations", "FE: Age Group", "FE: Gender",
              "FE: Type of Vehicle", "FE: Weather"),
    fmt = c(0, 0, 0, 0, 0)
)

tt_reg <- modelsummary(models,
  stars = c("+" = .1, "*" = .05, "**" = .01),
  coef_map = cm,
  gof_map = gm) |>
  group_tt(j = list("Hospitalization" = 2:4,
                    "Died within 24 hours" = 5:7))

tt_reg |>
  theme_tt("tabular") |>
  save_tt(file.path(dir_post, "tex", "table_reg.tex"),
          overwrite = TRUE)
  • coef_map renames the coefficients
  • gof_map renames the goodness-of-fit statistics
  • Since the modelsummary function returns a tinytable object, you can use the tinytable functions to polish the table

Logit Regression of Hospitalization and Death within 24 Hours

Last Comments

In this post, I introduced the tinytable package, which is a gamechanger of creating tables in R. In the wrangling process, I firmly believe that tinytable is more flexible and powerful than kableExtra and gt for the following reasons:

  • Covers almost all the features of kableExtra and gt & gtExtras. You can use multirow & multicolumn, highlight cells, format cells, and mathematical expressions
  • Allows to export tables not only in HTML and LaTeX but also in PDF (with tinytex) and Typst
  • Compiles faster than kableExtra and gt. I think this is because tinytable is a small package that only uses baseR

Have a happy R life 🥂!