Skip to contents

The R package {gt} is becoming increasingly popular for creating aesthetically pleasing tables. nflplotR supports rendering of team logos, team wordmarks, and player headshots in gt tables similar to ggplot2. This article will provide some typical examples.

Team Logos & Wordmarks

The functions gt_nfl_logos() and gt_nfl_wordmarks() come with a powerful locations argument that allows usage of gt selection helpers. We will create an example dataframe to show how this all works.

df <- data.frame(
  row_group_column = c("AFC", "NFC", "AFC", "NFC"),
  row_name_column = c("LAC", "SEA"),
  column_a = 11:12,
  column_b = c("KC", "LA")
) 

Our example dataframe in a gt table without any formatting.

gt::gt(df)
row_group_column row_name_column column_a column_b
AFC LAC 11 KC
NFC SEA 12 LA
AFC LAC 11 KC
NFC SEA 12 LA

The column row_group_column is intended to serve as row group variable so let’s apply this.

gt::gt(df, groupname_col = "row_group_column")
row_name_column column_a column_b
AFC
LAC 11 KC
LAC 11 KC
NFC
SEA 12 LA
SEA 12 LA

We also would like to render images in the stub, i.e. the rownames so we tell gt about the row_name_column.

example_table <- gt::gt(
  df, 
  groupname_col = "row_group_column", 
  rowname_col = "row_name_column"
) |> 
  # align the complete table left
  gt::tab_options(
    table.align = "left"
  )
example_table
column_a column_b
AFC
LAC 11 KC
LAC 11 KC
NFC
SEA 12 LA
SEA 12 LA

This is our final table. We have valid NFL abbreviations in the cell body, in row group labels and in the stub. We can now use nflplotR to render images instead of those abbreviations.

Cell Body

To render images in the cell body, i.e. the rows of the table, we can either use the columns argument or the appropriate locations helper.

example_table |> 
  nflplotR::gt_nfl_logos(columns = "column_b")
column_a column_b
AFC
LAC 11 The KC NFL logo
LAC 11 The KC NFL logo
NFC
SEA 12 The LA NFL logo
SEA 12 The LA NFL logo

Please note, that the locations helper will allow you to selectively apply the function to a set of rows

example_table |> 
  nflplotR::gt_nfl_logos(locations = gt::cells_body(rows = gt::starts_with("LAC")))
column_a column_b
AFC
LAC 11 The KC NFL logo
LAC 11 The KC NFL logo
NFC
SEA 12 LA
SEA 12 LA

Row Group Label

Rendering images outside of the cell body will always require the appropriate call to the locations argument. The columns argument cannot handle anything outside cell bodies.

example_table |> 
  nflplotR::gt_nfl_logos(locations = gt::cells_row_groups())
column_a column_b
The AFC NFL logo
LAC 11 KC
LAC 11 KC
The NFC NFL logo
SEA 12 LA
SEA 12 LA

Stub

Now we would like to convert rownames to images.

example_table |> 
  nflplotR::gt_nfl_wordmarks(locations = gt::cells_stub())
column_a column_b
AFC
The LAC NFL logo 11 KC
The LAC NFL logo 11 KC
NFC
The SEA NFL logo 12 LA
The SEA NFL logo 12 LA

Column Spanners

We use another table to demonstrate how to convert team names to logos and wordmarks in column spanners.

Here only logos

df <- data.frame(a = 3, b = 4, c = 5, d = 6)

df |> 
  gt::gt() |> 
  gt::tab_spanner("KC", c(a, b)) |> 
  gt::tab_spanner("LAC", c(c, d)) |> 
  nflplotR::gt_nfl_logos(locations = gt::cells_column_spanners())
The KC NFL logo
The LAC NFL logo
a b c d
3 4 5 6

And now mix logo and wordmark

df <- data.frame(a = 3, b = 4, c = 5, d = 6)

df |> 
  gt::gt() |> 
  gt::tab_spanner("KC", c(a, b)) |> 
  gt::tab_spanner("LAC", c(c, d)) |> 
  nflplotR::gt_nfl_logos(locations = gt::cells_column_spanners("KC")) |> 
  nflplotR::gt_nfl_wordmarks(locations = gt::cells_column_spanners("LAC"))
The KC NFL logo
The LAC NFL logo
a b c d
3 4 5 6

Combine all together

The locations argument allows multiple locations in one call by wrapping them in a list.

example_table |> 
  nflplotR::gt_nfl_wordmarks(locations = gt::cells_stub()) |> 
  nflplotR::gt_nfl_logos(
    locations = list(
      gt::cells_body(), gt::cells_row_groups()
    )
  )
