Formatting

library(tinytable)
options(tinytable_tt_digits = 3)
options(tinytable_theme_placement_latex_float = "H")
x <- mtcars[1:4, 1:5]

Numbers, dates, strings, etc.

The tt() function is minimalist; it’s inteded purpose is simply to draw nice tables. Users who want to format numbers, dates, strings, and other variables in different ways should process their data before supplying it to the tt() table-drawing function. To do so, we can use the format_tt() function supplied by the tinytable.

In a very simple case—such as printing 2 significant digits of all numeric variables—we can use the digits argument of tt():

dat <- data.frame(
     w = c(143002.2092, 201399.181, 100188.3883),
     x = c(1.43402, 201.399, 0.134588),
     y = as.Date(sample(1:1000, 3), origin = "1970-01-01"),
     z = c(TRUE, TRUE, FALSE))

tt(dat, digits = 2)
w x y z
143002 1.43 1970-04-30 True
201399 201.4 1970-07-17 True
100188 0.13 1972-04-01 False

We can get more fine-grained control over formatting by calling format_tt() after tt(), optionally by specifying the columns to format with j:

tt(dat) |> 
  format_tt(
    j = 2:4,
    digits = 1,
    date = "%B %d %Y") |>
  format_tt(
    j = 1,
    digits = 2,
    num_mark_big = " ",
    num_mark_dec = ",",
    num_fmt = "decimal")
w x y z
143 002,21 1.4 April 30 1970 True
201 399,18 201.4 July 17 1970 True
100 188,39 0.1 April 01 1972 False

We can use a regular expression in j to select columns, and the ?sprintf function to format strings, numbers, and to do string interpolation (similar to the glue package, but using Base R):

dat <- data.frame(
     a = c("Burger", "Halloumi", "Tofu", "Beans"),
     b = c(1.43202, 201.399, 0.146188, 0.0031),
     c = c(98938272783457, 7288839482, 29111727, 93945))
tt(dat) |>
  format_tt(j = "a", sprintf = "Food: %s") |>
  format_tt(j = 2, digits = 1) |>
  format_tt(j = "c", digits = 2, num_suffix = TRUE)
a b c
Food: Burger 1.432 99T
Food: Halloumi 201.399 7.3B
Food: Tofu 0.146 29M
Food: Beans 0.003 94K

Finally, if you like the format_tt() interface, you can use it directly with numbers, vectors, or data frames:

format_tt(pi, digits = 1)
[1] "3"
format_tt(dat, digits = 1, num_suffix = TRUE)
         a     b   c
1   Burger     1 99T
2 Halloumi   201  7B
3     Tofu   0.1 29M
4    Beans 0.003 94K

Significant digits and decimals

By default, format_tt() formats numbers to ensure that the smallest value in a vector (column) has at least a certain number of significant digits. For example,

k <- data.frame(x = c(0.000123456789, 12.4356789))
tt(k, digits = 2)
x
0.00012
12.43568

We can alter this behavior to ensure to round significant digits on a per-cell basis, using the num_fmt argument in format_tt():

tt(k) |> format_tt(digits = 2, num_fmt = "significant_cell")
x
0.00012
12

The numeric formatting options in format_tt() can also be controlled using global options:

options("tinytable_tt_digits" = 2)
options("tinytable_format_num_fmt" = "significant_cell")
tt(k)
x
0.00012
12

Replacement

Missing values can be replaced by a custom string using the replace argument (default ""):

tab <- data.frame(a = c(NA, 1, 2), b = c(3, NA, 5))

tt(tab)
a b
NA 3
1 NA
2 5
tt(tab) |> format_tt()
a b
3
1
2 5
tt(tab) |> format_tt(replace = "-")
a b
- 3
1 -
2 5

We can also specify multiple value replacements at once using a named list of vectors:

tmp <- data.frame(x = 1:5, y = c(pi, NA, NaN, -Inf, Inf))
dict <- list("-" = c(NA, NaN), "-∞" = -Inf, "∞" = Inf)
tt(tmp) |> format_tt(replace = dict, digits = 2)
x y
1 3.1
2 -
3 -
4 -∞
5

Escape special characters

LaTeX and HTML use special characters to indicate strings which should be interpreted rather than displayed as text. For example, including underscores or dollar signs in LaTeX can cause compilation errors in some documents. To display those special characters, we need to substitute or escape them with backslashes, depending on the output format. The escape argument of format_tt() can be used to do this automatically:

dat <- data.frame(
    "LaTeX" = c("Dollars $", "Percent %", "Underscore _"),
    "HTML" = c("<br>", "<sup>4</sup>", "<emph>blah</emph>")
)

tt(dat) |> format_tt(escape = TRUE)
LaTeX HTML
Dollars $ <br>
Percent % <sup>4</sup>
Underscore _ <emph>blah</emph>

When applied to a tt() table, format_tt() will determine the type of escaping to do automatically. When applied to a string or vector, we must specify the type of escaping to apply:

format_tt("_ Dollars $", escape = "latex")
[1] "\\_ Dollars \\$"

Markdown

Markdown can be rendered in cells by using the markdown argument of the format_tt() function (note: this requires installing the markdown as an optional dependency).

dat <- data.frame( markdown = c(
  "This is _italic_ text.",
  "This sentence ends with a superscript.^2^")
)

tt(dat) |>
  format_tt(j = 1, markdown = TRUE) |>
  style_tt(j = 1, align = "c")
markdown
This is italic text.
This sentence ends with a superscript.2

Markdown syntax can be particularly useful when formatting URLs in a table:

dat <- data.frame(
  `Package (link)` = c(
    "[`marginaleffects`](https://www.marginaleffects.com/)",
    "[`modelsummary`](https://www.modelsummary.com/)",
    "[`tinytable`](https://vincentarelbundock.github.io/tinytable/)",
    "[`countrycode`](https://vincentarelbundock.github.io/countrycode/)",
    "[`WDI`](https://vincentarelbundock.github.io/WDI/)",
    "[`softbib`](https://vincentarelbundock.github.io/softbib/)",
    "[`tinysnapshot`](https://vincentarelbundock.github.io/tinysnapshot/)",
    "[`altdoc`](https://etiennebacher.github.io/altdoc/)",
    "[`tinyplot`](https://grantmcdermott.com/tinyplot/)",
    "[`parameters`](https://easystats.github.io/parameters/)",
    "[`insight`](https://easystats.github.io/insight/)"
  ),
  Purpose = c(
    "Interpreting statistical models",
    "Data and model summaries",
    "Draw beautiful tables easily",
    "Convert country codes and names",
    "Download data from the World Bank",
    "Software bibliographies in R",
    "Snapshots for unit tests using `tinytest`",
    "Create documentation website for R packages",
    "Extension of base R plot functions",
    "Extract from model objects",
    "Extract information from model objects"
  ),
  check.names = FALSE
)

tt(dat) |> format_tt(j = 1, markdown = TRUE)

Vincent sometimes contributes to these R packages.

Package (link) Purpose
marginaleffects Interpreting statistical models
modelsummary Data and model summaries
tinytable Draw beautiful tables easily
countrycode Convert country codes and names
WDI Download data from the World Bank
softbib Software bibliographies in R
tinysnapshot Snapshots for unit tests using `tinytest`
altdoc Create documentation website for R packages
tinyplot Extension of base R plot functions
parameters Extract from model objects
insight Extract information from model objects

Custom functions

On top of the built-in features of format_tt, a custom formatting function can be specified via the fn argument. The fn argument takes a function that accepts a single vector and returns a string (or something that coerces to a string like a number).

tt(x) |> 
  format_tt(j = "mpg", fn = function(x) paste0(x, " mpg")) |>
  format_tt(j = "drat", fn = \(x) signif(x, 2))
mpg cyl disp hp drat
21 mpg 6 160 110 3.9
21 mpg 6 160 110 3.9
22.8 mpg 4 108 93 3.8
21.4 mpg 6 258 110 3.1

For example, the scales package which is used internally by ggplot2 provides a bunch of useful tools for formatting (e.g. dates, numbers, percents, logs, currencies, etc.). The label_*() functions can be passed to the fn argument.

Note that we call format_tt(escape = TRUE) at the end of the pipeline because the column names and cells include characters that need to be escaped in LaTeX: _, %, and $. This last call is superfluous in HTML.

thumbdrives <- data.frame(
  date_lookup = as.Date(c("2024-01-15", "2024-01-18", "2024-01-14", "2024-01-16")),
  price = c(18.49, 19.99, 24.99, 24.99),
  price_rank = c(1, 2, 3, 3),
  memory = c(16e9, 12e9, 10e9, 8e9),
  speed_benchmark = c(0.6, 0.73, 0.82, 0.99)
)

tt(thumbdrives) |>
  format_tt(j = 1, fn = scales::label_date("%e %b", locale = "fr")) |>
  format_tt(j = 2, fn = scales::label_currency()) |>
  format_tt(j = 3, fn = scales::label_ordinal()) |> 
  format_tt(j = 4, fn = scales::label_bytes()) |> 
  format_tt(j = 5, fn = scales::label_percent())  |>
  format_tt(escape = TRUE)
date_lookup price price_rank memory speed_benchmark
2024-01-15 $18.49 1st 16 GB 60%
2024-01-18 $19.99 2nd 12 GB 73%
2024-01-14 $24.99 3rd 10 GB 82%
2024-01-16 $24.99 3rd 8 GB 99%

Quarto data processing

Quarto automatically applies some data processing to the content of the tables it renders. By default, tinytable disables this processing, because it can enter in conflict with styling and formatting features of the package.

To enable Quarto data processing, we can use the quarto argument of the format_tt() function. This argument allows users to mark certain cells explicitly for processing by Quarto, by wrapping them in a special “span” called “data-qmd”, supported by Quarto:

k <- data.frame(Thing = "qwerty", Citation = "@Lovelace1842")

tt(k) |> format_tt(quarto = TRUE)
Thing Citation
qwerty Lovelace (1842)

Some users may want to apply Quarto data processing to all tables. This can be done with themes:

theme_quarto <- function(x) format_tt(x, quarto = TRUE)
options(tinytable_tt_theme = theme_quarto)

tt(k)
Thing Citation
qwerty Lovelace (1842)

Back to normal:

options(tinytable_tt_theme = NULL)

Alternatively, users can set a global option to process all tables in Quarto, but they will then have to mark each cell with special content using format_tt(quarto):

options(tinytable_quarto_disable_processing = FALSE)

tt(x)
mpg cyl disp hp drat
21.0 6 160 110 3.90
21.0 6 160 110 3.90
22.8 4 108 93 3.85
21.4 6 258 110 3.08

Notice that Quarto is now processing the table, so we lose the default tinytable theme and get the default striped Quarto look.

Back to normal:

options(tinytable_quarto_disable_processing = TRUE)

References

Lovelace, Augusta Ada. 1842. “Sketch of the Analytical Engine Invented by Charles Babbage, by LF Menabrea, Officer of the Military Engineers, with Notes Upon the Memoir by the Translator.” Taylor’s Scientific Memoirs 3: 666–731.