Beautiful tables in R with gtExtras

Merging static tables with graphics is a powerful combo

gt
tables
Author

Thomas Mock

Published

June 13, 2022

I’m very excited to have the first release of gtExtras available on CRAN!

The goal of gtExtras is to provide opinionated helper functions to assist in creating beautiful and functional tables with gt.

The functions are generally wrappers around boilerplate table-making code or adding opinionated functions like data journalism inspired table themes and inline graphics. The gt package is amazing, make sure to go read the official documentation.

For installation:

install.packages("gtExtras")
# or if wanting the dev version
# if needed install.packages("remotes")
remotes::install_github("jthomasmock/gtExtras")

Using gtExtras

Overall, there are a lot of available functions in gtExtras:

length(ls.str('package:gtExtras', mode='function'))
[1] 65

You can read about each of the functions in the function reference.

Overall, there are four families of functions in gtExtras:

  • Themes: 7 themes that style almost every element of a gt table, built off of data journalism-styled tables
  • Utilities: Helper functions for aligning/padding numbers, adding fontawesome icons, images, highlighting, dividers, styling by group, creating two tables or two column layouts, extracting ordered data from a gt table internals, or generating a random dataset for reprex
  • Plotting: 12 plotting functions for inline sparklines, win-loss charts, distributions (density/histogram), percentiles, dot + bar, bar charts, confidence intervals, or summarizing an entire dataframe!
  • Colors: 3 functions, a palette for “Hulk” style scale (purple/green), coloring rows with good defaults from paletteer, or adding a “color box” along with the cell value

Also see the Plotting with gtExtras article for more examples of combining tables and graphics together.

A subset of functions are included below, or see the full function reference.

Importantly, gtExtras is not at all a replacement for gt, but rather is almost a “cookbook” where common or repeated function calls are grouped into their own respective functions. At a technical level, gtExtras is literally just gt functions under the hood and I’ll highlight a few examples of how to do the same thing in each package.

Load libraries

Themes

The package includes seven different themes, and 3 examples are the gt_theme_538() styled after FiveThirtyEight style tables, the gt_theme_espn() styled after ESPN style tables, and the gt_theme_nytimes() styled after The New York Times tables.

head(mtcars) %>%
  gt() %>% 
  gt_theme_538() %>% 
  tab_header(title = "Table styled like the FiveThirtyEight")
Table styled like the FiveThirtyEight
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
head(mtcars) %>%
  gt() %>% 
  gt_theme_guardian() %>% 
  tab_header(title = "Table styled like the Guardian")
Table styled like the Guardian
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
head(mtcars) %>% 
  gt() %>% 
  gt_theme_nytimes() %>% 
  tab_header(title = "Table styled like the NY Times")
Table styled like the NY Times
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

There are also themes that are bit more specific or somewhat “tongue in cheek”:

head(mtcars) %>% 
  gt() %>% 
  gt_theme_excel() %>% 
  tab_header(title = "Table styled like Excel")
Table styled like Excel
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
head(mtcars) %>% 
  gt() %>% 
  gt_theme_dot_matrix() %>% 
  tab_header(title = "Table styled like a dot matrix printer")
Table styled like a dot matrix printer
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

If you wanted to write your own gt_theme_YOURTHEME() function, you could do this with something like the below:

my_theme <- function(gt_object, ...){
  gt_object %>%
    tab_options(
      column_labels.background.color = "black",
      heading.align = "left",
      ...
    ) %>%
    tab_style(
      style = cell_text(color = "red", size = px(32)),
      locations = cells_title("title")
    )
}
head(mtcars) %>%
  gt() %>%
  my_theme() %>%
  tab_header("My own custom theme!")
My own custom theme!
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

Hulk data_color

This is an opinionated diverging color palette. It diverges from low to high as purple to green. It is a good alternative to a red-green diverging palette as a color-blind friendly palette. The specific colors come from colorbrewer2.

Basic usage below, where a specific column is passed.

# basic use
head(mtcars) %>%
  gt::gt() %>%
  gt_hulk_col_numeric(mpg)
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

Trim provides a tighter range of purple/green so the colors are less pronounced.

head(mtcars) %>%
  gt::gt() %>%
  # trim gives smaller range of colors
  # so the green and purples are not as dark
  gt_hulk_col_numeric(mpg:disp, trim = TRUE) 
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

Reverse makes higher values represented by purple and lower by green. The default is to have high = green, low = purple.

