Skip to main content

NHL — additional Python functions — Models and calculators

ImpactConfig​

ImpactConfig(goals_per_win: 'float', replacement_ev_off: 'float', replacement_ev_def: 'float', league_xg_rate_ev: 'float', league_xg_rate_pp: 'float', league_xg_rate_pk: 'float', rapm_lambda_grid: 'list[float]' = <factory>, penalty_goal_weight: 'float' = 0.18, faceoff_goal_weight: 'float' = 0.02, rink_x_goal_line: 'float' = 89.0, danger_high: 'dict' = <factory>, danger_medium: 'dict' = <factory>, xg_booster_league: 'str' = 'nhl') -> None

League-specific constants consumed by every player-impact engine function.

Parameters

ParameterTypeDefaultDescription
goals_per_winfloatgoals-per-win denominator for GAR->WAR (Task 6.2 fits the NHL value from team wins vs goal differential; seeded here until fit).
replacement_ev_offfloatEV offense replacement-level rate (xG/60), subtracted before summing GAR.
replacement_ev_deffloatEV defense replacement-level rate (xGA/60 suppressed).
league_xg_rate_evfloatleague-average even-strength xG rate (per 60), used as the RAPM intercept sanity check.
league_xg_rate_ppfloatleague-average power-play xGF rate (per 60).
league_xg_rate_pkfloatleague-average penalty-kill xGA rate (per 60).
rapm_lambda_gridlist[float]<factory>candidate ridge penalties for the skater RAPM CV.
penalty_goal_weightfloat0.18goals-per-(penalty drawn - taken) conversion.
faceoff_goal_weightfloat0.02goals-per-(faceoff win - 0.5) conversion.
rink_x_goal_linefloat89.0absolute rink x-coordinate of the goal line (feet), used by the shot-geometry expansion.
danger_highdict<factory>{"max_distance": float, "max_angle": float} band for "high" danger.
danger_mediumdict<factory>same shape, wider band for "medium" danger; outside both -> "low".
xg_booster_leaguestr'nhl'which league's published boosters back this league's nhl_xg scoring (the PWHL borrows the NHL boosters -- a documented approximation).

LeagueConstants​

LeagueConstants(hfa: 'float', margin_sd: 'float', avg_xgf: 'float', avg_total_goals: 'float', total_scale: 'float', shrink_k: 'float', prop_kappa: 'dict', pos_priors: 'dict', prop_team_volume_slope: 'float', in_game_wp_artifact: 'str', min_season: 'int') -> None

Fitted, league-specific constants for the NHL/PWHL prediction spine.

Parameters

ParameterTypeDefaultDescription
hfafloathome-ice edge, expected-goals units.
margin_sdfloatstandard deviation of the final goal margin (deliberately WIDE for hockey).
avg_xgffloatleague mean even-strength xG-for, per game.
avg_total_goalsfloatleague mean total goals per game.
total_scalefloatmultiplier converting rating differential to total-goals deviation.
shrink_kfloatgames-played prior strength for rating shrinkage.
prop_kappadictempirical-Bayes shrinkage strength per player-prop stat family.
pos_priorsdictper-position (F/D) per-stat-family prior rates.
prop_team_volume_slopefloatgame-script tilt on a player-prop projection (favored team -> fewer late shots-for). SEEDED PLACEHOLDER (~0.04), not yet fitted -- a future prop-fit task should estimate it from the realized shots-vs-exp_margin slope, mirroring how fit_props.py fits prop_kappa/pos_priors.
in_game_wp_artifactstrfilename of the bundled in-game win-probability model under sportsdataverse/nhl/models/.
min_seasonintearliest season this league's prediction spine supports.

add_shot_geometry​

add_shot_geometry(df: 'pl.DataFrame', *, league: 'str' = 'nhl') -> 'pl.DataFrame'

Attach distance_to_net / shot_angle / shot_danger (descriptive output only).

Distance/angle are computed off x_fixed/y against the rink goal-line x-coordinate in LEAGUE_CONSTANTS[league].rink_x_goal_line; shot_danger buckets into high/medium/low using the danger_high/danger_medium distance+angle bands from the same config. These are output columns only -- never fed back into the boosters (Decision D2; a new feature would force a retrain).

Parameters

ParameterTypeDefaultDescription
dfDataFrameany frame carrying x_fixed and y columns.
leaguestr'nhl'"nhl" or "pwhl" -- selects the danger-zone bands.

Returns

df with distance_to_net:Float64, shot_angle:Float64, shot_danger:Utf8 appended.

col_nametypedescription
event_typecharacterStandardized event type code.
eventcharacterEvent description label.
secondary_typecharacterSecondary event type (e.g. shot type).
event_team_abbrcharacterAbbreviation of the team credited with the event.
event_team_typecharacterWhether the event team is home or away.
descriptioncharacterFull text description of the event.
periodintegerPeriod number.
period_typecharacterPeriod type (REG/OT/SO).
period_timecharacterElapsed time in the period (MM:SS).
period_secondsintegerElapsed seconds in the period.
period_seconds_remainingintegerSeconds remaining in the period.
period_time_remainingcharacterTime remaining in the period (MM:SS).
game_secondsintegerElapsed seconds in the game.
game_seconds_remainingintegerSeconds remaining in regulation.
home_scoreintegerHome team final score.
away_scoreintegerAway team final score.
event_player_1_namecharacterName of the primary event player.
event_player_1_typecharacterRole of the primary event player.
event_player_1_idintegerPlayer id of the primary event player.
event_player_2_namecharacterName of the secondary event player.
event_player_2_typecharacterRole of the secondary event player.
event_player_2_idintegerPlayer id of the secondary event player.
event_player_3_namecharacterName of the tertiary event player.
event_player_3_typecharacterRole of the tertiary event player.
event_player_3_idintegerPlayer ID of the tertiary event player.
event_goalie_namecharacterName of the goalie on the event.
event_goalie_idintegerPlayer id of the goalie on the event.
penalty_severitycharacterSeverity of the penalty.
penalty_minutesintegerPenalty minutes.
strength_statecharacterStrength state (e.g. 5v5, 5v4).
strength_codecharacterStrength state code (e.g., all, even, pp, pk).
strengthcharacterStrength label (Even, Power Play, Shorthanded).
empty_netlogicalWhether the net was empty.
extra_attackerlogicalWhether an extra attacker was on the ice.
xintegerRaw x-coordinate of the event.
yintegerRaw y-coordinate of the event.
x_fixedintegerNormalized x coordinate (home shoots right).
y_fixedintegerNormalized y coordinate (home shoots right).
shot_distancedoubleDistance of the shot from the net.
shot_angledoubleAngle of the shot relative to the net.
home_skatersintegerNumber of home skaters on the ice.
away_skatersintegerNumber of away skaters on the ice.
home_on_1characterName of home skater 1 on the ice.
home_on_2characterName of home skater 2 on the ice.
home_on_3characterName of home skater 3 on the ice.
home_on_4characterName of home skater 4 on the ice.
home_on_5characterName of home skater 5 on the ice.
home_on_6characterName of home skater 6 on the ice.
home_on_7characterName of home skater 7 on the ice.
away_on_1characterName of away skater 1 on the ice.
away_on_2characterName of away skater 2 on the ice.
away_on_3characterName of away skater 3 on the ice.
away_on_4characterName of away skater 4 on the ice.
away_on_5characterName of away skater 5 on the ice.
away_on_6characterName of away skater 6 on the ice.
away_on_7characterName of away skater 7 on the ice.
home_goaliecharacterName of the home goalie on the ice.
away_goaliecharacterName of the away goalie on the ice.
num_onintegerNumber of players coming on (line change).
players_oncharacterNames of players coming on.
num_offintegerNumber of players going off (line change).
players_offcharacterNames of players going off.
game_idintegerUnique game identifier.
seasoncharacterSeason year (echoed from arg).
season_typecharacterSeason type code (echoed from arg).
home_abbrcharacterHome team abbreviation.
away_abbrcharacterAway team abbreviation.
event_idxintegerSequential event index within the game.
event_idintegerESPN event id (echoed from arg).
pptReplayUrlcharacterURL to the play replay, if available.
away_goalie_inintegerWhether the away goalie is on the ice (1/0).
home_goalie_inintegerWhether the home goalie is on the ice (1/0).
reasoncharacterReason for the event (e.g. stoppage reason).
secondaryReasoncharacterSecondary reason for a stoppage.
ids_oncharacterPlayer ids coming on.
ids_offcharacterPlayer ids going off.
home_on_1_idintegerPlayer id of home skater 1 on the ice.
away_on_1_idintegerPlayer id of away skater 1 on the ice.
home_on_2_idintegerPlayer id of home skater 2 on the ice.
away_on_2_idintegerPlayer id of away skater 2 on the ice.
home_on_3_idintegerPlayer id of home skater 3 on the ice.
away_on_3_idintegerPlayer id of away skater 3 on the ice.
home_on_4_idintegerPlayer id of home skater 4 on the ice.
away_on_4_idintegerPlayer id of away skater 4 on the ice.
home_on_5_idintegerPlayer id of home skater 5 on the ice.
away_on_5_idintegerPlayer id of away skater 5 on the ice.
home_on_6_idintegerPlayer id of home skater 6 on the ice.
away_on_6_idintegerPlayer id of away skater 6 on the ice.
home_on_7_idintegerPlayer id of home skater 7 on the ice.
away_on_7_idintegerPlayer id of away skater 7 on the ice.
home_goalie_idintegerPlayer ID of the home goalie on the ice.
away_goalie_idintegerPlayer ID of the away goalie on the ice.
xgdoubleExpected goals value for the shot event.
game_datecharacterGame date.
distance_to_netdouble
shot_dangercharacter

