Skip to content

About

NBA shot probability modeling using Python and machine learning, with model comparison, calibration, validation, and feature importance analysis.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

NBA Shot Make Probability Model

Objective

The goal of this project is to predict the probability that an NBA field-goal attempt is made using the provided shot-context and player-tracking data.

The final output is a make_prob between 0 and 1 for every shot in testing.csv.gz.

Because the project is evaluated using log-loss, I treated this as a probability-estimation problem rather than a make/miss classification problem. Log-loss evaluates the probability assigned to the observed outcome rather than only whether a classification was correct [1].

No external basketball data were used.


Validation Strategy

The labeled training data contains two seasons, while the testing dataset comes from a different season.

Rather than randomly splitting individual shots, I validated across seasons:

  • Train on Season 1 → validate on Season 2
  • Train on Season 2 → validate on Season 1

This more closely matches the final task, where the model must generalize to a season it was not trained on. It also follows the general grouped cross-validation principle of keeping meaningful groups separate between training and validation data [2].

All model-selection decisions were based on held-out-season log-loss.


Model Comparison

I started with simple baselines and added complexity only when it could be tested under the same validation framework.

Model Mean Held-Out-Season Log-Loss
CatBoost + tracking features 0.627153
CatBoost basic 0.628034
Logistic regression 0.641976
CatBoost + historical priors 0.684050
Constant probability 0.689642

The final tracking-enhanced CatBoost model reduced log-loss by approximately 9.1% relative to the constant-probability baseline.

More importantly, the tracking features improved the otherwise similar CatBoost model from 0.628034 to 0.627153.

Model Comparison


Model Development

Constant Probability Baseline

The first baseline predicts the training-season average make probability for every validation shot.

Mean held-out-season log-loss:

0.689642

Logistic Regression

I next trained a logistic regression model using basic shot and game-context variables.

Mean held-out-season log-loss:

0.641976

This confirmed that the supplied shot-context variables contained substantial predictive information before introducing a nonlinear model.

CatBoost

The dataset contains a mix of continuous variables and categorical identifiers such as shooter, defender, and team IDs.

CatBoost supports categorical variables directly and was designed to handle them within gradient-boosted trees without requiring conventional one-hot encoding of high-cardinality categories [3][4].

The basic CatBoost model achieved:

0.628034

This improved substantially on the logistic regression baseline.


Player-Tracking Feature Engineering

One of the most useful supplied variables was closestdefapproach.

It contains four sequential defender-distance measurements leading into the shot.

Example:

{20.05,18.82,16.57,13.86}

I parsed this sequence into four individual measurements:

def_dist_t0
def_dist_t1
def_dist_t2
def_dist_t3

I then derived features describing how defensive pressure changed before the attempt:

  • total defender closing distance
  • interval-by-interval closing distance
  • mean defender distance
  • minimum and maximum defender distance
  • defender-distance range
  • defender-distance standard deviation
  • defender-approach slope
  • shooter-speed × defender-closing interaction

I also created additional shot-context features including:

  • defender distance relative to shot distance
  • shot distance × defender distance
  • nearest contester distance
  • mean contester distance
  • contester-distance variability
  • low-shot-clock indicators
  • log-transformed dribbles
  • absolute court location

The purpose was to represent not only how close a defender was, but how defensive pressure developed leading into the shot.


Did the Tracking Features Help?

To test whether the additional engineering actually improved the model, I compared two otherwise similar CatBoost models.

Feature Set Mean Log-Loss
Basic CatBoost 0.628034
CatBoost + tracking features 0.627153

The tracking-enhanced model produced the lower held-out-season log-loss, so I retained those features in the final model.


Historical Player and Team Priors

I also tested whether explicit historical performance summaries would improve the model.

I created smoothed estimates for:

  • shooter make rate
  • closest defender opponent make rate
  • offensive team make rate
  • defensive team opponent make rate

To reduce target leakage, training observations used leave-one-out calculations, while validation observations received values calculated only from the opposite training season. Cross-fitting or leave-one-out approaches are commonly used when creating target-based encodings so that an observation's own target does not directly determine its feature value [5].

This model performed substantially worse:

0.684050 mean held-out-season log-loss

I therefore removed these features from the final model.

This was an important negative result: features that appeared intuitively useful did not necessarily generalize well across seasons.


Feature Importance

The final model relied most heavily on a combination of shot type, shot geometry, defensive pressure, player movement, and player identity.

The highest-ranked features included:

  1. shottype
  2. defender_dist_per_shot_dist
  3. distance
  4. locationx
  5. shooterspeed
  6. num_contesters
  7. shooter_id
  8. shotclock

Several features derived from the defender-approach sequence also appeared among the most important predictors.

Feature Importance

The engineered defender_dist_per_shot_dist variable ranked second in the final model.

Feature importance is used here to describe which variables the fitted model relied on most heavily. It should not be interpreted as evidence of a causal relationship.


Probability Calibration

Because the project is evaluated using predicted probabilities, I also examined whether predicted make probabilities corresponded to observed make rates on the held-out seasons.

A well-calibrated binary classifier should produce outcomes at approximately the same frequency as the probability it assigns [6].

Out-of-Season Calibration

The calibration curve remained close to the ideal diagonal across the observed probability range.

Observed make rates were slightly higher than predicted rates in several bins, indicating mild underprediction, but the differences were small.

I did not apply an additional calibration model because only two labeled seasons were available, and further fitting to the same validation data could reduce generalization to the unseen testing season.


Final Model

The final model is the tracking-enhanced CatBoost model.

It was selected because it:

  • produced the lowest held-out-season log-loss
  • improved on the same CatBoost approach without the added tracking features
  • remained well calibrated on held-out seasons
  • was simpler than alternative approaches that did not improve validation performance

After model selection, I retrained the model using all labeled training shots and generated a make probability for every shot in testing.csv.gz.

The final predictions are stored in:

submission.csv

with the required columns:

shot_id,make_prob

The analysis script verifies that:

  • every test shot receives a prediction
  • the shot_id order matches the provided submission template
  • no probabilities are missing
  • all probabilities fall between 0 and 1

Reproducing the Analysis

Install the required Python packages:

pip install pandas numpy matplotlib scikit-learn catboost

Run:

python analysis.py

The script performs:

  1. data loading
  2. feature engineering
  3. whole-season validation
  4. baseline comparison
  5. CatBoost experiments
  6. historical-prior testing
  7. blend testing
  8. calibration analysis
  9. feature-importance analysis
  10. final model training
  11. test prediction generation

Key Result

The final tracking-enhanced CatBoost model achieved a mean held-out-season log-loss of:

0.627153

compared with:

  • 0.628034 — basic CatBoost
  • 0.641976 — logistic regression
  • 0.689642 — constant-probability baseline

The final model was chosen because it produced the strongest out-of-season probability estimates while avoiding additional complexity that did not improve validation performance.


References

[1] Scikit-learn developers. sklearn.metrics.log_loss.
https://scikit-learn.org/stable/modules/generated/sklearn.metrics.log_loss.html

[2] Scikit-learn developers. GroupKFold.
https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GroupKFold.html

[3] CatBoost documentation. Categorical features.
https://catboost.ai/docs/en/features/categorical-features

[4] Prokhorenkova, L., Gusev, G., Vorobev, A., Dorogush, A. V., & Gulin, A. (2018). CatBoost: unbiased boosting with categorical features. Advances in Neural Information Processing Systems 31.
https://proceedings.neurips.cc/paper/2018/hash/14491b756b3a51daac41c24863285549-Abstract.html

[5] Scikit-learn developers. Target Encoder's Internal Cross Fitting.
https://scikit-learn.org/stable/auto_examples/preprocessing/plot_target_encoder_cross_val.html

[6] Scikit-learn developers. Probability calibration.
https://scikit-learn.org/stable/modules/calibration.html

About

NBA shot probability modeling using Python and machine learning, with model comparison, calibration, validation, and feature importance analysis.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages