Pregame

Reference

The equations in the model

Every feature for a game is computed from information available before tip-off. The target is home_win\mathrm{home\_win}, equal to 1 when the home team wins and 0 otherwise. Pregame does not convert these probabilities into betting value.

Possessions

A game has one possession count, the average of the Basketball-Reference estimate for each team.

Q^team=FGA+0.4 FTA−1.07⋅OREBOREB+OppDREB⋅(FGA−FGM)+TOV\widehat{Q}_{\mathrm{team}}= \mathrm{FGA}+0.4\,\mathrm{FTA}-1.07\cdot\frac{\mathrm{OREB}}{\mathrm{OREB}+\mathrm{OppDREB}}\cdot(\mathrm{FGA}-\mathrm{FGM})+\mathrm{TOV}Q=Q^home+Q^away2Q=\dfrac{\widehat{Q}_{\mathrm{home}}+\widehat{Q}_{\mathrm{away}}}{2}

Season to date

Counting stats are summed over the team’s completed games in the same season, excluding the game being predicted. An unplayed game adds nothing. Games played increments only when the game has a score. G=0G=0 leaves a rate missing rather than treating it as zero.

WinPct=WG\mathrm{WinPct}=\frac{W}{G}OffRtg=100PQ,DefRtg=100PAQ,NetRtg=OffRtg−DefRtg\mathrm{OffRtg}=100\frac{P}{Q},\qquad \mathrm{DefRtg}=100\frac{P_A}{Q},\qquad \mathrm{NetRtg}=\mathrm{OffRtg}-\mathrm{DefRtg}Pace=48QM/5\mathrm{Pace}=48\frac{Q}{M/5}

PP is points scored, PAP_A opponent points, QQ possessions, and MM the recorded player-minutes. Dividing by 5 turns player-minutes into team-minutes. Ratings and pace are computed from the sums, so a game with more possessions weighs more than one with fewer.

Shooting and possession rates are stored as fractions. A 55% effective field-goal rate is 0.55. A zero denominator is missing.

Effective field-goal percentage(FGM+0.5 FG3M)/FGA(\mathrm{FGM}+0.5\,\mathrm{FG3M})/\mathrm{FGA}
Turnover percentageTOV/Q\mathrm{TOV}/Q
Offensive rebound percentageOREB/(OREB+OppDREB)\mathrm{OREB}/(\mathrm{OREB}+\mathrm{OppDREB})
Free-throw rateFTM/FGA\mathrm{FTM}/\mathrm{FGA}
Opponent effective field-goal percentage(OppFGM+0.5 OppFG3M)/OppFGA(\mathrm{OppFGM}+0.5\,\mathrm{OppFG3M})/\mathrm{OppFGA}
Opponent turnover percentageOppTOV/Q\mathrm{OppTOV}/Q
Defensive rebound percentageDREB/(DREB+OppOREB)\mathrm{DREB}/(\mathrm{DREB}+\mathrm{OppOREB})

Last 10 games

Points, opponent points and possessions are summed over the previous completed games this season, at most 10, with a shorter window early in the season. The current game is excluded, and the result is carried onto later unplayed rows.

OffRtgL10=100∑i=1nPi∑i=1nQi,n≤10\mathrm{OffRtg}_{L10}=100\frac{\sum_{i=1}^{n} P_i}{\sum_{i=1}^{n} Q_i},\quad n\le 10DefRtgL10=100∑i=1nPA,i∑i=1nQi,NetRtgL10=OffRtgL10−DefRtgL10\mathrm{DefRtg}_{L10}=100\frac{\sum_{i=1}^{n} P_{A,i}}{\sum_{i=1}^{n} Q_i},\qquad \mathrm{NetRtg}_{L10}=\mathrm{OffRtg}_{L10}-\mathrm{DefRtg}_{L10}

Prior season and the blend

Last season’s net rating is the season total, then shrunk toward a league-average net rating of zero by 0.6. It is worth 10 games in the blend. A missing prior, or a missing current net rating, is treated as zero inside the blend only. The stored previous-season rating itself stays missing.

NP=100∑P−∑PA∑QN_P=100\frac{\sum P-\sum P_A}{\sum Q}NetRtgBlend=G N0+10 (0.6 NP,0)G+10\mathrm{NetRtgBlend}=\frac{G\,N_{0}+10\,(0.6\,N_{P,0})}{G+10}