Example

import polars as pl
from sportsdataverse.nhl.nhl_xg import add_shot_geometry
out = add_shot_geometry(pl.DataFrame({"x_fixed": [80], "y": [0]}))

adjust_rate_opponent​

adjust_rate_opponent(game_rates: 'pl.DataFrame', *, for_col: 'str', against_col: 'str', hfa: 'float', avg: 'float', shrink_k: 'float', max_iter: 'int' = 100, tol: 'float' = 0.0001) -> 'pl.DataFrame'

Opponent-adjust a per-game for/against rate by iterative fixed-point, then shrink.

League-agnostic: every constant (hfa, avg, shrink_k) is passed in -- no NHL/PWHL number is hard-coded here. This is the flagged T7.2 "rate-iterative + shrinkage" shared-solver candidate (the hockey counterpart of the NFL/CFB per-play ridge); for_col/against_col are symmetric (offense sees opponent defense).

Parameters

ParameterTypeDefaultDescription
game_ratesDataFrameone row per (team, opponent, game) with columns season, team, opp_team, is_home, neutral_site, and the two numeric rate columns named by for_col/against_col.
for_colstrname of the team's own-side rate column (e.g. "xgf").
against_colstrname of the team's against-side rate column (e.g. "xga").
hfafloathome-ice edge added to the home side / subtracted from the away side.
avgfloatleague mean rate to adjust and shrink toward.
shrink_kfloatgames-played prior strength for the post-convergence shrink.
max_iterint100maximum fixed-point iterations.
tolfloat0.0001convergence tolerance on the max absolute update.

Returns

A polars DataFrame, one row per (season, team). |col_name |type | |:------------|:------| |season |Int64 | |team |String | |adj_for |Float64| |adj_against |Float64| |adj_net |Float64| |raw_for |Float64| |raw_against |Float64| |games |Int64 |

No returns table is published for this function: no capture: it needs a schedule with date / home_abbr / away_abbr / neutral_site, and no loader returns one (load_nhl_schedules carries game_date / home_team_abbr).

Example

from sportsdataverse.nhl.nhl_team_ratings import adjust_rate_opponent
adjust_rate_opponent(
game_rates, for_col="xgf", against_col="xga",
hfa=0.2, avg=2.55, shrink_k=15.0,
)

as_of_ratings_split​

as_of_ratings_split(df: 'pl.DataFrame', cutoff_date: '_dt.date', *, date_col: 'str' = 'date') -> 'pl.DataFrame'

Filter a frame to rows strictly before cutoff_date (the leakage boundary).

Parameters

ParameterTypeDefaultDescription
dfDataFramea polars DataFrame with a date column.
cutoff_datedatethe game date being predicted; only strictly-earlier rows are kept.
date_colstr'date'name of the date column (default "date").

Returns

The subset of df with df[date_col] < cutoff_date.

col_nametypedescription
game_idintegerUnique game identifier.
season_fullcharacterFull season label (e.g. 20212022).
game_typecharacterGame type the row belongs to.
game_datecharacterGame date.
game_timecharacterScheduled start time of the game.
home_team_abbrcharacterHome team abbreviation.
away_team_abbrcharacterAway team abbreviation.
home_team_namecharacterHome team name.
away_team_namecharacterAway team name.
home_scoreintegerHome team final score.
away_scoreintegerAway team final score.
game_statecharacterGame state (e.g., FINAL, LIVE).
venuecharacterVenue where the game was played.
series_lettercharacter
playoff_roundintegerPlayoff round identifier.
series_game_numberinteger
seasonintegerSeason year (echoed from arg).
game_jsonlogicalWhether processed game JSON is available.
game_json_urlcharacterURL to the processed game JSON.
PBPlogicalWhether play-by-play data is available.
team_boxlogicalWhether team box score data is available.
player_boxlogicalWhether player box score data is available.
skater_boxlogicalWhether skater box data is available.
goalie_boxlogicalWhether goalie box data is available.
game_infologicalWhether game info data is available.
game_rosterslogicalWhether game rosters data is available.
scoringlogical
penaltieslogicalPenalty count.
scratcheslogical
linescorelogical
three_starslogicalWhether three stars data is available.
shiftslogicalNumber of shifts.
officialslogicalWhether officials data is available.
shots_by_periodlogicalWhether shots-by-period data is available.
shootoutlogicalWhether shootout data is available.

Example

import datetime as dt
import polars as pl
from sportsdataverse.nhl.nhl_prediction_constants import as_of_ratings_split
df = pl.DataFrame({"date": [dt.date(2023, 1, 1), dt.date(2023, 1, 2)]})
as_of_ratings_split(df, dt.date(2023, 1, 2))

