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 |