The tab_footnote() function

Using a subset of the sza dataset, let’s create a new gt table. The body cells in the sza column will receive background color fills according to their data values (with data_color()). After that, the use of tab_footnote() lets us add a footnote to the sza column label (explaining what the color gradient signifies).

sza |>
  dplyr::filter(
    latitude == 20 &
      month == "jan" &
      !is.na(sza)
  ) |>
  dplyr::select(-latitude, -month) |>
  gt() |>
  data_color(
    columns = sza,
    palette = c("white", "yellow", "navyblue"),
    domain = c(0, 90)
  ) |>
  tab_footnote(
    footnote = "Color indicates the solar zenith angle.",
    locations = cells_column_labels(columns = sza)
  )
tst sza1
0700 84.9
0730 78.7
0800 72.7
0830 66.1
0900 61.5
0930 56.5
1000 52.1
1030 48.3
1100 45.5
1130 43.6
1200 43.0
1 Color indicates the solar zenith angle.

Of course, we can add more than one footnote to the table, but, we have to use several calls of tab_footnote(). This variation of the sza table has three footnotes: one on the "TST" column label and two on the "SZA" column label (these were capitalized with opt_all_caps()). We will ultimately have three calls of tab_footnote() and while the order of calls usually doesn’t matter, it does have a subtle effect here since two footnotes are associated with the same text content (try reversing the second and third calls and observe the effect in the footer).

sza |>
  dplyr::filter(
    latitude == 20 &
      month == "jan" &
      !is.na(sza)
  ) |>
  dplyr::select(-latitude, -month) |>
  gt() |>
  opt_all_caps() |>
  cols_align(align = "center") |>
  cols_width(everything() ~ px(200)) |>
  tab_footnote(
    footnote = md("TST stands for *True Solar Time*."),
    locations = cells_column_labels(columns = tst)
  ) |>
  tab_footnote(
    footnote = md("SZA stands for *Solar Zenith Angle*."),
    locations = cells_column_labels(columns = sza)
  ) |>
  tab_footnote(
    footnote = "Higher Values indicate sun closer to horizon.",
    locations = cells_column_labels(columns = sza)
  ) |>
  tab_options(footnotes.multiline = FALSE)
tst1 sza2,3
0700 84.9
0730 78.7
0800 72.7
0830 66.1
0900 61.5
0930 56.5
1000 52.1
1030 48.3
1100 45.5
1130 43.6
1200 43.0
1 TST stands for True Solar Time. 2 SZA stands for Solar Zenith Angle. 3 Higher Values indicate sun closer to horizon.

Text in the footer (both from footnotes and also from source notes) tends to widen the table and, by extension, all the columns within it. We can limit that by explicitly setting column width values, which is what was done above with cols_width(). There can also be a correspondingly large amount of vertical space taken up by the footer since footnotes will, by default, each start on a new line. In the above example, we used tab_options(footnotes.multiline = FALSE) to make it so that all footer text is contained in a single block of text.

Let’s move on to another footnote-laden table, this one based on the towny dataset. We have a header part, with a title and a subtitle. We can choose which of these could be associated with a footnote and in this case it is the "subtitle" (one of two options in the cells_title() helper function). This table has a stub with row labels and some of those labels are associated with a footnote. So long as row labels are unique, they can be easily used as row identifiers in cells_stub(). The third footnote is placed on the "Density" column label. Here, changing the order of the tab_footnote() calls has no effect on the final table rendering.

towny |>
  dplyr::filter(csd_type == "city") |>
  dplyr::arrange(desc(population_2021)) |>
  dplyr::select(name, density_2021, population_2021) |>
  dplyr::slice_head(n = 10) |>
  gt(rowname_col = "name") |>
  tab_header(
    title = md("The 10 Largest Municipalities in `towny`"),
    subtitle = "Population values taken from the 2021 census."
  ) |>
  fmt_integer() |>
  cols_label(
    density_2021 = "Density",
    population_2021 = "Population"
  ) |>
  tab_footnote(
    footnote = "Part of the Greater Toronto Area.",
    locations = cells_stub(rows = c(
      "Toronto", "Mississauga", "Brampton", "Markham", "Vaughan"
    ))
  ) |>
  tab_footnote(
    footnote = md("Density is in terms of persons per km^2^."),
    locations = cells_column_labels(columns = density_2021)
  ) |>
  tab_footnote(
    footnote = "Census results made public on February 9, 2022.",
    locations = cells_title(groups = "subtitle")
  ) |>
  tab_source_note(source_note = md(
    "Data taken from the `towny` dataset (in the **gt** package)."
  )) |>
  opt_footnote_marks(marks = "letters")
The 10 Largest Municipalities in towny
Population values taken from the 2021 census.a
Densityb Population
Torontoc 4,428 2,794,356
Ottawa 365 1,017,449
Mississaugac 2,453 717,961
Bramptonc 2,469 656,480
Hamilton 509 569,353
London 1,004 422,324
Markhamc 1,605 338,503
Vaughanc 1,186 323,103
Kitchener 1,878 256,885
Windsor 1,573 229,660
Data taken from the towny dataset (in the gt package).
a Census results made public on February 9, 2022.
b Density is in terms of persons per km2.
c Part of the Greater Toronto Area.

In the above table, we elected to change the footnote marks to letters instead of the default numbers (done through opt_footnote_marks()). A source note was also added; this was mainly to demonstrate that source notes will be positioned beneath footnotes in the footer section.

For our final example, let’s make a relatively small table deriving from the sp500 dataset. The set of tab_footnote() calls used here (four of them) have minor variations that allow for interesting expressions of footnotes. Two of the footnotes target values in the body of the table (using the cells_body() helper function to achieve this). On numeric values that right-aligned, gt will opt to place the footnote on the left of the content so as to not disrupt the alignment. However, the placement argument can be used to force the positioning of the footnote mark after the content. We can also opt to include footnotes that have no associated footnote marks whatsoever. This is done by not providing anything to locations. These ‘markless’ footnotes will precede the other footnotes in the footer section.

sp500 |>
  dplyr::filter(date >= "2015-01-05" & date <="2015-01-10") |>
  dplyr::select(-c(adj_close, volume, high, low)) |>
  dplyr::mutate(change = close - open) |>
  dplyr::arrange(date) |>
  gt() |>
  tab_header(title = "S&P 500") |>
  fmt_date(date_style = "m_day_year") |>
  fmt_currency() |>
  cols_width(everything() ~ px(150)) |>
  tab_footnote(
    footnote = "More red days than green in this period.",
    locations = cells_column_labels(columns = change)
  ) |>
  tab_footnote(
    footnote = "Lowest opening value.",
    locations = cells_body(columns = open, rows = 3),
  ) |>
  tab_footnote(
    footnote = "Devastating losses on this day.",
    locations = cells_body(columns = change, rows = 1),
    placement = "right"
  ) |>
  tab_footnote(footnote = "All values in USD.") |>
  opt_footnote_marks(marks = "LETTERS") |>
  opt_footnote_spec(spec_ref = "i[x]", spec_ftr = "x.")
S&P 500
date open close change[A]
Jan 5, 2015 $2,054.44 $2,020.58 −$33.86[B]
Jan 6, 2015 $2,022.15 $2,002.61 −$19.54
Jan 7, 2015 [C] $2,005.55 $2,025.90 $20.35
Jan 8, 2015 $2,030.61 $2,062.14 $31.53
Jan 9, 2015 $2,063.45 $2,044.81 −$18.64
All values in USD.
A. More red days than green in this period.
B. Devastating losses on this day.
C. Lowest opening value.

Aside from changing the footnote marks to consist of "LETTERS", we’ve also changed the way the marks are formatted. In our use of opt_footnote_spec(), the spec_ref option governs the footnote marks across the table. Here, we describe marks that are italicized and set between square brackets (with "i[x]"). The spec_ftr argument is used for the footer representation of the footnote marks. As described in the example with "x.", it is rendered as a footnote mark followed by a period.