booster_cache_dir​

booster_cache_dir(override: 'str | Path | None' = None) -> 'Path'

Resolve the local cache directory for the downloaded nhl_xg_models boosters.

Precedence: explicit override argument > NHL_XG_MODEL_DIR env var > ~/.cache/nhl_xg_models.

Parameters

ParameterTypeDefaultDescription
overridestr | Path | NoneNonean explicit directory (e.g. a committed test-fixture dir); wins over the env var when given.

Returns

The resolved pathlib.Path (not created here -- ensure_xg_models creates it on first download).

Example

from sportsdataverse.nhl.nhl_player_impact_constants import booster_cache_dir
d = booster_cache_dir()

brier_score​

brier_score(y_true: 'np.ndarray', p_pred: 'np.ndarray') -> 'float'

Mean squared error between predicted probabilities and binary outcomes.

Parameters

ParameterTypeDefaultDescription
y_truendarrayArray of binary outcomes (0/1).
p_predndarrayArray of predicted probabilities in [0, 1].

Returns

The Brier score (0.0 is a perfect forecast).

Example

import numpy as np
from sportsdataverse._common.metrics import brier_score
brier_score(np.array([1, 0]), np.array([0.9, 0.1]))

build_design​

build_design(stints: 'pl.DataFrame') -> "tuple['sp.csr_matrix', np.ndarray, np.ndarray, list[int]]"

Build the sparse RAPM design matrix -- two rows per stint (one per attacking team).

Parameters

ParameterTypeDefaultDescription
stintsDataFramea build_stints-shaped frame.

Returns

(X, y, w, player_index) where X is a scipy.sparse.csr_matrix with columns off_<player> (all on-ice attackers), def_<player> (all on-ice defenders), then a trailing home-ice indicator and intercept column; y is the attacking team's xGF per 60; w is stint duration (seconds); player_index maps each off_/def_ column pair's position to a player_id (so column j is off_<player_index[j]> and column j + n_players is def_<player_index[j]>).

Example

from sportsdataverse.nhl.nhl_rapm import build_design
X, y, w, player_index = build_design(stints)

build_stints​

build_stints(shifts: 'pl.DataFrame', scored: 'pl.DataFrame', *, as_of: 'int | None' = None) -> 'pl.DataFrame'

Fold load_nhl_shifts CHANGE events into contiguous constant-personnel intervals.

Per game: resolves each shift row's full team name (event_team) to home/away via team_fullname_to_abbr + the game's home_abbr/away_abbr (from scored), then folds ids_on/ids_off deltas chronologically into a running on-ice set per side. A new interval begins at every distinct game_seconds boundary; the final interval is closed at the last scored event's game_seconds + 1 for that game (there is no explicit "end of game" CHANGE row in the shift-chart feed).

Known simplification: shift-chart id lists do not distinguish position, so home_ids/away_ids may include the on-ice goalie's id alongside skaters; home_goalie/away_goalie are instead sourced from the overlapping scored events' home_goalie_id/away_goalie_id (the modal value in the interval).

Parameters

ParameterTypeDefaultDescription
shiftsDataFramea load_nhl_shifts-shaped frame.
scoredDataFramean nhl_xg-scored frame (for the game's home_abbr/away_abbr and each interval's on-ice xG-for and goalie).
as_ofint | NoneNonean optional per-game game_seconds cutoff -- intervals starting at or after as_of are dropped. This is the leakage boundary for any forward-looking use: features for a game/date must use only stints strictly before that game's cutoff.

Returns

one row per interval -- game_id:Int64, period:Int64, start_s:Int64, end_s:Int64, duration:Int64, home_ids:List(Int64), away_ids:List(Int64), home_goalie:Int64, away_goalie:Int64, strength_state:Utf8, xgf_home:Float64, xgf_away:Float64. Empty/malformed shifts returns a zero-row frame with this schema.

No returns table is published for this function: no capture: it runs longer than the capture allows even on 53 games (build_stints makes one eager filter per shift change).

Example

import polars as pl
from sportsdataverse.nhl.nhl_xg import nhl_xg
from sportsdataverse.nhl.nhl_rapm import build_stints
pbp = pl.read_parquet("tests/fixtures/nhl_player_impact/pbp_sample.parquet")
shifts = pl.read_parquet("tests/fixtures/nhl_player_impact/shifts_sample.parquet")
scored = nhl_xg(pbp, model_dir="tests/fixtures/nhl_player_impact/xg_models")
stints = build_stints(shifts, scored)

calibration_table​

calibration_table(y_true: 'np.ndarray', p_pred: 'np.ndarray', n_bins: 'int' = 10) -> 'pl.DataFrame'

Bucket predicted probabilities into bins and compare to actual outcome rates.

Parameters

ParameterTypeDefaultDescription
y_truendarrayArray of binary outcomes (0/1).
p_predndarrayArray of predicted probabilities in [0, 1].
n_binsint10Number of equal-width probability bins.

Returns

A polars.DataFrame with columns bin_mid, mean_pred, mean_actual, n (one row per non-empty bin).

col_nametypedescription
bin_middouble
mean_preddouble
mean_actualdouble
ninteger

Example

import numpy as np
from sportsdataverse._common.metrics import calibration_table
calibration_table(np.array([1, 0, 1, 0]), np.array([0.9, 0.1, 0.8, 0.2]))

ensure_xg_models​

ensure_xg_models(model_dir: 'str | Path | None' = None) -> 'Path'

Return a dir holding the 3 published booster files, downloading any missing ones.

Mirrors the fastRhockey/nflverse download-on-demand + cache pattern -- the documented exception to "no first-use download" (the boosters are a large, already-published, already-validated artifact; see Decision D1 in the design spec). An explicit model_dir whose files already exist (e.g. the committed offline test fixtures) never touches the network.

Parameters

ParameterTypeDefaultDescription
model_dirstr | Path | NoneNonedirectory to check/populate; None resolves via booster_cache_dir() (env NHL_XG_MODEL_DIR override, else ~/.cache/nhl_xg_models).

Returns

The resolved directory containing all 3 booster files.

Example

from sportsdataverse.nhl.nhl_xg import ensure_xg_models
d = ensure_xg_models() # downloads on first use, cached after

get_constants​

get_constants(league: 'str') -> 'LeagueConstants'

Resolve the fitted-constants row for a league.

Parameters

ParameterTypeDefaultDescription
leaguestr"nhl" or "pwhl".

Returns

The LeagueConstants row for league.

Example

from sportsdataverse.nhl.nhl_prediction_constants import get_constants
get_constants("nhl").margin_sd

load_xg_models​

load_xg_models(model_dir: 'str | Path | None' = None) -> 'dict'

Load the two published boosters (+ embedded feature names) and the penalty-shot constant.

Parameters

ParameterTypeDefaultDescription
model_dirstr | Path | NoneNoneNone downloads the canonical nhl_xg_models release on first use and caches under booster_cache_dir(); pass a dir to use local models (the offline test suite always passes the committed fixture dir).

Returns

dict with keys m5v5/mst (xgboost.Booster), feats_5v5/feats_st (embedded feature-name lists), and ps (penalty-shot constant probability).

Example