A team at +8+8 last season and +4+4 through five games this season blends to (5⋅4+10⋅0.6⋅8)/(5+10)=4.53(5\cdot 4+10\cdot 0.6\cdot 8)/(5+10)=4.53. The weights are fixed. They are not learned.

Rest and schedule density

Rest is the whole days between this scheduled date and the team’s previous scheduled date, minus one, capped at 7. The first game on record, and the first game of a new season, is set to 7.

RestDays=min⁡(7, Dt−Dt−1−1)\mathrm{RestDays}=\min(7,\ D_t-D_{t-1}-1)BackToBack=1 when RestDays=0, else 0\mathrm{BackToBack}=1\ \text{when RestDays}=0,\ \text{else}\ 0

Games in the last seven days counts scheduled dates for that team in the half-open window [Dt−7 days, Dt)[D_t-7\ \mathrm{days},\ D_t). A game exactly seven days earlier is included. The current date is not. The count is schedule density, not a filter for games that were actually completed.

Elo

Elo is its own probability. It is not a coefficient inside logistic regression or boosting. A team starts at 1,500. Between seasons the rating is pulled toward 1,505:

Rnew=0.75 Rold+0.25⋅1505R_{\mathrm{new}}=0.75\,R_{\mathrm{old}}+0.25\cdot 1505

Home court is 55 points, and 0 at a neutral site. The pregame home-win probability is recorded before the result moves either rating.

P(H wins)=11+10−(RH−RA+H)/400P(H\ \mathrm{wins})=\frac{1}{1+10^{-(R_H-R_A+H)/400}}

After a completed game, MOV=∣PH−PA∣\mathrm{MOV}=|P_H-P_A| and DD is the winner’s pregame advantage, including home court. Y=1Y=1 if the home team wins.

M=(MOV+3)0.87.5+0.006 DM=\frac{(\mathrm{MOV}+3)^{0.8}}{7.5+0.006\,D}ΔR=20 M (Y−P(H wins))\Delta R=20\,M\,(Y-P(H\ \mathrm{wins}))RH′=RH+ΔR,RA′=RA−ΔRR_H'=R_H+\Delta R,\qquad R_A'=R_A-\Delta R

diff_elo\mathrm{diff\_elo} is RH−RAR_H-R_A without the 55-point home advantage. elo_home_prob\mathrm{elo\_home\_prob} already includes it. home_court\mathrm{home\_court} is a separate input, 1 at a home site and 0 at a neutral site.

What the classifiers see

There are 31 inputs: 11 side features and 20 home-minus-away differences, difff=fH−fA\mathrm{diff}_f=f_H-f_A.

Side features

  • home_court
  • home_back_to_back
  • away_back_to_back
  • home_games_last_7d
  • away_games_last_7d
  • home_games_played
  • away_games_played
  • home_players_out
  • away_players_out
  • home_key_players_out
  • away_key_players_out

Differences

  • diff_win_pct
  • diff_off_rtg
  • diff_def_rtg
  • diff_net_rtg
  • diff_pace
  • diff_efg_pct
  • diff_tov_pct
  • diff_oreb_pct
  • diff_ft_rate
  • diff_opp_efg_pct
  • diff_opp_tov_pct
  • diff_dreb_pct
  • diff_net_rtg_l10
  • diff_net_rtg_blend
  • diff_rest_days
  • diff_missing_minutes
  • diff_missing_impact
  • diff_missing_impact_delta
  • diff_available_impact
  • diff_elo

Availability

For a completed game, a player on the roster is out if he logged no minutes. The roster is everyone who played for the team in its previous 10 completed games this season, excluding a player whose last appearance before this game was for another team.

Expected minutes are the mean minutes over the player’s last 10 appearances before the game. Impact uses the last 100 appearances. On-court and off-court plus-minus are each shrunk by 250 minutes toward zero. Off-court minutes are team minutes divided by 5, minus the player’s minutes, and cannot be negative. Off-court plus-minus is the team margin minus the player’s plus-minus.