# option to reverse the color palette
# so that purple is higher
head(mtcars) %>%
  gt::gt() %>%
  # reverse = green for low, purple for high
  gt_hulk_col_numeric(mpg:disp, reverse = TRUE) 
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

gt_color_rows()

The gt_color_rows() function is a more generic, thin boilerplate wrapper around gt::data_color(). A quick example of gt::data_color() is below.

Using named colors or hex colors is very simple!

head(mtcars) %>% 
  gt() %>% 
  gt::data_color(
    columns = mpg:disp, colors = c("white", "red")
  )
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

Some complexity arises when wanting to use a color palette with tighter control. The code below is good but requires you to write out a lot of your own control.

color_fn <- scales::col_numeric(
    domain = NULL,
  palette = as.character(
    paletteer::paletteer_d(
      palette = "ggsci::red_material" ,
      type = "continuous"))
  )

head(mtcars) %>% 
  gt() %>% 
  gt::data_color(columns = mpg:disp, colors = color_fn)
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

gtExtras::gt_color_rows() simple to use but provides rich color choices thanks to the native inclusion of paletteer::paletteer_d(). This can provide 100s of discrete (ie categorical) or continuous color palettes.

Note that it is very close to the color_fn from above but throws a warning if domain is NULL since that will use range within EACH column rather than a shared range ACROSS columns.

# basic use
mtcars %>%
  head() %>%
  gt() %>%
  gt_color_rows(mpg:disp)
Warning: Domain not specified, defaulting to observed range within each
specified column.
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

You can change the specific palette with palette = "package_name::palette_name"

# recognizes all of the dynamic palettes from paletteer
mtcars %>%
  head() %>%
  gt() %>%
  gt_color_rows(mpg:disp, palette = "ggsci::blue_material")
Warning: Domain not specified, defaulting to observed range within each
specified column.
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

You can also use custom-defined palettes with named colors in R or hex color values however, this has minimal value over gt::data_color(). The main difference would be ability to specify a domain and throwing a warning without it.

mtcars %>%
  head() %>%
  gt() %>%
  gt_color_rows(mpg:disp, palette = c("white", "green"))
Warning: Domain not specified, defaulting to observed range within each
specified column.
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1
    # could also use palette = c("#ffffff", "##00FF00")

gt-native example gives you basically the same result.

mtcars %>%
  head() %>%
  gt() %>%
  gt::data_color(mpg:disp, colors = c("white", "green"))
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

Lastly, you can also provide categorical or discrete data to be colored.

# provide type = "discrete"
mtcars %>%
  head() %>%
  gt() %>%
  gt_color_rows(
    cyl, 
    palette = "ggthemes::colorblind", 
    # note that you can manually define range like c(4, 6, 8)
    domain = range(mtcars$cyl),
    pal_type = "discrete"
   )
mpg cyl disp hp drat wt qsec vs am gear carb
21.0 6 160 110 3.90 2.620 16.46 0 1 4 4
21.0 6 160 110 3.90 2.875 17.02 0 1 4 4
22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
18.1 6 225 105 2.76 3.460 20.22 1 0 3 1

Again, this function is an example of something that is easily possible with gt but would require a good chunk of repeated boilerplate code.

gt_highlight_rows()

This provides the ability to highlight and optionally bold entire rows within an existing gt table. Basic use defaults to a light-blue highlight which can be changed with the fill argument.

head_car <- head(mtcars[,1:5]) %>% 
  tibble::rownames_to_column("car")

gt(head_car) %>% 
  gt_highlight_rows(rows = 2, font_weight = "normal") 
car mpg cyl disp hp drat
Mazda RX4 21.0 6 160 110 3.90
Mazda RX4 Wag 21.0 6 160 110 3.90
Datsun 710 22.8 4 108 93 3.85
Hornet 4 Drive 21.4 6 258 110 3.08
Hornet Sportabout 18.7 8 360 175 3.15
Valiant 18.1 6 225 105 2.76

You can optionally specify a target column with target_col that will be bold, while the rest of the row’s text will be default weight.

gt(head_car) %>% 
  gt_highlight_rows(
    rows = 5, 
    fill = "lightgrey",
    bold_target_only = TRUE,
    target_col = car
    )
car mpg cyl disp hp drat
Mazda RX4 21.0 6 160 110 3.90
Mazda RX4 Wag 21.0 6 160 110 3.90
Datsun 710 22.8 4 108 93 3.85
Hornet 4 Drive 21.4 6 258 110 3.08
Hornet Sportabout 18.7 8 360 175 3.15
Valiant 18.1 6 225 105 2.76

And because gtExtras is just using gt::tab_style() under the hood, it also accepts logical statements for specific rows.

gt(head_car) %>% 
  gt_highlight_rows(
    rows = drat == 3.08,# a logic statement
    fill = "lightgrey",
    bold_target_only = TRUE,
    target_col = car
    )
car mpg cyl disp hp drat
Mazda RX4 21.0 6 160 110 3.90
Mazda RX4 Wag 21.0 6 160 110 3.90
Datsun 710 22.8 4 108 93 3.85
Hornet 4 Drive 21.4 6 258 110 3.08
Hornet Sportabout 18.7 8 360 175 3.15
Valiant 18.1 6 225 105 2.76

The equivalent gt code - again, easy to figure out but some repeated calls.

gt(head_car) %>%
  tab_style(
    style = cell_fill(color = "lightgrey"), 
    locations = cells_body(everything(), rows = drat == 3.08)
    ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_body(car, rows = drat == 3.08)
  )
car mpg cyl disp hp drat
Mazda RX4 21.0 6 160 110 3.90
Mazda RX4 Wag 21.0 6 160 110 3.90
Datsun 710 22.8 4 108 93 3.85
Hornet 4 Drive 21.4 6 258 110 3.08
Hornet Sportabout 18.7 8 360 175 3.15
Valiant 18.1 6 225 105 2.76

Plotting in gt with gtExtras

As we get into plotting with gt the complexity really ramps up internally, as you’re wrapping gt + ggplot2 code internally.

Note that if you just want to create ggplot2 plots and embed them in gt, you can use gt::ggplot_image()! That gives you full and separate control of the two items, ie just use ggplot2 and use gt separately.

plot_object <-
  ggplot(
    data = gtcars,
    aes(x = hp, y = trq, size = msrp)
  ) +
  geom_point(color = "blue") +
  theme(legend.position = "none")

dplyr::tibble(
  text = "Here is a ggplot:",
  ggplot = NA
) %>%
  gt() %>%
  text_transform(
    locations = cells_body(columns = ggplot),
    fn = function(x) {
      plot_object %>%
        ggplot_image(height = px(200))
    }
  )
text ggplot
Here is a ggplot:

However, with the plotting functions in gtExtras, I’ve made some opinionated choices as to the output style/size/options - taking away some of your creativity for a simple to use user interface.

A side effect of these gtExtras functions is that almost exclusively they require you to pass datasets with list columns. These are easy enough to create!

mtcars %>%
   dplyr::group_by(cyl) %>%
   # must end up with list of data for each row in the input dataframe
   dplyr::summarize(mpg_data = list(mpg), .groups = "drop")
# A tibble: 3 × 2
    cyl mpg_data  
  <dbl> <list>    
1     4 <dbl [11]>
2     6 <dbl [7]> 
3     8 <dbl [14]>

gt_sparkline()

A typical sparkline for your table!

mtcars %>%
   dplyr::group_by(cyl) %>%
   # must end up with list of data for each row in the input dataframe
   dplyr::summarize(mpg_data = list(mpg), .groups = "drop") %>%
   gt() %>%
   gt_plt_sparkline(mpg_data)
cyl mpg_data
4 21.4
6 19.7
8 15.0

gt_plt_dist()

If you’d rather plot some distributions, you could use gt_plt_dist(). This functions defaults to an inline density plot, but accepts a type = "boxplot", "histogram", "rug_strip" or "density".

mtcars %>%
   dplyr::group_by(cyl) %>%
   # must end up with list of data for each row in the input dataframe
   dplyr::summarize(mpg_data = list(mpg), .groups = "drop") %>%
   gt() %>%
   gt_plt_dist(mpg_data)
cyl mpg_data
4
6
8

gt_bar_plot()

The gt_bar_plot function takes an existing gt_tbl object and adds horizontal barplots via native HTML. This is a wrapper around raw HTML strings, gt::text_transform() and gt::cols_align(). Note that values default to being normalized to the percent of the maximum observed value in the specified column. You can turn this off if the values already represent a percentage value representing 0-100.

