Learning gt: Creating a Retro League Table

R
data analysis
sports
Author

David Schoch

Published

November 18, 2024

It has been way too long since I had time to prepare a blog post that is neither a story about Open Source Development, or an announcement of yet another R package.

I meant to look into gt, a package to create wonderful-looking tables in R, for a very long time but everytime I get some sort of motivation, I shy away because of how complicated it looks to built a table. Hundreds of lines of codes for a 10 line table? Yeah no thank you knitr::kable() it is. But not this time.

Learning by breaking

Whenever I want to learn something new in R, I need a personal project for which this might be relevant. I am very bad at following tutorials. Basically all my data science, web scrapping, and javascript knowledge comes from my football analytics website that I have been maintaining for about 8 years. So I decided, why not football data again and decided to create a simple league table, but with a twist. I try to do something mildly absurd that requires enough extra effort to learn as much about the package as possible. When I wanted to built my first quarto website, I created quartocities, an homage to geocities and the beauty of 90s webpages. I didn’t break quarto, but got to learn so much about Lua, shortcodes, filters just to get those funky gifs everywhere. I am not gonna add that level of absurdity to the league table. For whatever reason, I decided that the table should have a vintage look. With this, I was hopping to get into as many of the styling elements as possible.

Setup

Besides the gt package, I will also use the gtExtras package which offers even more styling possibilities.

library(gt)
library(gtExtras)
library(tidyverse)

# some retro color palettes
cols <- c("#FAEBCC", "#b3e3e0", "#68C7C1", "#FACA78", "#F57F5B", "#DD5341", "#794A3A")
wld <- c("#354065", "#711233", "#666666")

The underlying data (current league table and results) was obtained from weltfussball.de.

Vanilla table

Here is the table without any form of styling.

tbl1 <- tbl |>
  gt()

tbl1
number img mannschaft pk sp wdl tore dif form
1 crests/bayern-munchen-2017-2025-logo.png Bayern München 48 19 15, 3, 1 58:16 42 W, W, W, W, D, D, W, W, W, W, W, D, W, L, W, W, W, W, W
2 crests/bayer-04-leverkusen-2005-logo.png Bayer Leverkusen 42 19 12, 6, 1 46:26 20 W, L, W, W, D, D, W, D, D, D, W, W, W, W, W, W, W, W, D
3 crests/eintracht-frankfurt-1998-logo.png Eintracht Frankfurt 37 19 11, 4, 4 44:26 18 L, W, W, W, W, D, L, D, W, W, W, W, D, L, L, W, W, W, D
4 crests/vfb-stuttgart-2014-logo.png VfB Stuttgart 32 19 9, 5, 5 36:28 8 L, D, W, W, D, D, L, W, D, L, W, D, W, W, L, W, W, W, L
5 crests/rb-leipzig-2020-logo.png RB Leipzig 32 19 9, 5, 5 34:29 5 W, W, D, D, W, W, W, W, L, D, L, L, W, W, L, W, L, D, D
6 crests/mainz-2018-logo.png 1. FSV Mainz 05 31 19 9, 4, 6 33:23 10 D, D, L, W, L, W, L, D, D, W, W, W, L, W, W, W, L, L, W
7 crests/vfl-wolfsburg-2002-logo.png VfL Wolfsburg 28 19 8, 4, 7 42:34 8 L, W, L, L, D, W, L, D, D, W, W, W, W, L, L, W, W, L, D
8 crests/borussia-monchengladbach-1999-logo.png Bor. Mönchengladbach 27 19 8, 3, 8 30:29 1 L, W, L, L, W, L, W, D, W, D, W, L, D, W, W, L, L, L, W
9 crests/werder-bremen-1987-logo.png Werder Bremen 27 19 7, 6, 6 33:36 -3 D, D, W, L, W, L, W, D, L, W, L, D, W, W, W, L, D, L, D
10 crests/sc-freiburg-2008-logo.png SC Freiburg 27 19 8, 3, 8 26:36 -10 W, L, W, W, L, W, W, L, D, D, L, W, D, W, L, W, L, L, L
11 crests/borussia-dortmund-1993-logo.png Borussia Dortmund 26 19 7, 5, 7 34:33 1 W, D, W, L, W, L, W, L, W, L, W, D, D, D, W, L, L, L, D
12 crests/fc-augsburg-2002-logo.png FC Augsburg 25 19 7, 4, 8 23:34 -11 D, L, W, L, L, W, L, W, D, D, L, W, D, L, L, L, W, W, W
13 crests/st-pauli-2005-logo.png FC St. Pauli 20 19 6, 2, 11 17:21 -4 L, L, L, D, W, L, L, D, W, L, L, W, L, L, W, L, L, W, W
14 crests/union-berlin-1966-logo.png 1. FC Union Berlin 20 19 5, 5, 9 16:27 -11 D, W, D, W, L, W, W, D, L, D, L, L, L, D, L, L, L, W, L
15 crests/tsg-1899-hoffenheim-2014-logo.png 1899 Hoffenheim 18 19 4, 6, 9 25:37 -12 W, L, L, L, L, D, W, D, L, D, W, L, D, D, L, L, L, W, D
16 crests/1-fc-heidenheim-2007-logo.png 1. FC Heidenheim 1846 14 19 4, 2, 13 24:40 -16 W, W, L, L, W, L, L, D, L, L, L, L, L, L, L, W, D, L, L
17 crests/holstein-kiel.png Holstein Kiel 12 19 3, 3, 13 28:48 -20 L, L, L, D, L, D, L, L, W, L, L, L, L, L, W, L, W, L, D
18 crests/vfl-bochum-2000-logo.png VfL Bochum 10 19 2, 4, 13 17:43 -26 L, L, L, D, L, L, L, L, L, D, L, L, L, D, W, L, W, D, L