OnRate=48∑PMon∑Minon+250\mathrm{OnRate}=48\frac{\sum \mathrm{PM}_{\mathrm{on}}}{\sum \mathrm{Min}_{\mathrm{on}}+250}OffRate=48∑PMoff∑Minoff+250\mathrm{OffRate}=48\frac{\sum \mathrm{PM}_{\mathrm{off}}}{\sum \mathrm{Min}_{\mathrm{off}}+250}Impact=OnRate−OffRate\mathrm{Impact}=\mathrm{OnRate}-\mathrm{OffRate}w=ExpectedMin48⋅Impactw=\frac{\mathrm{ExpectedMin}}{48}\cdot \mathrm{Impact}

players_out\mathrm{players\_out} counts absentees. key_players_out\mathrm{key\_players\_out} counts those absentees whose expected minutes are at least 20. Missing minutes and missing impact sum the absentees’ expected minutes and weights. Available impact sums the weights of players who played.

MissingImpactDelta=MissingImpact−MissingImpact‾earlier\mathrm{MissingImpactDelta}=\mathrm{MissingImpact}-\overline{\mathrm{MissingImpact}}_{\mathrm{earlier}}

The baseline is the mean missing impact over the team’s earlier completed games this season. Upcoming games repeat the last completed lineup, then use the same weights. The saved logistic and boosting models were trained on who actually played.

Logistic regression

Missing inputs are filled with the training median, then each column is standardised with the training mean and the training standard deviation (divisor nn). The score is on that standardised scale, not the raw feature scale.

zj=xj−μjsjz_j=\frac{x_j-\mu_j}{s_j}z=β0+∑j=131βjzjz=\beta_0+\sum_{j=1}^{31}\beta_j z_jP(home win)=11+e−zP(\mathrm{home\ win})=\frac{1}{1+e^{-z}}

The penalty is L2 with C=1C=1, a maximum of 2,000 iterations, and an unpenalised intercept. CC is scikit-learn’s inverse penalty strength. A coefficient says how the standardised feature pushes the log-odds, with the other standardised features held fixed.

Histogram gradient boosting

This is scikit-learn’s HistGradientBoostingClassifier\mathrm{HistGradientBoostingClassifier}, not LightGBM. It does not use the logistic imputation or standardisation. Missing values stay missing and the trees handle them. Each tree is added at learning rate 0.08:

Fm(x)=Fm−1(x)+0.08 hm(x)F_m(x)=F_{m-1}(x)+0.08\,h_m(x)P(home win)=11+e−F(x)P(\mathrm{home\ win})=\frac{1}{1+e^{-F(x)}}

Two hundred iterations, depth 3, at least 40 samples in a leaf, L2 regularisation 1, early stopping off, and random_state=0\mathrm{random\_state}=0. The settings were chosen on 2020-21 and then frozen.

Training cut and the live refit

The Model Lab fit uses completed games with a season start year before 2021 as training, and 2021-22 onward as the test. Upcoming games are in neither set. Rows are ordered by season, date and game id. The Elo home-court value of 55 was tuned on 2016-17 through 2020-21.

Refresh slate fits the same two models again on every completed game and saves those pipelines for the slate. The Model Lab numbers stay the fixed holdout.

How a probability is scored

Before log loss, accuracy and Brier score, a probability is clipped to [10−6, 1−10−6][10^{-6},\ 1-10^{-6}]. y=1y=1 for a home win.

LogLoss=−1n∑i=1n[yiln⁡p^i+(1−yi)ln⁡(1−p^i)]\mathrm{LogLoss}=-\frac{1}{n}\sum_{i=1}^{n}\left[y_i\ln \hat p_i+(1-y_i)\ln(1-\hat p_i)\right]y^=1 when p^≥0.5\hat y=1\ \text{when}\ \hat p\ge 0.5Accuracy=1n∑i=1n1(y^i=yi)\mathrm{Accuracy}=\frac{1}{n}\sum_{i=1}^{n}\mathbf{1}(\hat y_i=y_i)Brier=1n∑i=1n(p^i−yi)2\mathrm{Brier}=\frac{1}{n}\sum_{i=1}^{n}(\hat p_i-y_i)^2

Reliability uses ten quantile bins. Each bin compares the mean predicted probability with the share of home wins. The same three scores are also computed for 0, 1, 2, 3 and 4 or more key players missing, adding both teams.