Author: Mike Stackhouse
Date: 2021-09-28

# 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')]

Species | Petal.Width
--- | ---
setosa | 0.2
setosa | 0.2
setosa | 0.2
setosa | 0.2
setosa | 0.2
setosa | 0.4
setosa | 0.3
setosa | 0.2
setosa | 0.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,]

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

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,]

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 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,]

Species | Sepal.Length | Sepal.Width | Petal.Length | Petal.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,]

Species | Sepal.Length | Sepal.Width | Petal.Length | Petal.Width
--- | --- | --- | --- | ---
Species of Flower | 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

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,]

Species | Sepal.Length | Sepal.Width | Petal.Length | Petal.Width
--- | --- | --- | --- | ---
Species of Flower | 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

## 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,]

Species | Sepal.Length | Petal.Length
--- | --- | ---
| Sepal |  | Petal |
Species of Flower | Length | Width | Length | 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

## 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,]

Species | Sepal.Length | Petal.Length
--- | --- | ---
Species of\line flower | Sepal |  | Petal |
Species of Flower | Length | Width | Length | 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

    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!

- To pick up the basics of using pharmaRTF, see `vignette("pharmaRTF")`
- To learn more advanced usage of pharmaRTF, see `vignette("advanced_usage")`
- To see how to read titles and footnotes from external files, see `vignette("tf_from_file")`