Skip to Content
Course content

220: Win Probability Models

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I remember the first time I tried to build a win probability model for a sports project. I had my logistic regression dialed in, my training data was clean, and I felt confident. Then I ran my predictions and saw values like -1.42 and 3.11. I spent twenty minutes scratching my head, wondering why my "probability" model was returning negative numbers and values way above 100%.

# The "What is going on?" code
game_data <- data.frame(
  score_diff = c(3, -7, 10, -2, 14),
  time_left = c(300, 120, 60, 600, 10),
  win = c(1, 0, 1, 0, 1)
)

model <- glm(win ~ score_diff + time_left, data = game_data, family = binomial)

# Predict probability for a new game state
new_game <- data.frame(score_diff = 7, time_left = 180)
prob <- predict(model, new_game)
print(prob) 
# Output: [1] 1.1245  <-- Wait, 112% probability?

The Log-Odds Trap

The issue here is one of the most common "gotchas" in R's glm (Generalized Linear Model) function. By default, predict() returns the linear predictor. In the context of a binomial model, this isn't a probability; it's the log-odds (also called the logit).

Mathematically, the model calculates a linear combination of your predictors, but it hasn't passed that result through the sigmoid function yet to squash it between 0 and 1. When you see a positive number, the model thinks the outcome is likely; a negative number means it's unlikely. But for a win probability model, "likely" isn't useful—we need a specific percentage.

Switching to Response Probabilities

To fix this, you have to explicitly tell R that you want the "response" (the probability) rather than the link function's raw output. You do this by adding type = "response" to the predict call.

# The fixed version
prob_fixed <- predict(model, new_game, type = "response")
print(prob_fixed)
# Output: [1] 0.7542  <-- Now we're talking. 75.4% chance of winning.

I usually suggest making this a habit immediately. If you're using family = binomial, you almost always want type = "response". If you forget it, you're just staring at the internal math of the model rather than the actual prediction.

Evaluating Model Calibration with Brier Scores

Once you have probabilities, you can't just use "accuracy" to see if your model is good. If a model predicts a 51% chance of winning and the team wins, it was "accurate," but it wasn't very confident. If it predicts 99% and they lose, that's a huge failure.

Instead, I use the Brier Score. It's essentially the Mean Squared Error of your probabilities. It measures the distance between the predicted probability (e.g., 0.70) and the actual outcome (1 or 0). A score of 0 is a perfect model; 0.25 is basically the same as guessing 50% every time.

# Calculating a simple Brier Score
predictions <- predict(model, game_data, type = "response")
actuals <- game_data$win

brier_score <- mean((predictions - actuals)^2)
print(brier_score)

If your Brier score is high, don't just throw more data at the model. Look at your features. Are you missing the "game state"? For example, in football, a 3-point lead with 10 seconds left is very different from a 3-point lead with 10 minutes left. The interaction between score and time is where the real signal lives.




📋 Practical Task

Building a Baseball Game-State Probability Model

You have been provided with a small sample dataset of baseball game snapshots. Your goal is to build a logistic regression model that predicts the probability of a home team win based on the run_diff (Home runs minus Away runs) and the inning.

Dataset:

baseball_data <- data.frame(
  run_diff = c(2, -1, 0, 5, -3, 1, -2, 4, 0, -1),
  inning = c(3, 2, 9, 7, 4, 8, 5, 6, 3, 9),
  win = c(1, 0, 1, 1, 0, 1, 0, 1, 0, 0)
)

Your Task:

  1. Create a glm model using the binomial family to predict win based on run_diff and inning.
  2. Create a new data frame called test_game representing a game in the 5th inning where the home team is down by 2 runs (run_diff = -2).
  3. Predict the win probability for this test_game. Ensure your output is a probability between 0 and 1.
  4. Calculate the Brier Score for the model using the original baseball_data to determine how well the model fits the training set.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.