Skip to contents

The gt package has been developed to make it easy to turn data.frame and tidyverse tibble datasets into output quality tables, with a useful array of functions to help with formatting, structure and adding supporting elements to tables (such as headings and source notes). You are strongly advised to look at the gt package documentation.

The deckhand package includes the output_table() function to enable gt pipelines to work seemlessly with the deckhand CSS as well as providing access to some styling options.

library(deckhand)
library(gt)

small_df <- palmerpenguins::penguins |>
  dplyr::mutate(
    penguin_id = paste("Penguin", dplyr::row_number(), sep = " #")
  ) |>
  tidyr::drop_na() |>
  dplyr::group_by(species) |>
  dplyr::sample_n(3) |>
  dplyr::ungroup() |>
  dplyr::select(penguin_id, species, bill_length_mm, 
                flipper_length_mm, body_mass_g)

small_df
#> # A tibble: 9 × 5
#>   penguin_id   species   bill_length_mm flipper_length_mm body_mass_g
#>   <chr>        <fct>              <dbl>             <int>       <int>
#> 1 Penguin #122 Adelie              37.7               198        3500
#> 2 Penguin #18  Adelie              42.5               197        4500
#> 3 Penguin #84  Adelie              35.1               193        4200
#> 4 Penguin #302 Chinstrap           52                 197        4150
#> 5 Penguin #313 Chinstrap           47.6               195        3850
#> 6 Penguin #334 Chinstrap           49.3               203        4050
#> 7 Penguin #162 Gentoo              46.8               215        5150
#> 8 Penguin #167 Gentoo              45.8               210        4200
#> 9 Penguin #213 Gentoo              45.3               208        4200

Simple {gt} and output_table() integration

At its simplest you can pipe your data.frame into gt::gt() and then pass this directly to output_table().

small_df |>
  gt::gt() |>
  output_table()
penguin_id species bill_length_mm flipper_length_mm body_mass_g
Penguin #122 Adelie 37.7 198 3500
Penguin #18 Adelie 42.5 197 4500
Penguin #84 Adelie 35.1 193 4200
Penguin #302 Chinstrap 52.0 197 4150
Penguin #313 Chinstrap 47.6 195 3850
Penguin #334 Chinstrap 49.3 203 4050
Penguin #162 Gentoo 46.8 215 5150
Penguin #167 Gentoo 45.8 210 4200
Penguin #213 Gentoo 45.3 208 4200

You can also use the various gt helper functions to design and layout your table as desired. Depending on number formatting you may find that the scales package works more clearly with output_table() than the gt packages fmt_*() functions, this is particularly the case for percentages where scales::percent() must be used before you pass the data.frame to gt::gt().

small_out <- small_df |>
  gt::gt(rowname_col = "penguin_id") |>
  gt::tab_header("Size of penguins") |>
  gt::cols_label(
    "species" = "Species",
    "bill_length_mm" = "Bill length (mm)",
    "flipper_length_mm" = "Flipper length (mm)",
    "body_mass_g" = "Body mass (g)"
  ) |>
  gt::cols_align(align = "left", columns = "species") |>
  gt::cols_align(align = "center", columns = "flipper_length_mm") |>
  gt::fmt_integer(columns = "body_mass_g")

small_out |>
  output_table()
Size of penguins
Species Bill length (mm) Flipper length (mm) Body mass (g)
Penguin #122 Adelie 37.7 198 3,500
Penguin #18 Adelie 42.5 197 4,500
Penguin #84 Adelie 35.1 193 4,200
Penguin #302 Chinstrap 52.0 197 4,150
Penguin #313 Chinstrap 47.6 195 3,850
Penguin #334 Chinstrap 49.3 203 4,050
Penguin #162 Gentoo 46.8 215 5,150
Penguin #167 Gentoo 45.8 210 4,200
Penguin #213 Gentoo 45.3 208 4,200

Additional output_table() options

The output_table() function includes some additional features to further help with table styling.

Unique ID

If you want to explicitly style a particular table you can supply a unique id for your table, you can then use your own custom CSS to modify the styling of that table

small_out |>
  output_table(id = "example-table")
