library(gt)
library(tidyverse)Overview
This post is inspired by themockup’s blog and RStudio’s documentation of gt package1.
1 You can find the documentation here.
“We can construct a wide variety of useful tables with a cohesive set of table parts. These include the table header, the stub, the column labels and spanner column labels, the table body, and the table footer.”
--- The gt philosophy
1 Component
The typical gt table starts with converting a data frame into a gt object. The gt object is then modified by adding various components to it. The components include the table header, the stub, the column labels and spanner column labels, the table body, and the table footer.
2 Example: sp500 data
As always, we install and load gt and tidyverse packages.
gt()function can convert data.frame intogtobject. Agtobject can be directly output and rendered into HTML.
# Define the start and end dates for the data range
start_date <- "2010-06-07"
end_date <- "2010-06-14"
# Create a gt table based on preprocessed
# `sp500` table data
sp500_gt <- sp500 |>
dplyr::filter(date >= start_date & date <= end_date) |>
dplyr::select(-adj_close) |>
gt()
sp500_gt| date | open | high | low | close | volume |
|---|---|---|---|---|---|
| 2010-06-14 | 1095.00 | 1105.91 | 1089.03 | 1089.63 | 4425830000 |
| 2010-06-11 | 1082.65 | 1092.25 | 1077.12 | 1091.60 | 4059280000 |
| 2010-06-10 | 1058.77 | 1087.85 | 1058.77 | 1086.84 | 5144780000 |
| 2010-06-09 | 1062.75 | 1077.74 | 1052.25 | 1055.69 | 5983200000 |
| 2010-06-08 | 1050.81 | 1063.15 | 1042.17 | 1062.00 | 6192750000 |
| 2010-06-07 | 1065.84 | 1071.36 | 1049.86 | 1050.47 | 5467560000 |
2.1 tab_header: title and subtitle
- The
gtobject can be further modified by adding components to it, for exampletab_headeradds Table Headers (including title and subtitle).- For narratives like title or subtitle, you can also use markdown format with the
mdfunction.
- For narratives like title or subtitle, you can also use markdown format with the
sp500_gt |>
tab_header(
title = "S&P 500",
subtitle = "June 7-14, 2010"
)| S&P 500 | |||||
| June 7-14, 2010 | |||||
| date | open | high | low | close | volume |
|---|---|---|---|---|---|
| 2010-06-14 | 1095.00 | 1105.91 | 1089.03 | 1089.63 | 4425830000 |
| 2010-06-11 | 1082.65 | 1092.25 | 1077.12 | 1091.60 | 4059280000 |
| 2010-06-10 | 1058.77 | 1087.85 | 1058.77 | 1086.84 | 5144780000 |
| 2010-06-09 | 1062.75 | 1077.74 | 1052.25 | 1055.69 | 5983200000 |
| 2010-06-08 | 1050.81 | 1063.15 | 1042.17 | 1062.00 | 6192750000 |
| 2010-06-07 | 1065.84 | 1071.36 | 1049.86 | 1050.47 | 5467560000 |
Note that if your Quarto HTML has CSS style, the markdown format in gt object may not work as expected. Like the following title will be bold but with underline (my CSS style).
sp500_gt |>
tab_header(
title = md("**S&P 500**"),
subtitle = md("*June 7-14*, 2010")
)| S&P 500 | |||||
| June 7-14, 2010 | |||||
| date | open | high | low | close | volume |
|---|---|---|---|---|---|
| 2010-06-14 | 1095.00 | 1105.91 | 1089.03 | 1089.63 | 4425830000 |
| 2010-06-11 | 1082.65 | 1092.25 | 1077.12 | 1091.60 | 4059280000 |
| 2010-06-10 | 1058.77 | 1087.85 | 1058.77 | 1086.84 | 5144780000 |
| 2010-06-09 | 1062.75 | 1077.74 | 1052.25 | 1055.69 | 5983200000 |
| 2010-06-08 | 1050.81 | 1063.15 | 1042.17 | 1062.00 | 6192750000 |
| 2010-06-07 | 1065.84 | 1071.36 | 1049.86 | 1050.47 | 5467560000 |
2.2 fmt_<column_type>: format columns
fmt_<column_type>functions can be used to format the columns of the table.- For example,
fmt_currencycan be used to format theclosecolumn as currency. fmt_datecan be used to format thedatecolumn as date.fmt_numbercan be used to format thevolumecolumn as a number.
- For example,
sp500_gt |>
tab_header(
title = "S&P 500",
subtitle = "June 7-14, 2010"
) |>
fmt_currency(
columns = vars(c(open, high, low, close)),
currency = "USD"
) |>
fmt_date(
columns = vars(date),
date_style = "wday_month_day_year"
) |>
fmt_number(
columns = vars(volume),
suffixing = TRUE
)| S&P 500 | |||||
| June 7-14, 2010 | |||||
| date | open | high | low | close | volume |
|---|---|---|---|---|---|
| Monday, June 14, 2010 | $1,095.00 | $1,105.91 | $1,089.03 | $1,089.63 | 4.43B |
| Friday, June 11, 2010 | $1,082.65 | $1,092.25 | $1,077.12 | $1,091.60 | 4.06B |
| Thursday, June 10, 2010 | $1,058.77 | $1,087.85 | $1,058.77 | $1,086.84 | 5.14B |
| Wednesday, June 9, 2010 | $1,062.75 | $1,077.74 | $1,052.25 | $1,055.69 | 5.98B |
| Tuesday, June 8, 2010 | $1,050.81 | $1,063.15 | $1,042.17 | $1,062.00 | 6.19B |
| Monday, June 7, 2010 | $1,065.84 | $1,071.36 | $1,049.86 | $1,050.47 | 5.47B |
2.3 tab_source_note: source note
tab_source_notecan be used to add a source note to the table underneath the table.
sp500_gt |>
tab_header(
title = "S&P 500",
subtitle = "June 7-14, 2010"
) |>
tab_source_note(
source_note = "Data from Yahoo Finance"
)| S&P 500 | |||||
| June 7-14, 2010 | |||||
| date | open | high | low | close | volume |
|---|---|---|---|---|---|
| 2010-06-14 | 1095.00 | 1105.91 | 1089.03 | 1089.63 | 4425830000 |
| 2010-06-11 | 1082.65 | 1092.25 | 1077.12 | 1091.60 | 4059280000 |
| 2010-06-10 | 1058.77 | 1087.85 | 1058.77 | 1086.84 | 5144780000 |
| 2010-06-09 | 1062.75 | 1077.74 | 1052.25 | 1055.69 | 5983200000 |
| 2010-06-08 | 1050.81 | 1063.15 | 1042.17 | 1062.00 | 6192750000 |
| 2010-06-07 | 1065.84 | 1071.36 | 1049.86 | 1050.47 | 5467560000 |
| Data from Yahoo Finance | |||||
2.4 tab_footnote: footnotes
Beside the markdown format, Quarto HTML also accepts HTML code using htmltools::p function. But it may only apply to HTML not PDF. As Figure 1 shows, footnotes are located at the bottom of the table but at the top of source notes.
cells_*(): target cells in the table
We were able to supply the reference locations in the table by using the cells_body() helper function and supplying the necessary targeting through the columns and rows arguments. Other cells_*() functions have similar interfaces and they allow us to target cells in different parts of the table.
sp500_gt |>
tab_header(
title = "S&P 500",
subtitle = "June 7-14, 2010"
) |>
tab_source_note(
source_note = htmltools::p(align="right", "Data from Yahoo Finance")
) |>
tab_footnote(
footnote = "All values are in USD.",
locations = cells_body(
columns = vars(open),
rows = date == "2010-06-14"
)
)| S&P 500 | |||||
| June 7-14, 2010 | |||||
| date | open | high | low | close | volume |
|---|---|---|---|---|---|
| 2010-06-14 | 1 1095.00 | 1105.91 | 1089.03 | 1089.63 | 4425830000 |
| 2010-06-11 | 1082.65 | 1092.25 | 1077.12 | 1091.60 | 4059280000 |
| 2010-06-10 | 1058.77 | 1087.85 | 1058.77 | 1086.84 | 5144780000 |
| 2010-06-09 | 1062.75 | 1077.74 | 1052.25 | 1055.69 | 5983200000 |
| 2010-06-08 | 1050.81 | 1063.15 | 1042.17 | 1062.00 | 6192750000 |
| 2010-06-07 | 1065.84 | 1071.36 | 1049.86 | 1050.47 | 5467560000 |
Data from Yahoo Finance |
|||||
| 1 All values are in USD. | |||||
2.5 tab_row_group: row groups
- We can make a new row group with each tab_row_group() call.
- The inputs are row group names in the label argument, and row references in the rows argument.
- key arguments:
label: the name of the row group.rows: the logical vector that specifies which rows belong to the group.
Note that the sequence of adding tab_row_group will affect the order of the row groups in the final table. Lastest added group will be on the top.
sp500_gt |>
tab_header(
title = "S&P 500",
subtitle = "June 7-14, 2010"
) |>
tab_row_group(
label = "Last three days",
rows = date %in% c("2010-06-10", "2010-06-11", "2010-06-14")
) |>
tab_row_group(
label = "First three days",
rows = date %in% c("2010-06-07", "2010-06-08", "2010-06-09")
) | S&P 500 | |||||
| June 7-14, 2010 | |||||
| date | open | high | low | close | volume |
|---|---|---|---|---|---|
| First three days | |||||
| 2010-06-09 | 1062.75 | 1077.74 | 1052.25 | 1055.69 | 5983200000 |
| 2010-06-08 | 1050.81 | 1063.15 | 1042.17 | 1062.00 | 6192750000 |
| 2010-06-07 | 1065.84 | 1071.36 | 1049.86 | 1050.47 | 5467560000 |
| Last three days | |||||
| 2010-06-14 | 1095.00 | 1105.91 | 1089.03 | 1089.63 | 4425830000 |
| 2010-06-11 | 1082.65 | 1092.25 | 1077.12 | 1091.60 | 4059280000 |
| 2010-06-10 | 1058.77 | 1087.85 | 1058.77 | 1086.84 | 5144780000 |
2.6 tab_spanner: column groups and spann column labels
- key arguments:
label: the name of the column group.columns: the columns that belong to the group.
sp500_gt |>
tab_header(
title = "S&P 500",
subtitle = "June 7-14, 2010"
) |>
tab_spanner(
label = "Price",
columns = vars(open, high, low, close)
) |>
tab_spanner(
label = "Volume",
columns = vars(volume)
)| S&P 500 | |||||
| June 7-14, 2010 | |||||
| date |
Price
|
Volume
|
|||
|---|---|---|---|---|---|
| open | high | low | close | volume | |
| 2010-06-14 | 1095.00 | 1105.91 | 1089.03 | 1089.63 | 4425830000 |
| 2010-06-11 | 1082.65 | 1092.25 | 1077.12 | 1091.60 | 4059280000 |
| 2010-06-10 | 1058.77 | 1087.85 | 1058.77 | 1086.84 | 5144780000 |
| 2010-06-09 | 1062.75 | 1077.74 | 1052.25 | 1055.69 | 5983200000 |
| 2010-06-08 | 1050.81 | 1063.15 | 1042.17 | 1062.00 | 6192750000 |
| 2010-06-07 | 1065.84 | 1071.36 | 1049.86 | 1050.47 | 5467560000 |
2.7 summary_rows: summary rows
summary_rowscan be used to add summary rows to the table bygroup.- if you want to have grant summary statistics, use
grand_summary_rowsinstead.
- if you want to have grant summary statistics, use
- key arguments:
fns: a list of functions to apply to the columns.columns: the columns to which the functions are applied.
Note that the fmt argument is used to format the summary values with a list. There are multiple formatting functions for numeric variables available in the gt package:
fmt_number(cols = vars(...), decimals = 2): format numbers with a specified number of 2 decimal places.fmt_scientific(cols = vars(...), decimals = 3): format numbers in scientific notation with a specified number of 3 decimal places.
sp500_gt |>
tab_header(
title = "S&P 500",
subtitle = "June 7-14, 2010"
) |>
grand_summary_rows(
fns = list(
"Mean" = ~ mean(., na.rm =TRUE),
"SD" = ~ sd(., na.rm =TRUE)
),
columns = vars(open, high, low, close, volume),
fmt = list(
~ fmt_scientific(data = .,
columns = vars(volume),
decimals = 3),
~ fmt_number(data = .,
columns = vars(open, high, low, close),
decimals = 2)
)
)| S&P 500 | ||||||
| June 7-14, 2010 | ||||||
| date | open | high | low | close | volume | |
|---|---|---|---|---|---|---|
| 2010-06-14 | 1095.00 | 1105.91 | 1089.03 | 1089.63 | 4425830000 | |
| 2010-06-11 | 1082.65 | 1092.25 | 1077.12 | 1091.60 | 4059280000 | |
| 2010-06-10 | 1058.77 | 1087.85 | 1058.77 | 1086.84 | 5144780000 | |
| 2010-06-09 | 1062.75 | 1077.74 | 1052.25 | 1055.69 | 5983200000 | |
| 2010-06-08 | 1050.81 | 1063.15 | 1042.17 | 1062.00 | 6192750000 | |
| 2010-06-07 | 1065.84 | 1071.36 | 1049.86 | 1050.47 | 5467560000 | |
| Mean | — | 1,069.30 | 1,083.04 | 1,061.53 | 1,072.70 | 5.212 × 109 |
| SD | — | 16.41 | 15.43 | 17.91 | 18.66 | 8.454 × 108 |
2.8 cols_xxx(): manupulate columns’ positions
- Functions:
cols_move_to_start(): move columns to the start of the table.cols_move_to_end(): move columns to the end of the table.cols_hide(): hide columns.
3 Styling of the table
A basic gt table can be created as so
data("iris")
glimpse(iris)Rows: 150
Columns: 5
$ Sepal.Length <dbl> 5.1, 4.9, 4.7, 4.6, 5.0, 5.4, 4.6, 5.0, 4.4, 4.9, 5.4, 4.…
$ Sepal.Width <dbl> 3.5, 3.0, 3.2, 3.1, 3.6, 3.9, 3.4, 3.4, 2.9, 3.1, 3.7, 3.…
$ Petal.Length <dbl> 1.4, 1.4, 1.3, 1.5, 1.4, 1.7, 1.4, 1.5, 1.4, 1.5, 1.5, 1.…
$ Petal.Width <dbl> 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.3, 0.2, 0.2, 0.1, 0.2, 0.…
$ Species <fct> setosa, setosa, setosa, setosa, setosa, setosa, setosa, s…
You can add row names (rowname_col argument) and add group names (groupname_col argument) into the table:
iris_gt <- iris |>
arrange(desc(Sepal.Length)) |> # 6 types of iris with largest sepal length
mutate(Rank = 1:nrow(iris)) |>
slice_head(n = 3, by = Species) |> # select top 3 Sepal length of each species
gt(groupname_col = "Species", rowname_col = "Rank")
iris_gt| Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | |
|---|---|---|---|---|
| virginica | ||||
| 1 | 7.9 | 3.8 | 6.4 | 2.0 |
| 2 | 7.7 | 3.8 | 6.7 | 2.2 |
| 3 | 7.7 | 2.6 | 6.9 | 2.3 |
| versicolor | ||||
| 13 | 7.0 | 3.2 | 4.7 | 1.4 |
| 14 | 6.9 | 3.1 | 4.9 | 1.5 |
| 18 | 6.8 | 2.8 | 4.8 | 1.4 |
| setosa | ||||
| 71 | 5.8 | 4.0 | 1.2 | 0.2 |
| 78 | 5.7 | 4.4 | 1.5 | 0.4 |
| 79 | 5.7 | 3.8 | 1.7 | 0.3 |
3.1 tab_style: change style of cells
tab_style(data, style, locations)allows you to change the style (style) of cells (locations) in the table.
style argument
- the background color of the cell (
cell_fill(): color) - the cell’s text color, font, and size (
cell_text(): color, font, size) - the text style (
cell_text(): style), enabling the use of italics or oblique text. - the text weight (
cell_text(): weight), allowing the use of thin to bold text (the degree of choice is greater with variable fonts) - the alignment and indentation of text (
cell_text(): align and indent) - the cell borders (
cell_borders())
iris_gt_colored <- iris_gt |>
tab_style( # style for virginica
style = list(
cell_fill(color = "lightblue"),
cell_text(weight = "bold")
),
locations = cells_body(
columns = colnames(iris),
rows = Species == "virginica")
) |>
tab_style( # style for versicolor
style = list(
cell_fill(color = "royalblue"),
cell_text(color = "red", weight = "bold")
),
locations = cells_body(
columns = colnames(iris),
rows = Species == "versicolor")
)
iris_gt_colored| Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | |
|---|---|---|---|---|
| virginica | ||||
| 1 | 7.9 | 3.8 | 6.4 | 2.0 |
| 2 | 7.7 | 3.8 | 6.7 | 2.2 |
| 3 | 7.7 | 2.6 | 6.9 | 2.3 |
| versicolor | ||||
| 13 | 7.0 | 3.2 | 4.7 | 1.4 |
| 14 | 6.9 | 3.1 | 4.9 | 1.5 |
| 18 | 6.8 | 2.8 | 4.8 | 1.4 |
| setosa | ||||
| 71 | 5.8 | 4.0 | 1.2 | 0.2 |
| 78 | 5.7 | 4.4 | 1.5 | 0.4 |
| 79 | 5.7 | 3.8 | 1.7 | 0.3 |
- Next, the boarder could be added into the table:
iris_gt_colored |>
tab_style( # tab_style to change style of cells,
# cells_borders provides the formatting
# locations tells it where add black borders to all column labels
style = cell_borders(
sides = "left",
color = "black",
weight = px(1.2)
),
locations = cells_body(columns = colnames(iris))
) |>
# Add botton line below the column names
tab_style(
style = cell_borders(
sides = "bottom",
color = "black",
weight = px(3)
),
locations = cells_column_labels(columns = gt::everything())
)| Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | |
|---|---|---|---|---|
| virginica | ||||
| 1 | 7.9 | 3.8 | 6.4 | 2.0 |
| 2 | 7.7 | 3.8 | 6.7 | 2.2 |
| 3 | 7.7 | 2.6 | 6.9 | 2.3 |
| versicolor | ||||
| 13 | 7.0 | 3.2 | 4.7 | 1.4 |
| 14 | 6.9 | 3.1 | 4.9 | 1.5 |
| 18 | 6.8 | 2.8 | 4.8 | 1.4 |
| setosa | ||||
| 71 | 5.8 | 4.0 | 1.2 | 0.2 |
| 78 | 5.7 | 4.4 | 1.5 | 0.4 |
| 79 | 5.7 | 3.8 | 1.7 | 0.3 |
3.2 tab_options: table output options
tab_optionsallows you to change the output options of the table. There are multiple options available:
table.font.names=allows you to change the font of the table.
iris_gt_colored |>
tab_options(
table.font.names = c("Tenor Sans")
)| Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | |
|---|---|---|---|---|
| virginica | ||||
| 1 | 7.9 | 3.8 | 6.4 | 2.0 |
| 2 | 7.7 | 3.8 | 6.7 | 2.2 |
| 3 | 7.7 | 2.6 | 6.9 | 2.3 |
| versicolor | ||||
| 13 | 7.0 | 3.2 | 4.7 | 1.4 |
| 14 | 6.9 | 3.1 | 4.9 | 1.5 |
| 18 | 6.8 | 2.8 | 4.8 | 1.4 |
| setosa | ||||
| 71 | 5.8 | 4.0 | 1.2 | 0.2 |
| 78 | 5.7 | 4.4 | 1.5 | 0.4 |
| 79 | 5.7 | 3.8 | 1.7 | 0.3 |
table.width=allows you to change the width of the table.
iris_gt_colored |>
tab_options(table.width = px(700))| Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | |
|---|---|---|---|---|
| virginica | ||||
| 1 | 7.9 | 3.8 | 6.4 | 2.0 |
| 2 | 7.7 | 3.8 | 6.7 | 2.2 |
| 3 | 7.7 | 2.6 | 6.9 | 2.3 |
| versicolor | ||||
| 13 | 7.0 | 3.2 | 4.7 | 1.4 |
| 14 | 6.9 | 3.1 | 4.9 | 1.5 |
| 18 | 6.8 | 2.8 | 4.8 | 1.4 |
| setosa | ||||
| 71 | 5.8 | 4.0 | 1.2 | 0.2 |
| 78 | 5.7 | 4.4 | 1.5 | 0.4 |
| 79 | 5.7 | 3.8 | 1.7 | 0.3 |