Building better tables in less time

The Bullock package provides three functions to help you make tables: regTable(), latexTable(), and latexTablePDF(). The functions can be used separately but are designed to be used together. regTable() generates a matrix of regression results, latexTable() turns it into LaTeX code, and latexTablePDF() renders the LaTeX code as PDF.

Many other packages will help you to make tables. The outstanding feature of this package is that it offers you less. For example, this package offers you no opportunity to change colors or fonts, to add rules and borders in inappropriate places, or to create tables with baroque layouts. This package makes easy things easy, and it makes creating bad tables quite difficult.

The other major feature of this package is that it makes tables easier to read by prioritizing the careful use of white space between different table elements. With that in mind, it produces “Tufte tables,” in which standard errors are printed to the right of their estimates and in smaller type. Relative to conventional formatting—which places standard errors below the estimates and in parentheses—the formatting used here makes it easier for readers to instantly identify the information that they need. To my knowledge, the table design used here was first used in Edward Tufte’s essay on The Cognitive Style of PowerPoint.

Quick start

Begin by generating a few regression objects and then putting the results into a matrix:

data(iris)
lm1    <- lm(Sepal.Length ~ Petal.Length,               data = iris)
lm2    <- lm(Sepal.Length ~ Petal.Length + Petal.Width, data = iris)
lm3    <- lm(Sepal.Length ~ Petal.Length * Petal.Width, data = iris)
lmList <- list(lm1, lm2, lm3)
rT1    <- regTable(lmList)
rT1
#>                          Sepal.Length  Sepal.Length  Sepal.Length
#>                             Est    SE     Est    SE     Est    SE
#>              (Intercept)  4.307 0.078   4.191 0.097   4.577 0.112
#>             Petal.Length  0.409 0.019   0.542 0.069   0.442 0.066
#>              Petal.Width               -0.320 0.160  -1.239 0.219
#> Petal.Length:Petal.Width                              0.189 0.034

Then use latexTable() convert the matrix into LaTeX code:

lT1 <- latexTable(
  mat      = rT1,
  rowNames = c("Intercept", "Petal length", "Petal width", "Petal length $\\times$ petal width"),
  colNames = lt_colNumbers(),
  caption  = '\\textit{Sepal length as a function of petal length and petal width.}'
)

head(lT1)
#> [1] "\newcommand\myTable[1]{%"
#> [2] "  \begin{table}[#1]"
#> [3] "    \setlength{\extrarowheight}{2pt}"
#> [4] "    \begin{center}"
#> [5] "      \hspace*{-0in}"
#> [6] "      \setlength{\tabcolsep}{2.75pt}"

tail(lT1)
#> [1] "        \textit{Sepal length as a function of petal length and petal width.}"
#> [2] "      }"
#> [3] "    \end{center}"
#> [4] "  \end{table}"
#> [5] "}%"
#> [6] "\myTable{p}"

You see that lT1 contains LaTeX code for a macro called myTable. You can render the table directly to a PDF file by calling latexTablePDF():

latexTablePDF(lT1, outputFilenameStem = 'irisTable')

Or you can write the macro to a .tex file:

latexTablePDF(lT1, outputFilenameStem = 'irisTable', writeTex = TRUE)

The .tex file can then be inserted into your master LaTeX document by adding \input{irisTable.tex} or \include{irisTable.tex} into your master LaTeX document. (For more on \input and \include, see the LaTeX wikibook and Stack Overflow.)

Updating tables with update()

Suppose that you produce a complex table with a long latexTable() call. You then want to produce a new table that is similar. For example, you may want to produce a new table with different data but all of the same design features (the same row names, the same spacing, etc.). You could issue another long latexTable() call to produce the new table. Or you could just use update():

lm1v <- update(lm1, subset = (Species == 'versicolor'))
lm2v <- update(lm2, subset = (Species == 'versicolor'))
lm3v <- update(lm3, subset = (Species == 'versicolor'))
rT2  <- regTable(list(lm1v, lm2v, lm3v))
lT2  <- update(lT1, mat = rT2)
lT2[23:26]  # just showing the data rows
#> [1] "                                Intercept && 2.41 & .45 &&   2.38 & .45 &&    .99 & 2.88\tabularnewline"
#> [2] "                             Petal length &&  .83 & .10 &&    .93 & .17 &&   1.26 &  .68\tabularnewline"
#> [3] "                              Petal width &&      &     &&   -.32 & .40 &&    .84 & 2.40\tabularnewline"
#> [4] "        Petal length $\times$ petal width &&      &     &&        &     &&   -.26 &  .54\tabularnewline"

Tweaking tables

Instead of using update(), you may prefer to edit your table “by hand” after it is produced by latexTable(). This is easy to do, because latexTable() returns an object of the “character” class—that is, a vector of strings. Each string is a line of LaTeX code. The simple format of the returned object makes it easy to edit the object before writing it to disk as a .tex or PDF file:

lT2[24:26]  
#> [1] "                             Petal length &&  .83 & .10 &&    .93 & .17 &&   1.26 &  .68\tabularnewline"
#> [2] "                              Petal width &&      &     &&   -.32 & .40 &&    .84 & 2.40\tabularnewline"
#> [3] "        Petal length $\times$ petal width &&      &     &&        &     &&   -.26 &  .54\tabularnewline"

# Capitalize first letter of each word that is preceded by a space
lT2[24:26] <- gsub("([[:space:]])([[:alpha:]])", "\\1\\U\\2", lT2[24:26], perl=TRUE)
lT2[24:26]
#> [1] "                             Petal Length &&  .83 & .10 &&    .93 & .17 &&   1.26 &  .68\tabularnewline"
#> [2] "                              Petal Width &&      &     &&   -.32 & .40 &&    .84 & 2.40\tabularnewline"
#> [3] "        Petal Length $\times$ Petal Width &&      &     &&        &     &&   -.26 &  .54\tabularnewline"

In practice, you will probably want to tweak tables only when you have unusual formatting requirements that cannot be satisfied by the latexTable() options.

Cutting and pasting with headerFooter = FALSE

You may not want to generate standalone .tex or PDF files. Instead, you may already have the skeleton of a LaTeX table set up in your master LaTeX document, and you may just want to copy the table rows (specified as LaTeX code) from R into that skeleton table. This is easy to do with the headerFooter argument to latexTable():

latexTable(
  headerFooter  = FALSE,
  mat           = rT1,
  rowNames      = c("Intercept", "Petal length", "Petal width", "Petal length $\\times$ petal width"),
  spacerColumns = c(0, 2, 4)
)
#> [1] "                                Intercept && 4.31 & .08 &&   4.19 & .10 &&    4.58 & .11\tabularnewline"
#> [2] "                             Petal length &&  .41 & .02 &&    .54 & .07 &&     .44 & .07\tabularnewline"
#> [3] "                              Petal width &&      &     &&   -.32 & .16 &&   -1.24 & .22\tabularnewline"
#> [4] "        Petal length $\times$ petal width &&      &     &&        &     &&     .19 & .03\tabularnewline"

Using latexTable() in R Markdown and Rnw documents

It is easy to use latexTable() in R Markdown and Rnw documents. See the Using latexTable() with R Markdown and Rnw documents vignette for details.

Technical note: column spacing in LaTeX

Ordinary methods for inserting space between columns in LaTeX involve the \tabcolsep and \extracolsep LaTeX lengths. Unfortunately, \cmidrule and other commands in the booktabs package don’t recognize that these LaTeX lengths are spaces between columns. As a result, rules (horizontal lines) drawn by booktabs commands extend into the intercolumn region if \tabcolsep and \extracolsep are used to provide intercolumn space. A similar problem occurs if \hspace is used to provide intercolumn space.

Thus, to fine-tune the spacing of the LaTeX tables produced by latexTable(), blank columns can be inserted at arbitrary positions via the spacerColumns argument. This is not the simplest way to adjust intercolumn space, but it solves the problem of positioning horizontal rules. In addition, no other approach gives you the freedom to insert horizontal space at arbitrary positions (useful for distinguishing tiers of columns from each other), and no other approach allows variation in the widths of the spaces between columns.