moneyBBall
Basketball analytics and NCAA-to-NBA draft projection based on published formulas and peer-reviewed research.
Design principles
1. Schema-flexible. Every function resolves canonical stat names (PTS,
STL_PCT, TM_MP, …) against whatever your DataFrame actually calls them.
Point it at a new dataset, extend the alias table if needed, and the metrics
work unchanged.
2. Honest about provenance. Many canonical metrics (Offensive Rating, PER,
Usage%, AST%) are defined in terms of team and opponent totals. With only
individual player rows they can be approximated but not computed exactly.
Every metric declares its inputs and reports a reliability tier
(EXACT, APPROXIMATED, PASSTHROUGH, DERIVED, UNAVAILABLE), so you
know which numbers can bear weight.
3. Research-cited. Formulas carry their sources: Oliver's Basketball on Paper, Kubatko et al. (2007) JQAS, Myers' BPM 2.0, Hollinger's PER, Sill's RAPM regularisation, Pelton's age adjustment, Vashro on steal rate, and Cheng on free-throw percentage as a shooting-touch proxy.
Modules
| Module | Purpose |
|---|---|
schema |
Canonical-name resolution, coverage auditing, team-context detection |
core |
Provenance system: MetricSpec, ComputationReport, Reliability |
units |
Detect and harmonise mixed per-game / season-total columns |
reconstruct |
Recover raw counts from rate-only datasets |
possessions |
Possession estimation, pace, per-100 normalisation |
four_factors |
Oliver's Four Factors, offensive and defensive (original and regression-fitted weights) |
box_metrics |
BPM, PER, Game Score, VORP, TS%, usage/rate family |
shrinkage |
Empirical-Bayes (flat and hierarchical) and James-Stein small-sample correction |
quality |
Rate validation, artifact detection, robust standardisation |
draft |
NCAA-to-NBA prospect features from the draft literature |
survival |
Kaplan-Meier, Cox, censoring-aware label construction |
similarity |
PCA play-style decomposition, Mahalanobis-distance player comps |
Validation
BPM reconstruction, unit harmonisation, and rate-artifact detection have been checked against a real NCAA box-score dataset and an independent reference. See VALIDATION.md for methodology, results, and the data-quality issues these modules handle.
Installation
pip install moneyBBall
# Local development
pip install -e ".[dev]"
# With survival analysis (Cox models)
pip install -e ".[survival]"
Usage
Quickstart
Resolve your schema once, then call any add_* function. Each one returns
your DataFrame with new columns appended.
import pandas as pd
from moneybball import SchemaResolver, draft
df = pd.read_csv("players.csv")
resolver = SchemaResolver(df.columns)
df = draft.add_prospect_features(df, resolver=resolver)
add_prospect_features skips any feature whose inputs it can't resolve
rather than raising, so it's safe to run on a dataset you haven't audited
yet. For real-world data, though, run it after the fuller pipeline below,
since features computed on mixed units or unreconstructed counts will be
wrong.
Recommended pipeline order
Run these steps in order, since later ones depend on earlier ones: units first, then reconstruction, then everything else.
import pandas as pd
from moneybball import SchemaResolver, ComputationReport
from moneybball import units, reconstruct, quality, box_metrics, draft
df = pd.read_csv("players.csv")
df = df.drop_duplicates(subset=["pid", "year"])
r = SchemaResolver(df.columns)
report = ComputationReport()
# 1. Audit what this dataset supports
print(r.coverage().query("available"))
print("team context:", r.has_team_context())
# 2. Fix units FIRST. Nothing downstream is valid without this
detection = units.detect_stat_units(df, resolver=r)
print(detection.evidence)
df = units.harmonize_units(df, resolver=r, target="season_total",
detection=detection)
print(units.verify_scoring_identity(df, resolver=r))
# 3. Recover missing raw counts
r = SchemaResolver(df.columns)
df = reconstruct.reconstruct_counting_stats(df, resolver=r, report=report)
# 4. Clean small-sample artifacts
r = SchemaResolver(df.columns)
print(quality.find_impossible_rates(df, resolver=r))
df = quality.clip_rates(df, resolver=r)
df = quality.add_sample_size_flag(df, resolver=r)
# 5. Compute metrics and prospect features
df = box_metrics.add_box_plus_minus_linear(df, resolver=r, report=report)
df = draft.add_prospect_features(df, resolver=r, report=report)
df = draft.add_age_adjusted_production(df, production_col="bpm", report=report)
# 6. Audit what you can trust
print(report.to_frame())
print("trusted:", report.trusted())
Small-sample correction
Three-point percentage needs roughly 750 attempts to become reliable (Blackport 2014). A college season provides a fraction of that, so nearly every college 3P% is under-sampled.
from moneybball import shrinkage
df = shrinkage.empirical_bayes_rate(df, made_col="TPM", attempted_col="TPA")
df = shrinkage.add_reliability_weight(df, "TPA", stat_key="TP_PCT")
A 4-for-7 shooter moves substantially toward the population mean; a 200-for-500 shooter barely moves.
Testing
pip install -e ".[dev]"
pytest --cov=moneybball
91 tests pass, including:
- BPM's steal coefficient is the largest positive weight (Myers)
- Oliver's weights match the published 40/25/20/15
- PER normalises to exactly 15.00
- Turnover recovery round-trips to
rtol=1e-9 - Robust z-score resists a single 1072 artifact where classical z-score fails
- Empirical Bayes shrinks small samples more than large ones
- Censored careers become
NaN, not false negatives
Known limitations
- RAPM is not computable from box scores. It needs play-by-play lineup data. BPM is its box-score approximation, and is treated as such here.
- Full BPM 2.0 coefficients are only partially published. This implements Myers' simplified linear version; correlation with a full-model reference is 0.81, not 1.0.
- Team-context metrics need team data. Usage%, AST%, TRB%, ORtg, PER and
Win Shares all require team/opponent totals absent from a player-only
dataset. They raise a clear
KeyErrorrather than silently approximating. - PER's
normalisecentres against whatever rows you pass, which equals the true league average only if you pass the full league. - Pelton's 0.5/year age penalty is in WARP units. Applied to another
production scale it should be rescaled, e.g. by the ratio of that scale's
standard deviation to WARP's. This package does not fit the penalty
against outcome labels; doing so is a modelling decision left to the
caller, consistent with
moneybballcomputing statistics rather than training predictive models. - Rate ceilings in
qualityare judgement calls, not published constants. Review them against your own data before relying on the clipping.
Key references
- Oliver, D. (2004). Basketball on Paper. Potomac Books.
- Kubatko, J., Oliver, D., Pelton, K., & Rosenbaum, D. (2007). "A Starting Point for Analyzing Basketball Statistics." JQAS 3(3).
- Myers, D. (2020). "About Box Plus/Minus (BPM)." Basketball-Reference.
- Sill, J. (2010). "Improved NBA Adjusted +/− Using Regularization and Out-of-Sample Testing." MIT Sloan Sports Analytics Conference.
- Rosenbaum, D. (2004). "Measuring How NBA Players Help Their Teams Win."
- Pelton, K. "Explaining Kevin Pelton's NBA draft projection system." ESPN.
- Vashro, L. (2014). "How Do We Assess 'Potential' Among NBA Draft Prospects?" Canis Hoopus.
- Cheng, C. (2020). "Scouting NBA Three-Point Shooting." Harvard Sports Analysis Collective.
- Blackport, D. (2014). "How Long Does It Take For Three Point Shooting To Stabilize?" Nylon Calculus.
- Vaci, N., Cocić, D., Gula, B., & Bilalić, M. (2019). "Large data and Bayesian modeling: aging curves of NBA players." Behavior Research Methods 51(4).
- Cui, Y., et al. (2019). "Key Anthropometric and Physical Determinants…NBA Draft Combine." Frontiers in Psychology.
- Efron, B. & Morris, C. (1975). "Data Analysis Using Stein's Estimator." JASA 70(350).
- Casella, G. (1985). "An Introduction to Empirical Bayes Data Analysis." The American Statistician 39(2), 83-87.
- Brown, L. D. (2008). "In-season prediction of batting averages: A field test of empirical Bayes and Bayes methodologies." Annals of Applied Statistics 2(1).
- Gelman, A. & Hill, J. (2007). Data Analysis Using Regression and Multilevel/Hierarchical Models. Cambridge University Press.
- Morris, C. N. (1983). "Parametric Empirical Bayes Inference: Theory and Applications." JASA 78(381), 47-55.
- Searle, S.R., Casella, G., & McCulloch, C.E. (1992). Variance Components. Wiley.
- Jolliffe, I.T. (2002). Principal Component Analysis (2nd ed.). Springer Series in Statistics.
- Alagappan, M. (2012). "From 5 to 13: Redefining the Positions in Basketball." MIT Sloan Sports Analytics Conference.
- Mahalanobis, P.C. (1936). "On the Generalised Distance in Statistics." Proceedings of the National Institute of Sciences of India, 2(1), 49-55.
- Ledoit, O. & Wolf, M. (2004). "A well-conditioned estimator for large-dimensional covariance matrices." Journal of Multivariate Analysis, 88(2), 365-411.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file moneybball-0.4.0.tar.gz.
File metadata
- Download URL: moneybball-0.4.0.tar.gz
- Upload date:
- Size: 76.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9e62983251884d1750df052afe65d7d5be319ed6262c9f1203637c4cc3d0d8e
|
|
| MD5 |
838338f9d01f2e8d1e2a3a2bf57f30e8
|
|
| BLAKE2b-256 |
77a642309bd77cbd4c34eee0f837dd82ffcba23778e2ebc7e069d03ad9532c25
|
Provenance
The following attestation bundles were made for moneybball-0.4.0.tar.gz:
Publisher:
publish.yml on SidharthJoly/36120-26SP-group11-25664929-package
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
moneybball-0.4.0.tar.gz -
Subject digest:
e9e62983251884d1750df052afe65d7d5be319ed6262c9f1203637c4cc3d0d8e - Sigstore transparency entry: 2470950691
- Sigstore integration time:
-
Permalink:
SidharthJoly/36120-26SP-group11-25664929-package@30255f17aa09a6b572b8adbfbfdfeb28b66dd8be -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/SidharthJoly
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@30255f17aa09a6b572b8adbfbfdfeb28b66dd8be -
Trigger Event:
push
-
Statement type:
File details
Details for the file moneybball-0.4.0-py3-none-any.whl.
File metadata
- Download URL: moneybball-0.4.0-py3-none-any.whl
- Upload date:
- Size: 68.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e42dbb739905358f663c13d2cd06b6e9b4ae9f55c6bf179eb914fa9443fcb71c
|
|
| MD5 |
f49df9a769cad911495abbee5c92218f
|
|
| BLAKE2b-256 |
a0683ef45e782e5a9ce22561cde752beab7fbffe088368872450f2bcd5cc25ef
|
Provenance
The following attestation bundles were made for moneybball-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on SidharthJoly/36120-26SP-group11-25664929-package
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
moneybball-0.4.0-py3-none-any.whl -
Subject digest:
e42dbb739905358f663c13d2cd06b6e9b4ae9f55c6bf179eb914fa9443fcb71c - Sigstore transparency entry: 2470950821
- Sigstore integration time:
-
Permalink:
SidharthJoly/36120-26SP-group11-25664929-package@30255f17aa09a6b572b8adbfbfdfeb28b66dd8be -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/SidharthJoly
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@30255f17aa09a6b572b8adbfbfdfeb28b66dd8be -
Trigger Event:
push
-
Statement type: