Huxtable Tips

Mike Stackhouse

2021-09-28

Notes

Huxtable v5.0.0 introduced some backwards compatibility breaking changes. Read about these updates here. The changes that impact pharmaRTF and its related usage were: - The way indexing was handled has changed. You can no longer index columns like ht[1:5]. The new syntax is ht[1:5, ]. - Additionally, the add_columns argument now changed from a default of TRUE instead of FALSE.

pharmaRTF is compatible back to Huxtable v4.7.1.

Introduction

While pharmaRTF can cover your titles, footnotes, and encapsulating document properties - a key feature to creating the tables you need is having a good understand of huxtable. Huxtable has plenty of great user documentation within its own vignettes, available here, but within this vignette we will cover some core features necessary to create beautiful tables.

A Small Note

Some of the syntax we use throughout this document uses the %>% pipe operator. This is to display support for a tidyverse style of coding. You can find the %>% operator in the magittr package.

Huxtable Basics

Something important to understand about huxtable is that it starts as an abstraction of a data frame. This means that in general, you can index and slice the huxtable object the same way you would with a normal data.frame object in R.

ht <- huxtable::as_hux(iris[, c("Species", "Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width")], 
                       add_colnames=TRUE)
ht[1:10, c('Species', 'Petal.Width')]
SpeciesPetal.Width
setosa0.2
setosa0.2
setosa0.2
setosa0.2
setosa0.2
setosa0.4
setosa0.3
setosa0.2
setosa0.2

As you can see here, a nice feature of huxtable is that it has a lot of automatic printing options. Within the code of an R markdown file, it automatically outputs as an HTML table. When developing within an R script, it will also print formatted text to your console. This can be very useful for development as you get an idea of how your display will look within the RTF document.

huxtable::print_screen(ht[1:10,])
#>        Species   Sepal.Length   Sepal.Width   Petal.Length   Petal.Width  
#>        setosa             5.1           3.5            1.4           0.2  
#>        setosa             4.9           3              1.4           0.2  
#>        setosa             4.7           3.2            1.3           0.2  
#>        setosa             4.6           3.1            1.5           0.2  
#>        setosa             5             3.6            1.4           0.2  
#>        setosa             5.4           3.9            1.7           0.4  
#>        setosa             4.6           3.4            1.4           0.3  
#>        setosa             5             3.4            1.5           0.2  
#>        setosa             4.4           2.9            1.4           0.2  
#> 
#> Column names: Species, Sepal.Length, Sepal.Width, Petal.Length, Petal.Width

Changing Table Shape

One thing to note is that the default width of a huxtable table will not fill a whole landscape RTF document (when the page size is 11" x 8.5"). A good rule of thumb to follow is that setting the huxtable table width to 1.5 will fill the page properly

# Assign the attribute
huxtable::width(ht) <- 1.5

# Pipe the attribute
ht <- ht %>% 
  huxtable::set_width(1.5)

ht[1:10,]
SpeciesSepal.LengthSepal.WidthPetal.LengthPetal.Width
setosa5.13.51.40.2
setosa4.93  1.40.2
setosa4.73.21.30.2
setosa4.63.11.50.2
setosa5  3.61.40.2
setosa5.43.91.70.4
setosa4.63.41.40.3
setosa5  3.41.50.2
setosa4.42.91.40.2

By default, all columns will have an equal width. You can set individual column widths as a ratio of the total width using the col_width property functions. Note that you want these values to sum to 1 - if they are less than one, the columns won’t fill up the total width of the table

# Assign the attribute
huxtable::col_width(ht) <- c(.4, .15, .15, .15, .15)

# Pipe the attribute
ht <- ht %>% 
  huxtable::set_col_width(c(.4, .15, .15, .15, .15))

ht[1:10,]
SpeciesSepal.LengthSepal.WidthPetal.LengthPetal.Width
setosa5.13.51.40.2
setosa4.93  1.40.2
setosa4.73.21.30.2
setosa4.63.11.50.2
setosa5  3.61.40.2
setosa5.43.91.70.4
setosa4.63.41.40.3
setosa5  3.41.50.2
setosa4.42.91.40.2

Column Headers

The add_colnames argument inserts the column names into the huxtable table. Something important to understand here is that this argument actually inserts the column names as the first row of the table. Therefore, there are no special properties of the column names compared to other data points within the table:

ht[1,]
SpeciesSepal.LengthSepal.WidthPetal.LengthPetal.Width

This is important to understand, because it actually makes creating column headers very easy in that you don’t have to worry about the variable names in the data. Simply create a data frame of the desired column headers and bind it to the data, and then do not use the add_colnames argument.

column_headers <- data.frame(
  Species = "Species of Flower",
  Sepal.Length = "Sepal Length",
  Sepal.Width = "Sepal Width",
  Petal.Length = "Petal Length",
  Petal.Width = "Petal Width",
  stringsAsFactors = FALSE
)

ht <- huxtable::as_hux(rbind(column_headers, iris))
ht[1:10,]
SpeciesSepal.LengthSepal.WidthPetal.LengthPetal.Width
Species of FlowerSepal LengthSepal WidthPetal LengthPetal Width
setosa5.13.51.40.2
setosa4.931.40.2
setosa4.73.21.30.2
setosa4.63.11.50.2
setosa53.61.40.2
setosa5.43.91.70.4
setosa4.63.41.40.3
setosa53.41.50.2

Given that the column headers are just another row of the data frame, this also means that you can use multiple rows as your column headers. More on how to take advantage of that later.

Cell Formatting

Huxtable supports many different cell formatting properties, including borders, bold, italic, horizontal and vertical alignment, and cell padding. All of these tend to be important when creating outputs in clinical reports. To set a cell property, you simply specify the rows and columns you want to change and apply the property function. When in doubt, consult the documentation to understand the proper values. As with essentially all of the huxtable property functions, there are multiple methods of applying the property to suite your preferred syntax style.

# Assign attributes directly to object
huxtable::bold(ht[1, ]) <- TRUE
huxtable::italic(ht[2, ]) <- TRUE

# Use piping
ht <- ht %>%
  # Bottom border top row
  huxtable::set_bottom_border(1, 1:ncol(ht), 1) %>% 
  # Thicker bottom border on 2nd row
  huxtable::set_bottom_border(2, 1:ncol(ht), 4) %>% 
  # Align the 3rd row, 2nd column right
  huxtable::set_align(3, 2, 'right') 

ht[1:10,]
SpeciesSepal.LengthSepal.WidthPetal.LengthPetal.Width
Species of FlowerSepal LengthSepal WidthPetal LengthPetal Width
setosa5.13.51.40.2
setosa4.931.40.2
setosa4.73.21.30.2
setosa4.63.11.50.2
setosa53.61.40.2
setosa5.43.91.70.4
setosa4.63.41.40.3
setosa53.41.50.2

Cell Merging

Another very useful feature of huxtable is the ability to specify and merge cells. This is done simply by selecting the rows and columns you’d like to merge of the huxtable object. The top left cell’s value will take on the value of the merged cells.

To tie this in with a practical example, think of spanning headers. As mentioned, since column headers are just rows of the huxtable object, we can create spanning headers by creating 2 rows of column headers and merging cells.


# Create the column headers data frame
column_headers <- data.frame(
  Species = c("", "Species of Flower"),
  Sepal.Length = c("Sepal", "Length"),
  Sepal.Width = c("", "Width"),
  Petal.Length = c("Petal", "Length"),
  Petal.Width = c("", "Width"),
  stringsAsFactors = FALSE
)

# Create the huxtable table
ht <- huxtable::as_hux(rbind(column_headers, iris)) %>% 
  # Merge the Sepal cell over the Length and Width
  huxtable::merge_cells(1, 2:3) %>% 
  # Merge the Petal cell over the Length and Width
  huxtable::merge_cells(1, 4:5) %>% 
  # Align the top cells for both Sepal and Petal
  huxtable::set_align(1,2, 'center') %>% 
  huxtable::set_align(1,4, 'center') %>% 
  # Bold all the column header rows
  huxtable::set_bold(1:2, 1:ncol(iris), TRUE) %>% 
  # Bottom border on 1st column header row 
  huxtable::set_bottom_border(1, 2:4, 1) %>% 
  # Bottom border on 2nd row
  huxtable::set_bottom_border(2, 1:ncol(iris), 2)

ht[1:10,]
SpeciesSepal.LengthPetal.Length
SepalPetal
Species of FlowerLengthWidthLengthWidth
setosa5.13.51.40.2
setosa4.931.40.2
setosa4.73.21.30.2
setosa4.63.11.50.2
setosa53.61.40.2
setosa5.43.91.70.4
setosa4.63.41.40.3

Some Final Notes

Getting your huxtable table right takes some experimentation, and when outputting to RTF it will take multiple iterations to get it right. One very important note is that it’s common to have to insert line breaks in your data manually - be it to make your data flow nicely within cells, or to organize column headers properly. Within pharmaRTF, for this you will need to use RTF syntax. Instead of the traditional \n - this is just \line. But in R, you need to add an extra backslash (\) because backslashes are break characters. So to insert a line break in your text, simply add \\line.

A second step that you need to take is to set escape_contents to false. This is because by default, huxtable will do some cleaning when generating the output to try to catch certain syntax - and when the RTF generates, the escape character itself has been escaped. The result is that \line will appear in your document as text. Fortunately, this is just one extra line of code.

# Overwrite the column header cell
ht[2, 1] <- "Species of\\line flower"
# Set escape contents to false
huxtable::escape_contents(ht) <- FALSE
# Set display width
huxtable::width(ht) <- 1.5
ht[1:10,]
SpeciesSepal.LengthPetal.Length
Species of\line flowerSepalPetal
Species of FlowerLengthWidthLengthWidth
setosa5.13.51.40.2
setosa4.931.40.2
setosa4.73.21.30.2
setosa4.63.11.50.2
setosa53.61.40.2
setosa5.43.91.70.4
setosa4.63.41.40.3

doc <- rtf_doc(ht, header_rows = 2)
write_rtf(doc, file='table4.rtf')

And within the RTF output, the lines will display properly.

Further Reading

Be sure to check out our other vignettes!