Two things might catch your eye. There seem to be two list columns (wdl and form). We will deal with them later. In general, these types of columns are usually used to create som visualization WITHIN the column.

The column img contains paths to crests of the clubs. These can be displayed with the gt_img_rows() function from gtExtras. We also align the imiges with cols_align().

tbl1 <- tbl1 |>
  gt_img_rows(img, img_source = "local", height = "25px") |>
  cols_align(
    align = "center",
    columns = img
  )
tbl1
number img mannschaft pk sp wdl tore dif form
1 Bayern München 48 19 15, 3, 1 58:16 42 W, W, W, W, D, D, W, W, W, W, W, D, W, L, W, W, W, W, W
2 Bayer Leverkusen 42 19 12, 6, 1 46:26 20 W, L, W, W, D, D, W, D, D, D, W, W, W, W, W, W, W, W, D
3 Eintracht Frankfurt 37 19 11, 4, 4 44:26 18 L, W, W, W, W, D, L, D, W, W, W, W, D, L, L, W, W, W, D
4 VfB Stuttgart 32 19 9, 5, 5 36:28 8 L, D, W, W, D, D, L, W, D, L, W, D, W, W, L, W, W, W, L
5 RB Leipzig 32 19 9, 5, 5 34:29 5 W, W, D, D, W, W, W, W, L, D, L, L, W, W, L, W, L, D, D
6 1. FSV Mainz 05 31 19 9, 4, 6 33:23 10 D, D, L, W, L, W, L, D, D, W, W, W, L, W, W, W, L, L, W
7 VfL Wolfsburg 28 19 8, 4, 7 42:34 8 L, W, L, L, D, W, L, D, D, W, W, W, W, L, L, W, W, L, D
8 Bor. Mönchengladbach 27 19 8, 3, 8 30:29 1 L, W, L, L, W, L, W, D, W, D, W, L, D, W, W, L, L, L, W
9 Werder Bremen 27 19 7, 6, 6 33:36 -3 D, D, W, L, W, L, W, D, L, W, L, D, W, W, W, L, D, L, D
10 SC Freiburg 27 19 8, 3, 8 26:36 -10 W, L, W, W, L, W, W, L, D, D, L, W, D, W, L, W, L, L, L
11 Borussia Dortmund 26 19 7, 5, 7 34:33 1 W, D, W, L, W, L, W, L, W, L, W, D, D, D, W, L, L, L, D
12 FC Augsburg 25 19 7, 4, 8 23:34 -11 D, L, W, L, L, W, L, W, D, D, L, W, D, L, L, L, W, W, W
13 FC St. Pauli 20 19 6, 2, 11 17:21 -4 L, L, L, D, W, L, L, D, W, L, L, W, L, L, W, L, L, W, W
14 1. FC Union Berlin 20 19 5, 5, 9 16:27 -11 D, W, D, W, L, W, W, D, L, D, L, L, L, D, L, L, L, W, L
15 1899 Hoffenheim 18 19 4, 6, 9 25:37 -12 W, L, L, L, L, D, W, D, L, D, W, L, D, D, L, L, L, W, D
16 1. FC Heidenheim 1846 14 19 4, 2, 13 24:40 -16 W, W, L, L, W, L, L, D, L, L, L, L, L, L, L, W, D, L, L
17 Holstein Kiel 12 19 3, 3, 13 28:48 -20 L, L, L, D, L, D, L, L, W, L, L, L, L, L, W, L, W, L, D
18 VfL Bochum 10 19 2, 4, 13 17:43 -26 L, L, L, D, L, L, L, L, L, D, L, L, L, D, W, L, W, D, L