mtcars %>%
  head() %>%
  dplyr::select(cyl, mpg) %>%
  dplyr::mutate(mpg_pct_max = round(mpg/max(mpg) * 100, digits = 2),
                mpg_scaled = mpg/max(mpg) * 100) %>%
  dplyr::mutate(mpg_unscaled = mpg) %>%
  gt() %>%
  gt_plt_bar_pct(column = mpg_scaled, scaled = TRUE) %>%
  gt_plt_bar_pct(column = mpg_unscaled, scaled = FALSE, fill = "blue", background = "lightblue") %>%
  cols_align("center", contains("scale")) %>%
  cols_width(4 ~ px(125),
             5 ~ px(125))
cyl mpg mpg_pct_max mpg_scaled mpg_unscaled
6 21.0 92.11
6 21.0 92.11
4 22.8 100.00
6 21.4 93.86
8 18.7 82.02
6 18.1 79.39

gt_merge_stack()

The gt_merge_stack() function takes an existing gt table and merges column 1 and column 2, stacking column 1’s text on top of column 2’s. Top text is in all caps with black bold text, while the lower text is smaller and dark grey.

Note that team_nick has the team nickname over the team’s division.

team_df <- readRDS(url("https://github.com/nflverse/nflfastR-data/raw/master/teams_colors_logos.rds"))

team_df %>%
  dplyr::select(team_nick, team_abbr, team_conf, team_division, team_wordmark) %>%
  head(8) %>%
  gt(groupname_col = "team_conf") %>%
  gt_merge_stack(col1 = team_nick, col2 = team_division) %>%
  gt_img_rows(team_wordmark)
team_nick team_abbr team_wordmark
NFC
Cardinals
NFC West
ARI
Falcons
NFC South
ATL
Panthers
AFC North
CAR
Bears
AFC East
CHI
AFC
Ravens
NFC South
BAL
Bills
NFC North
BUF
Bengals
AFC North
CIN
Browns
AFC North
CLE

gt_plt_winloss()

This function takes a list-column of win loss values (ie, 0=loss, 0.5 = tie, 1 = win) and ouputs an inline plot representing the win/loss squares with blue = win, red = loss, grey = tie. Points are also also redundantly coded with height, where wins are highest, ties are middle, and losses are at the bottom.

The example below generates an example dataset and then embeds a plot.

create_input_df <- function(repeats = 3){
  
  input_df <- dplyr::tibble(
    team = c("A1", "B2", "C3", "C4"),
    Wins = c(3, 2, 1, 1),
    Losses = c(2, 3, 2, 4),
    Ties = c(0, 0, 2, 0),
    outcomes = list(
      c(1, .5, 0) %>% rep(each = repeats),
      c(0, 1, 0.5) %>% rep(each = repeats),
      c(0, 0.5, 1) %>% rep(each = repeats),
      c(0.5, 1, 0) %>% rep(each = repeats)
    )
  )
  
  input_df
  
}

create_input_df(5) %>% 
  dplyr::glimpse()
Rows: 4
Columns: 5
$ team     <chr> "A1", "B2", "C3", "C4"
$ Wins     <dbl> 3, 2, 1, 1
$ Losses   <dbl> 2, 3, 2, 4
$ Ties     <dbl> 0, 0, 2, 0
$ outcomes <list> <1.0, 1.0, 1.0, 1.0, 1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.0, 0.0, …

Now that we have way to quickly generate example data, we can show the ability to incrementally add the win/losses.

Starting with 3 games. Please ignore the Wins/Loss/Ties columns, as they are simply placeholders. I am iterating the length of the outcomes list row.

create_input_df(1) %>% 
  gt() %>% 
  gt_plt_winloss(outcomes, max_wins = 15) %>% 
  tab_options(data_row.padding = px(2))
team Wins Losses Ties outcomes
A1 3 2 0
B2 2 3 0
C3 1 2 2
C4 1 4 0

And moving to 12 games, we can see that the scale is unchanged, and “empty” points are replaced with outcomes once the values are present in the data.

create_input_df(4) %>% 
  gt() %>% 
  gt_plt_winloss(outcomes, max_wins = 15) %>% 
  tab_options(data_row.padding = px(2))
team Wins Losses Ties outcomes
A1 3 2 0
B2 2 3 0
C3 1 2 2
C4 1 4 0

You can also switch over to ‘squares’ instead of ‘pills’ by changing the type argument.

create_input_df(4) %>% 
  gt() %>% 
  gt_plt_winloss(outcomes, max_wins = 15, type = "square") %>% 
  tab_options(data_row.padding = px(2))
team Wins Losses Ties outcomes
A1 3 2 0
B2 2 3 0
C3 1 2 2
C4 1 4 0

A more realistic use case is seen below with data from nflreadr:

Creating dataset
library(dplyr)
library(tidyr)
library(nflreadr)

games_df <- nflreadr::load_schedules() %>% 
  filter(season == 2020, game_type == "REG") %>% 
  select(game_id, team_home = home_team, team_away = away_team, result, week) %>% 
  pivot_longer(contains('team'), names_to = 'home_away', values_to = 'team', names_prefix = 'team_') %>% 
  mutate(
    result = ifelse(home_away == 'home', result, -result),
    win = ifelse(result == 0 , 0.5, ifelse(result > 0, 1, 0))
  ) %>% 
  select(week, team, win) %>% 
  mutate(
    team = case_when(
      team == 'STL' ~ 'LA',
      team == 'OAK' ~ 'LV',
      team == 'SD' ~ 'LAC',
      T ~ team
    )
  )

team_df <- nflreadr::load_teams() %>% 
  select(team_wordmark, team_abbr, team_conf, team_division)

joined_df <- games_df %>% 
  group_by(team) %>% 
  summarise(
    Wins = length(win[win==1]),
    Losses = length(win[win==0]),
    outcomes = list(win), .groups = "drop") %>% 
  left_join(team_df, by = c("team" = "team_abbr")) %>% 
  select(team_wordmark, team_conf, team_division, Wins:outcomes)

final_df <- joined_df %>% 
  filter(team_conf == "AFC") %>% 
  group_by(team_division) %>% 
  arrange(desc(Wins)) %>% 
  ungroup() %>% 
  arrange(team_division)
final_df %>% 
  gt(groupname_col = "team_division") %>%
  gt_plt_winloss(outcomes, max_wins = 16) %>% 
  gt_img_rows(columns = team_wordmark) %>% 
  gt_theme_538() %>% 
  tab_header(title = "2020 Results by Division for the AFC")
2020 Results by Division for the AFC
team_wordmark team_conf Wins Losses outcomes
AFC East
AFC 13 3
AFC 10 6
AFC 7 9
AFC 2 14
AFC North
AFC 12 4
AFC 11 5
AFC 11 5
AFC 4 11
AFC South
AFC 11 5
AFC 11 5
AFC 4 12
AFC 1 15
AFC West
AFC 14 2
AFC 8 8
AFC 7 9
AFC 5 11

Closing

So that has been a brief overview of some of the possibilities of gtExtras - so go out, use gt, bring in gtExtras when you’d like to extend some of the work you’re doing and let me know if you enjoy the package!

─ Session info ───────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.2.0 (2022-04-22)
 os       macOS Monterey 12.2.1
 system   aarch64, darwin20
 ui       X11
 language (EN)
 collate  en_US.UTF-8
 ctype    en_US.UTF-8
 tz       America/Chicago
 date     2022-06-13
 pandoc   2.18 @ /Applications/RStudio.app/Contents/MacOS/quarto/bin/tools/ (via rmarkdown)
 quarto   0.9.577 @ /usr/local/bin/quarto

─ Packages ───────────────────────────────────────────────────────────────────
 package     * version  date (UTC) lib source
 dplyr       * 1.0.9    2022-04-28 [1] CRAN (R 4.2.0)
 forcats     * 0.5.1    2021-01-27 [1] CRAN (R 4.2.0)
 ggplot2     * 3.3.6    2022-05-03 [1] CRAN (R 4.2.0)
 gt          * 0.6.0    2022-05-24 [1] CRAN (R 4.2.0)
 gtExtras    * 0.4.0    2022-06-09 [1] CRAN (R 4.2.0)
 nflreadr    * 1.2.0.12 2022-06-02 [1] Github (nflverse/nflreadr@94e3400)
 purrr       * 0.3.4    2020-04-17 [1] CRAN (R 4.2.0)
 readr       * 2.1.2    2022-01-30 [1] CRAN (R 4.2.0)
 sessioninfo * 1.2.2    2021-12-06 [1] CRAN (R 4.2.0)
 stringr     * 1.4.0    2019-02-10 [1] CRAN (R 4.2.0)
 tibble      * 3.1.7    2022-05-03 [1] CRAN (R 4.2.0)
 tidyr       * 1.2.0    2022-02-01 [1] CRAN (R 4.2.0)
 tidyverse   * 1.3.1    2021-04-15 [1] CRAN (R 4.2.0)

 [1] /Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/library

──────────────────────────────────────────────────────────────────────────────