median_shots <- median(team_summary$shots, na.rm = TRUE)
median_quality <- median(team_summary$avg_xg_per_shot, na.rm = TRUE)
team_summary_plot <- team_summary |>
mutate(
style_group = case_when(
shots >= median_shots &
avg_xg_per_shot >= median_quality ~ "High volume, high danger",
shots >= median_shots &
avg_xg_per_shot < median_quality ~ "High volume, lower danger",
shots < median_shots &
avg_xg_per_shot >= median_quality ~ "Lower volume, high danger",
TRUE ~ "Lower volume, lower danger"
),
focus_team = event_team %in% c("Colorado Avalanche", "Vancouver Canucks")
)
team_summary_plot |>
ggplot(aes(x = shots, y = avg_xg_per_shot)) +
# quadrant shading
annotate(
"rect",
xmin = median_shots,
xmax = Inf,
ymin = median_quality,
ymax = Inf,
alpha = 0.08,
fill = "#00b140"
) +
annotate(
"rect",
xmin = median_shots,
xmax = Inf,
ymin = -Inf,
ymax = median_quality,
alpha = 0.06,
fill = "#236192"
) +
annotate(
"rect",
xmin = -Inf,
xmax = median_shots,
ymin = median_quality,
ymax = Inf,
alpha = 0.06,
fill = "#6f263d"
) +
# median lines
geom_vline(
xintercept = median_shots,
linetype = "dashed",
linewidth = 0.5,
color = "#667085"
) +
geom_hline(
yintercept = median_quality,
linetype = "dashed",
linewidth = 0.5,
color = "#667085"
) +
# all teams in gray
geom_point(
aes(size = total_xg),
alpha = 0.45,
color = "gray35"
) +
# highlight Avalanche and Canucks
geom_point(
data = team_summary_plot |> filter(focus_team),
aes(size = total_xg, color = event_team),
alpha = 0.95
) +
# labels for selected teams
ggrepel::geom_text_repel(
data = team_summary_plot |>
filter(
focus_team |
event_team %in%
c(
"Edmonton Oilers",
"New Jersey Devils",
"Florida Panthers",
"Calgary Flames",
"Pittsburgh Penguins"
)
),
aes(label = event_team, color = event_team),
size = 3,
max.overlaps = Inf,
show.legend = FALSE
) +
scale_color_manual(
values = c(
"Colorado Avalanche" = "#6F263D",
"Vancouver Canucks" = "#00205B"
),
na.value = "gray35"
) +
scale_y_continuous(labels = percent_format(accuracy = 0.1)) +
labs(
title = "Teams have different offensive styles",
subtitle = "Avalanche and Canucks are highlighted against league-wide shot volume and shot danger.",
x = "Total shot events",
y = "Average expected goals per shot",
size = "Total xG",
color = "Team",
caption = "xG = expected goals. Source: NHL shot-event data."
) +
theme_minimal(base_size = 12) +
theme(
legend.position = "right",
plot.title = element_text(face = "bold"),
plot.subtitle = element_text(size = 10),
panel.grid.minor = element_blank()
)