Size of penguins
Species Bill length (mm) Flipper length (mm) Body mass (g)
Penguin #122 Adelie 37.7 198 3,500
Penguin #18 Adelie 42.5 197 4,500
Penguin #84 Adelie 35.1 193 4,200
Penguin #302 Chinstrap 52.0 197 4,150
Penguin #313 Chinstrap 47.6 195 3,850
Penguin #334 Chinstrap 49.3 203 4,050
Penguin #162 Gentoo 46.8 215 5,150
Penguin #167 Gentoo 45.8 210 4,200
Penguin #213 Gentoo 45.3 208 4,200

Highlighting min/max cells

The deckhand CSS includes classes to provide cell highlighting for highest and lowest values, you do this by setting min_max_styling = TRUE and adding the flags !!min!! and !!max!! to the cells you want highlighted.

First you need to add the min/max flags, you can use dplyr::case_when() to set these, as you will be creating a character vector you will also need to coerce the unaffected values to a character form (the scales package has useful functions for this).

small_df |>
  dplyr::mutate(
    bill_length2 = dplyr::case_when(
      bill_length_mm == max(bill_length_mm) ~ 
        paste(bill_length_mm, "!!max!!"),
      bill_length_mm == min(bill_length_mm) ~ 
        paste(bill_length_mm, "!!min!!"),
      TRUE ~ 
        scales::comma(bill_length_mm, accuracy = 0.1)
    )
  ) |>
  dplyr::select(penguin_id, bill_length_mm, bill_length2)
#> # A tibble: 9 × 3
#>   penguin_id   bill_length_mm bill_length2
#>   <chr>                 <dbl> <chr>       
#> 1 Penguin #122           37.7 37.7        
#> 2 Penguin #18            42.5 42.5        
#> 3 Penguin #84            35.1 35.1 !!min!!
#> 4 Penguin #302           52   52 !!max!!  
#> 5 Penguin #313           47.6 47.6        
#> 6 Penguin #334           49.3 49.3        
#> 7 Penguin #162           46.8 46.8        
#> 8 Penguin #167           45.8 45.8        
#> 9 Penguin #213           45.3 45.3

To apply the styling you then set the min_max_styling argument of output_table() to TRUE.

small_df |>
  dplyr::mutate(
    bill_length2 = dplyr::case_when(
      bill_length_mm == max(bill_length_mm) ~ 
        paste(scales::comma(bill_length_mm, accuracy = 0.1), "!!max!!"),
      bill_length_mm == min(bill_length_mm) ~ 
        paste(scales::comma(bill_length_mm, accuracy = 0.1), "!!min!!"),
      TRUE ~ 
        scales::comma(bill_length_mm, accuracy = 0.1)
    )
  ) |>
  dplyr::select(penguin_id, bill_length2) |>
  gt::gt() |>
  output_table(min_max_styling = TRUE)
penguin_id bill_length2
Penguin #122 37.7
Penguin #18 42.5
Penguin #84 35.1
Penguin #302 52.0
Penguin #313 47.6
Penguin #334 49.3
Penguin #162 46.8
Penguin #167 45.8
Penguin #213 45.3

You can also easily apply this styling column- or row-wise across a table by first pivoting your data.frame into long format and applying the styling conditionally by column or row variable (row-wise requires a unique row id is present in the data).

Column-wise styling

small_df |>
  tidyr::pivot_longer(cols = c(-penguin_id, -species)) |>
  dplyr::group_by(name) |>
  dplyr::mutate(
    value = dplyr::case_when(
      value == max(value) ~ 
        paste(scales::comma(value, accuracy = 0.1), "!!max!!"),
      value == min(value) ~ 
        paste(scales::comma(value, accuracy = 0.1), "!!min!!"),
      TRUE ~ 
        scales::comma(value, accuracy = 0.1)
    )
  ) |>
  dplyr::ungroup() |>
  tidyr::pivot_wider(names_from = name, values_from = value) |>
  gt::gt() |>
  output_table(min_max_styling = TRUE)
penguin_id species bill_length_mm flipper_length_mm body_mass_g
Penguin #122 Adelie 37.7 198.0 3,500.0
Penguin #18 Adelie 42.5 197.0 4,500.0
Penguin #84 Adelie 35.1 193.0 4,200.0
Penguin #302 Chinstrap 52.0 197.0 4,150.0
Penguin #313 Chinstrap 47.6 195.0 3,850.0
Penguin #334 Chinstrap 49.3 203.0 4,050.0
Penguin #162 Gentoo 46.8 215.0 5,150.0
Penguin #167 Gentoo 45.8 210.0 4,200.0
Penguin #213 Gentoo 45.3 208.0 4,200.0

