Tiny Tables

tinytable is a small but powerful R package to draw HTML, LaTeX, Word, PDF, Markdown, and Typst tables. The interface is minimalist, but it gives users direct and convenient access to powerful frameworks to create endlessly customizable tables.

Install the latest version from R-Universe or CRAN:

install.packages("tinytable",
    repos = c("https://vincentarelbundock.r-universe.dev", "https://cran.r-project.org"))

This tutorial introduces the main functions of the package. It is also available as a single PDF document.

Load the library and set some global options:

library(tinytable)
options(tinytable_tt_digits = 3)
options(tinytable_theme_placement_latex_float = "H")

Draw a first table:

x <- mtcars[1:4, 1:5]
tt(x)
tinytable_dnhrh3fjjg2keu5t0sh1
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

Width

The width arguments indicating what proportion of the line width the table should cover. This argument accepts a number between 0 and 1 to control the whole table width, or a vector of numeric values between 0 and 1, representing each column.

tt(x, width = 0.5)
tinytable_v9qcpv04ce46n4ewm6ux
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08
tt(x, width = 1)
tinytable_rav2dspw185nlc2i9dbb
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

We can control individual columns by supplying a vector. In that case, the sum of width elements determines the full table width. For example, this table takes 70% of available width, with the first column 3 times as large as the other ones.

tt(x, width = c(.3, .1, .1, .1, .1))
tinytable_kx05xzdnpvfo6xj24x2u
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

When the sum of the width vector exceeds 1, it is automatically normalized to full-width. This is convenient when we only want to specify column width in relative terms:

tt(x, width = c(3, 2, 1, 1, 1))
tinytable_outelrn6us7trwo8zuxt
mpg cyl disp hp drat
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

When specifying a table width, the text is automatically wrapped to appropriate size:

lorem <- data.frame(
  Lorem = "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.",
  Ipsum = " Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos."
)

tt(lorem, width = 3/4)
tinytable_c1xpn651awaap61n09o1
Lorem Ipsum
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos.

Footnotes

The notes argument accepts single strings or named lists of strings:

n <- "Fusce id ipsum consequat ante pellentesque iaculis eu a ipsum. Mauris id ex in nulla consectetur aliquam. In nec tempus diam. Aliquam arcu nibh, dapibus id ex vestibulum, feugiat consequat erat. Morbi feugiat dapibus malesuada. Quisque vel ullamcorper felis. Aenean a sem at nisi tempor pretium sit amet quis lacus."

tt(lorem, notes = n, width = 1)
tinytable_bbn6z3y7wft55gki4s8w

A full-width table with wrapped text in cells and a footnote.

Lorem Ipsum
Fusce id ipsum consequat ante pellentesque iaculis eu a ipsum. Mauris id ex in nulla consectetur aliquam. In nec tempus diam. Aliquam arcu nibh, dapibus id ex vestibulum, feugiat consequat erat. Morbi feugiat dapibus malesuada. Quisque vel ullamcorper felis. Aenean a sem at nisi tempor pretium sit amet quis lacus.
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos.

When notes is a named list, the names are used as identifiers and displayed as superscripts:

tt(x, notes = list(a = "Blah.", b = "Blah blah."))
tinytable_hito4g64dabkapzcomlw
mpg cyl disp hp drat
a Blah.
b Blah blah.
21 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

We can also add markers in individual cells by providing coordinates:

tt(x, notes = list(
    a = list(i = 0:1, j = 1, text = "Blah."),
    b = "Blah blah."
  )
)
tinytable_nz6hyuyxkcsm2x7m3frx
mpga cyl disp hp drat
a Blah.
b Blah blah.
21 a 6 160 110 3.9
21 6 160 110 3.9
22.8 4 108 93 3.85
21.4 6 258 110 3.08

Captions and cross-references

In Quarto, one should always specify captions cross-references using chunk options, and should not use the caption argument. This is because Quarto automatically post-processes tables, and may introduce conflict with the captions inserted by tinytable. For example:

@tbl-blah shows that...

```{r}
#| label: tbl-blah
#| tbl-cap: "Blah blah blah"
library(tinytable)
tt(mtcars[1:4, 1:4])
```

And here is the rendered version of the code chunk above:

