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

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

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)
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
mpg cyl
21 6
21 6
dim(a)
[1] 2 2
ncol(a)
[1] 2
nrow(a)
[1] 2
colnames(a)
[1] "mpg" "cyl"

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)
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 = "")
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)
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)
Combine two tiny tables.
mpg cyl gear
21 6
21 6
22.8 4
vs am gear
1 0 3
0 0 3

Renaming columns

As noted above, tinytable tries to be standards-compliant, by defining methods for many base R functions. The benefit of this approach is that instead of having to learn a tinytable-specific syntax, users can rename columns using all the tools they already know:

a <- tt(mtcars[1:2, 1:2])
colnames(a) <- c("a", "b")
a
a b
21 6
21 6

In a pipe-based workflow, we can use the setNames() function from base R:

mtcars[1:2, 1:2] |>
    tt() |>
    setNames(c("a", "b"))
a b
21 6
21 6