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:

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

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.

mfr model year trim bdy_style hp hp_rpm trq trq_rpm mpg_c mpg_h drivetrain trsmn msrp
United States
Ford GT 2017 Base Coupe coupe 647 6250 550 5900 11 18 rwd 7a 447000
Dodge Viper 2017 GT Coupe coupe 645 5000 600 5000 12 19 rwd 6m 95895
Italy
Ferrari LaFerrari 2015 Base Coupe coupe 949 9000 664 6750 12 16 rwd 7a 1416362
Lamborghini Aventador 2015 LP 700-4 Coupe coupe 700 8250 507 5500 11 18 awd 7a 397500
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
Mercedes-Benz AMG GT 2016 S Coupe coupe 503 6250 479 1750 16 22 rwd 7a 129900

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 %>%
  dplyr::group_by(ctry_origin) %>%
  dplyr::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
Mercedes-Benz AMG GT 2016 S Coupe coupe 503 6250 479 1750 16 22 rwd 7a 129900
United States
Dodge Viper 2017 GT Coupe coupe 645 5000 600 5000 12 19 rwd 6m 95895
Ford GT 2017 Base Coupe coupe 647 6250 550 5900 11 18 rwd 7a 447000
Italy
Ferrari LaFerrari 2015 Base Coupe coupe 949 9000 664 6750 12 16 rwd 7a 1416362
Lamborghini Aventador 2015 LP 700-4 Coupe coupe 700 8250 507 5500 11 18 awd 7a 397500

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 `ctry_origin`
order_countries <- c("Germany", "Italy", "United States", "Japan")

# Reorder the table rows by our specific ordering of groups
gtcars_8 %>%
  dplyr::arrange(
    factor(ctry_origin, levels = order_countries), mfr, desc(msrp)
  ) %>%
  dplyr::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
Mercedes-Benz AMG GT 2016 S Coupe coupe 503 6250 479 1750 16 22 rwd 7a 129900
Italy
Ferrari LaFerrari 2015 Base Coupe coupe 949 9000 664 6750 12 16 rwd 7a 1416362
Lamborghini Aventador 2015 LP 700-4 Coupe coupe 700 8250 507 5500 11 18 awd 7a 397500
United States
Dodge Viper 2017 GT Coupe coupe 645 5000 600 5000 12 19 rwd 6m 95895
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).

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
Mercedes-Benz AMG GT 2016 S Coupe coupe 503 6250 479 1750 16 22 rwd 7a 129900
Italy
Ferrari LaFerrari 2015 Base Coupe coupe 949 9000 664 6750 12 16 rwd 7a 1416362
Lamborghini Aventador 2015 LP 700-4 Coupe coupe 700 8250 507 5500 11 18 awd 7a 397500
United States
Dodge Viper 2017 GT Coupe coupe 645 5000 600 5000 12 19 rwd 6m 95895
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.

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
Mercedes-Benz AMG GT 2016 S Coupe 7a 16 22 503 6250 479 1750 129900
Italy
Ferrari LaFerrari 2015 Base Coupe 7a 12 16 949 9000 664 6750 1416362
Lamborghini Aventador 2015 LP 700-4 Coupe 7a 11 18 700 8250 507 5500 397500
United States
Dodge Viper 2017 GT Coupe 6m 12 19 645 5000 600 5000 95895
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.

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
Mercedes-Benz AMG GT 2016 S Coupe 7a 16 22 503 6250 479 1750 129900
Italy
Ferrari LaFerrari 2015 Base Coupe 7a 12 16 949 9000 664 6750 1416362
Lamborghini Aventador 2015 LP 700-4 Coupe 7a 11 18 700 8250 507 5500 397500
United States
Dodge Viper 2017 GT Coupe 6m 12 19 645 5000 600 5000 95895
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.

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>, ....

Year Trim Transmission Performance MSRP
MPG HP Torque
Germany
BMW i8 2016 Mega World Coupe 6am 28c
29h
357
@5800rpm
420
@3700rpm
140700
Mercedes-Benz AMG GT 2016 S Coupe 7a 16c
22h
503
@6250rpm
479
@1750rpm
129900
Italy
Ferrari LaFerrari 2015 Base Coupe 7a 12c
16h
949
@9000rpm
664
@6750rpm
1416362
Lamborghini Aventador 2015 LP 700-4 Coupe 7a 11c
18h
700
@8250rpm
507
@5500rpm
397500
United States
Dodge Viper 2017 GT Coupe 6m 12c
19h
645
@5000rpm
600
@5000rpm
95895
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.

Year Trim Transmission Performance MSRP
MPG HP Torque
Germany
BMW i8 2016 Mega World Coupe 6am 28c
29h
357
@5800rpm
420
@3700rpm
$140,700
Mercedes-Benz AMG GT 2016 S Coupe 7a 16c
22h
503
@6250rpm
479
@1750rpm
$129,900
Italy
Ferrari LaFerrari 2015 Base Coupe 7a 12c
16h
949
@9000rpm
664
@6750rpm
$1,416,362
Lamborghini Aventador 2015 LP 700-4 Coupe 7a 11c
18h
700
@8250rpm
507
@5500rpm
$397,500
United States
Dodge Viper 2017 GT Coupe 6m 12c
19h
645
@5000rpm
600
@5000rpm
$95,895
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 cells_styles() helper function, which contains arguments for all the styles that are supported (use ?cells_styles for more information on this). Here we will use a text size of 12px in 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_data and then supply our target columns to the columns argument.

