Skip to main content

moneyBBall

tests codecov PyPI Python versions License Checked with mypy

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 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

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 KeyError rather than silently approximating.
  • PER's normalise centres 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 or refit; AgeAdjustment(fit_penalty=True) estimates it from labelled data instead.
  • Rate ceilings in quality are 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).

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

moneybball-0.3.0.tar.gz (60.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

moneybball-0.3.0-py3-none-any.whl (58.0 kB view details)

Uploaded Python 3

File details

Details for the file moneybball-0.3.0.tar.gz.

File metadata

  • Download URL: moneybball-0.3.0.tar.gz
  • Upload date:
  • Size: 60.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for moneybball-0.3.0.tar.gz
Algorithm Hash digest
SHA256 3a8e80caee1d75ecaa3dc615b49b5bf3de59a319088cace19a080daddf75ba0f
MD5 4bd6d0f704116bf420cbc7e111376021
BLAKE2b-256 3859d2fca5ed5e2ad22da67a5ee3dab8eaa660f649f520cf47b708f407f677e7

See more details on using hashes here.

Provenance

The following attestation bundles were made for moneybball-0.3.0.tar.gz:

Publisher: publish.yml on SidharthJoly/36120-26SP-group11-25664929-package

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file moneybball-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: moneybball-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 58.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for moneybball-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 160f274e5e0653204c98e028eaafb958bb7910bd5ef2b653b30111b94108dd51
MD5 64e49aa21d7712f421e8d08193b71cd7
BLAKE2b-256 d70609338e26511d4854564dedc8c87b12957cb055b61139f64d0e60049a8f86

See more details on using hashes here.

Provenance

The following attestation bundles were made for moneybball-0.3.0-py3-none-any.whl:

Publisher: publish.yml on SidharthJoly/36120-26SP-group11-25664929-package

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.6.2

2 files

0.6.1

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

This release

0.3.0 This release

2 files

0.2.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page