Skip to content

Train CatBoost

Train the categorical player-state baseline with:

uv run nba-train-catboost 2025-26

The command builds or validates the shared single-lineup possession dataset, fits three chronological selection folds, evaluates an untouched final regular-season holdout, and fits one model on the full regular season for playoff inference.

The default 2025-26 CPU run takes about two minutes on the reference development machine.

Runtime options

Option Default Purpose
--iterations 1,000 Maximum trees per selection fold
--cv-folds 3 Expanding chronological validation folds
--validation-fraction 0.1 Validation share within pre-test games
--test-fraction 0.15 Untouched final regular-season share
--seed 17 CatBoost random seed

Use a short environment smoke test with:

uv run nba-train-catboost 2025-26 --iterations 10

The model intentionally exposes no other CatBoost parameters in this first slice. CatBoost resolves unspecified defaults, and the run records all of them in resolved_parameters.json. This is defaults-first training, not a hidden hyperparameter search.

Feature representation

Each player has one categorical feature with values:

Value State
0 Absent
1 On offense
2 On defense

A final binary categorical feature records whether the home team is on offense. Every feature is passed directly to CatBoost; no external one-hot encoder is fitted.

See Tree Models for the equations, invariance contract, and interpretation boundary.

Model artifacts

Each atomic run is stored under:

artifacts/models/catboost/<season>/<run-id>/
Artifact Purpose
selection_model.cbm Latest-fold model truncated to its best iteration
test_model.cbm Final-training refit used for untouched holdout predictions
model.cbm Full-regular-season refit used for playoff predictions
fold_metrics.parquet Validation metrics and selected trees by fold
training_history.parquet Learn and validation RMSE at every iteration
test_metrics.parquet Mean and CatBoost holdout metrics
test_predictions.parquet Holdout predictions in offense and home frames
feature_importance.parquet PredictionValuesChange importance by feature
game_splits.parquet Exact chronological game membership
player_columns.json NBA player ID to categorical feature mapping
resolved_parameters.json Requested and all fitted CatBoost parameters
model_parameters.json Feature, target, and selection contract
manifest.json Versions, hashes, counts, and artifact integrity

latest.json points to the newest completed run. A failed or interrupted run does not replace it.

Inspect results

uv run python - <<'PY'
import json
from pathlib import Path

import pandas as pd

root = Path("artifacts/models/catboost/2025-26")
run_id = json.loads((root / "latest.json").read_text())["run_id"]
run = root / run_id

print(pd.read_parquet(run / "test_metrics.parquet").to_string(index=False))
print(
    pd.read_parquet(run / "feature_importance.parquet")
    .head(25)
    .loc[:, ["rank", "feature_name", "player_name", "feature_importance"]]
    .to_string(index=False)
)
print(json.loads((run / "resolved_parameters.json").read_text())["selection"])
PY

Feature importance has no sign and must not be reported as player value. Use it to inspect the fitted tree model, then use contextual counterfactuals or SHAP analysis for player-level interpretation.

Refresh evaluation

After training, regenerate the common regular-holdout and playoff report:

uv run nba-evaluate-models 2025-26

Both commands register their immutable artifacts in the project-local MLflow store. See Track experiments with MLflow for the UI and backfill workflow.

Run focused correctness checks with:

uv run pytest -q tests/test_catboost_modeling.py tests/test_model_evaluation.py