Created
October 18, 2022 13:10
-
-
Save Henryjean/9d6c624ce1768f479c6046cb08803a56 to your computer and use it in GitHub Desktop.
Make a diamond plot from EPA per play data (colorblind friendly)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
library(tidyverse) | |
library(nflplotR) | |
library(nflreadr) | |
# need this for rotation and saving | |
library(grid) | |
# need this for labels | |
library(ggtext) | |
# data loading and wrangling copied from: https://www.nflfastr.com/articles/beginners_guide.html#figures-with-qb-stats | |
# get pbp and filter to regular season rush and pass plays | |
pbp <- nflreadr::load_pbp(2022) %>% | |
dplyr::filter(season_type == "REG") %>% | |
dplyr::filter(!is.na(posteam) & (rush == 1 | pass == 1)) | |
# offense epa | |
offense <- pbp %>% | |
dplyr::group_by(team = posteam) %>% | |
dplyr::summarise(off_epa = mean(epa, na.rm = TRUE)) | |
# defense epa | |
defense <- pbp %>% | |
dplyr::group_by(team = defteam) %>% | |
dplyr::summarise(def_epa = mean(epa, na.rm = TRUE)) | |
# join offense and defense | |
df <- offense %>% | |
inner_join(defense, by = "team") | |
# set max and min for off and def epa (want them to be symmetric for chart) | |
off_epa_min <- -.2 | |
off_epa_max <- .2 | |
def_epa_min <- -.2 | |
def_epa_max <- .2 | |
# set rotation to 45 degrees | |
rotation <- 45 | |
# Colorblind friendly | |
p_cb <- df %>% | |
ggplot(aes(x = off_epa, y = def_epa)) + | |
# add color blocking | |
annotate("rect", xmin = (off_epa_max + off_epa_min) / 2, xmax = off_epa_max, | |
ymin = def_epa_min, ymax = (def_epa_max + def_epa_min) / 2, fill= "#a6dba0", alpha = .5, color = 'transparent') + | |
annotate("rect", xmin = off_epa_min, xmax = (off_epa_max + off_epa_min) / 2, | |
ymin = (def_epa_max + def_epa_min) / 2, ymax = def_epa_max, fill= "#c2a5cf", alpha = .5, color = 'transparent') + | |
annotate("rect", xmin = (off_epa_max + off_epa_min) / 2, xmax = off_epa_max, | |
ymin = (def_epa_max + def_epa_min) / 2, ymax = def_epa_max, fill= "#f7f7f7", alpha = .5, color = 'transparent') + | |
annotate("rect", xmin = off_epa_min, xmax = (off_epa_max + off_epa_min) / 2, | |
ymin = def_epa_min, ymax = (def_epa_max + def_epa_min) / 2, fill= "#f7f7f7", alpha = .5, color = 'transparent') + | |
# add team logos | |
geom_nfl_logos(aes(team_abbr = team), width = 0.08, alpha = 0.75, angle = -1*rotation) + | |
scale_y_reverse(limits = c(def_epa_max, def_epa_min), breaks = seq(.20, -.20, -.05), | |
labels = scales::number_format(style_positive = "plus", accuracy = .01)) + | |
scale_x_continuous(limits = c(off_epa_min, off_epa_max), breaks = seq(-.2, .2, .05), | |
labels = scales::number_format(style_positive = "plus", accuracy = .01)) + | |
coord_equal() + | |
# add axis labels | |
labs( | |
x = "Offensive EPA/play", | |
y = "Defensive EPA/play" | |
) + | |
# thematic stuff | |
theme_minimal(base_family = 'Roboto') + | |
theme(axis.text.x = element_text(angle=(-1 * rotation), hjust = 0.5, margin = margin(t = -9.5)), | |
axis.text.y = element_text(angle=(-1 * rotation), hjust = 0.5, margin = margin(r = -5)), | |
axis.title.x = element_text(size = 12, | |
#angle=(-1 * rotation + 45), | |
vjust = 0.5, | |
margin = margin(t = 10), | |
face = 'bold', | |
color = "black"), | |
axis.title.y = element_text(size = 12, | |
angle=(-1 * rotation - 45), | |
hjust = 0.5, | |
margin = margin(r = 10), | |
color = "black", | |
face = 'bold'), | |
plot.margin = margin(1.15, .5, .5, -.25, unit = 'in'), | |
panel.grid.minor = element_blank(), | |
plot.background = element_rect(fill = 'floralwhite', color = "floralwhite")) + | |
# hack together a title and subtitle | |
annotate(geom = 'text', x = .2, y = -.2, label = "2022 NFL Offensive and Defensive EPA per Play", angle = -1 * rotation, vjust = -3.5, fontface = 'bold', size = 4, family = 'Roboto') + | |
annotate(geom = 'text', x = .2, y = -.2, label = paste0("As of ", format.Date(Sys.Date(), "%B %d, %Y"), ""), angle = -1 * rotation, vjust = -2.5, size = 3, family = 'Roboto') + | |
annotate(geom = 'text', x = -.19, y = .19, label = "Data: @nflfastR | Chart: @owenlhjphillips", angle = -1 * rotation, hjust = -0.55, vjust = 5, size = 2.5, family = 'Roboto') + | |
# hack together a few chart guides (ie, 'Good D, Bad D') | |
geom_richtext(aes(x = .175, y = .175, label = "Good O, Bad D"), angle = -1 * rotation, size = 2.75, family = 'Roboto', fontface = 'bold', color = 'black', fill = "#f7f7f7") + | |
geom_richtext(aes(x = -.18, y = -.18, label = "Bad O, Good D"), angle = -1 * rotation, size = 2.75, family = 'Roboto', fontface = 'bold', color = 'black', fill = "#f7f7f7") + | |
geom_richtext(aes(x = -.18, y = .18, label = "Bad O, Bad D"), angle = -1 * rotation, size = 2.75, family = 'Roboto', fontface = 'bold', color = 'white', fill = "#762a83", label.colour = 'black') + | |
geom_richtext(aes(x = .175, y = -.175, label = "Good O, Good D"), angle = -1 * rotation, size = 2.75, family = 'Roboto', fontface = 'bold', color = 'white', fill = "#1b7837", label.colour = 'black') | |
# save plot | |
png("epa_diamond_plot_cb.png", res = 300, width = 6, height = 6, units = 'in', bg = 'floralwhite') | |
print(p_cb, vp=viewport(angle=rotation, | |
width = unit(6, "in"), | |
height = unit(6, "in"))) | |
dev.off() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment