Created
February 26, 2025 03:49
-
-
Save OTStats/2a94ee1a10efa05b26c0f292530a04d6 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
# -- Load libraries | |
library(tidyverse) | |
library(glue) | |
library(ggpath) | |
# -- Read data | |
df <- read_csv("Downloads/20250222_shots_2024.csv") %>% | |
janitor::clean_names() | |
df %>% tail() | |
# # -- Look at goalies goals allowed over expected | |
# df %>% | |
# filter( | |
# # shot_was_on_goal == 1, | |
# event %in% c("SHOT", "GOAL"), | |
# shot_on_empty_net == 0 | |
# ) %>% | |
# group_by(goalie_name_for_shot) %>% | |
# summarise(goals_allowed = sum(goal), | |
# xg = sum(x_goal), | |
# diff = xg - goals_allowed) %>% | |
# arrange(diff) | |
prepped_for_viz <- | |
df %>% | |
filter( | |
# shot_was_on_goal == 1, | |
# event %in% c("SHOT", "GOAL"), | |
shot_on_empty_net == 0 | |
) %>% | |
transmute( | |
id, | |
team = if_else(is_home_team == 1, away_team_code, home_team_code), | |
goalie_name_for_shot, | |
xg = x_goal, | |
goal | |
) %>% | |
group_by(team, goalie_name_for_shot) %>% | |
mutate( | |
goalie_shot_id = row_number(), | |
cum_xg = cumsum(xg), | |
cum_ga = cumsum(goal), | |
goal_xg_diff = cum_ga - cum_xg | |
) | |
# -- Pull in NHL logos | |
# Courtesy of Ivo Villanueva CSV | |
# Need to manually adjust some team abbrevation in order to join tables | |
data_git_nhl <- read.csv("https://raw.githubusercontent.com/IvoVillanueva/NHL/main/dataNHL.csv") | |
logos_df <- | |
data_git_nhl %>% | |
transmute( | |
team = str_to_upper(url_abr), | |
logo = espn_logo, | |
primary | |
) %>% | |
distinct() %>% | |
add_row(team = "SEA") %>% | |
add_row(team = "UTA") %>% | |
mutate( | |
team = | |
case_when( | |
team == "TB" ~ "TBL", | |
team == "VGS" ~ "VGK", | |
team == "LA" ~ "LAK", | |
team == "NJ" ~ "NJD", | |
team == "SJ" ~ "SJS", | |
team == "SEA" ~ "SEA", | |
team == "UTA" ~ "UTA", | |
TRUE ~ team | |
) | |
) %>% | |
mutate(logo = case_when( | |
team == "VGK" ~ "https://a.espncdn.com/i/teamlogos/nhl/500/vgs.png", | |
team == "SEA" ~ "https://a.espncdn.com/i/teamlogos/nhl/500/sea.png", | |
team == "UTA" ~ "https://a.espncdn.com/i/teamlogos/nhl/500/utah.png", | |
TRUE ~ logo), | |
primary = case_when( | |
team == "SEA" ~ "#001628", | |
team == "UTA" ~ "#71AFE5", | |
TRUE ~ primary | |
) | |
) | |
prepped_for_viz %>% | |
left_join( | |
logos_df, by = "team" | |
) %>% | |
mutate(x = if_else(goalie_shot_id == 100, 100, NA), | |
y = if_else(goalie_shot_id == 100, 25, NA)) %>% | |
mutate(logo = if_else(!is.na(x), logo, NA)) %>% | |
# filter(any(goalie_shot_id > 750)) %>% | |
ggplot( | |
aes( | |
x = goalie_shot_id, | |
goal_xg_diff, | |
color = primary) | |
) + | |
geom_from_path( | |
# data = logos_df %>% filter(team != "ARI"), | |
aes(x = x, y = y, path = logo), | |
width = 0.15, inherit.aes = FALSE) + | |
geom_line( | |
aes(group = goalie_name_for_shot), | |
size = .4 | |
) + | |
geom_hline(yintercept = 0) + | |
scale_x_continuous(minor_breaks = NULL, | |
labels = scales::comma_format(), | |
# labels = c(0, "", 500, "", "1,000", "") | |
) + | |
scale_y_continuous( | |
minor_breaks = NULL, | |
limits = c(-35, 35), | |
breaks = seq(-30, 30, by = 10), expand = c(0,0)) + | |
scale_color_identity() + | |
facet_wrap( | |
~team, | |
ncol = 4 | |
) + | |
theme_minimal() + | |
theme( | |
plot.title = element_text(hjust = 0, size = 16, face = "bold"), | |
plot.subtitle = element_text(hjust = 0, size = 12), | |
axis.title = element_text(size = 8), | |
axis.text = element_text(size = 6), | |
panel.grid.major = element_line(linewidth = .4), | |
strip.background = element_blank(), | |
strip.text = element_blank(), | |
plot.margin = margin(12, 12, 12, 12) | |
) + | |
labs( | |
title = "NHL 2025: Goals Prevented by Goaltender", | |
subtitle = "Cumulative goals prevented from all shots faced in NHL | Each line is a different goaltender", | |
y = "", | |
x = "Cumulative Shot Faced", | |
caption = "Created by @OTStats\nData as of 2/24/2025\nData Source: @MoneyPuck.com" | |
) | |
ggsave( | |
filename = "20250225 NHL goaltender goals prevented.png", | |
height = 12, width = 8, dpi = "print", bg = "white" | |
) | |
install.packages("gghighlight") | |
library(gghighlight) | |
prepped_for_viz %>% | |
left_join( | |
logos_df, by = "team" | |
) %>% | |
ggplot( | |
aes( | |
x = goalie_shot_id, | |
y = goal_xg_diff, | |
group = str_c(goalie_name_for_shot, "-", team) | |
) | |
) + | |
geom_line(aes(color = primary)) + | |
gghighlight(any(goal_xg_diff < -22) || any(goal_xg_diff > 10), use_group_by = T) + | |
scale_color_identity() | |
# event %in% c("SHOT", "GOAL") | |
# | |
# goalieNameForShot | |
# team | |
# teamcode # team that shot | |
# shotWasOnGoal | |
# shotOnEmptyNet |
Author
OTStats
commented
Feb 26, 2025
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment