← Back home
Machine learning · data engineering · testing

NBA Player Stat Prediction

An end-to-end Python pipeline for ingesting NBA data, engineering leakage-safe historical features, training gradient boosting models, and evaluating them against a rolling-average baseline on future games.

Pythonscikit-learnSQLAlchemySQLiteMatplotlibpytestGitHub Actions
1,230
Games ingested
26,547
Player-game rows
4.85
Points MAE
23 / 28
Trailing windows won
points model vs. baseline

The problem

Predicting a player's next box-score line sounds simple until time enters the picture. A useful system has to learn only from information that would have actually been available before each game, and it has to be judged against a reasonable baseline rather than against nothing.

I built the project around that constraint: preserve chronology, avoid feature leakage, and make the evaluation reproducible from stored model artifacts.

System design

From source data to reproducible evaluation.

01
ESPN JSON
02
Validation + SQLite
03
Feature Engineering
04
Model Training
05
Evaluation + Reports

Leakage prevention

Rolling statistics are shifted so that a row cannot use information from the game it is trying to predict. Training and validation also move forward through time rather than randomly shuffling past and future observations together.

That makes the reported holdout performance a more realistic test of how the system would behave on genuinely unseen future games.

Evaluation

The project compares tuned gradient boosting regressors with a 10-game rolling-average baseline on identical chronological holdout rows. For points, the model reached 4.85 MAE and outperformed the baseline in 23 of 28 trailing seven-day windows; the assists model won 22 of 28.

The visualizations below are rebuilt natively for this portfolio from the project's reporting data rather than embedded as exported chart images.

Chronological holdout · points

Trailing 7-day MAE

23/28windows won
Gradient boosting10-game baseline
Rolling mean absolute error for the points model versus baselineAcross 28 complete trailing seven-day windows, the gradient boosting model has lower error than the ten-game rolling-average baseline in 23 windows.4.504.755.005.255.50Mar 17Mar 24Mar 31Apr 7Apr 13
Each point evaluates the same chronological holdout rows for the model and baseline.23 wins · 5 losses
Prediction quality

Useful signal, with a visible ceiling.

The chronological holdout contains 5,645 player-game observations. The model tracks typical scoring outcomes reasonably well, but predictions compress toward the middle instead of following extreme performances all the way up.

That limitation is especially clear on ceiling games: every one of the 188 holdout performances of 30+ points was underpredicted, with an average underprediction of 13.9 points.

Chronological holdout · points

Actual vs. predicted

5,645 rows2-point density cells
More observations = brighterPerfect prediction
Actual versus predicted points on the chronological holdoutAll points-model holdout observations are aggregated into two-point density cells. The model shows useful predictive signal but compresses extreme scoring outcomes toward more typical values.00101020203030404050506060Actual pointsPredicted points
The density follows the parity line for typical outcomes, but high-scoring games sit well below it as predictions compress toward the mean.Max: 31.7 predicted · 60 actual
Model interpretability

Recent scoring drives the model.

Holdout permutation importance measures how much prediction error increases when one feature is shuffled while the others stay intact. The player's 10-game scoring average dominates the points model, with recent minutes the strongest secondary signal.

Because several rolling features are correlated, these values describe the contribution of each feature given the others rather than a complete measure of the underlying basketball concept's importance.

Model interpretability · points

Permutation importance

MAE increase when shuffled
Permutation importance for the points modelThe ten-game points average is the dominant feature. Recent minutes are the strongest secondary feature, followed by activity, shorter-term scoring form, rest, opponent history, and recent rebounds.0.00.51.01.510-game points avg+1.80Recent minutes+0.16Activity proxy+0.0335-game points avg+0.015Days rest+0.010Vs. opponent points+0.006Recent rebounds+0.006Increase in holdout MAE after feature shuffling
Shuffling the 10-game scoring average increases error by about 1.80 points, far more than any other feature. Error bars show the standard deviation across permutation repeats.

Engineering, not just modeling

Reproducible artifacts

Serialized model pipelines include evaluation-window metadata so reported metrics can be reproduced later.

Automated testing

A pytest suite runs in GitHub Actions CI to catch regressions in the data and modeling pipeline.

CLI workflow

Evaluation reports are regenerated from saved models in one command instead of relying on a notebook-only workflow.

Database-backed pipeline

SQLAlchemy and SQLite provide a structured persistence layer for game and player-level records.

Next

See the implementation.

The repository contains the data pipeline, feature engineering, model training, automated tests, evaluation CLI, and generated reporting workflow.

Open GitHub repository ↗