Predicting NBA Game-level Results via a Machine-Learning Pipeline

DATA 607 - Data Science in Context Presentation

Author

Kailot C. Harris

Published

September 21, 2026

Agenda

  1. Data Pipeline and Rolling Metrics
  2. Machine Learning Workflow
  3. Findings and Model Evaluation

Background and Motivation

Inspiration:

‘Exploring Machine Learning: Predicting the NBA’ by author Kishore Annambhotla.

https://medium.com/@k4annam/exploring-machine-learning-predicting-the-nba-607aea31c955


Objective:

Build a Machine Learning pipeline using real-time data to predict NBA game outcomes.


R Packages utilized:

hoopR, tidyverse, slider, and tidymodels

Data and Feature Engineering

Data Source:

Game-level box score data from hoopR package (2019-2026 seasons)

box_scores <- hoopR::load_nba_team_box(seasons = 2019:2026)

Rolling Metrics:

18-game rolling averages are computed for:

— Offensive output and shooting metrics (Effective FG%, True Shooting %)

— Advanced box stats (Rebounds, Assists, Turnovers, Steals, Blocks)

Contextual factors:

Days of rest and home court advantage (HCA)

Click to view feature engineering R code
#Empirically derived rolling-calc-window
n_rolling_days <- 18

full_features_df <- box_scores |>
  arrange(team_id, game_date) |>
  group_by(team_id) |>
  mutate(
    #define home-game advantage
    hga = if_else(str_detect(team_home_away, "away"),0,1),
    
    #define last game outcome
    lgo = lag(as.integer(team_winner), n = 1, default = NA),
    
    #define effective field goal percentage
    efg = (field_goals_made + 0.5*three_point_field_goals_made)/(field_goals_attempted),
    
    #define turnover rate percentage
    tov = (total_turnovers)/(field_goals_attempted + 0.44 * free_throws_attempted + total_turnovers),
    
    #define free throw rate
    ftr = free_throws_attempted/field_goals_attempted,
    
    #define true shooting percentage
    ts = team_score / (2* (field_goals_attempted + (0.44 * free_throws_attempted))),
    
    # Rolling n-game average of offensive rating and shooting %
    roll_pts_scored = slide_dbl(team_score, mean, .before = n_rolling_days, .after = -1),
    roll_field_goal_pct = slide_dbl(field_goal_pct, mean, .before = n_rolling_days, .after = -1),
    roll_oreb = slide_dbl(offensive_rebounds, mean, .before = n_rolling_days, .after = -1),
    roll_dreb = slide_dbl(defensive_rebounds, mean, .before = n_rolling_days, .after = -1),
    roll_reb = slide_dbl(total_rebounds, mean, .before = n_rolling_days, .after = -1),
    roll_ast = slide_dbl(assists, mean, .before = n_rolling_days, .after = -1),
    roll_stl = slide_dbl(steals, mean, .before = n_rolling_days, .after = -1),
    roll_blk = slide_dbl(blocks, mean, .before = n_rolling_days, .after = -1),
    roll_tov = slide_dbl(turnovers, mean, .before = n_rolling_days, .after = -1),
    roll_efg = slide_dbl(efg, mean, .before = n_rolling_days, .after = -1),
    roll_tovp = slide_dbl(tov, mean, .before = n_rolling_days, .after = -1),
    roll_ftr = slide_dbl(ftr, mean, .before = n_rolling_days, .after = -1),
    roll_ts = slide_dbl(ts, mean, .before = n_rolling_days, .after = -1),
    
    days_rest = as.numeric(game_date - lag(game_date))
  ) |>
  ungroup()

Machine Learning Pipeline

Train/Test split:

Training: Historical seasons (2019-2025)

Testing: 2026 season

Tidymodels Recipe:

Data normalization, feature disentanglement, and removal of collinear variables (i.e. non-independent)

Algorithm:

Random forest classification model (ranger) with 500 trees, evaluating Gini impurity

rf_spec <- rand_forest(trees=500, mode = "classification") |>
  set_engine("ranger", importance = "impurity")
nba_workflow <- workflow() |>
  add_recipe(nba_recipe) |>
  add_model(rf_spec)

nba_fit <- fit(nba_workflow, data = train_data)

Findings and Model Evaluation

2026 Season Metrics:

  • Accuracy: 63%

(compared to 50% random baseline)

  • ROC-AUC: 0.669

Key Drivers:
* Differential TS%
* Differential eFG%

2027 NBA Regular Season Predictions

Rank Team Projected 95% CI Lower 95% CI Upper SE
1 Knicks 56.1 47.8 64.4 4.21
2 Nuggets 50.5 41.9 59.1 4.40
3 Thunder 50.4 41.8 59.0 4.41
4 Celtics 48.8 40.1 57.5 4.44
5 Heat 48.0 39.3 56.7 4.46
6 Spurs 47.8 39.0 56.6 4.47
7 Clippers 47.6 38.8 56.4 4.47
8 Rockets 46.9 38.1 55.7 4.48
9 Raptors 45.5 36.7 54.3 4.50
10 Hornets 45.4 36.6 54.2 4.50

Monte Carlo Simulation Results

Summary and Conclusions

Summary:

  • An end-to-end Machine Learning pipeline was built using Random Forest classification

  • Achieved an accuracy of 63% on 2026 NBA Season game-level predictions

Takeaways:

  • Rolling features capture team momentum better than static seasonal stats.

  • Random Forest methodology provides a stable probability estimate for game outcomes.

Next steps:

  • Player-level tracking, injury awareness, XGBoost comparison, and star match-up differentials.

Citations and Acknowledgements

  • Medium article on NBA predictions using ML:

https://medium.com/@k4annam/exploring-machine-learning-predicting-the-nba-607aea31c955

  • Core R Packages:

tidymodels, hoopR, slider, and tidyverse

hoopR package: https://hoopr.sportsdataverse.org

  • Code structuring and tidymodels syntax debugging were assisted by Google Gemini (Flash model 3.6, 2026).

All logic, data pipeline design, and outputs were verified by the author.