column_a column_b
The AFC NFL logo
The LAC NFL logo 11 The KC NFL logo
The LAC NFL logo 11 The KC NFL logo
The NFC NFL logo
The SEA NFL logo 12 The LA NFL logo
The SEA NFL logo 12 The LA NFL logo

How about Column Labels?

Well…it’s complicated, because gt behaves inconsistent in my opinion.

The actually correct way would be a call to nflplotR::gt_nfl_logos or nflplotR::gt_nfl_wordmarks with the locations argument set to gt::cells_column_labels(). Currently, this wouldn’t render any images in column labels as discussed in the above linked issue.

However, as a convenient workaround, nflplotR supports logos and wordmarks in column labels through gt_nfl_cols_label().

LOGOS:

teams <- nflplotR::valid_team_names() |> head(6)
df <- cbind(1, 2, 3, 4, 5, 6) |> 
  as.data.frame() |> 
  rlang::set_names(teams)

gt::gt(df) |> 
  nflplotR::gt_nfl_cols_label() |> 
  # align the complete table left
  gt::tab_options(
    table.align = "left"
  )
The AFC NFL logo The ARI NFL logo The ATL NFL logo The BAL NFL logo The BUF NFL logo The CAR NFL logo
1 2 3 4 5 6

WORDMARKS (note how non matches remain unchanged):

gt::gt(df) |> 
  nflplotR::gt_nfl_cols_label(type = "wordmark") |> 
  # align the complete table left
  gt::tab_options(
    table.align = "left"
  )
AFC The ARI NFL logo The ATL NFL logo The BAL NFL logo The BUF NFL logo The CAR NFL logo
1 2 3 4 5 6

HEADSHOTS:

headshot_df <- data.frame(
  "00-0036355" = 1, 
  "00-0033873" = 2,
  check.names = FALSE
)
gt::gt(headshot_df) |> 
  nflplotR::gt_nfl_cols_label(type = "headshot") |> 
  # align the complete table left
  gt::tab_options(
    table.align = "left"
  )
1 2

Logos and Wordmarks Rendered by nflplotR

This example creates a table that renders all team logos and wordmarks. We split the table into 2 x 16 rows to avoid an overly long table and convert all variables starting with “logo” to logos and all variables starting with “wordmark” to wordmarks.

teams <- nflplotR::valid_team_names()
# remove conference logos from this example
teams <- teams[!teams %in% c("AFC", "NFC", "NFL")]
# create dataframe with all 32 team names
df <- data.frame(
  team_a = head(teams, 16),
  logo_a = head(teams, 16),
  wordmark_a = head(teams, 16),
  team_b = tail(teams, 16),
  logo_b = tail(teams, 16),
  wordmark_b = tail(teams, 16)
)
# create gt table and translate team names to logo/wordmark images
df |>
  gt::gt() |>
  nflplotR::gt_nfl_logos(columns = gt::starts_with("logo")) |>
  nflplotR::gt_nfl_wordmarks(columns = gt::starts_with("wordmark")) |> 
  # align the complete table left
  gt::tab_options(
    table.align = "left"
  )
team_a logo_a wordmark_a team_b logo_b wordmark_b
ARI The ARI NFL logo The ARI NFL logo LA The LA NFL logo The LA NFL logo
ATL The ATL NFL logo The ATL NFL logo LAC The LAC NFL logo The LAC NFL logo
BAL The BAL NFL logo The BAL NFL logo LV The LV NFL logo The LV NFL logo
BUF The BUF NFL logo The BUF NFL logo MIA The MIA NFL logo The MIA NFL logo
CAR The CAR NFL logo The CAR NFL logo MIN The MIN NFL logo The MIN NFL logo
CHI The CHI NFL logo The CHI NFL logo NE The NE NFL logo The NE NFL logo
CIN The CIN NFL logo The CIN NFL logo NO The NO NFL logo The NO NFL logo
CLE The CLE NFL logo The CLE NFL logo NYG The NYG NFL logo The NYG NFL logo
DAL The DAL NFL logo The DAL NFL logo NYJ The NYJ NFL logo The NYJ NFL logo
DEN The DEN NFL logo The DEN NFL logo PHI The PHI NFL logo The PHI NFL logo
DET The DET NFL logo The DET NFL logo PIT The PIT NFL logo The PIT NFL logo
GB The GB NFL logo The GB NFL logo SEA The SEA NFL logo The SEA NFL logo
HOU The HOU NFL logo The HOU NFL logo SF The SF NFL logo The SF NFL logo
IND The IND NFL logo The IND NFL logo TB The TB NFL logo The TB NFL logo
JAX The JAX NFL logo The JAX NFL logo TEN The TEN NFL logo The TEN NFL logo
KC The KC NFL logo The KC NFL logo WAS The WAS NFL logo The WAS NFL logo

Player Headshots

