Win Probability Fundamentals
Win probability models estimate the likelihood of winning given current game state: score, time remaining, and possession. These models enable evaluation of in-game decisions (was that three-pointer a good choice given the situation?), broadcast enhancement, and strategic optimization.
Building Win Probability Models
Win probability models train on historical game data, learning how score margin and time relate to eventual outcomes. The relationship is roughly logistic: small leads are uncertain early but increasingly decisive as time decreases. Very large leads are near-certain regardless of time.
import numpy as np
def calculate_win_probability(score_margin, seconds_remaining, possession=0):
"""
Calculate win probability based on game state
possession: 1 if home has ball, -1 if away, 0 if neutral/unknown
"""
# Logistic model parameters (fitted from historical data)
intercept = 0.0
margin_coef = 0.15
time_margin_interaction = -0.0002
# Adjust margin for possession value (~0.5 points)
adjusted_margin = score_margin + possession * 0.5
# Calculate log-odds
log_odds = intercept + margin_coef * adjusted_margin +
time_margin_interaction * adjusted_margin * seconds_remaining
# Convert to probability
win_prob = 1 / (1 + np.exp(-log_odds))
# Bound probabilities
win_prob = max(0.001, min(0.999, win_prob))
return round(win_prob, 3)
Applications
Win probability enables "expected wins added" analysis: how much did each play change win probability? Clutch players should show high win probability added in close games. Similarly, we can evaluate coaching decisions by whether they increased or decreased win probability.
Late Game Strategies
Win probability informs late game strategy. When should trailing teams foul? When should leading teams run clock versus attack? Win probability models provide quantitative answers to these questions, though actual implementation requires player-specific considerations (free throw ability, etc.).
Implementation in R
# Draft pick value analysis
library(tidyverse)
analyze_draft_value <- function(draft_history) {
draft_history %>%
group_by(pick) %>%
summarise(
n = n(),
avg_ws = mean(career_ws, na.rm = TRUE),
avg_vorp = mean(career_vorp, na.rm = TRUE),
star_rate = mean(career_ws >= 40) * 100,
bust_rate = mean(career_ws < 5) * 100,
.groups = "drop"
)
}
draft <- read_csv("draft_history.csv")
pick_value <- analyze_draft_value(draft)
# Value by pick
ggplot(pick_value, aes(x = pick, y = avg_ws)) +
geom_line(color = "#1d428a", size = 1) +
geom_point(aes(size = n), color = "#c8102e") +
labs(title = "Career Win Shares by Draft Pick",
x = "Pick", y = "Average Career Win Shares") +
theme_minimal()
Implementation in R
# Draft pick value analysis
library(tidyverse)
analyze_draft_value <- function(draft_history) {
draft_history %>%
group_by(pick) %>%
summarise(
n = n(),
avg_ws = mean(career_ws, na.rm = TRUE),
avg_vorp = mean(career_vorp, na.rm = TRUE),
star_rate = mean(career_ws >= 40) * 100,
bust_rate = mean(career_ws < 5) * 100,
.groups = "drop"
)
}
draft <- read_csv("draft_history.csv")
pick_value <- analyze_draft_value(draft)
# Value by pick
ggplot(pick_value, aes(x = pick, y = avg_ws)) +
geom_line(color = "#1d428a", size = 1) +
geom_point(aes(size = n), color = "#c8102e") +
labs(title = "Career Win Shares by Draft Pick",
x = "Pick", y = "Average Career Win Shares") +
theme_minimal()