Global styling

Next I do some very basic global styling. tab_options() is used to set styles for the whole table and tab_style() for specific cells. Here we just add a background color and some cell borders at various locations.

tbl2 <- tbl1 |>
  tab_style(
    style = cell_borders(color = "black", sides = c("top", "bottom"), weight = "3px"),
    locations = cells_body()
  ) |>
  tab_style(
    style = cell_borders(color = "black", sides = c("right"), weight = "3px"),
    locations = cells_body(columns = c(1, 4, 6, 8, 9))
  ) |>
  tab_options(
    table.width = pct(100),
    table.background.color = cols[1],
    table.border.top.color = "black",
    table.border.top.width = "3px",
    table.border.bottom.color = "black",
    table.border.bottom.width = "3px"
  )
tbl2
number img mannschaft pk sp wdl tore dif form
1 Bayern München 48 19 15, 3, 1 58:16 42 W, W, W, W, D, D, W, W, W, W, W, D, W, L, W, W, W, W, W
2 Bayer Leverkusen 42 19 12, 6, 1 46:26 20 W, L, W, W, D, D, W, D, D, D, W, W, W, W, W, W, W, W, D
3 Eintracht Frankfurt 37 19 11, 4, 4 44:26 18 L, W, W, W, W, D, L, D, W, W, W, W, D, L, L, W, W, W, D
4 VfB Stuttgart 32 19 9, 5, 5 36:28 8 L, D, W, W, D, D, L, W, D, L, W, D, W, W, L, W, W, W, L
5 RB Leipzig 32 19 9, 5, 5 34:29 5 W, W, D, D, W, W, W, W, L, D, L, L, W, W, L, W, L, D, D
6 1. FSV Mainz 05 31 19 9, 4, 6 33:23 10 D, D, L, W, L, W, L, D, D, W, W, W, L, W, W, W, L, L, W
7 VfL Wolfsburg 28 19 8, 4, 7 42:34 8 L, W, L, L, D, W, L, D, D, W, W, W, W, L, L, W, W, L, D
8 Bor. Mönchengladbach 27 19 8, 3, 8 30:29 1 L, W, L, L, W, L, W, D, W, D, W, L, D, W, W, L, L, L, W
9 Werder Bremen 27 19 7, 6, 6 33:36 -3 D, D, W, L, W, L, W, D, L, W, L, D, W, W, W, L, D, L, D
10 SC Freiburg 27 19 8, 3, 8 26:36 -10 W, L, W, W, L, W, W, L, D, D, L, W, D, W, L, W, L, L, L
11 Borussia Dortmund 26 19 7, 5, 7 34:33 1 W, D, W, L, W, L, W, L, W, L, W, D, D, D, W, L, L, L, D
12 FC Augsburg 25 19 7, 4, 8 23:34 -11 D, L, W, L, L, W, L, W, D, D, L, W, D, L, L, L, W, W, W
13 FC St. Pauli 20 19 6, 2, 11 17:21 -4 L, L, L, D, W, L, L, D, W, L, L, W, L, L, W, L, L, W, W
14 1. FC Union Berlin 20 19 5, 5, 9 16:27 -11 D, W, D, W, L, W, W, D, L, D, L, L, L, D, L, L, L, W, L
15