All of the above applies to gt_nfl_headshots() as well. All you need is a gsis ID.

df <- data.frame(
  A = c("00-0036355", "00-0033873"),
  B = c("00-0033077", "00-0035228")
)

df |>
  gt::gt() |> 
  nflplotR::gt_nfl_headshots(columns = gt::everything(), height = 50) |> 
  # align the complete table left
  gt::tab_options(
    table.align = "left"
  )
A B

Add Context to Data Values

When presenting data, whether in tables or other forms, context matters. For example, if you explain to a layperson that an NFL quarterback has a completion rate of 80%, they will not be able to interpret this value without context. Is it average, below average, or an incredible record?

There are various ways to add this context to tables. Statistically, a particular value can be classified very well by specifying its empirical quantile (usually given as a percentile). Once the percentiles have been determined, one can use gt::cols_merge_n_pct, for example, to add the context (the percentile) to the value. More complex styles can be implemented using the pattern argument in gt::cols_merge.

This chapter introduces another method of graphically representing quantiles in tables. The basic idea is that a “progress bar” is rendered which is filled between 0% and 100%, depending on the empirical quantile. Let’s take a look at some examples below.

Example Data

df <- data.frame(
  letters = sample(LETTERS, 10, TRUE),
  value = sample(100:500, 10, FALSE),
  pctl = rev(c(0, 1, 5, 10, 20, 45, 50, 75, 98, 100))
)

The Inline Percentage Bar

By default, a bar is inserted and the value is printed inside the bar. We set hide_col_pct (which is not the default) to make it easier to understand how the filling corresponds to the values in pctl.

gt::gt(df) |> 
  nflplotR::gt_pct_bar(
    "value", "pctl",
    hide_col_pct = FALSE
  )
letters value pctl
J 184 100
H 413 98
V 128 75
Q 429 50
L 201 45
T 241 20
O 421 10
R 136 5
L 463 1
V 231 0

This doesn’t really look good because values aren’t aligned and bars are short. So let’s change the column width and look again.

gt::gt(df) |> 
  nflplotR::gt_pct_bar(
    "value", "pctl",
    hide_col_pct = FALSE
  ) |> 
  gt::cols_width(value ~ gt::px(200))
letters value pctl
J 184 100
H 413 98
V 128 75
Q 429 50
L 201 45
T 241 20
O 421 10
R 136 5
L 463 1
V 231 0

The bars look better but notice how values are aligned right inside the filled bars. This is an important information that we can use to improve the style.

Now let’s align left and apply a left side padding:

gt::gt(df) |> 
  nflplotR::gt_pct_bar(
    "value", "pctl",
    hide_col_pct = FALSE,
    value_padding_left = "10px"
  ) |> 
  gt::cols_width(value ~ gt::px(200)) |> 
  gt::cols_align("left", "value")
letters value pctl
J 184 100
H 413 98
V 128 75
Q 429 50
L 201 45
T 241 20
O 421 10
R 136 5
L 463 1
V 231 0

Two annoying things remain. Some values corresponding to small percentiles overlap with the bars and very small percentiles (see pctl <= 5) overlap the outline. We can fix overlapping values and bars by applying separate padding values for every percentile (all function arguments of gt_pct_bar are vectorized). Rendering of very short percentile bars can be messed up by the border radius of the bar, which is why we see bars overlapping the outline. So let’s try and fix this

gt::gt(df) |> 
  nflplotR::gt_pct_bar(
    "value", "pctl",
    hide_col_pct = FALSE,
    value_padding_left = ifelse(df$pctl < 25, "110%", "10px"),
    fill_border.radius = "3px",
    background_border.radius = "5px"
  ) |> 
  gt::cols_width(value ~ gt::px(200)) |> 
  gt::cols_align("left", "value")
letters value pctl
J 184 100
H 413 98
V 128 75
Q 429 50
L 201 45
T 241 20
O 421 10
R 136 5
L 463 1
V 231 0

The Extra Percentage Bar

As an alternative to the inline percentage bar, you can also place the bar below the text. gt_pct_bar controls this via the value_position argument. This approach allows for a significantly flatter design of the bars, because the text is printed outside the bar.

gt::gt(df) |> 
  nflplotR::gt_pct_bar(
    "value", "pctl",
    hide_col_pct = FALSE,
    value_position = "above",
    # with value_position = "above", we need an absolute value of bar heights!
    background_fill.height = "5px",
    background_fill.color = "LightGray"
  ) |> 
  gt::cols_width(value ~ gt::px(100)) |> 
  gt::cols_align("center", "value")
letters value pctl
J 184 100
H 413 98
V 128 75
Q 429 50
L 201 45
T 241 20
O 421 10
R 136 5
L 463 1
V 231 0

Integrated Color Scales

gt_pct_bar can work with any color palette (= vector of color names allowed in html). It also comes with 3 integrated palettes named “hulk”, “hulk_teal”, and “blue_orange”. The default “hulk” palette is used in the above examples. Below we’ll look at the other color scales.

Here is an example with the integrated “hulk_teal” color scale.

gt::gt(df) |> 
  nflplotR::gt_pct_bar(
    "value", "pctl",
    hide_col_pct = FALSE,
    value_padding_left = ifelse(df$pctl < 25, "110%", "10px"),
    fill_border.radius = "3px",
    background_border.radius = "5px",
    fill_palette = "hulk_teal"
  ) |> 
  gt::cols_width(value ~ gt::px(200)) |> 
  gt::cols_align("left", "value")
letters value pctl
J 184 100
H 413 98
V 128 75
Q 429 50
L 201 45
T 241 20
O 421 10
R 136 5
L 463 1
V 231 0

And another example with the integrated “blue_orange” color scale.

gt::gt(df) |> 
  nflplotR::gt_pct_bar(
    "value", "pctl",
    hide_col_pct = FALSE,
    value_padding_left = ifelse(df$pctl < 25, "110%", "10px"),
    fill_border.radius = "3px",
    background_border.radius = "5px",
    fill_palette = "blue_orange"
  ) |> 
  gt::cols_width(value ~ gt::px(200)) |> 
  gt::cols_align("left", "value")
letters value pctl
J 184 100
H 413 98
V 128 75
Q 429 50
L 201 45
T 241 20
O 421 10
R 136 5
L 463 1
V 231 0

Advanced Styling

gt_pct_bar supports advanced styling of the background bar (through background_style.props), the fill bar (through fill_style.props), and the text (through text_style.props) via named lists of the form list(property = value). It is possible to pass every style property to these lists that is supported in html.

Here is an example where we overwrite the text color to be red and change the font weight to a bold font. Values in the property lists will overwrite default values.

gt::gt(df) |> 
  nflplotR::gt_pct_bar(
    "value", "pctl",
    hide_col_pct = FALSE,
    value_position = "above",
    background_fill.height = "5px",
    background_fill.color = "LightGray",
    value_style.props = list(
      color = "red", "font-weight" = 900
    )
  ) |> 
  gt::cols_width(value ~ gt::px(100)) |> 
  gt::cols_align("center", "value")
letters value pctl
J 184 100
H 413 98
V 128 75
Q 429 50
L 201 45
T 241 20
O 421 10
R 136 5
L 463 1
V 231 0

Isn’t this Already Available in gtExtras?

The great Thomas Mock has implemented the function gt_plt_bar_pct in his {gtExtras} package.

While at first glance it appears that this function does exactly the same thing as nflplotR::gt_pct_bar, there are significant differences. The main differences include:

  • nflplotR supports full control over styling of all 3 components (background, fill, text). It does not hard code any property.
  • nflplotR prints the value of another column on top of the bar to provide context.
  • nflplotR supports vectors of colors in both fill and text to implement color scales.
  • nflplotR does not manipulate other gt options like alignment or column width.
  • gtExtras does not support the value_position = "above" functionality as described in previous sections.

Basically, gtExtras::gt_plt_bar_pct is a special case of nflplotR::gt_pct_bar, which can be reproduced with the correct styling. See how Tom’s example is reproduced with nflplotR below.

data <- data.table::data.table(x = seq(1, 100, length.out = 6)) 
data <- data[, `:=`(x_unscaled = x, x_scaled = paste0(x / max(x) * 100, "%"))]
gt::gt(data) |> 
  nflplotR::gt_pct_bar(
    "x_unscaled", "x_unscaled",
    background_fill.color = "#e1e1e1",
    background_border.radius = "0px",
    background_border.color = "transparent",
    fill_palette = "forestgreen",
    fill_border.radius = "0px",
    value_colors = "transparent"
  ) |> 
  nflplotR::gt_pct_bar(
    "x_scaled", "x",
    background_fill.color = "#e1e1e1",
    background_border.radius = "0px",
    background_border.color = "transparent",
    fill_palette = "Purple",
    fill_border.radius = "0px",
    value_style.props = list(
      "color" = c(rep("black", 2), rep("white", 4)),
      "font-weight" = "bold"
    ),
    value_padding_left = ifelse(data$x <= 21, "110%", "10px")
  ) |> 
  gt::cols_width(gt::ends_with("scaled") ~ gt::px(100)) |> 
  gt::cols_align("left", gt::ends_with("scaled"))
x x_unscaled x_scaled
1.0 1.0 1%
20.8 20.8 20.8%
40.6 40.6 40.6%
60.4 60.4 60.4%
80.2 80.2 80.2%
100.0 100.0 100%