Skip to contents

Let’s make a display table using the gtcars dataset. We all know mtcars… what is gtcars? It’s basically a modernized mtcars for the gt age. It’s part of the gt package, and here is a preview of the tibble using dplyr’s glimpse() function:

# This is `gtcars`
glimpse(gtcars)
#> Rows: 47
#> Columns: 15
#> $ mfr         <chr> "Ford", "Ferrari", "Ferrari", "Ferrari", "Ferrari", "Ferra…
#> $ model       <chr> "GT", "458 Speciale", "458 Spider", "458 Italia", "488 GTB…
#> $ year        <dbl> 2017, 2015, 2015, 2014, 2016, 2015, 2017, 2015, 2015, 2015…
#> $ trim        <chr> "Base Coupe", "Base Coupe", "Base", "Base Coupe", "Base Co…
#> $ bdy_style   <chr> "coupe", "coupe", "convertible", "coupe", "coupe", "conver…
#> $ hp          <dbl> 647, 597, 562, 562, 661, 553, 680, 652, 731, 949, 573, 545…
#> $ hp_rpm      <dbl> 6250, 9000, 9000, 9000, 8000, 7500, 8250, 8000, 8250, 9000…
#> $ trq         <dbl> 550, 398, 398, 398, 561, 557, 514, 504, 509, 664, 476, 436…
#> $ trq_rpm     <dbl> 5900, 6000, 6000, 6000, 3000, 4750, 5750, 6000, 6000, 6750…
#> $ mpg_c       <dbl> 11, 13, 13, 13, 15, 16, 12, 11, 11, 12, 21, 16, 11, 16, 12…
#> $ mpg_h       <dbl> 18, 17, 17, 17, 22, 23, 17, 16, 16, 16, 22, 22, 18, 20, 20…
#> $ drivetrain  <chr> "rwd", "rwd", "rwd", "rwd", "rwd", "rwd", "awd", "awd", "r…
#> $ trsmn       <chr> "7a", "7a", "7a", "7a", "7a", "7a", "7a", "7a", "7a", "7a"…
#> $ ctry_origin <chr> "United States", "Italy", "Italy", "Italy", "Italy", "Ital…
#> $ msrp        <dbl> 447000, 291744, 263553, 233509, 245400, 198973, 298000, 29…

For the purpose of simply learning more about gt, let’s reduce this 47-row tibble to one that has only 8 rows with a little dplyring:

# Get a subset of 8 cars from the `gtcars` dataset: two
# from each manufacturer country of origin except the UK
gtcars_8 <-
  gtcars |>
  group_by(ctry_origin) |>
  slice_head(n = 2) |>
  ungroup() |>
  filter(ctry_origin != "United Kingdom")

# Show the `gtcars_8` tibble
glimpse(gtcars_8)
#> Rows: 8
#> Columns: 15
#> $ mfr         <chr> "BMW", "BMW", "Ferrari", "Ferrari", "Acura", "Nissan", "Fo…
#> $ model       <chr> "6-Series", "i8", "458 Speciale", "458 Spider", "NSX", "GT…
#> $ year        <dbl> 2016, 2016, 2015, 2015, 2017, 2016, 2017, 2016
#> $ trim        <chr> "640 I Coupe", "Mega World Coupe", "Base Coupe", "Base", "…
#> $ bdy_style   <chr> "coupe", "coupe", "coupe", "convertible", "coupe", "coupe"…
#> $ hp          <dbl> 315, 357, 597, 562, 573, 545, 647, 650
#> $ hp_rpm      <dbl> 5800, 5800, 9000, 9000, 6500, 6400, 6250, 6400
#> $ trq         <dbl> 330, 420, 398, 398, 476, 436, 550, 650
#> $ trq_rpm     <dbl> 1400, 3700, 6000, 6000, 2000, 3200, 5900, 3600
#> $ mpg_c       <dbl> 20, 28, 13, 13, 21, 16, 11, 15
#> $ mpg_h       <dbl> 30, 29, 17, 17, 22, 22, 18, 22
#> $ drivetrain  <chr> "rwd", "awd", "rwd", "rwd", "awd", "awd", "rwd", "rwd"
#> $ trsmn       <chr> "8am", "6am", "7a", "7a", "9a", "6a", "7a", "7m"
#> $ ctry_origin <chr> "Germany", "Germany", "Italy", "Italy", "Japan", "Japan", …
#> $ msrp        <dbl> 77300, 140700, 291744, 263553, 156000, 101770, 447000, 883…

Let’s make a display table from this dataset. In doing so we’ll fulfill the following 10 requirements:

  1. putting the cars into characteristic groups (by the car manufacturer’s country of origin)
  2. removing some of the columns that we don’t want to present
  3. incorporating some columns into a column group
  4. formatting the currency data and using a monospaced font for easier reading of that data
  5. giving the table a title and a subtitle
  6. adding footnotes to draw attention to some of the more interesting data points and to explain some of the more unusual aspects of the data
  7. placing a citation for the dataset at the bottom of the table
  8. transforming the transmission (trsmn) codes so that they are readable and understandable
  9. styling some cells according to basic criteria
  10. highlighting the cars that are considered to be grand tourers

Row Groups

