The tab_spanner_delim() function

With a subset of the towny dataset, we can create a gt table and then use tab_spanner_delim() to automatically generate column spanner labels. In this case we have some column names in the form population_<year>. The underscore character is the delimiter that separates a common word "population" and a year value. In this default way of splitting, fragments to the right are lowest (really they become new column labels) and moving left we get spanners. Let’s have a look at how tab_spanner_delim() handles these column names:

towny_subset_gt <-
  towny |>
  dplyr::select(name, starts_with("population")) |>
  dplyr::filter(grepl("^F", name)) |>
  gt() |>
  tab_spanner_delim(delim = "_") |>
  fmt_integer()

towny_subset_gt
name
population
1996 2001 2006 2011 2016 2021
Faraday 1,638 1,581 1,578 1,468 1,401 1,612
Fauquier-Strickland 684 678 568 530 536 467
Fort Erie 27,183 28,143 29,925 29,960 30,710 32,901
Fort Frances 8,790 8,315 8,103 7,952 7,739 7,466
French River 2,847 2,810 2,659 2,442 2,662 2,828
Front of Yonge 2,530 2,639 2,803 2,752 2,602 2,595
Frontenac Islands 1,661 1,638 1,862 1,864 1,760 1,930

The spanner created through this use of tab_spanner_delim() is automatically given an ID value by gt. Because it’s hard to know what the ID value is, we can use tab_info() to inspect the table’s indices and ID values.

towny_subset_gt |> tab_info()
Information on ID and Label Values
ID Idx
Lvl
Label
Columns
name 1 name
population_1996 2 1996
population_2001 3 2001
population_2006 4 2006
population_2011 5 2011
population_2016 6 2016
population_2021 7 2021
Rows
<< Index values 1 to 7 >>

Spanners
spanner-population_1996 1 population

From this informational table, we see that the ID for the spanner is "spanner-population_1996". Also, the columns are still accessible by the original column names (tab_spanner_delim() did change their labels though). Let’s use tab_style() along with cells_column_spanners() to add some styling to the spanner label of the towny_subset_gt table.

towny_subset_gt |>
  tab_style(
    style = cell_text(weight = "bold", transform = "capitalize"),
    locations = cells_column_spanners(spanners = "spanner-population_1996")
  )
name
population
1996 2001 2006 2011 2016 2021
Faraday 1,638 1,581 1,578 1,468 1,401 1,612
Fauquier-Strickland 684 678 568 530 536 467
Fort Erie 27,183 28,143 29,925 29,960 30,710 32,901
Fort Frances 8,790 8,315 8,103 7,952 7,739 7,466
French River 2,847 2,810 2,659 2,442 2,662 2,828
Front of Yonge 2,530 2,639 2,803 2,752 2,602 2,595
Frontenac Islands 1,661 1,638 1,862 1,864 1,760 1,930

We can plan ahead a bit and refashion the column names with dplyr before introducing the table to gt() and tab_spanner_delim(). Here the column labels have underscore delimiters where splitting is not wanted (so a period or space character is used instead). The usage of tab_spanner_delim() gives two levels of spanners. We can further touch up the labels after that with cols_label_with() and text_transform().

towny |>
  dplyr::slice_max(population_2021, n = 5) |>
  dplyr::select(name, ends_with("pct")) |>
  dplyr::rename_with(
    .fn = function(x) {
      x |>
        sub("pop_change_", "Population Change.", x = _) |>
        sub("_pct", ".pct", x = _)
    }
  ) |>
  gt(rowname_col = "name") |>
  tab_spanner_delim(delim = ".") |>
  fmt_number(decimals = 1, scale_by = 100) |>
  cols_label_with(
    fn = function(x) gsub("pct", "%", x)
  ) |>
  text_transform(
    fn = function(x) gsub("_", " - ", x, fixed = TRUE),
    locations = cells_column_spanners()
  ) |>
  tab_style(
    style = cell_text(align = "center"),
    locations = cells_column_labels()
  ) |>
  tab_style(
    style = "padding-right: 36px;",
    locations = cells_body()
  )
Population Change
1996 - 2001
2001 - 2006
2006 - 2011
2011 - 2016
2016 - 2021
% % % % %
Toronto 4.0 0.9 4.5 4.5 2.3
Ottawa 7.3 4.9 8.8 5.8 8.9
Mississauga 12.6 9.1 6.7 1.1 −0.5
Brampton 21.3 33.3 20.8 13.3 10.6
Hamilton 4.8 2.9 3.0 3.3 6.0

With a summarized, filtered, and pivoted version of the pizzaplace dataset, we can create another gt table and then use tab_spanner_delim() with the delimiter/separator also used in tidyr::pivot_wider(). We can also process the generated column labels with cols_label_with().

pizzaplace |>
  dplyr::select(name, date, type, price) |>
  dplyr::group_by(name, date, type) |>
  dplyr::summarize(
    revenue = sum(price),
    sold = dplyr::n(),
    .groups = "drop"
  ) |>
  dplyr::filter(date %in% c("2015-01-01", "2015-01-02", "2015-01-03")) |>
  dplyr::filter(type %in% c("classic", "veggie")) |>
  tidyr::pivot_wider(
    names_from = date,
    names_sep = ".",
    values_from = c(revenue, sold),
    values_fn = sum,
    names_sort = TRUE
  ) |>
  gt(rowname_col = "name", groupname_col = "type") |>
  tab_spanner_delim(delim = ".") |>
  sub_missing(missing_text = "") |>
  fmt_currency(columns = starts_with("revenue")) |>
  data_color(
    columns = starts_with("revenue"),
    method = "numeric",
    palette = c("white", "lightgreen")
  ) |>
  cols_label_with(
    fn = function(x) {
      paste0(x, " (", vec_fmt_datetime(x, format = "E"), ")")
    }
  )
revenue
sold
2015-01-01 (Thu) 2015-01-02 (Fri) 2015-01-03 (Sat) 2015-01-01 (Thu) 2015-01-02 (Fri) 2015-01-03 (Sat)
classic
big_meat $60.00 $96.00 $96.00 5 8 8
classic_dlx $156.50 $93.00 $80.50 10 6 5
hawaiian $50.75 $137.75 $113.50 4 10 8
ital_cpcllo $150.50 $121.50 $48.50 8 7 3
napolitana $32.50 $106.00 $92.50 2 6 6
pep_msh_pep $82.50 $11.00 $22.00 6 1 2
pepperoni $77.75 $150.00 $102.75 6 12 8
the_greek $73.50 $113.50 $146.00 5 7 7
veggie
five_cheese $129.50 $111.00 $74.00 7 6 4
four_cheese $98.10 $50.65 $119.25 6 3 7
green_garden $96.25 $12.00 $16.00 7 1 1
ital_veggie $25.50 $63.25 $58.75 2 4 3
mediterraneo $52.25 $20.25 $48.25 3 1 3
mexicana $165.50 $92.75 $72.50 9 5 4
spin_pesto $33.25 $33.25 $103.75 2 2 6
spinach_fet $40.50 $64.50 $36.25 2 4 2
veggie_veg $44.25 $97.00 $116.50 3 5 7