Table 1 shows that…

library(tinytable)
tt(mtcars[1:4, 1:4])
Table 1: Blah blah blah
tinytable_v0na8k9yhzo2p0s0alwl
mpg cyl disp hp
21 6 160 110
21 6 160 110
22.8 4 108 93
21.4 6 258 110

One exception to the injunction above is when rendering a Quarto document to LaTeX using theme_tt("multipage")". In that case, one must avoid using the Quarto chunk option, because these options trigger Quarto post-processing that will conflict with the longtblr environment used to split long tables across multiple pages.

The alternative is to use to refer to tables using standard LaTeX syntax: \ref{tbl-ex-multipage}. Then, use the caption argument in tt() to specify both the label and the caption:

tt(iris, caption = "Example table.\\label{tbl-ex-multipage}") |>
  theme_tt("multipage")

For standalone tables in any format (i.e., outside Quarto), you can use the caption argument like so:

tt(x, caption = "Blah blah.\\label{tbl-blah}")

Math

To insert LaTeX-style mathematical expressions in a tinytable, we enclose the expression in dollar signs: $...$. The expression will then rendered as a mathematical expression by MathJax (for HTML), LaTeX, or Pandoc. Do not forget to double escape any backslashes.

dat <- data.frame(Math = c(
  "$x^2 + y^2 = z^2$",
  "$\\frac{1}{2}$"
))
tt(dat) |> style_tt(j = 1, align = "c")
tinytable_7ze4b1z0zzssr1lddaos
Math
$x^2 + y^2 = z^2$
$\frac{1}{2}$

Line breaks and text wrapping

Manual line breaks work sligthly different in LaTeX (PDF) or HTML. This table shows the two strategies. For HTML, we insert a <br> tag. For LaTeX, we wrap the string in curly braces {}, and then insert two (escaped) backslashes: \\\\

d <- data.frame(
  "{Sed ut \\\\ perspiciatis unde}",
  "dicta sunt<br> explicabo. Nemo"
) |> setNames(c("LaTeX line break", "HTML line break"))
tt(d, width = 1)
tinytable_celdwnftbu374s19g0fx
LaTeX line break HTML line break
{Sed ut \\ perspiciatis unde} dicta sunt
explicabo. Nemo

Output formats

tinytable can produce tables in HTML, Word, Markdown, LaTeX, Typst, PDF, or PNG format. An appropriate output format for printing is automatically selected based on (1) whether the function is called interactively, (2) is called within RStudio, and (3) the output format of the Rmarkdown or Quarto document, if applicable. Alternatively, users can specify the print format in print() or by setting a global option:

tt(x) |> print("markdown")
tt(x) |> print("html")
tt(x) |> print("latex")

options(tinytable_print_output = "markdown")

With the save_tt() function, users can also save tables directly to PNG (images), PDF or Word documents, and to any of the basic formats. All we need to do is supply a valid file name with the appropriate extension (ex: .png, .html, .pdf, etc.):

tt(x) |> save_tt("path/to/file.png")
tt(x) |> save_tt("path/to/file.pdf")
tt(x) |> save_tt("path/to/file.docx")
tt(x) |> save_tt("path/to/file.html")
tt(x) |> save_tt("path/to/file.tex")
tt(x) |> save_tt("path/to/file.md")

save_tt() can also return a string with the table in it, for further processing in R. In the first case, the table is printed to console with cat(). In the second case, it returns as a single string as an R object.

tt(mtcars[1:10, 1:5]) |>
  group_tt(
    i = list(
      "Hello" = 3,
      "World" = 8),
    j = list(
      "Foo" = 2:3,
      "Bar" = 4:5)) |>
  print("markdown")