Row-wise styling

tibble::tibble(
    penguin_id = paste("Penguin", 1:10, sep = " #"),
    var1 = runif(10, 20, 40),
    var2 = runif(10, 20, 40),
    var3 = runif(10, 20, 40),
    var4 = runif(10, 20, 40),
    var5 = runif(10, 20, 40),
    var6 = runif(10, 20, 40)
  ) |>
  tidyr::pivot_longer(cols = -penguin_id) |>
  dplyr::group_by(penguin_id) |>
  dplyr::mutate(
    value = dplyr::case_when(
      value == max(value) ~ 
        paste(scales::comma(value, accuracy = 0.1), "!!max!!"),
      value == min(value) ~ 
        paste(scales::comma(value, accuracy = 0.1), "!!min!!"),
      TRUE ~ 
        scales::comma(value, accuracy = 0.1)
    )
  ) |>
  dplyr::ungroup() |>
  tidyr::pivot_wider(names_from = name, values_from = value) |>
  gt::gt() |>
  output_table(min_max_styling = TRUE)
penguin_id var1 var2 var3 var4 var5 var6
Penguin #1 29.4 22.3 29.0 36.7 35.3 33.0
Penguin #2 28.9 29.4 21.6 28.8 24.1 27.8
Penguin #3 35.5 32.7 21.4 38.9 38.7 24.7
Penguin #4 30.4 31.3 21.7 23.1 26.8 39.6
Penguin #5 39.3 32.5 24.7 24.4 32.2 24.0
Penguin #6 36.6 30.4 39.8 32.0 37.8 35.6
Penguin #7 24.8 30.7 33.4 37.9 20.8 31.9
Penguin #8 27.3 33.4 38.2 30.8 32.5 29.0
Penguin #9 23.5 37.6 27.3 24.3 20.7 34.2
Penguin #10 31.8 34.1 30.9 29.8 29.6 32.2

Drop header rows

It may be the case you have a table where you do not want to show the column headings, in this case you can set drop_header = TRUE to remove them from the output table.

tibble::tribble(
    ~question, ~response, ~value,
    "A1", "I saw lots of penguins", 0.60,
    "A1", "I saw a few penguins", 0.25,
    "A1", "I saw no penguins", 0.15
  ) |>
  dplyr::mutate(
    question = dplyr::case_when(
      question == "A1" ~ "How many penguins did you see?"
    ),
    value = scales::percent(value, accuracy = 1)
  ) |>
  gt::gt(groupname_col = "question") |>
  output_table(drop_header = TRUE)
How many penguins did you see?
I saw lots of penguins 60%
I saw a few penguins 25%
I saw no penguins 15%

Smaller text size

Sometimes you might be needed to include a large table in your report, in which case the default padding within the table might need to be reduced, you can make your table take up a “smaller” amount of space than it otherwise would by setting small = TRUE.

palmerpenguins::penguins |>
  dplyr::mutate(
    penguin_id = paste("Penguin", dplyr::row_number(), sep = " #")
  ) |>
  tidyr::drop_na() |>
  dplyr::group_by(species) |>
  dplyr::sample_n(6) |>
  dplyr::ungroup() |>
  dplyr::select(penguin_id, species, bill_length_mm, 
                flipper_length_mm, body_mass_g) |>
  gt::gt() |>
  output_table(small = TRUE)
penguin_id species bill_length_mm flipper_length_mm body_mass_g
Penguin #132 Adelie 43.1 197 3500
Penguin #88 Adelie 36.9 189 3500
Penguin #40 Adelie 39.8 184 4650
Penguin #144 Adelie 40.7 190 3725
Penguin #46 Adelie 39.6 190 4600
Penguin #92 Adelie 41.1 205 4300
Penguin #339 Chinstrap 45.7 195 3650
Penguin #340 Chinstrap 55.8 207 4000
Penguin #344 Chinstrap 50.2 198 3775
Penguin #311 Chinstrap 49.7 195 3600
Penguin #321 Chinstrap 50.9 196 3675
Penguin #330 Chinstrap 50.7 203 4050
Penguin #155 Gentoo 48.7 210 4450
Penguin #190 Gentoo 44.4 219 5250
Penguin #173 Gentoo 50.2 218 5700
Penguin #161 Gentoo 43.3 209 4400
Penguin #221 Gentoo 43.5 220 4700
Penguin #216 Gentoo 54.3 231 5650