from sportsdataverse.nhl.nhl_xg import load_xg_models
models = load_xg_models("tests/fixtures/nhl_player_impact/xg_models")

log_loss_score​

log_loss_score(y_true: 'np.ndarray', p_pred: 'np.ndarray', eps: 'float' = 1e-15) -> 'float'

Binary cross-entropy loss between predicted probabilities and outcomes.

Parameters

ParameterTypeDefaultDescription
y_truendarrayArray of binary outcomes (0/1).
p_predndarrayArray of predicted probabilities in [0, 1].
epsfloat1e-15Clipping bound to avoid log(0).

Returns

The mean log loss.

Example

import numpy as np
from sportsdataverse._common.metrics import log_loss_score
log_loss_score(np.array([1, 0]), np.array([0.9, 0.1]))

mae​

mae(a: 'np.ndarray', b: 'np.ndarray') -> 'float'

Mean absolute error between two arrays.

Parameters

ParameterTypeDefaultDescription
andarrayFirst array of values.
bndarraySecond array of values (same length as a).

Returns

The mean absolute error.

Example

import numpy as np
from sportsdataverse._common.metrics import mae
mae(np.array([1.0, 2.0]), np.array([1.5, 2.5]))

nhl_expected_assists​

nhl_expected_assists(pbp: 'pl.DataFrame', *, league: 'str' = 'nhl', xg_model: 'ShotXGModel | None' = None, return_as_pandas: 'bool' = False) -> 'pl.DataFrame | pd.DataFrame'

Per-player expected primary/secondary assists from xG-weighted goal credit.