Year Trim Transmission Performance MSRP
MPG HP Torque
Germany
BMW i8 2016 Mega World Coupe 6am 28c
29h
357
@5800rpm
420
@3700rpm
$140,700
Mercedes-Benz AMG GT 2016 S Coupe 7a 16c
22h
503
@6250rpm
479
@1750rpm
$129,900
Italy
Ferrari LaFerrari 2015 Base Coupe 7a 12c
16h
949
@9000rpm
664
@6750rpm
$1,416,362
Lamborghini Aventador 2015 LP 700-4 Coupe 7a 11c
18h
700
@8250rpm
507
@5500rpm
$397,500
United States
Dodge Viper 2017 GT Coupe 6m 12c
19h
645
@5000rpm
600
@5000rpm
$95,895
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_data() location helper function, we supply a function to the fn argument that processes a vector of text. If we intend to render as an HTML table, we can directly apply HTML tags in the transformation function. The function we provide here will build strings that read better in a display table.

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
Mercedes-Benz AMG GT 2016 S Coupe 7 Speed
Automatic
16c
22h
503
@6250rpm
479
@1750rpm
$129,900
Italy
Ferrari LaFerrari 2015 Base Coupe 7 Speed
Automatic
12c
16h
949
@9000rpm
664
@6750rpm
$1,416,362
Lamborghini Aventador 2015 LP 700-4 Coupe 7 Speed
Automatic
11c
18h
700
@8250rpm
507
@5500rpm
$397,500
United States
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
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).

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
Mercedes-Benz AMG GT 2016 S Coupe 7 Speed
Automatic
16c
22h
503
@6250rpm
479
@1750rpm
$129,900
Italy
Ferrari LaFerrari 2015 Base Coupe 7 Speed
Automatic
12c
16h
949
@9000rpm
664
@6750rpm
$1,416,362
Lamborghini Aventador 2015 LP 700-4 Coupe 7 Speed
Automatic
11c
18h
700
@8250rpm
507
@5500rpm
$397,500
United States
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
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.

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
Mercedes-Benz AMG GT 2016 S Coupe 7 Speed
Automatic
16c
22h
503
@6250rpm
479
@1750rpm
$129,900
Italy
Ferrari LaFerrari 2015 Base Coupe 7 Speed
Automatic
12c
16h
949
@9000rpm
664
@6750rpm
$1,416,362
Lamborghini Aventador 2015 LP 700-4 Coupe 7 Speed
Automatic
11c
18h
700
@8250rpm
507
@5500rpm
$397,500
United States
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
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 edmunds.com.

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 glyph should be attached. It will handle the placement of the note glyph and also place the footnote in the footnotes area. Here, we’ll use the cells_data() location helper function. There are several location helper functions for targeting all parts of the table (e.g,. cells_data(), cells_stub(), etc.). Each location helper has their own interface for targeting cells, help is available at ?gt::location_cells.

What cells_data() expects is columns (column names, which can be conveniently provided in vars()) 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 can either provided targeted column labels in the columns argument or spanner column labels in the groups argument. Here, we are targeting a footnote to the msrp column label so we will use columns = vars(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 %>% 
  dplyr::arrange(desc(mpg_c)) %>%
  dplyr::slice(1) %>%
  dplyr::mutate(car = paste(mfr, model)) %>%
  dplyr::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 %>% 
  dplyr::arrange(desc(hp)) %>%
  dplyr::slice(1) %>%
  dplyr::mutate(car = paste(mfr, model)) %>%
  dplyr::pull(car)

# Create a display table with `gtcars`, using all of the previous
# statements piped together + additional `tab_footnote()` stmts
tab <-
  gtcars %>%
  dplyr::arrange(
    factor(ctry_origin, levels = order_countries),
    mfr, desc(msrp)
  ) %>%
  dplyr::mutate(car = paste(mfr, model)) %>%
  dplyr::select(-mfr, -model) %>%
  dplyr::group_by(ctry_origin) %>%
  gt(rowname_col = "car") %>%
  cols_hide(columns = vars(drivetrain, bdy_style)) %>%
  cols_move(
    columns = vars(trsmn, mpg_c, mpg_h),
    after = vars(trim)
  ) %>%
  tab_spanner(
    label = md("*Performance*"),
    columns = vars(mpg_c, mpg_h, hp, hp_rpm, trq, trq_rpm)
  ) %>%
  cols_merge(
    col_1 = vars(mpg_c),
    col_2 = vars(mpg_h),
    pattern = "{1}c<br>{2}h"
  ) %>%
  cols_merge(
    col_1 = vars(hp),
    col_2 = vars(hp_rpm),
    pattern = "{1}<br>@{2}rpm"
  ) %>%
  cols_merge(
    col_1 = vars(trq),
    col_2 = vars(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 = vars(msrp),
    currency = "USD",
    decimals = 0
  ) %>%
  cols_align(
    align = "center",
    columns = vars(mpg_c, hp, trq)
  ) %>%
  tab_style(
    style = cells_styles(text_size = px(12)),
    locations = cells_data(columns = vars(trim, trsmn, mpg_c, hp, trq))
  ) %>%
  text_transform(
    locations = cells_data(columns = vars(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 [edmunds.com](https://www.edmunds.com).")
  ) %>%
  tab_footnote(
    footnote = md("Best gas mileage (city) of all the **gtcars**."),
    locations = cells_data(
      columns = vars(mpg_c),
      rows = best_gas_mileage_city)
  ) %>%
  tab_footnote(
    footnote = md("The highest horsepower of all the **gtcars**."),
    locations = cells_data(
      columns = vars(hp),
      rows = highest_horsepower)
  ) %>%
  tab_footnote(
    footnote = "All prices in U.S. dollars (USD).",
    locations = cells_column_labels(
      columns = vars(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
NA 259
@6100rpm
243 $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 edmunds.com.
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.

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.