Models
What's on This Page
This page walks through the models behind my player and team cards, including my xG, RAPM, WAR, Elo, and SRS models, plus a glossary of common hockey analytics terms.
Expected Goals (xG)
Expected goals (xG) is a probability estimate of how likely any given shot is to become a goal, used because it captures shot quality, treating a breakaway and a point shot as the different values they actually are, instead of counting both as the same single shot.
The xG model estimates the probability that a given shot becomes a goal. It's trained entirely on shot-event data that I scrape myself, rather than relying on a third-party model. Every shot is broken down into a set of engineered features: distance and angle to the net, whether the shot came from behind the net, shot speed, whether it was a rebound or off the rush, whether it crossed the royal road, lateral movement, score state, shooter/goalie handedness (including whether the shot came from the shooter's off wing), shot type, the period it occurred in, and context from the prior event (its type, how much time had passed since it, how far the puck had traveled, and whether it came from the same team).
Rather than training one blended model across every strength state, I train a separate gradient-boosted classifier for each strength state that has enough shot volume to support its own model. 5v5, 5v4, and 4v5 get a deeper hyperparameter search since they have the most data, while lower-volume strength states use a smaller grid. Hyperparameters are selected using grouped cross-validation, grouped by season, so the model is never validated on shots from a season it partially trained on. The winning candidate is picked by AUC, then refit on the full training set.
The result is a set of per-strength-state models that score every shot in the dataset with a predicted goal probability. That predicted xG is the foundation everything downstream is built on. It's the response variable that RAPM regresses on, and it's what gets compared against actual goals to measure a player's finishing ability.
Model Performance
The full training set includes 1,730,154 shots. Of the 25 strength-state combinations shots have occurred in, only 9 had enough volume (a minimum of 500 shots) to support their own model. The rest are rare situations (double-minor and matching-penalty combinations, empty-net situations with additional penalties, and so on) ranging from 1 to 279 shots each, and are scored using whichever trained strength-state model is the closest match, rather than getting a dedicated model of their own. 3-on-3 shots from before the 2015-2016 season (when 3-on-3 overtime was introduced) are tagged separately from the modern 3v3 model, since pre-2015 3-on-3 was a rare, different context rather than the 3-on-3 overtimes we see today. They don't have enough volume for their own model, though, so they still end up scored by the modern 3v3 model as the closest match.
| Strength State | Shots Modeled | AUC |
|---|---|---|
| 5v5 | 1,342,496 | 0.779 |
| 5v4 | 241,061 | 0.705 |
| 4v5 | 40,708 | 0.794 |
| EN Against | 34,697 | 0.725 |
| 4v4 | 29,278 | 0.747 |
| 3v3 | 13,343 | 0.677 |
| EN For | 11,583 | 0.771 |
| 5v3 | 8,706 | 0.642 |
| 4v3 | 4,892 | 0.613 |
AUC (area under the ROC curve) measures how well the model separates goals from non-goals on held-out validation folds, where 0.5 is no better than random and 1.0 is perfect separation. Performance varies a lot by strength state with 4v5 and 5v5 scoring the highest, likely because they have the most consistent structure and by far the most data, while lower-volume special-teams states like 4v3 and 5v3 are noisier and harder to separate with fewer examples to learn from.
Regularized Adjusted Plus-Minus (RAPM)
Raw plus-minus has an obvious problem: it credits and blames players for everything that happens while they're on the ice, regardless of who else is out there. RAPM isolates each player's individual contribution using ridge regression across every shift (stint) in a season.
Every stint is reconstructed from the shift charts, with the predicted xG from every shot that occurred during it attached. Each stint becomes a regression row from both teams' perspective, one-hot encoded with a column per offensive player on the ice and a column per defensive player, with the response variable being the xG rate generated during that stint (falling back to goal rate where xG isn't available), capped at a maximum implied rate so a handful of very short, high-scoring stints can't dominate the fit. A handful of dense context covariates (score state, zone starts, back-to-backs, power-play expiry windows, home ice, and a few interaction terms) are fed in alongside the sparse player columns, using a column-scaling trick that effectively lets the context covariates and player indicators use two different ridge penalties in the same fit. The ridge penalty for the player columns is chosen using grouped cross-validation, grouped by game so no game leaks across a fold.
Even strength and special teams are fit as two separate regressions, each with their own alpha search, since power-play and penalty-kill units behave very differently from 5v5 play. The even-strength fit pools 5v5, 4v4, and 3v3 shifts together rather than modeling 5v5 in isolation, with a pair of context flags telling the model which strength state each stint was played at. This lets 4v4 and 3v3 shifts, which are individually too sparse to support their own regression, still contribute signal. New or low-minute players get a TOI-weighted prior pulled from their own prior-season RAPM scores, so a rookie's first few shifts aren't taken completely at face value. The output is a rate stat per player, per situation (5v5 offense, 5v5 defense, 5v4 offense/power play, 4v5 defense/penalty kill) that represents their estimated on-ice impact independent of who they're playing with or against.
Wins Above Replacement (WAR)
RAPM gives a rate. WAR turns that rate into something with a fixed, interpretable scale, which is a player's wins added over a season relative to a replacement-level player (your typical 13th forward or 7th defenseman).
For each of the four RAPM-derived components (5v5 offense, 5v5 defense, power play, penalty kill), I compute a replacement-level rate per position from the lowest-TOI players on each team's own roster below a rank threshold, so replacement level reflects a player who's realistically available on a roster rather than some fixed league-wide percentile. A player's WAR for that component is the difference between their own RAPM rate and replacement level, scaled by their ice time and converted from goals to wins using a Pythagorean-exponent-derived conversion factor (a sabermetrics method that relates goals scored and allowed to win percentage, used here in reverse to translate a goal swing into a win swing) computed from that season's actual league scoring environment. In practice that's worked out to somewhere between 5.0 to 6.0 goals per win depending on the season, with earlier seasons showing a goals per win close to 5.0 and more recent seasons showing a goals per win closer to 6.0.
Two more components get added on top. The first is a finishing-impact component (actual goals minus predicted xG on a player's own shots). Finishing uses the same idea as batting average regression: credibility crosses 50% at 100 shots, so a full-time forward clears that within a couple months of a season while a short call-up stays mostly unshrunk toward 0. The second component is a penalty-drawing/taking component (net penalty minutes drawn versus taken, valued by the xG a power play is worth per minute). Credibility here crosses 50% at just 10 combined drawn-and-taken penalty events, a much lower bar than finishing's, since penalty events are far rarer than shots. A penalty taken at 5v5 works out to about 0.096 xG against per minute, while a penalty taken while already shorthanded is valued around 0.246 xG per minute, since it pushes the game further into a lopsided state. The single penalty value comes from (average power-play xG/60 minus average penalty-kill xG/60) ÷ 60. The double-penalty value swaps in the 5-on-3 rate: (average 5-on-3 xG/60 minus average power-play xG/60, plus penalty-kill xG/60) ÷ 60.
Goalies skip RAPM entirely and get their own WAR built from Goals Saved Above Expected (GSAx) at even strength and on the penalty kill, measured against the same team-rank replacement level as skaters. GSAx follows the same credibility-shrinkage idea, based on shots against instead of shots taken or penalty events, crossing 50% at 300 shots against. A player with only a handful of shots, penalty events, or shots against won't show the full unshrunk swing in either direction. That swing only shows up once there's enough of a sample to trust it.
Elo Ratings
A team's win-loss record alone doesn't capture the strength of the opponents it played or how close those games actually were. Elo addresses that by giving every team a single rating that updates after each game based on how the actual result compared to what was expected, so a win over a strong team moves the rating more than a win over a weak one.
Team Elo ratings update after every game of a season, starting every team at a rating of 1500. Before a game, each team's expected result is calculated from the ratings gap between the two teams using the standard logistic Elo expectation formula, where \( R_A \) and \( R_B \) are the two teams' ratings, \( H = 30 \) is a fixed home-ice advantage added to the home team's rating, and \( S = 400 \) is the Elo scale factor that controls how quickly the expected score approaches 0 or 1 as the ratings gap grows:
$$ E_A = \dfrac{1}{1 + 10^{\frac{R_B - (R_A + H)}{S}}} $$
After the game, both ratings shift based on the difference between the actual result \( S_A \) and the expected result \( E_A \), scaled by a K-factor of 20 and a margin-of-victory multiplier \( M \):
$$ R_A' = R_A + K (S_A - E_A) \times M $$
Results aren't scored as a simple win/loss. Each result type is assigned a fixed value for \( S_A \), so a regulation win is worth full credit while overtime and shootout results are worth partial credit in both directions, and the rating system reflects how close a game actually was, not just who won:
| Result | Value |
|---|---|
| Regulation Win | 1.0 |
| Overtime Win | 0.9 |
| Shootout Win | 0.7 |
| Shootout Loss | 0.3 |
| Overtime Loss | 0.1 |
| Regulation Loss | 0.0 |
The margin-of-victory multiplier scales the rating shift by how lopsided the final score was, log-scaled and dampened as the gap between the two teams' ratings widens, so a blowout win over a weaker opponent doesn't inflate a team's rating as much as the raw goal differential alone would suggest, where \( R_w \) and \( R_l \) are the winning and losing team's ratings. Shootout results always use a flat multiplier of 1.0 instead, since the shootout goal differential isn't a meaningful measure of game control. This multiplier is adapted from Neil Paine's NHL Elo methodology:
$$ M = \dfrac{(0.6686 \ln(\text{margin}) + 0.8048) \times 2.05}{(R_w - R_l) \times 0.001 + 2.05} $$
Because ratings update sequentially after every result, Elo reflects a team's current form and trajectory over the course of a season, rather than a single fixed-in-time snapshot.
Simple Rating System (SRS)
Elo reacts game by game, so a team's rating at any point reflects the particular order those results happened to fall in, not just its overall performance. SRS solves for a whole season's worth of results all at once instead of updating sequentially, so the fit isn't sensitive to which order the games were played in.
It estimates a single rating for every team, plus one shared home-ice advantage value \( H \), by minimizing the sum of squared residuals between each team's predicted and actual goal margins across every game in a season. A (home) team's predicted margin \( \hat{m}_h \) against an away team in any given game is simply the difference between the two teams' ratings, adjusted for home ice:
$$ \hat{m}_h = (R_h - R_a) + H $$
The optimization then solves for the full set of team ratings and \( H \) that minimizes the total squared error between every predicted margin \( \hat{m}_g \) and the actual margin \( m_g \) across all \( n \) games in the season, with the constraint that the league-wide average rating \( \bar{R} \) is zero:
$$ \text{minimize} \sum_{g=1}^{n} (m_g - \hat{m}_g)^2 \quad \text{by changing each team's } R \text{ and the overall } H \text{, while keeping } \bar{R} = 0 $$
The final output is a single rating per team. Taking the difference between any two teams' ratings and adding the home-ice advantage gives the model's predicted goal margin if those two teams played each other, with \( R_h \) as the home team and \( R_a \) as the away team.
Because SRS looks at the whole season at once instead of reacting game-by-game, it tends to be a more stable end-of-season snapshot of team strength, while Elo is better suited to tracking how a team has trended over the course of the year.
Glossary
A reference for common hockey analytics, statistics, and data science terms, whether you're reading through this site or hockey analytics content elsewhere.
Basic Hockey Terms
Time on Ice (TOI)
The amount of game time a player spends on the ice, reported in either total minutes or as a per-game average.
Shift/Stint
A continuous stretch of ice time between stoppages or line changes. RAPM treats each stint as a single row of data to regress on.
Strength State
The number of skaters each team has on the ice, (5v5, 5v4, 4v5, etc.).
Even Strength (ES)
Play with both teams skating the same number of skaters, most commonly 5v5.
Special Teams
Play with the two teams skating a different number of skaters, covering both the power play and penalty kill.
Score State
The goal differential at a given point in a game (tied, up one, down two, etc.), which strongly influences how teams play.
Score Effects
The tendency for teams to change their style of play based on score state (trailing teams tend to attack more, leading teams tend to play more defensively).
Home-Ice Advantage
The statistical edge a team gets from playing in its own arena.
Shooting Percentage (SH%)
The percentage of shots on goal that result in a goal.
Save Percentage (SV%)
The percentage of shots on goal a goalie stops.
Faceoff Win Percentage (FO%)
The percentage of faceoffs a player or team wins.
Primary Points
A player's goals plus primary (first) assists, excluding secondary assists. Often used as a cleaner measure of individual offensive contribution, since secondary assists are noisier and less repeatable.
Plus-Minus
A raw count of a player's goals for minus goals against while they're on the ice at even strength. Simple, but credits and blames a player for every goal regardless of who else was on the ice, which is the exact problem RAPM is built to solve.
Off-Wing (Off Wing)
A shot taken from the side of the ice opposite a player's shooting hand (like. a left shot playing the right wing), which tends to open up a better shooting angle toward the net.
Zone Entry / Zone Exit
A player carrying, passing, or dumping the puck into the offensive zone (entry) or out of the defensive zone (exit). A "controlled" entry/exit keeps possession of the puck, while a dump-in/dump-out gives it up in exchange for territory.
Zone Start Percentage (OZS% or DZS%)
The percentage of a player's non-neutral-zone and non-on-the-fly shifts that begin with an offensive-zone or defensive-zone faceoff.
Advanced Hockey Terms
Corsi (CF%)
The share of all shot attempts (shots on goal, missed shots, and blocked shots) a team or player accounts for while on the ice. A common proxy for puck possession.
Fenwick (FF%)
The share of unblocked shot attempts (shots on goal and missed shots) a team or player accounts for while on the ice. (Corsi, but without blocked shots).
On-Ice Shooting %/Save % (oiSH%/oiSV%)
A team or player's shooting and save percentages while they're on the ice, as opposed to just their own individual shots or saves. The two components that make up PDO.
PDO
The sum of a team or player's on-ice shooting percentage and save percentage. A PDO far from 100 often signals unsustainable puck luck rather than true talent.
High-Danger Chance (HDC)
A shot attempt taken from an area of the ice with a high historical likelihood of becoming a goal, typically close to the net or in the slot.
Medium-Danger Chance (MDC)
A shot attempt taken from an area of the ice with a moderate historical likelihood of becoming a goal, typically between the high-danger slot area and the outer perimeter.
Low-Danger Chance (LDC)
A shot attempt taken from an area of the ice with a low historical likelihood of becoming a goal, typically from the perimeter or a sharp angle.
Royal Road
An imaginary line running the length of the ice through the center of both nets. A shot or pass that crosses it tends to be more dangerous, since it forces the goalie to move laterally.
Quality of Competition (QoC)
A measure of how strong the opposing players a skater faces are on average, relative to the rest of the league.
Quality of Teammates (QoT)
A measure of how strong a player's own teammates are on average, relative to the rest of the league.
Hockey Model Terms
Expected Goals (xG)
The probability that a given shot results in a goal, based on shot location, type, and situational context. See the xG model above.
Expected Goals Against (xGA)
The sum of expected goals value from shots taken against a team or while a player is on the ice.
Goals Saved Above Expected (GSAx)
A goalie's actual goals allowed subtracted from the number of goals an average goalie would be expected to allow on the same shots, based on xG.
Regularized Adjusted Plus-Minus (RAPM)
A ridge-regression-based method for isolating a player's on-ice impact from the effects of their teammates and competition. See the RAPM model above.
Replacement Level
The performance baseline WAR is measured against. The production expected from a readily available fringe player.
Wins Above Replacement (WAR)
A single number expressing how many wins a player added over a season compared to a replacement-level player. See the WAR model above.
Elo Rating
A sequentially updating team-strength rating that shifts after every game based on the result and how expected it was. See Elo ratings above.
Simple Rating System (SRS)
A team-strength rating fit across an entire season at once by minimizing error between predicted and actual goal margins. See SRS above.
Data Science Terms
Hyperparameter
A setting chosen before training a model (like tree depth or regularization strength) rather than learned from the data itself, usually tuned by testing different values and picking the best-performing one.
Cross-Validation
A technique for testing a model's performance by training on some data and validating on held-out data it hasn't seen, repeated across multiple splits to make sure results generalize rather than reflecting one lucky split.
ROC Curve (Receiver Operating Characteristic)
A plot of a classification model's true positive rate against its false positive rate across every possible decision threshold, used to visualize how well it separates two outcomes.
AUC (Area Under the Curve)
The area under the ROC curve, condensing it into a single number: a measure of how well a classification model separates positive and negative outcomes, where 0.5 is no better than random and 1.0 is perfect separation.
Gradient Boosting
A machine learning technique that builds a strong model out of many small decision trees, each one trained to correct the errors of the trees before it.
Ridge Regression
A regression technique that shrinks coefficient estimates toward zero to reduce overfitting, especially useful when predictors (like individual players) are numerous and collinear.
One-Hot Encoding
A way of turning a categorical value into numeric columns a model can use, with one column per possible category that's set to 1 for the matching category and 0 for every other.
Shrinkage
The general statistical practice of pulling an estimate toward a baseline value (like zero, league average, or a prior season) to reduce noise from small sample sizes.