+------+-----+------+-----+------+
|      | Foo        | Bar        |
+------+-----+------+-----+------+
| mpg  | cyl | disp | hp  | drat |
+======+=====+======+=====+======+
| 21   | 6   | 160  | 110 | 3.9  |
+------+-----+------+-----+------+
| 21   | 6   | 160  | 110 | 3.9  |
+------+-----+------+-----+------+
| Hello                          |
+------+-----+------+-----+------+
| 22.8 | 4   | 108  | 93  | 3.85 |
+------+-----+------+-----+------+
| 21.4 | 6   | 258  | 110 | 3.08 |
+------+-----+------+-----+------+
| 18.7 | 8   | 360  | 175 | 3.15 |
+------+-----+------+-----+------+
| 18.1 | 6   | 225  | 105 | 2.76 |
+------+-----+------+-----+------+
| 14.3 | 8   | 360  | 245 | 3.21 |
+------+-----+------+-----+------+
| World                          |
+------+-----+------+-----+------+
| 24.4 | 4   | 147  | 62  | 3.69 |
+------+-----+------+-----+------+
| 22.8 | 4   | 141  | 95  | 3.92 |
+------+-----+------+-----+------+
| 19.2 | 6   | 168  | 123 | 3.92 |
+------+-----+------+-----+------+ 
tt(mtcars[1:10, 1:5]) |>
  group_tt(
    i = list(
      "Hello" = 3,
      "World" = 8),
    j = list(
      "Foo" = 2:3,
      "Bar" = 4:5)) |>
  save_tt("markdown")
[1] "+------+-----+------+-----+------+\n|      | Foo        | Bar        |\n+------+-----+------+-----+------+\n| mpg  | cyl | disp | hp  | drat |\n+======+=====+======+=====+======+\n| 21   | 6   | 160  | 110 | 3.9  |\n+------+-----+------+-----+------+\n| 21   | 6   | 160  | 110 | 3.9  |\n+------+-----+------+-----+------+\n| Hello                          |\n+------+-----+------+-----+------+\n| 22.8 | 4   | 108  | 93  | 3.85 |\n+------+-----+------+-----+------+\n| 21.4 | 6   | 258  | 110 | 3.08 |\n+------+-----+------+-----+------+\n| 18.7 | 8   | 360  | 175 | 3.15 |\n+------+-----+------+-----+------+\n| 18.1 | 6   | 225  | 105 | 2.76 |\n+------+-----+------+-----+------+\n| 14.3 | 8   | 360  | 245 | 3.21 |\n+------+-----+------+-----+------+\n| World                          |\n+------+-----+------+-----+------+\n| 24.4 | 4   | 147  | 62  | 3.69 |\n+------+-----+------+-----+------+\n| 22.8 | 4   | 141  | 95  | 3.92 |\n+------+-----+------+-----+------+\n| 19.2 | 6   | 168  | 123 | 3.92 |\n+------+-----+------+-----+------+"

Combination and exploration

Tables can be explored, modified, and combined using many of the usual base R functions:

a <- tt(mtcars[1:2, 1:2])
a
tinytable_fjvlp364xc969w78iol5
mpg cyl
21 6
21 6
dim(a)
[1] 2 2
ncol(a)
[1] 2
nrow(a)
[1] 2
colnames(a)
[1] "mpg" "cyl"

Rename columns:

colnames(a) <- c("a", "b")
a
tinytable_df00any0l73tqnji3a31
a b
21 6
21 6

Tables can be combined with the usual rbind() function:

a <- tt(mtcars[1:3, 1:2], caption = "Combine two tiny tables.")
b <- tt(mtcars[4:5, 8:10]) 

rbind(a, b)
tinytable_n5vqoiva70n471beny7j
Combine two tiny tables.
mpg cyl vs am gear
21 6
21 6
22.8 4
vs am gear
1 0 3
0 0 3
rbind(a, b) |> format_tt(replace = "")
tinytable_xol7jw4qxpkr10e702kl
Combine two tiny tables.
mpg cyl vs am gear
21 6
21 6
22.8 4
vs am gear
1 0 3
0 0 3

The rbind2() S4 method is slightly more flexible than rbind(), as it supports arguments headers and use.names.

Omit y header:

rbind2(a, b, headers = FALSE)
tinytable_l40gr29z6edtuidsbi9r
Combine two tiny tables.
mpg cyl vs am gear
21 6
21 6
22.8 4
1 0 3
0 0 3

Bind tables by position rather than column names:

rbind2(a, b, use_names = FALSE)
tinytable_vtg9813bxo8hame92g3m
Combine two tiny tables.
mpg cyl gear
21 6
21 6
22.8 4
vs am gear
1 0 3
0 0 3