Let’s again use dplyr to help make groupings by the ctry_origin column, which provides the country of origin for the vehicle manufacturer of the car. We can simply use dplyr::group_by() on the gtcars dataset and pass that to gt(). What you get is a display table that arranges the cars into row groups, with the name of the group displayed prominently above.

# Use `group_by()` on `gtcars` and pass that to `gt()`
gtcars_8 |>
  group_by(ctry_origin) |>
  gt()
mfr model year trim bdy_style hp hp_rpm trq trq_rpm mpg_c mpg_h drivetrain trsmn msrp
Germany
BMW 6-Series 2016 640 I Coupe coupe 315 5800 330 1400 20 30 rwd 8am 77300
BMW i8 2016 Mega World Coupe coupe 357 5800 420 3700 28 29 awd 6am 140700
Italy
Ferrari 458 Speciale 2015 Base Coupe coupe 597 9000 398 6000 13 17 rwd 7a 291744
Ferrari 458 Spider 2015 Base convertible 562 9000 398 6000 13 17 rwd 7a 263553
Japan
Acura NSX 2017 Base Coupe coupe 573 6500 476 2000 21 22 awd 9a 156000
Nissan GT-R 2016 Premium Coupe coupe 545 6400 436 3200 16 22 awd 6a 101770
United States
Ford GT 2017 Base Coupe coupe 647 6250 550 5900 11 18 rwd 7a 447000
Chevrolet Corvette 2016 Z06 Coupe coupe 650 6400 650 3600 15 22 rwd 7m 88345

Getting the row groups in the preferred order can be done easily with dplyr’s arrange() function. For example, we can have groups that are arranged alphabetically by manufacturer (mfr) and then sorted by highest sticker price (msrp) to lowest.

gtcars_8 |>
  group_by(ctry_origin) |>
  arrange(mfr, desc(msrp)) |>
  gt()
mfr model year trim bdy_style hp hp_rpm trq trq_rpm mpg_c mpg_h drivetrain trsmn msrp
Japan
Acura NSX 2017 Base Coupe coupe 573 6500 476 2000 21 22 awd 9a 156000
Nissan GT-R 2016 Premium Coupe coupe 545 6400 436 3200 16 22 awd 6a 101770
Germany
BMW i8 2016 Mega World Coupe coupe 357 5800 420 3700 28 29 awd 6am 140700
BMW 6-Series 2016 640 I Coupe coupe 315 5800 330 1400 20 30 rwd 8am 77300
United States
Chevrolet Corvette 2016 Z06 Coupe coupe 650 6400 650 3600 15 22 rwd 7m 88345
Ford GT 2017 Base Coupe coupe 647 6250 550 5900 11 18 rwd 7a 447000
Italy
Ferrari 458 Speciale 2015 Base Coupe coupe 597 9000 398 6000 13 17 rwd 7a 291744
Ferrari 458 Spider 2015 Base convertible 562 9000 398 6000 13 17 rwd 7a 263553

We could also use factor levels to get a more particular ordering within arrange(). For example, we can first arrange the groups themselves (the country of origin–ctry_origin) by our own preferred ordering and then arrange by mfr and descending msrp as before. Then, group_by(ctry_origin) can be used on the sorted tibble before passing this to gt().

# Define our preferred order for `ctry_origin`
order_countries <- c("Germany", "Italy", "United States", "Japan")

# Reorder the table rows by our specific ordering of groups
gtcars_8 |>
  arrange(
    factor(ctry_origin, levels = order_countries), mfr, desc(msrp)
  ) |>
  group_by(ctry_origin) |>
  gt()
mfr model year trim bdy_style hp hp_rpm trq trq_rpm mpg_c mpg_h drivetrain trsmn msrp
Germany
BMW i8 2016 Mega World Coupe coupe 357 5800 420 3700 28 29 awd 6am 140700
BMW 6-Series 2016 640 I Coupe coupe 315 5800 330 1400 20 30 rwd 8am 77300
Italy
Ferrari 458 Speciale 2015 Base Coupe coupe 597 9000 398 6000 13 17 rwd 7a 291744
Ferrari 458 Spider 2015 Base convertible 562 9000 398 6000 13 17 rwd 7a 263553
United States
Chevrolet Corvette 2016 Z06 Coupe coupe 650 6400 650 3600 15 22 rwd 7m 88345
Ford GT 2017 Base Coupe coupe 647 6250 550 5900 11 18 rwd 7a 447000
Japan
Acura NSX 2017 Base Coupe coupe 573 6500 476 2000 21 22 awd 9a 156000
Nissan GT-R 2016 Premium Coupe coupe 545 6400 436 3200 16 22 awd 6a 101770

The last variation is to combine the manufacturer name with the model name, using those combined strings as row labels for the table. This is just a little more dplyr where we can use dplyr::mutate() to make a new car column followed by dplyr::select() where we remove the mfr and model columns. When introducing the tibble to the gt() function, we can now use the rowname_col argument to specify a column that will serve as row labels (which is the newly made car column).

# Reorder the table rows by our specific ordering of groups
tab <-
  gtcars_8 |>
  arrange(
    factor(ctry_origin, levels = order_countries),
    mfr, desc(msrp)
  ) |>
  mutate(car = paste(mfr, model)) |>
  select(-mfr, -model) |>
  group_by(ctry_origin) |>
  gt(rowname_col = "car")