Each goal credits its assist1 player its relative danger goal_xg / mean_goal_xg as x_primary (and assist2 likewise as x_secondary). Normalizing to the league-mean goal xG is what makes the total credit unbiased -- Sum(x_primary + x_secondary) ~= Sum(actual assists) -- while still rewarding a playmaker who sets up high-danger goals (relative danger > 1) over one who feeds tap-ins (< 1). Crediting raw goal_xg (~0.1-0.2) instead would put expected assists on the xG scale, an order of magnitude below the assist count, and could never be unbiased against actual assists. assists_above_expected = (primary + secondary) - (x_primary + x_secondary) (positive = the player's assisted goals were lower-danger than average, so they out-assisted their shot quality); primary_share = primary / (primary + secondary).

Parameters

ParameterTypeDefaultDescription
pbpDataFrameParsed pbp frame (Task-0.1 contract).
leaguestr'nhl'League key (unused today -- assist credit is league-agnostic; kept for signature parity with the other microstat models and the PWHL shim).
xg_modelShotXGModel | NoneNoneA fitted ~sportsdataverse.nhl.nhl_microstat_constants.ShotXGModel; fit on pbp when None.
return_as_pandasboolFalseReturn a pandas DataFrame instead of polars.

Returns

Per-player frame: player_id, primary_assists, secondary_assists, x_primary_assists, x_secondary_assists, assists_above_expected, primary_share. Zero-row input returns a zero-row frame with this schema.

col_nametypedescription
player_idcharacterUnique player identifier.
primary_assistsinteger
secondary_assistsinteger
x_primary_assistsdouble
x_secondary_assistsdouble
assists_above_expecteddouble
primary_sharedouble

Example

from sportsdataverse.nhl.nhl_expected_assists import nhl_expected_assists

out = nhl_expected_assists(pbp)

# PWHL

out_pwhl = nhl_expected_assists(pwhl_pbp, league="pwhl")

nhl_goalie_gsax​

nhl_goalie_gsax(pbp: 'pl.DataFrame', shifts: 'pl.DataFrame', *, model_dir: "'str | None'" = None, league: 'str' = 'nhl', return_as_pandas: 'bool' = False) -> "'pl.DataFrame | pd.DataFrame'"

Per-goalie goals-saved-above-expected (GSAx) for the games in pbp.

Scores every unblocked shot via nhl_xg, attributes each shot to the defending goalie (attribute_goalie), and aggregates xga = sum(xg), ga = count(goals), gsax = xga - ga. gsax_per_60 uses an on-ice-seconds proxy derived from the pbp event span each goalie is credited on (see toi_seconds_by_goalie) -- shifts is accepted for interface parity with the rest of the player-impact spine but is not currently required for TOI.

Parameters

ParameterTypeDefaultDescription
pbpDataFramea load_nhl_pbp_full-shaped frame (or an already nhl_xg-scored one -- re-scoring is idempotent since the prior xg column is dropped first).
shiftsDataFramea load_nhl_shifts-shaped frame (currently unused; accepted for interface parity -- see the module docstring).
model_dirstr | NoneNonepassed through to nhl_xg (booster directory).
leaguestr'nhl'"nhl" or "pwhl".
return_as_pandasboolFalsereturn a pandas DataFrame instead of polars.

Returns

player_id:Int64, goalie:Utf8, shots:Int64, xga:Float64, ga:Int64, gsax:Float64, gsax_per_60:Float64. League-wide sum(gsax) == sum(xga) - sum(goals), which is ~= 0 at large sample and exactly zero only under perfect league-wide xG calibration. Empty/malformed input returns a zero-row frame with this schema -- never raises.

col_nametypedescription
player_idintegerUnique player identifier.
goaliecharacterWhether the player was the goalie.
shotsintegerShots on goal.
xgadouble
gaintegerGoals against (goalies).
gsaxdouble
gsax_per_60double

Example

import polars as pl
from sportsdataverse.nhl.nhl_gsax import nhl_goalie_gsax
pbp = pl.read_parquet("tests/fixtures/nhl_player_impact/pbp_sample.parquet")
gsax = nhl_goalie_gsax(pbp, pl.DataFrame(), model_dir="tests/fixtures/nhl_player_impact/xg_models")
print(gsax.sort("gsax", descending=True))

# Pipeline next step

gsax.filter(pl.col("shots") >= 10).sort("gsax_per_60", descending=True).head()

nhl_skater_rapm​

nhl_skater_rapm(pbp: 'pl.DataFrame', shifts: 'pl.DataFrame', *, model_dir: "'str | None'" = None, league: 'str' = 'nhl', lam: 'float | None' = None, as_of: 'int | None' = None, strength_states: 'list[str] | None' = None, return_as_pandas: 'bool' = False, _stints: 'pl.DataFrame | None' = None) -> "'pl.DataFrame | pd.DataFrame'"

Per-skater xG-based Regularized Adjusted Plus-Minus (RAPM), per 60 minutes.

Builds shift stints (build_stints), the sparse off/def design matrix (build_design), and solves the weighted ridge (weighted_ridge). Offensive rating is the off_<player> coefficient; defensive rating is the negated def_<player> coefficient (suppressing xG-against is positive value) -- xg_rapm = xg_rapm_off + xg_rapm_def.

Parameters

ParameterTypeDefaultDescription
pbpDataFramea load_nhl_pbp_full-shaped frame.
shiftsDataFramea load_nhl_shifts-shaped frame.
model_dirstr | NoneNonepassed through to nhl_xg.
leaguestr'nhl'"nhl" or "pwhl" -- selects the ridge lambda-grid via LEAGUE_CONSTANTS when lam is not given.
lamfloat | NoneNonean explicit ridge penalty; None selects via k-fold CV over LEAGUE_CONSTANTS[league].rapm_lambda_grid.
as_ofint | NoneNoneforwarded to build_stints -- the leakage-boundary cutoff.
strength_stateslist[str] | NoneNonerestrict the design matrix to these strength_state values (e.g. ["5v5"] for an even-strength-only fit, as used by nhl_skater_war's ev_off/ev_def components so they don't overlap with nhl_special_teams_value's PP/PK components). None (default) uses every strength state, matching the general-purpose all-situations RAPM.
return_as_pandasboolFalsereturn a pandas DataFrame instead of polars.
_stintsDataFrame | NoneNoneinternal test hook -- inject a pre-built stints frame, bypassing pbp/shifts/scoring (not part of the public contract).

Returns

player_id:Int64, xg_rapm_off:Float64, xg_rapm_def:Float64, xg_rapm:Float64, toi_minutes:Float64. Empty input returns a zero-row frame with this schema.

No returns table is published for this function: no capture: it runs longer than the capture allows even on 53 games (build_stints makes one eager filter per shift change).

Example

import polars as pl
from sportsdataverse.nhl.nhl_rapm import nhl_skater_rapm
pbp = pl.read_parquet("tests/fixtures/nhl_player_impact/pbp_sample.parquet")
shifts = pl.read_parquet("tests/fixtures/nhl_player_impact/shifts_sample.parquet")
rapm = nhl_skater_rapm(pbp, shifts, model_dir="tests/fixtures/nhl_player_impact/xg_models")
print(rapm.sort("xg_rapm", descending=True).head(10))

nhl_skater_war​

nhl_skater_war(pbp: 'pl.DataFrame', shifts: 'pl.DataFrame', *, model_dir: "'str | None'" = None, league: 'str' = 'nhl', return_as_pandas: 'bool' = False) -> "'pl.DataFrame | pd.DataFrame'"

Per-skater GAR/WAR composite -- EV + special-teams + faceoffs + penalties.

Parameters

ParameterTypeDefaultDescription
pbpDataFramea load_nhl_pbp_full-shaped frame.
shiftsDataFramea load_nhl_shifts-shaped frame.
model_dirstr | NoneNonepassed through to nhl_xg/nhl_skater_rapm/ nhl_special_teams_value.
leaguestr'nhl'"nhl" or "pwhl".
return_as_pandasboolFalsereturn a pandas DataFrame instead of polars.

Returns

player_id:Int64, ev_off:Float64, ev_def:Float64, pp:Float64, pk:Float64, pens:Float64, faceoffs:Float64, gar:Float64, war:Float64. ev_off/ev_def are (5v5-only RAPM rate - replacement level) * EV TOI/60; gar sums every component; war = gar / goals_per_win. Empty input returns a zero-row frame with this schema.

No returns table is published for this function: no capture: it runs longer than the capture allows even on 53 games (build_stints makes one eager filter per shift change).

Example

import polars as pl
from sportsdataverse.nhl.nhl_war import nhl_skater_war
pbp = pl.read_parquet("tests/fixtures/nhl_player_impact/pbp_sample.parquet")
shifts = pl.read_parquet("tests/fixtures/nhl_player_impact/shifts_sample.parquet")
war = nhl_skater_war(pbp, shifts, model_dir="tests/fixtures/nhl_player_impact/xg_models")
print(war.sort("war", descending=True).head(10))

nhl_team_ratings​

nhl_team_ratings(seasons: 'Union[int, list[int]]', *, league: 'str' = 'nhl', as_of_date: '_dt.date | None' = None, return_as_pandas: 'bool' = False) -> 'Union[pl.DataFrame, pd.DataFrame]'

Opponent-adjusted, shrunk even-strength xG (+ goal) team ratings.

Loads pbp + schedule for seasons, restricts to even strength, applies the as-of-date leakage split if requested, opponent-adjusts + shrinks both the xG rate (primary) and the realized-goal rate (concurrent sanity rating) via adjust_rate_opponent, and derives off/def/net ranks.

Parameters

ParameterTypeDefaultDescription
seasonsUnion[int, list[int]]an int or iterable of seasons.
leaguestr'nhl'"nhl" or "pwhl" -- resolves HFA/avg/shrink_k via sportsdataverse.nhl.nhl_prediction_constants.get_constants.
as_of_datedate | NoneNoneif given, only games strictly before this date are used (the leakage boundary for a predictive backtest).
return_as_pandasboolFalsereturn a pandas DataFrame instead of polars.

Returns

A polars (or pandas) DataFrame, one row per (season, team). Empty input seasons return a zero-row frame with the documented schema. |col_name |type | |:----------|:------| |season |Int64 | |team |String | |adj_xgf |Float64| |adj_xga |Float64| |adj_xg_net |Float64| |adj_gf |Float64| |adj_ga |Float64| |games |Int64 | |off_rank |Int64 | |def_rank |Int64 | |net_rank |Int64 | |net_z |Float64|

col_nametypedescription
seasonintegerSeason year (echoed from arg).
teamcharacterTeam name.
adj_xgfdouble
adj_xgadouble
adj_xg_netdouble
adj_gfdouble
adj_gadouble
gamesintegerGames played.
off_rankinteger
def_rankinteger
net_rankinteger
net_zdouble

Example

from sportsdataverse.nhl.nhl_team_ratings import nhl_team_ratings

ratings = nhl_team_ratings(2023)
print(ratings.sort("net_rank").head())

# As-of-date leakage-safe rating

import datetime as dt
ratings = nhl_team_ratings(2023, as_of_date=dt.date(2023, 1, 1))

# Pipeline next step (one line)

ratings.filter(pl.col("team") == "TOR")

nhl_unit_ratings​

nhl_unit_ratings(pbp: 'pl.DataFrame', shifts: 'pl.DataFrame', *, model_dir: "'str | None'" = None, league: 'str' = 'nhl', unit_type: 'str' = 'forward_line', min_toi: 'float' = 20.0, return_as_pandas: 'bool' = False, _stints: 'pl.DataFrame | None' = None, _rapm: 'pl.DataFrame | None' = None) -> "'pl.DataFrame | pd.DataFrame'"

Per on-ice skater combination: observed xGF/xGA + shrinkage-blended summed RAPM.

Parameters

ParameterTypeDefaultDescription
pbpDataFramea load_nhl_pbp_full-shaped frame.
shiftsDataFramea load_nhl_shifts-shaped frame.
model_dirstr | NoneNonepassed through to nhl_xg/nhl_skater_rapm.
leaguestr'nhl'"nhl" or "pwhl".
unit_typestr'forward_line'"forward_line" (3-skater combinations) or "defense_pair" (2-skater combinations) -- see the module's data-availability caveat.
min_toifloat20.0minimum minutes-together for a unit to be reported.
return_as_pandasboolFalsereturn a pandas DataFrame instead of polars.
_stintsDataFrame | NoneNoneinternal test hook -- inject a pre-built stints frame.
_rapmDataFrame | NoneNoneinternal test hook -- inject a pre-built skater-RAPM frame (paired with stints`; both must be given together to bypass real computation).

Returns

team:Utf8, unit_ids:Utf8 (sorted "id-id-id"), unit_players:Utf8, toi_minutes:Float64, on_ice_xgf:Float64, on_ice_xga:Float64, on_ice_xgf_pct:Float64, summed_rapm:Float64, unit_value:Float64. Empty input returns a zero-row frame with this schema.

col_nametypedescription
teamcharacterTeam name.
unit_idscharacter
unit_playerscharacter
toi_minutesdoubleTime on ice in minutes.
on_ice_xgfdouble
on_ice_xgadouble
on_ice_xgf_pctdouble
summed_rapmdouble
unit_valuedouble

Example

import polars as pl
from sportsdataverse.nhl.nhl_unit_ratings import nhl_unit_ratings
pbp = pl.read_parquet("tests/fixtures/nhl_player_impact/pbp_sample.parquet")
shifts = pl.read_parquet("tests/fixtures/nhl_player_impact/shifts_sample.parquet")
units = nhl_unit_ratings(pbp, shifts, model_dir="tests/fixtures/nhl_player_impact/xg_models")
print(units.sort("unit_value", descending=True).head(10))

nhl_xg​

nhl_xg(pbp: 'pl.DataFrame', *, model_dir: 'str | Path | None' = None, league: 'str' = 'nhl', return_as_pandas: 'bool' = False) -> "'pl.DataFrame | pd.DataFrame'"

Score every unblocked shot in pbp with the published nhl_xg_models boosters.

Ports fastRhockey's helper_nhl_calculate_xg -- routes 5v5 shots to the 5v5 booster and every other strength state to the special-teams booster, overrides penalty shots with the constant xg_model_ps, then left-joins xg back onto pbp by event_id. Attaches the danger/distance/angle expansion (add_shot_geometry) after scoring.

Known issue -- the published boosters over-predict for seasons through 2023-24. Measured 2026-09-02 against the 2026-04 boosters currently in the nhl_xg_models release: observed goals / sum(xg) is 0.771 at 5v5 (n=1,724,290 shots) and 0.768 on special teams (n=349,232), where a correctly-levelled model gives 1.0 -- i.e. xg is inflated by roughly 25-30% for every season from 2009-10 through 2023-24. At 5v5 the two most recent seasons are much closer (2024-25 0.949, 2025-26 0.913). The cause is not identified. It is not a defect in the feature frame this function builds, and it is not the missing-MISSED_SHOT training corpus recorded here previously: every season carries missed shots (27.7-34.8% of Fenwick events), the trainer's Fenwick selector takes MISSED_SHOT alongside SHOT and GOAL, and the published artifacts' base_score (0.07368 / 0.10533) matches the Fenwick goal rate (0.0695) rather than the shots-on-goal rate (0.0977). Leave-one-season-out refits land at goals / sum(xg) of 0.95-1.05 per season, so the miscalibration is a property of the published artifact rather than of the data it was trained on. Shot RANKING is far less affected (rank AUC 0.778 / 0.760), so xg is still usable for ordering chances -- but any SUM of xg (per game, per player, team totals, goals-above-expected, and nhl_gsax downstream) is inflated for pre-2024-25 seasons. Tracking: sportsdataverse-py#444; evidence: fastRhockey-nhl-data#11. To check whether this still applies to the boosters you have, restrict to the rows this function actually scored -- xg non-null, i.e. unblocked shots only -- and compare sum(xg) against the goals on those same rows, separately for strength_state == "5v5" and for the rest, since the two come from different boosters and are quoted separately above; a corrected booster gives a ratio near 1.0 for each. Comparing against a season's full goal total instead would fold in shootout and penalty-shot goals and every unscored row, and would not validate the numbers above. The same measurement is packaged as nhl_data_build.xg_parity.artifact_calibration(pbp, booster, variant=...) (variant is "5v5" or "st") in fastRhockey-nhl-data.

Parameters

ParameterTypeDefaultDescription
pbpDataFramea load_nhl_pbp_full-shaped frame.
model_dirstr | Path | NoneNonebooster directory; None downloads-and-caches on first use (see ensure_xg_models). Offline callers should pass the committed fixture dir.
leaguestr'nhl'"nhl" or "pwhl" -- selects the danger-zone geometry bands (the PWHL borrows the NHL boosters themselves; see xg_booster_league).
return_as_pandasboolFalsereturn a pandas DataFrame instead of polars.

Returns

pbp with xg:Float64, distance_to_net:Float64, shot_angle:Float64, shot_danger:Utf8 appended (null/absent for non-shot rows). Empty/malformed input returns the input frame with a null xg column -- never raises.

col_nametypedescription
event_typecharacterStandardized event type code.
eventcharacterEvent description label.
secondary_typecharacterSecondary event type (e.g. shot type).
event_team_abbrcharacterAbbreviation of the team credited with the event.
event_team_typecharacterWhether the event team is home or away.
descriptioncharacterFull text description of the event.
periodintegerPeriod number.
period_typecharacterPeriod type (REG/OT/SO).
period_timecharacterElapsed time in the period (MM:SS).
period_secondsintegerElapsed seconds in the period.
period_seconds_remainingintegerSeconds remaining in the period.
period_time_remainingcharacterTime remaining in the period (MM:SS).
game_secondsintegerElapsed seconds in the game.
game_seconds_remainingintegerSeconds remaining in regulation.
home_scoreintegerHome team final score.
away_scoreintegerAway team final score.
event_player_1_namecharacterName of the primary event player.
event_player_1_typecharacterRole of the primary event player.
event_player_1_idintegerPlayer id of the primary event player.
event_player_2_namecharacterName of the secondary event player.
event_player_2_typecharacterRole of the secondary event player.
event_player_2_idintegerPlayer id of the secondary event player.
event_player_3_namecharacterName of the tertiary event player.
event_player_3_typecharacterRole of the tertiary event player.
event_player_3_idintegerPlayer ID of the tertiary event player.
event_goalie_namecharacterName of the goalie on the event.
event_goalie_idintegerPlayer id of the goalie on the event.
penalty_severitycharacterSeverity of the penalty.
penalty_minutesintegerPenalty minutes.
strength_statecharacterStrength state (e.g. 5v5, 5v4).
strength_codecharacterStrength state code (e.g., all, even, pp, pk).
strengthcharacterStrength label (Even, Power Play, Shorthanded).
empty_netlogicalWhether the net was empty.
extra_attackerlogicalWhether an extra attacker was on the ice.
xintegerRaw x-coordinate of the event.
yintegerRaw y-coordinate of the event.
x_fixedintegerNormalized x coordinate (home shoots right).
y_fixedintegerNormalized y coordinate (home shoots right).
shot_distancedoubleDistance of the shot from the net.
shot_angledoubleAngle of the shot relative to the net.
home_skatersintegerNumber of home skaters on the ice.
away_skatersintegerNumber of away skaters on the ice.
home_on_1characterName of home skater 1 on the ice.
home_on_2characterName of home skater 2 on the ice.
home_on_3characterName of home skater 3 on the ice.
home_on_4characterName of home skater 4 on the ice.
home_on_5characterName of home skater 5 on the ice.
home_on_6characterName of home skater 6 on the ice.
home_on_7characterName of home skater 7 on the ice.
away_on_1characterName of away skater 1 on the ice.
away_on_2characterName of away skater 2 on the ice.
away_on_3characterName of away skater 3 on the ice.
away_on_4characterName of away skater 4 on the ice.
away_on_5characterName of away skater 5 on the ice.
away_on_6characterName of away skater 6 on the ice.
away_on_7characterName of away skater 7 on the ice.
home_goaliecharacterName of the home goalie on the ice.
away_goaliecharacterName of the away goalie on the ice.
num_onintegerNumber of players coming on (line change).
players_oncharacterNames of players coming on.
num_offintegerNumber of players going off (line change).
players_offcharacterNames of players going off.
game_idintegerUnique game identifier.
seasoncharacterSeason year (echoed from arg).
season_typecharacterSeason type code (echoed from arg).
home_abbrcharacterHome team abbreviation.
away_abbrcharacterAway team abbreviation.
event_idxintegerSequential event index within the game.
event_idintegerESPN event id (echoed from arg).
pptReplayUrlcharacterURL to the play replay, if available.
away_goalie_inintegerWhether the away goalie is on the ice (1/0).
home_goalie_inintegerWhether the home goalie is on the ice (1/0).
reasoncharacterReason for the event (e.g. stoppage reason).
secondaryReasoncharacterSecondary reason for a stoppage.
ids_oncharacterPlayer ids coming on.
ids_offcharacterPlayer ids going off.
home_on_1_idintegerPlayer id of home skater 1 on the ice.
away_on_1_idintegerPlayer id of away skater 1 on the ice.
home_on_2_idintegerPlayer id of home skater 2 on the ice.
away_on_2_idintegerPlayer id of away skater 2 on the ice.
home_on_3_idintegerPlayer id of home skater 3 on the ice.
away_on_3_idintegerPlayer id of away skater 3 on the ice.
home_on_4_idintegerPlayer id of home skater 4 on the ice.
away_on_4_idintegerPlayer id of away skater 4 on the ice.
home_on_5_idintegerPlayer id of home skater 5 on the ice.
away_on_5_idintegerPlayer id of away skater 5 on the ice.
home_on_6_idintegerPlayer id of home skater 6 on the ice.
away_on_6_idintegerPlayer id of away skater 6 on the ice.
home_on_7_idintegerPlayer id of home skater 7 on the ice.
away_on_7_idintegerPlayer id of away skater 7 on the ice.
home_goalie_idintegerPlayer ID of the home goalie on the ice.
away_goalie_idintegerPlayer ID of the away goalie on the ice.
game_datecharacterGame date.
xgdoubleExpected goals value for the shot event.
distance_to_netdouble
shot_dangercharacter

Example

import polars as pl
from sportsdataverse.nhl.nhl_xg import nhl_xg
pbp = pl.read_parquet("tests/fixtures/nhl_player_impact/pbp_sample.parquet")
scored = nhl_xg(pbp, model_dir="tests/fixtures/nhl_player_impact/xg_models")
print(scored.filter(pl.col("xg").is_not_null()).height)

# Pandas round-trip

scored_pd = nhl_xg(pbp, return_as_pandas=True)

prepare_xg_features​

prepare_xg_features(pbp: 'pl.DataFrame') -> 'pl.DataFrame'

Port of helper_nhl_prepare_xg_data -- one row per unblocked shot, model features.

Parameters

ParameterTypeDefaultDescription
pbpDataFramea load_nhl_pbp_full-shaped frame (x, x_fixed, strength_state, home_skaters/away_skaters, game_seconds, event_id, secondary_type, event_team_abbr, home_abbr/away_abbr, season, empty_net -- see load_nhl_pbp_full's returns table).

Returns

one row per unblocked shot (SHOT/MISSED_SHOT/GOAL) carrying every era one-hot, shot-type one-hot, last-event one-hot, and the derived rebound/rush/cross_ice_event/total_skaters_on/ event_team_advantage/empty_net columns the boosters expect. Empty/ malformed input returns a zero-row frame (never raises).

col_nametypedescription
event_typecharacterStandardized event type code.
eventcharacterEvent description label.
secondary_typecharacterSecondary event type (e.g. shot type).
event_team_abbrcharacterAbbreviation of the team credited with the event.
event_team_typecharacterWhether the event team is home or away.
descriptioncharacterFull text description of the event.
periodintegerPeriod number.
period_typecharacterPeriod type (REG/OT/SO).
period_timecharacterElapsed time in the period (MM:SS).
period_secondsintegerElapsed seconds in the period.
period_seconds_remainingintegerSeconds remaining in the period.
period_time_remainingcharacterTime remaining in the period (MM:SS).
game_secondsintegerElapsed seconds in the game.
game_seconds_remainingintegerSeconds remaining in regulation.
home_scoreintegerHome team final score.
away_scoreintegerAway team final score.
event_player_1_namecharacterName of the primary event player.
event_player_1_typecharacterRole of the primary event player.
event_player_1_idintegerPlayer id of the primary event player.
event_player_2_namecharacterName of the secondary event player.
event_player_2_typecharacterRole of the secondary event player.
event_player_2_idintegerPlayer id of the secondary event player.
event_player_3_namecharacterName of the tertiary event player.
event_player_3_typecharacterRole of the tertiary event player.
event_player_3_idintegerPlayer ID of the tertiary event player.
event_goalie_namecharacterName of the goalie on the event.
event_goalie_idintegerPlayer id of the goalie on the event.
penalty_severitycharacterSeverity of the penalty.
penalty_minutesintegerPenalty minutes.
strength_statecharacterStrength state (e.g. 5v5, 5v4).
strength_codecharacterStrength state code (e.g., all, even, pp, pk).
strengthcharacterStrength label (Even, Power Play, Shorthanded).
empty_netintegerWhether the net was empty.
extra_attackerlogicalWhether an extra attacker was on the ice.
xintegerRaw x-coordinate of the event.
yintegerRaw y-coordinate of the event.
x_fixedintegerNormalized x coordinate (home shoots right).
y_fixedintegerNormalized y coordinate (home shoots right).
shot_distancedoubleDistance of the shot from the net.
shot_angledoubleAngle of the shot relative to the net.
home_skatersintegerNumber of home skaters on the ice.
away_skatersintegerNumber of away skaters on the ice.
home_on_1characterName of home skater 1 on the ice.
home_on_2characterName of home skater 2 on the ice.
home_on_3characterName of home skater 3 on the ice.
home_on_4characterName of home skater 4 on the ice.
home_on_5characterName of home skater 5 on the ice.
home_on_6characterName of home skater 6 on the ice.
home_on_7characterName of home skater 7 on the ice.
away_on_1characterName of away skater 1 on the ice.
away_on_2characterName of away skater 2 on the ice.
away_on_3characterName of away skater 3 on the ice.
away_on_4characterName of away skater 4 on the ice.
away_on_5characterName of away skater 5 on the ice.
away_on_6characterName of away skater 6 on the ice.
away_on_7characterName of away skater 7 on the ice.
home_goaliecharacterName of the home goalie on the ice.
away_goaliecharacterName of the away goalie on the ice.
num_onintegerNumber of players coming on (line change).
players_oncharacterNames of players coming on.
num_offintegerNumber of players going off (line change).
players_offcharacterNames of players going off.
game_idintegerUnique game identifier.
seasoncharacterSeason year (echoed from arg).
season_typecharacterSeason type code (echoed from arg).
home_abbrcharacterHome team abbreviation.
away_abbrcharacterAway team abbreviation.
event_idxintegerSequential event index within the game.
event_idintegerESPN event id (echoed from arg).
pptReplayUrlcharacterURL to the play replay, if available.
away_goalie_inintegerWhether the away goalie is on the ice (1/0).
home_goalie_inintegerWhether the home goalie is on the ice (1/0).
reasoncharacterReason for the event (e.g. stoppage reason).
secondaryReasoncharacterSecondary reason for a stoppage.
ids_oncharacterPlayer ids coming on.
ids_offcharacterPlayer ids going off.
home_on_1_idintegerPlayer id of home skater 1 on the ice.
away_on_1_idintegerPlayer id of away skater 1 on the ice.
home_on_2_idintegerPlayer id of home skater 2 on the ice.
away_on_2_idintegerPlayer id of away skater 2 on the ice.
home_on_3_idintegerPlayer id of home skater 3 on the ice.
away_on_3_idintegerPlayer id of away skater 3 on the ice.
home_on_4_idintegerPlayer id of home skater 4 on the ice.
away_on_4_idintegerPlayer id of away skater 4 on the ice.
home_on_5_idintegerPlayer id of home skater 5 on the ice.
away_on_5_idintegerPlayer id of away skater 5 on the ice.
home_on_6_idintegerPlayer id of home skater 6 on the ice.
away_on_6_idintegerPlayer id of away skater 6 on the ice.
home_on_7_idintegerPlayer id of home skater 7 on the ice.
away_on_7_idintegerPlayer id of away skater 7 on the ice.
home_goalie_idintegerPlayer ID of the home goalie on the ice.
away_goalie_idintegerPlayer ID of the away goalie on the ice.
xgdoubleExpected goals value for the shot event.
game_datecharacterGame date.
event_zonecharacter
last_event_typecharacter
last_event_teamcharacter
time_since_lastinteger
last_xinteger
last_yinteger
last_event_zonecharacter
distance_from_lastdouble
era_2011_2013integer
era_2014_2018integer
era_2019_2021integer
era_2022_2024integer
era_2025_oninteger
total_skaters_oninteger
event_team_advantageinteger
reboundinteger
rushinteger
cross_ice_eventinteger
wrist_shotinteger
snap_shotinteger
slap_shotinteger
backhandinteger
wrap_aroundinteger
tip_ininteger
deflectedinteger
pokeinteger
battedinteger
between_legsinteger
cradleinteger
last_faceoffinteger
last_giveawayinteger
last_takeawayinteger
last_blocked_shotinteger
last_hitinteger
last_missed_shotinteger
last_shotinteger
last_stopinteger
last_penaltyinteger
last_goalinteger

Example

import polars as pl
from sportsdataverse.nhl.nhl_xg import prepare_xg_features
pbp = pl.read_parquet("tests/fixtures/nhl_player_impact/pbp_sample.parquet")
feat = prepare_xg_features(pbp)
print(feat.shape)

spearman_corr​

spearman_corr(a: 'np.ndarray', b: 'np.ndarray') -> 'float'

Spearman rank correlation between two arrays.

Parameters

ParameterTypeDefaultDescription
andarrayFirst array of values.
bndarraySecond array of values (same length as a).

Returns

The Spearman rank correlation coefficient.

Example

import numpy as np
from sportsdataverse._common.metrics import spearman_corr
spearman_corr(np.array([1, 2, 3]), np.array([3, 1, 2]))

team_fullname_to_abbr​

team_fullname_to_abbr(name: 'str') -> 'str | None'

Map an NHL full team display name to its abbreviation, or None if unknown.

Parameters

ParameterTypeDefaultDescription
namestra full team display name as it appears in load_nhl_shifts's event_team column (e.g. "Buffalo Sabres").

Returns

The team abbreviation matching load_nhl_pbp_full's event_team_abbr / home_abbr / away_abbr convention, or None for an unmapped name.

Example

from sportsdataverse.nhl.nhl_player_impact_constants import team_fullname_to_abbr
team_fullname_to_abbr("Buffalo Sabres") # "BUF"

team_game_xg_rates​

team_game_xg_rates(pbp: 'pl.DataFrame', schedule: 'pl.DataFrame', *, even_strength_only: 'bool' = True) -> 'pl.DataFrame'

Per-(game, team) even-strength xG-for/against + realized goals.

Parameters

ParameterTypeDefaultDescription
pbpDataFramea play-by-play frame shaped like load_nhl_pbp_full/load_nhl_pbp_lite (needs game_id, event_team_abbr, home_abbr, away_abbr, home_skaters, away_skaters, home_goalie_in, away_goalie_in, xg).
scheduleDataFramea schedule frame with game_id, season, date, home_abbr, away_abbr, neutral_site (home_goals/away_goals are accepted but ignored -- realized gf/ga are derived from the pbp's own GOAL events, never from schedule scores; see the module note on the load_nhl_schedule(s) placeholder-score bug for seasons <= 2023).
even_strength_onlyboolTruerestrict to home_skaters == away_skaters == 5 with both goalies in net (filters out PP/PK/empty-net distortion).

Returns

A polars DataFrame, one row per (game_id, team), both home and away. |col_name |type | |:------------|:------| |game_id |String | |season |Int64 | |date |Date | |team |String | |opp_team |String | |is_home |Boolean| |neutral_site |Boolean| |xgf |Float64| |xga |Float64| |gf |Int64 | |ga |Int64 |

No returns table is published for this function: no capture: it needs a schedule with date / home_abbr / away_abbr / neutral_site, and no loader returns one (load_nhl_schedules carries game_date / home_team_abbr).

Example

from sportsdataverse.nhl.nhl_team_ratings import team_game_xg_rates
from sportsdataverse.nhl import load_nhl_pbp_full, load_nhl_schedules

pbp = load_nhl_pbp_full([2023])
sched = load_nhl_schedules([2023])
rates = team_game_xg_rates(pbp, sched)
print(rates.filter(pl.col("team") == "TOR").head())

weighted_ridge​

weighted_ridge(X: 'Any', y: 'np.ndarray', w: 'np.ndarray', lam: 'float') -> 'np.ndarray'

Solve the weighted ridge normal equations (X'WX + lam*I)^-1 X'Wy.

Dense path (numpy.linalg.solve) for small/dense X; conjugate-gradient (scipy.sparse.linalg.cg) for scipy.sparse X (the skater-RAPM design matrix, ~thousands of columns).

Parameters

ParameterTypeDefaultDescription
XAnydesign matrix, dense numpy.ndarray or any scipy.sparse matrix.
yndarrayresponse vector.
wndarraynonnegative observation weights (e.g. stint duration in seconds).
lamfloatridge penalty.

Returns

The fitted coefficient vector.

Example

import numpy as np
from sportsdataverse.nhl.nhl_player_impact_constants import weighted_ridge
X = np.array([[1.0, 0.0], [0.0, 1.0], [1.0, 1.0]])
y = np.array([2.0, -1.0, 1.0])
beta = weighted_ridge(X, y, np.ones(3), lam=1e-6)