← NHL RAPM overview

This page summarizes the method. The full analysis code, fitted per-season constants, and reproduction steps live in the AdjustedPlusMinusRidge repository →

NHL Adjusted Plus-Minus (RAPM) — Methodology

What these ratings are

Each player gets two ratings, both measured in shots per 60 minutes of 5-on-5 play, relative to the league average for that season:

  • Offense rating — how many more (or fewer) shots the player's team generates while they are on the ice, above or below an average player.
  • Defense rating — how many fewer (or more) shots the player's team allows while they are on the ice. It is oriented so that higher is better: a positive defense rating means the player suppresses shots relative to average.
  • Total rating — offense rating plus defense rating: the player's net impact on the 5-on-5 shot balance per 60 minutes, relative to average.

A rating of 0 is exactly league average. A +5 offense rating means "about five more shots generated per 60 minutes than an average player would produce in the same role."

Why "adjusted", and why relative to average

Raw on-ice shot differentials reward players who happen to skate with strong linemates or against weak opponents. RAPM (Regularized Adjusted Plus-Minus) is a single ridge regression across every 5-on-5 shift in the season that estimates each player's independent contribution while holding their teammates and opponents fixed. Because all players are estimated jointly, the ratings are already teammate- and competition-adjusted.

The ratings are expressed relative to league average by construction. The model fits one league-average shot rate (the intercept) plus a per-player deviation from it, and the deviations are constrained to average out to zero across the league (an exposure-weighted sum-to-zero constraint). This makes "0 = average" an exact, interpretable baseline rather than an artifact of the fit.

Why REML instead of cross-validation for the penalty

Ridge regression needs a penalty strength that controls how much player ratings are shrunk toward average. On this design the usual cross-validation choice is numerically unstable — the design has a near-flat direction that lets the intercept trade off against a uniform shift of all player ratings, so CV can pin the penalty at its search boundary and hand back an unhealthy fit.

We instead select the penalty by REML (restricted maximum likelihood) of the equivalent mixed model, treating player effects as a random effect. REML has a well-defined interior optimum with real curvature in all 9 seasons, which removes the instability. Across the 9 seasons the selected penalty (variance ratio λ) ranges from about 26 to 126 (see seasons_meta.csv). The sum-to-zero constraint additionally pins the intercept to the exact weighted league-average shot rate, so the "relative to average" interpretation above holds exactly.

Data-quality caveat: compare rankings within a season, levels across seasons cautiously

The input is public play-by-play and shift data. Its completeness varies by season: the shift/event coverage is essentially complete in 2018-2019 (coverage-dispersion statistic rowsum_std = 0.006) and least complete in 2023-2024 (rowsum_std = 0.278); per-season figures are in seasons_meta.csv. Lower-coverage seasons undercount some events, which can shift the overall level of a season's ratings up or down.

Within a season, the rankings are robust. The ordering of players is stable across estimator choices — the standard and constrained estimators agree on the offense ranking with a Spearman correlation between 0.89 and 0.98 across seasons. Across seasons, compare cautiously. A player who is +4 in one season and +3 in another is not necessarily better in the first — part of that gap can be the season's data-completeness level rather than a real change in play. Use the ratings to rank players within a season and to identify tiers; be careful reading small cross-season level differences as signal.

Coverage of the export

9 seasons (2016-17 through 2024-25), 8,215 player-seasons in total, of which 6,232 clear the 200 weighted-minute eligibility bar used as the site's default filter (the eligible flag in each rapm_<season>.csv). Players below that bar are retained but flagged ineligible, because their ratings are estimated from little ice time and are noisy. Player positions (C/L/R/D) are not in the play-by-play source and are looked up separately from the public NHL player records; a small number of players whose record could not be resolved are left blank.

Generated by scripts/build_website_export.py. Ratings computed with the sum-to-zero + REML constrained estimator (apm/fit_constrained.py), the configuration validated as arm C in scripts/estimator_comparison.py.