# Show the table
tab
year trim bdy_style hp hp_rpm trq trq_rpm mpg_c mpg_h drivetrain trsmn msrp
Germany
BMW i8 2016 Mega World Coupe coupe 357 5800 420 3700 28 29 awd 6am 140700
BMW 6-Series 2016 640 I Coupe coupe 315 5800 330 1400 20 30 rwd 8am 77300
Italy
Ferrari 458 Speciale 2015 Base Coupe coupe 597 9000 398 6000 13 17 rwd 7a 291744
Ferrari 458 Spider 2015 Base convertible 562 9000 398 6000 13 17 rwd 7a 263553
United States
Chevrolet Corvette 2016 Z06 Coupe coupe 650 6400 650 3600 15 22 rwd 7m 88345
Ford GT 2017 Base Coupe coupe 647 6250 550 5900 11 18 rwd 7a 447000
Japan
Acura NSX 2017 Base Coupe coupe 573 6500 476 2000 21 22 awd 9a 156000
Nissan GT-R 2016 Premium Coupe coupe 545 6400 436 3200 16 22 awd 6a 101770

Hiding and Moving Some Columns

Let’s hide two columns that we don’t need to the final table: drivetrain and bdy_style. We can use the cols_hide() function to hide columns. The same end result might also have been achieved by using gtcars |> dplyr::select(-c(drivetrain, bdy_style)), before introducing the table to gt(). Why this function then? Sometimes you’ll need variables for conditional statements within gt but won’t want to display them in the end.

Aside from hiding columns, let’s move some of them. Again, this could be done with dplyr::select() but there are options here in gt via the cols_move_to_start(), cols_move(), and cols_move_to_end() functions.

# Use a few `cols_*()` functions to hide and move columns 
tab <- 
  tab |>
  cols_hide(columns = c(drivetrain, bdy_style)) |>
  cols_move(
    columns = c(trsmn, mpg_c, mpg_h),
    after = trim
  )

# Show the table
tab
year trim trsmn mpg_c mpg_h hp hp_rpm trq trq_rpm msrp
Germany
BMW i8 2016 Mega World Coupe 6am 28 29 357 5800 420 3700 140700
BMW 6-Series 2016 640 I Coupe 8am 20 30 315 5800 330 1400 77300
Italy
Ferrari 458 Speciale 2015 Base Coupe 7a 13 17 597 9000 398 6000 291744
Ferrari 458 Spider 2015 Base 7a 13 17 562 9000 398 6000 263553
United States
Chevrolet Corvette 2016 Z06 Coupe 7m 15 22 650 6400 650 3600 88345
Ford GT 2017 Base Coupe 7a 11 18 647 6250 550 5900 447000
Japan
Acura NSX 2017 Base Coupe 9a 21 22 573 6500 476 2000 156000
Nissan GT-R 2016 Premium Coupe 6a 16 22 545 6400 436 3200 101770

Putting Columns Into Groups

It’s sometimes useful to arrange variables/columns into groups by using spanner column labels. This can be done in gt by using the tab_spanner() function. It takes the label and columns arguments; label is the spanner column label and the columns are those columns that belong in this group.

Here, we’ll put the mpg_c, mpg_h, hp, hp_rpm, trq, trq_rpm columns under the Performance spanner column, and the remaining columns won’t be grouped together. This single spanner column label is styled with Markdown by using the md() helper.

# Put the first three columns under a spanner
# column with the label 'Performance'
tab <- 
  tab |>
  tab_spanner(
    label = "Performance",
    columns = c(mpg_c, mpg_h, hp, hp_rpm, trq, trq_rpm)
  )

# Show the table
tab
year trim trsmn Performance msrp
mpg_c mpg_h hp hp_rpm trq trq_rpm
Germany
BMW i8 2016 Mega World Coupe 6am 28 29 357 5800 420 3700 140700
BMW 6-Series 2016 640 I Coupe 8am 20 30 315 5800 330 1400 77300
Italy
Ferrari 458 Speciale 2015 Base Coupe 7a 13 17 597 9000 398 6000 291744
Ferrari 458 Spider 2015 Base 7a 13 17 562 9000 398 6000 263553
United States
Chevrolet Corvette 2016 Z06 Coupe 7m 15 22 650 6400 650 3600 88345
Ford GT 2017 Base Coupe 7a 11 18 647 6250 550 5900 447000
Japan
Acura NSX 2017 Base Coupe 9a 21 22 573 6500 476 2000 156000
Nissan GT-R 2016 Premium Coupe 6a 16 22 545 6400 436 3200 101770

Merging Columns Together and Labeling Them

Sometimes we’d like to combine the data from two columns into a single column. The cols_merge() function allows us to do this, we just need to describe how the data should be combined. For our table, let’s merge together the following pairs of columns:

  • mpg_c and mpg_h (miles per gallon in city and highway driving modes)
  • hp and hp_rpm (horsepower and associated RPM)
  • trq and trq_rpm (torque and associated RPM)

The cols_merge() function uses a col_1 column and a col_2 column. Once combined, the col_1 column will be retained and the col_2 column will be dropped. The pattern argument uses {1} and {2} to represent the content of col_1 and col_2. Here, we can use string literals to add text like rpm or the @ sign. Furthermore, because we are targeting an HTML table, we can use the <br> tag to insert a linebreak.

We are going to do something a little special with the pattern for the mpg_c and mpg_h columns and wrap it all with <</>>. This is special pattern syntax that tells gt to remove anything inside those double braces when there is an NA value. We find that is a single row that has NA in both columns (for the ‘Tesla Model S’) and rather than seeing "NA" appear in the table, it’s better to remove that entry entirely with this specialized pattern syntax.

Labeling columns essentially means that we are choosing display-friendly labels that are no longer simply the column names (the default label). The cols_label() function makes this relabeling possible. It accepts a series of named arguments in the form of <column_name> = <column_label>, ....

# Perform three column merges to better present
# MPG, HP, and torque; relabel all the remaining
# columns for a nicer-looking presentation
tab <- 
  tab |>
  cols_merge(
    columns = c(mpg_c, mpg_h),
    pattern = "<<{1}c<br>{2}h>>"
  ) |>
  cols_merge(
    columns = c(hp, hp_rpm),
    pattern = "{1}<br>@{2}rpm"
  ) |>
  cols_merge(
    columns = c(trq, trq_rpm),
    pattern = "{1}<br>@{2}rpm"
  ) |>
  cols_label(
    mpg_c = "MPG",
    hp = "HP",
    trq = "Torque",
    year = "Year",
    trim = "Trim",
    trsmn = "Transmission",
    msrp = "MSRP"
  )

# Show the table
tab
Year Trim Transmission Performance MSRP
MPG HP Torque
Germany
BMW i8 2016 Mega World Coupe 6am 28c
29h
357
@5800rpm
420
@3700rpm
140700
BMW 6-Series 2016 640 I Coupe 8am 20c
30h
315
@5800rpm
330
@1400rpm
77300
Italy
Ferrari 458 Speciale 2015 Base Coupe 7a 13c
17h
597
@9000rpm
398
@6000rpm
291744
Ferrari 458 Spider 2015 Base 7a 13c
17h
562
@9000rpm
398
@6000rpm
263553
United States
Chevrolet Corvette 2016 Z06 Coupe 7m 15c
22h
650
@6400rpm
650
@3600rpm
88345
Ford GT 2017 Base Coupe 7a 11c
18h
647
@6250rpm
550
@5900rpm
447000
Japan
Acura NSX 2017 Base Coupe 9a 21c
22h
573
@6500rpm
476
@2000rpm
156000
Nissan GT-R 2016 Premium Coupe 6a 16c
22h
545
@6400rpm
436
@3200rpm
101770

Using Formatter Functions

There are a number of formatter functions, all with the general naming convention fmt*(). The various formatters are convenient for applying formats to numeric or character values in the table’s field. Here, we will simply use fmt_currency() on the msrp column (we still refer to columns by their original names) to get USD currency will no decimal places. We’re not supplying anything for the rows argument and this means we want to apply the formatting to the entire column of data.

# Format the `msrp` column to USD currency
# with no display of the currency subunits
tab <- 
  tab |>
  fmt_currency(columns = msrp, decimals = 0)

# Show the table
tab
Year Trim Transmission Performance MSRP
MPG HP Torque
Germany
BMW i8 2016 Mega World Coupe 6am 28c
29h
357
@5800rpm
420
@3700rpm
$140,700
BMW 6-Series 2016 640 I Coupe 8am 20c
30h
315
@5800rpm
330
@1400rpm
$77,300
Italy
Ferrari 458 Speciale 2015 Base Coupe 7a 13c
17h
597
@9000rpm
398
@6000rpm
$291,744
Ferrari 458 Spider 2015 Base 7a 13c
17h
562
@9000rpm
398
@6000rpm
$263,553
United States
Chevrolet Corvette 2016 Z06 Coupe 7m 15c
22h
650
@6400rpm
650
@3600rpm
$88,345
Ford GT 2017 Base Coupe 7a 11c
18h
647
@6250rpm
550
@5900rpm
$447,000
Japan
Acura NSX 2017 Base Coupe 9a 21c
22h
573
@6500rpm
476
@2000rpm
$156,000
Nissan GT-R 2016 Premium Coupe 6a 16c
22h
545
@6400rpm
436
@3200rpm
$101,770

Column Alignment and Style Changes

We can change the alignment of data in columns with cols_align(). For our table, let’s center-align the mpg_c, hp, and trq columns. All other columns will maintain their default alignments.

It’s sometimes useful to modify the default styles of table cells. We can do this in a targeted way with the tab_style() function. That function require two key pieces of information: a style definition, and one or more locations (which cells should the styles be applied to?). The style argument commonly uses the cell_*() helper functions. There are three of these (cell_text(), cell_fill() and cell_borders()) and each contains different arguments for the different styles that are supported.

Here we will use the cell_text() helper function and specify a text size of "12px" for our targeted cells—both px(12) and "12px" work equally well here. We also use helper functions with the locations argument and these are the cells_*() functions. We would like to target the data cells in all columns except year and msrp so we need to use cells_body and then supply our target columns to its own columns argument.

# Center-align three columns in the gt table and
# modify the text size of a few columns of data
tab <- 
  tab |>
  cols_align(
    align = "center",
    columns = c(mpg_c, hp, trq)
  ) |>
  tab_style(
    style = cell_text(size = px(12)),
    locations = cells_body(
      columns = c(trim, trsmn, mpg_c, hp, trq)
    )
  )

# Show the table
tab
Year Trim Transmission Performance MSRP
MPG HP Torque
Germany
BMW i8 2016 Mega World Coupe 6am 28c
29h
357
@5800rpm
420
@3700rpm
$140,700
BMW 6-Series 2016 640 I Coupe 8am 20c
30h
315
@5800rpm
330
@1400rpm
$77,300
Italy
Ferrari 458 Speciale 2015 Base Coupe 7a 13c
17h
597
@9000rpm
398
@6000rpm
$291,744
Ferrari 458 Spider 2015 Base 7a 13c
17h
562
@9000rpm
398
@6000rpm
$263,553
United States
Chevrolet Corvette 2016 Z06 Coupe 7m 15c
22h
650
@6400rpm
650
@3600rpm
$88,345
Ford GT 2017 Base Coupe 7a 11c
18h
647
@6250rpm
550
@5900rpm
$447,000
Japan
Acura NSX 2017 Base Coupe 9a 21c
22h
573
@6500rpm
476
@2000rpm
$156,000
Nissan GT-R 2016 Premium Coupe 6a 16c
22h
545
@6400rpm
436
@3200rpm
$101,770

Text Transforms

A text transform via the text_transform() function is a great way to further manipulate text in data cells (even after they’ve been formatted with the fmt*() function). After targeting data cells with the cells_body() location helper function, we supply a function to the fn argument that processes a vector of text (the vector x will be the formatted text in each of the targeted cells). If we intend to render an HTML table, we can directly apply HTML tags in the transformation function. The function we provide here is a bit complicated but it will build strings in the trsmn column that read better in a display table.

# Transform the column of text in `trsmn` using
# a custom function within `text_transform()`;
# here `x` represents a character vector defined
# in the `cells_body()` function
tab <- 
  tab |>
  text_transform(
    locations = cells_body(columns = trsmn),
    fn = function(x) {
      
      # The first character of `x` always
      # indicates the number of transmission speeds
      speed <- substr(x, 1, 1)
      
      # We can carefully determine which transmission
      # type we have in `x` with a `dplyr::case_when()`
      # statement
      type <-
        dplyr::case_when(
          substr(x, 2, 3) == "am" ~ "Automatic/Manual",
          substr(x, 2, 2) == "m" ~ "Manual",
          substr(x, 2, 2) == "a" ~ "Automatic",
          substr(x, 2, 3) == "dd" ~ "Direct Drive"
        )
      
      # Let's paste together the `speed` and `type`
      # vectors to create HTML text replacing `x`
      paste(speed, " Speed<br><em>", type, "</em>")
    }
  )

# Show the table
tab
Year Trim Transmission Performance MSRP
MPG HP Torque
Germany
BMW i8 2016 Mega World Coupe 6 Speed
Automatic/Manual
28c
29h
357
@5800rpm
420
@3700rpm
$140,700
BMW 6-Series 2016 640 I Coupe 8 Speed
Automatic/Manual
20c
30h
315
@5800rpm
330
@1400rpm
$77,300
Italy
Ferrari 458 Speciale 2015 Base Coupe 7 Speed
Automatic
13c
17h
597
@9000rpm
398
@6000rpm
$291,744
Ferrari 458 Spider 2015 Base 7 Speed
Automatic
13c
17h
562
@9000rpm
398
@6000rpm
$263,553
United States
Chevrolet Corvette 2016 Z06 Coupe 7 Speed
Manual
15c
22h
650
@6400rpm
650
@3600rpm
$88,345
Ford GT 2017 Base Coupe 7 Speed
Automatic
11c
18h
647
@6250rpm
550
@5900rpm
$447,000
Japan
Acura NSX 2017 Base Coupe 9 Speed
Automatic
21c
22h
573
@6500rpm
476
@2000rpm
$156,000
Nissan GT-R 2016 Premium Coupe 6 Speed
Automatic
16c
22h
545
@6400rpm
436
@3200rpm
$101,770

Table Header: Title and Subtitle

The tab_header() function allows us to place a table title and, optionally, a subtitle at the top of the display table. It’s generally a good idea to have both in a table, where the subtitle provides additional information (though that isn’t quite the case in our example below).

# Add a table title and subtitle; we can use
# markdown with the `md()` helper function
tab <- 
  tab |>
  tab_header(
    title = md("The Cars of **gtcars**"),
    subtitle = "These are some fine automobiles"
  )

# Show the table
tab
The Cars of gtcars
These are some fine automobiles
Year Trim Transmission Performance MSRP
MPG HP Torque
Germany
BMW i8 2016 Mega World Coupe 6 Speed
Automatic/Manual
28c
29h
357
@5800rpm
420
@3700rpm
$140,700
BMW 6-Series 2016 640 I Coupe 8 Speed
Automatic/Manual
20c
30h
315
@5800rpm
330
@1400rpm
$77,300
Italy
Ferrari 458 Speciale 2015 Base Coupe 7 Speed
Automatic
13c
17h
597
@9000rpm
398
@6000rpm
$291,744
Ferrari 458 Spider 2015 Base 7 Speed
Automatic
13c
17h
562
@9000rpm
398
@6000rpm
$263,553
United States
Chevrolet Corvette 2016 Z06 Coupe 7 Speed
Manual
15c
22h
650
@6400rpm
650
@3600rpm
$88,345
Ford GT 2017 Base Coupe 7 Speed
Automatic
11c
18h
647
@6250rpm
550
@5900rpm
$447,000
Japan
Acura NSX 2017 Base Coupe 9 Speed
Automatic
21c
22h
573
@6500rpm
476
@2000rpm
$156,000
Nissan GT-R 2016 Premium Coupe 6 Speed
Automatic
16c
22h
545
@6400rpm
436
@3200rpm
$101,770

Adding a Source Citation

A source note can be added below the display table using the tab_source_note() function. We can even add multiple source notes with multiple calls of that function. Here, we supply a web URL and by using Markdown (with md()) it’s easy to create a link to the source of the data.

# Add a source note to the bottom of the table; this
# appears below the footnotes
tab <- 
  tab |>
  tab_source_note(
    source_note = md(
      "Source: Various pages within the Edmonds website."
    )
  )

# Show the table
tab
The Cars of gtcars
These are some fine automobiles
Year Trim Transmission Performance MSRP
MPG HP Torque
Germany
BMW i8 2016 Mega World Coupe 6 Speed
Automatic/Manual
28c
29h
357
@5800rpm
420
@3700rpm
$140,700
BMW 6-Series 2016 640 I Coupe 8 Speed
Automatic/Manual
20c
30h
315
@5800rpm
330
@1400rpm
$77,300
Italy
Ferrari 458 Speciale 2015 Base Coupe 7 Speed
Automatic
13c
17h
597
@9000rpm
398
@6000rpm
$291,744
Ferrari 458 Spider 2015 Base 7 Speed
Automatic
13c
17h
562
@9000rpm
398
@6000rpm
$263,553
United States
Chevrolet Corvette 2016 Z06 Coupe 7 Speed
Manual
15c
22h
650
@6400rpm
650
@3600rpm
$88,345
Ford GT 2017 Base Coupe 7 Speed
Automatic
11c
18h
647
@6250rpm
550
@5900rpm
$447,000
Japan
Acura NSX 2017 Base Coupe 9 Speed
Automatic
21c
22h
573
@6500rpm
476
@2000rpm
$156,000
Nissan GT-R 2016 Premium Coupe 6 Speed
Automatic
16c
22h
545
@6400rpm
436
@3200rpm
$101,770
Source: Various pages within the Edmonds website.

Using the Complete gtcars table and Adding Footnotes

Let’s bring it all together by putting together all the statements we developed for gtcars_8, and applying that to the complete gtcars dataset. At the same time, we’ll add a few interesting footnotes and our specific requirements for footnoting are:

a. identifying the car with the best gas mileage (city)
b. identifying the car with the highest horsepower
c. stating the currency of the MSRP

The tab_footnote() function expects note text for the footnote argument, and locations for where the footnote mark should be attached. It will handle the placement of the footnote mark and also place the footnote in the footnotes area. Here, we’ll use the cells_body() location helper function. There are several location helper functions for targeting all parts of the table (e.g,. cells_body(), cells_stub(), etc.). Each location helper has its own interface for targeting cells so refer to the documentation for examples of how they work in practice (e.g., use ?cells_body).

What cells_body() expects is columns (column names, which can be conveniently provided in c()) and rows (which can be a vector of row names or row indices). The cells_stub() location helper only expects a vector of rows. For cells_column_labels(), we provide column labels in its columns argument. Here, we are targeting a footnote to the msrp column label so we will use columns = msrp.

In terms of structuring the code, we’re taking all the previous statements and putting those in first. It should be noted that the order of the statements does not matter to the end result, we could also put in all of the tab_footnote() statements first (again, any in order) and expect the same output table.

# Use dplyr functions to get the car with the best city gas mileage;
# this will be used to target the correct cell for a footnote
best_gas_mileage_city <- 
  gtcars |> 
  arrange(desc(mpg_c)) |>
  slice(1) |>
  mutate(car = paste(mfr, model)) |>
  pull(car)

# Use dplyr functions to get the car with the highest horsepower
# this will be used to target the correct cell for a footnote
highest_horsepower <- 
  gtcars |> 
  arrange(desc(hp)) |>
  slice(1) |>
  mutate(car = paste(mfr, model)) |>
  pull(car)

# Define our preferred order for `ctry_origin`
order_countries <- c("Germany", "Italy", "United States", "Japan")

# Create a display table with `gtcars`, using all of the previous
# statements piped together + additional `tab_footnote()` stmts
tab <-
  gtcars |>
  arrange(
    factor(ctry_origin, levels = order_countries),
    mfr, desc(msrp)
  ) |>
  mutate(car = paste(mfr, model)) |>
  select(-mfr, -model) |>
  group_by(ctry_origin) |>
  gt(rowname_col = "car") |>
  cols_hide(columns = c(drivetrain, bdy_style)) |>
  cols_move(
    columns = c(trsmn, mpg_c, mpg_h),
    after = trim
  ) |>
  tab_spanner(
    label = "Performance",
    columns = c(mpg_c, mpg_h, hp, hp_rpm, trq, trq_rpm)
  ) |>
  cols_merge(
    columns = c(mpg_c, mpg_h),
    pattern = "<<{1}c<br>{2}h>>"
  ) |>
  cols_merge(
    columns = c(hp, hp_rpm),
    pattern = "{1}<br>@{2}rpm"
  ) |>
  cols_merge(
    columns = c(trq, trq_rpm),
    pattern = "{1}<br>@{2}rpm"
  ) |>
  cols_label(
    mpg_c = "MPG",
    hp = "HP",
    trq = "Torque",
    year = "Year",
    trim = "Trim",
    trsmn = "Transmission",
    msrp = "MSRP"
  ) |>
  fmt_currency(columns = msrp, decimals = 0) |>
  cols_align(
    align = "center",
    columns = c(mpg_c, hp, trq)
  ) |>
  tab_style(
    style = cell_text(size = px(12)),
    locations = cells_body(
      columns = c(trim, trsmn, mpg_c, hp, trq)
    )
  ) |>
  text_transform(
    locations = cells_body(columns = trsmn),
    fn = function(x) {
      
      speed <- substr(x, 1, 1)
      
      type <-
        dplyr::case_when(
          substr(x, 2, 3) == "am" ~ "Automatic/Manual",
          substr(x, 2, 2) == "m" ~ "Manual",
          substr(x, 2, 2) == "a" ~ "Automatic",
          substr(x, 2, 3) == "dd" ~ "Direct Drive"
        )
      
      paste(speed, " Speed<br><em>", type, "</em>")
    }
  ) |>
  tab_header(
    title = md("The Cars of **gtcars**"),
    subtitle = "These are some fine automobiles"
  ) |>
  tab_source_note(
    source_note = md(
      "Source: Various pages within the Edmonds website."
    )
  ) |>
  tab_footnote(
    footnote = md("Best gas mileage (city) of all the **gtcars**."),
    locations = cells_body(
      columns = mpg_c,
      rows = best_gas_mileage_city
    )
  ) |>
  tab_footnote(
    footnote = md("The highest horsepower of all the **gtcars**."),
    locations = cells_body(
      columns = hp,
      rows = highest_horsepower
    )
  ) |>
  tab_footnote(
    footnote = "All prices in U.S. dollars (USD).",
    locations = cells_column_labels(columns = msrp)
  )

# Show the table
tab
The Cars of gtcars
These are some fine automobiles
Year Trim Transmission Performance MSRP1
MPG HP Torque
Germany
Audi R8 2015 4.2 (Manual) Coupe 6 Speed
Manual
11c
20h
430
@7900rpm
317
@4500rpm
$115,900
Audi S8 2016 Base Sedan 8 Speed
Automatic/Manual
15c
25h
520
@5800rpm
481
@1700rpm
$114,900
Audi RS 7 2016 Quattro Hatchback 8 Speed
Automatic/Manual
15c
25h
560
@5700rpm
516
@1750rpm
$108,900
Audi S7 2016 Prestige quattro Hatchback 7 Speed
Automatic
17c
27h
450
@5800rpm
406
@1400rpm
$82,900
Audi S6 2016 Premium Plus quattro Sedan 7 Speed
Automatic
18c
27h
450
@5800rpm
406
@1400rpm
$70,900
BMW i8 2016 Mega World Coupe 6 Speed
Automatic/Manual
28c
29h2
357
@5800rpm
420
@3700rpm
$140,700
BMW M6 2016 Base Coupe 7 Speed
Automatic
15c
22h
560
@6000rpm
500
@1500rpm
$113,400
BMW M5 2016 Base Sedan 7 Speed
Automatic/Manual
15c
22h
560
@6000rpm
500
@1500rpm
$94,100
BMW 6-Series 2016 640 I Coupe 8 Speed
Automatic/Manual
20c
30h
315
@5800rpm
330
@1400rpm
$77,300
BMW M4 2016 Base Coupe 6 Speed
Manual
17c
24h
425
@5500rpm
406
@1850rpm
$65,700
Mercedes-Benz AMG GT 2016 S Coupe 7 Speed
Automatic
16c
22h
503
@6250rpm
479
@1750rpm
$129,900
Mercedes-Benz SL-Class 2016 SL400 Convertible 7 Speed
Automatic/Manual
20c
27h
329
@5250rpm
354
@1600rpm
$85,050
Porsche 911 2016 Carrera Coupe 7 Speed
Manual
20c
28h
350
@7400rpm
287
@5600rpm
$84,300
Porsche Panamera 2016 Base Sedan 7 Speed
Automatic
18c
28h
310
@6200rpm
295
@3750rpm
$78,100
Porsche 718 Boxster 2017 Base Convertible 6 Speed
Manual
21c
28h
300
@6500rpm
280
@1950rpm
$56,000
Porsche 718 Cayman 2017 Base Coupe 6 Speed
Manual
20c
29h
300
@6500rpm
280
@1950rpm
$53,900
Italy
Ferrari LaFerrari 2015 Base Coupe 7 Speed
Automatic
12c
16h
949
@9000rpm3
664
@6750rpm
$1,416,362
Ferrari F12Berlinetta 2015 Base Coupe 7 Speed
Automatic
11c
16h
731
@8250rpm
509
@6000rpm
$319,995
Ferrari GTC4Lusso 2017 Base Coupe 7 Speed
Automatic
12c
17h
680
@8250rpm
514
@5750rpm
$298,000
Ferrari FF 2015 Base Coupe 7 Speed
Automatic
11c
16h
652
@8000rpm
504
@6000rpm
$295,000
Ferrari 458 Speciale 2015 Base Coupe 7 Speed
Automatic
13c
17h
597
@9000rpm
398
@6000rpm
$291,744
Ferrari 458 Spider 2015 Base 7 Speed
Automatic
13c
17h
562
@9000rpm
398
@6000rpm
$263,553
Ferrari 488 GTB 2016 Base Coupe 7 Speed
Automatic
15c
22h
661
@8000rpm
561
@3000rpm
$245,400
Ferrari 458 Italia 2014 Base Coupe 7 Speed
Automatic
13c
17h
562
@9000rpm
398
@6000rpm
$233,509
Ferrari California 2015 Base Convertible 7 Speed
Automatic
16c
23h
553
@7500rpm
557
@4750rpm
$198,973
Lamborghini Aventador 2015 LP 700-4 Coupe 7 Speed
Automatic
11c
18h
700
@8250rpm
507
@5500rpm
$397,500
Lamborghini Huracan 2015 LP 610-4 Coupe 7 Speed
Automatic
16c
20h
610
@8250rpm
413
@6500rpm
$237,250
Lamborghini Gallardo 2014 LP 550-2 Coupe 6 Speed
Automatic
12c
20h
550
@8000rpm
398
@6500rpm
$191,900
Maserati Granturismo 2016 Sport Coupe 6 Speed
Automatic/Manual
13c
21h
454
@7600rpm
384
@4750rpm
$132,825
Maserati Quattroporte 2016 S Sedan 8 Speed
Automatic/Manual
16c
23h
404
@5500rpm
406
@1500rpm
$99,900
Maserati Ghibli 2016 Base Sedan 8 Speed
Automatic/Manual
17c
24h
345
@5250rpm
369
@1750rpm
$70,600
United States
Chevrolet Corvette 2016 Z06 Coupe 7 Speed
Manual
15c
22h
650
@6400rpm
650
@3600rpm
$88,345
Dodge Viper 2017 GT Coupe 6 Speed
Manual
12c
19h
645
@5000rpm
600
@5000rpm
$95,895
Ford GT 2017 Base Coupe 7 Speed
Automatic
11c
18h
647
@6250rpm
550
@5900rpm
$447,000
Tesla Model S 2017 75D 1 Speed
Direct Drive
259
@6100rpm
243
@NArpm
$74,500
Japan
Acura NSX 2017 Base Coupe 9 Speed
Automatic
21c
22h
573
@6500rpm
476
@2000rpm
$156,000
Nissan GT-R 2016 Premium Coupe 6 Speed
Automatic
16c
22h
545
@6400rpm
436
@3200rpm
$101,770
United Kingdom
Aston Martin Vanquish 2016 Base Coupe 8 Speed
Automatic/Manual
13c
21h
568
@6650rpm
465
@5500rpm
$287,250
Aston Martin DB11 2017 Base Coupe 8 Speed
Automatic/Manual
15c
21h
608
@6500rpm
516
@1500rpm
$211,195
Aston Martin Rapide S 2016 Base Sedan 8 Speed
Automatic/Manual
14c
21h
552
@6650rpm
465
@5500rpm
$205,300
Aston Martin Vantage 2016 V8 GT (Manual) Coupe 6 Speed
Manual
13c
19h
430
@7300rpm
361
@5000rpm
$103,300
Bentley Continental GT 2016 V8 Coupe 8 Speed
Automatic/Manual
15c
25h
500
@6000rpm
487
@1700rpm
$198,500
Jaguar F-Type 2016 Base (Manual) Coupe 6 Speed
Manual
16c
24h
340
@6500rpm
332
@3500rpm
$65,000
Lotus Evora 2017 2+2 Coupe 6 Speed
Manual
16c
24h
400
@7000rpm
302
@3500rpm
$91,900
McLaren 570 2016 Base Coupe 7 Speed
Automatic
16c
23h
570
@7500rpm
443
@5000rpm
$184,900
Rolls-Royce Dawn 2016 Base Convertible 8 Speed
Automatic
12c
19h
563
@5250rpm
575
@1500rpm
$335,000
Rolls-Royce Wraith 2016 Base Coupe 8 Speed
Automatic
13c
21h
624
@5600rpm
590
@1500rpm
$304,350
Source: Various pages within the Edmonds website.
1 All prices in U.S. dollars (USD).
2 Best gas mileage (city) of all the gtcars.
3 The highest horsepower of all the gtcars.

And that is it. The final table looks pretty good and conveys the additional information we planned for. That table can be used in a lot of different places like R Markdown, Shiny, email messages… wherever HTML is accepted.