mclovin 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mclovin-1.0.0/LICENSE +21 -0
- mclovin-1.0.0/PKG-INFO +319 -0
- mclovin-1.0.0/README.md +278 -0
- mclovin-1.0.0/pyproject.toml +49 -0
- mclovin-1.0.0/setup.cfg +4 -0
- mclovin-1.0.0/src/mclovin/__init__.py +118 -0
- mclovin-1.0.0/src/mclovin/basketball/__init__.py +65 -0
- mclovin-1.0.0/src/mclovin/basketball/box_metrics.py +916 -0
- mclovin-1.0.0/src/mclovin/basketball/coefficients.py +30 -0
- mclovin-1.0.0/src/mclovin/basketball/draft.py +954 -0
- mclovin-1.0.0/src/mclovin/basketball/four_factors.py +527 -0
- mclovin-1.0.0/src/mclovin/basketball/identities.py +55 -0
- mclovin-1.0.0/src/mclovin/basketball/possessions.py +247 -0
- mclovin-1.0.0/src/mclovin/basketball/reconstruct.py +201 -0
- mclovin-1.0.0/src/mclovin/core.py +177 -0
- mclovin-1.0.0/src/mclovin/evaluation.py +125 -0
- mclovin-1.0.0/src/mclovin/panel/__init__.py +29 -0
- mclovin-1.0.0/src/mclovin/panel/features.py +204 -0
- mclovin-1.0.0/src/mclovin/panel/splits.py +93 -0
- mclovin-1.0.0/src/mclovin/py.typed +0 -0
- mclovin-1.0.0/src/mclovin/quality.py +309 -0
- mclovin-1.0.0/src/mclovin/schema.py +167 -0
- mclovin-1.0.0/src/mclovin/shrinkage.py +615 -0
- mclovin-1.0.0/src/mclovin/similarity.py +625 -0
- mclovin-1.0.0/src/mclovin/survival.py +221 -0
- mclovin-1.0.0/src/mclovin/units.py +253 -0
- mclovin-1.0.0/src/mclovin/vocab/__init__.py +128 -0
- mclovin-1.0.0/src/mclovin/vocab/basketball.py +175 -0
- mclovin-1.0.0/src/mclovin.egg-info/PKG-INFO +319 -0
- mclovin-1.0.0/src/mclovin.egg-info/SOURCES.txt +42 -0
- mclovin-1.0.0/src/mclovin.egg-info/dependency_links.txt +1 -0
- mclovin-1.0.0/src/mclovin.egg-info/requires.txt +28 -0
- mclovin-1.0.0/src/mclovin.egg-info/top_level.txt +1 -0
- mclovin-1.0.0/tests/test_core_and_possessions.py +164 -0
- mclovin-1.0.0/tests/test_evaluation.py +76 -0
- mclovin-1.0.0/tests/test_metrics.py +629 -0
- mclovin-1.0.0/tests/test_panel.py +196 -0
- mclovin-1.0.0/tests/test_quality_defaults.py +58 -0
- mclovin-1.0.0/tests/test_reconstruct.py +108 -0
- mclovin-1.0.0/tests/test_research_modules.py +986 -0
- mclovin-1.0.0/tests/test_schema.py +162 -0
- mclovin-1.0.0/tests/test_similarity.py +341 -0
- mclovin-1.0.0/tests/test_units.py +274 -0
- mclovin-1.0.0/tests/test_vocab.py +75 -0
mclovin-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SidharthJoly
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
mclovin-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mclovin
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Trustworthy features from messy tabular data: schema resolution, provenance tiers, small-sample correction and leakage-safe panel features.
|
|
5
|
+
Author: SidharthJoly
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/SidharthJoly/36120-26SP-group10-25664929-package
|
|
8
|
+
Project-URL: Documentation, https://mclovin.readthedocs.io/
|
|
9
|
+
Keywords: feature-engineering,data-quality,panel-data,time-series,empirical-bayes,shrinkage,provenance,schema-matching,sports-analytics
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: pandas>=1.5
|
|
19
|
+
Requires-Dist: numpy>=1.23
|
|
20
|
+
Requires-Dist: scipy>=1.9
|
|
21
|
+
Provides-Extra: survival
|
|
22
|
+
Requires-Dist: lifelines>=0.27; extra == "survival"
|
|
23
|
+
Provides-Extra: models
|
|
24
|
+
Requires-Dist: scikit-learn>=1.0; extra == "models"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
28
|
+
Requires-Dist: build; extra == "dev"
|
|
29
|
+
Requires-Dist: twine; extra == "dev"
|
|
30
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pandas-stubs; extra == "dev"
|
|
32
|
+
Requires-Dist: scipy-stubs; python_version >= "3.10" and extra == "dev"
|
|
33
|
+
Requires-Dist: lifelines>=0.27; extra == "dev"
|
|
34
|
+
Requires-Dist: scikit-learn>=1.0; extra == "dev"
|
|
35
|
+
Provides-Extra: docs
|
|
36
|
+
Requires-Dist: sphinx>=7.0; extra == "docs"
|
|
37
|
+
Requires-Dist: furo; extra == "docs"
|
|
38
|
+
Requires-Dist: myst-parser>=2.0; extra == "docs"
|
|
39
|
+
Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
# mclovin
|
|
43
|
+
|
|
44
|
+
[](https://github.com/SidharthJoly/36120-26SP-group10-25664929-package/actions/workflows/tests.yml)
|
|
45
|
+
[](https://codecov.io/gh/SidharthJoly/36120-26SP-group10-25664929-package)
|
|
46
|
+
[](https://pypi.org/project/mclovin/)
|
|
47
|
+
[](https://test.pypi.org/project/mclovin/)
|
|
48
|
+
[](https://pypi.org/project/mclovin/)
|
|
49
|
+
[](https://pypi.org/project/mclovin/)
|
|
50
|
+
[](LICENSE)
|
|
51
|
+
[](https://mypy-lang.org/)
|
|
52
|
+
|
|
53
|
+
Trustworthy features from messy tabular data: schema resolution, provenance
|
|
54
|
+
tiers, small-sample correction and leakage-safe panel features.
|
|
55
|
+
|
|
56
|
+
It began as basketball code. What survived the move to a general-purpose
|
|
57
|
+
library is the part that was never really about basketball. The basketball
|
|
58
|
+
metrics are still here, in `mclovin.basketball`, as one domain pack among
|
|
59
|
+
others -- and the previous release is still on PyPI as
|
|
60
|
+
[moneyBBall 0.6.2](https://pypi.org/project/moneyBBall/).
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Design principles
|
|
65
|
+
|
|
66
|
+
**1. Schema-flexible.** Functions resolve canonical names (`PTS`, `STL_PCT`,
|
|
67
|
+
`TEMP_MAX`, …) against whatever your DataFrame actually calls them, using a
|
|
68
|
+
`Vocabulary` you point the resolver at. The resolver itself holds no domain
|
|
69
|
+
knowledge, which is what lets the same machinery work on a box score and a
|
|
70
|
+
weather archive without modification.
|
|
71
|
+
|
|
72
|
+
**2. Honest about provenance.** Many derived quantities are *defined* in terms
|
|
73
|
+
of context a given dataset may not carry -- team totals, station baselines,
|
|
74
|
+
cohort aggregates. Rather than quietly approximating, every metric declares
|
|
75
|
+
its inputs and reports a reliability tier
|
|
76
|
+
(`EXACT`, `APPROXIMATED`, `PASSTHROUGH`, `DERIVED`, `UNAVAILABLE`), so you
|
|
77
|
+
know which numbers can bear weight.
|
|
78
|
+
|
|
79
|
+
**3. Leakage-resistant by default.** Rolling windows require a full window,
|
|
80
|
+
horizon joins match on the key rather than on position, and splits are
|
|
81
|
+
chronological. The defaults are the careful choice, not the convenient one.
|
|
82
|
+
|
|
83
|
+
**4. Research-cited.** Domain formulas carry their sources: Oliver's
|
|
84
|
+
*Basketball on Paper*, Kubatko et al. (2007) *JQAS*, Myers' BPM 2.0,
|
|
85
|
+
Hollinger's PER, Sill's RAPM regularisation, Pelton's age adjustment, Vashro
|
|
86
|
+
on steal rate, and Cheng on free-throw percentage as a shooting-touch proxy.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Modules
|
|
91
|
+
|
|
92
|
+
| Module | Purpose |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `schema` | Canonical-name resolution, coverage auditing, context detection |
|
|
95
|
+
| `vocab` | Domain vocabularies: aliases, context markers, rate ceilings |
|
|
96
|
+
| `core` | Provenance system: `MetricSpec`, `ComputationReport`, `Reliability` |
|
|
97
|
+
| `panel` | Lags, deltas, rolling windows, horizon joins, temporal splits |
|
|
98
|
+
| `units` | Detect and harmonise mixed per-period / cumulative columns |
|
|
99
|
+
| `shrinkage` | Empirical-Bayes (flat and hierarchical) and James-Stein small-sample correction |
|
|
100
|
+
| `quality` | Rate validation, artifact detection, robust standardisation |
|
|
101
|
+
| `similarity` | PCA decomposition, GMM soft archetypes, Mahalanobis-distance neighbours |
|
|
102
|
+
| `survival` | Kaplan-Meier, Cox, censoring-aware label construction |
|
|
103
|
+
| `evaluation` | Shared regression and classification scoring helpers |
|
|
104
|
+
| `basketball` | The domain pack: Four Factors, possessions, BPM/PER, draft features, count reconstruction |
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Validation
|
|
109
|
+
|
|
110
|
+
BPM reconstruction, unit harmonisation, and rate-artifact detection have
|
|
111
|
+
been checked against a real NCAA box-score dataset and an independent
|
|
112
|
+
reference. See [VALIDATION.md](VALIDATION.md) for methodology, results,
|
|
113
|
+
and the data-quality issues these modules handle.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Installation
|
|
118
|
+
|
|
119
|
+
Published to [PyPI](https://pypi.org/project/mclovin/) and
|
|
120
|
+
[TestPyPI](https://test.pypi.org/project/mclovin/).
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# From PyPI (recommended)
|
|
124
|
+
pip install mclovin
|
|
125
|
+
|
|
126
|
+
# From TestPyPI (dependencies still resolve from PyPI)
|
|
127
|
+
pip install --index-url https://test.pypi.org/simple/ \
|
|
128
|
+
--extra-index-url https://pypi.org/simple/ mclovin
|
|
129
|
+
|
|
130
|
+
# Local development
|
|
131
|
+
pip install -e ".[dev]"
|
|
132
|
+
|
|
133
|
+
# With survival analysis (Cox models)
|
|
134
|
+
pip install -e ".[survival]"
|
|
135
|
+
|
|
136
|
+
# With the scoring helpers (scikit-learn)
|
|
137
|
+
pip install -e ".[models]"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Usage
|
|
143
|
+
|
|
144
|
+
### Quickstart
|
|
145
|
+
|
|
146
|
+
Resolve your schema once, then call any `add_*` function. Each one returns
|
|
147
|
+
your DataFrame with new columns appended.
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
import pandas as pd
|
|
151
|
+
import mclovin.basketball as bb
|
|
152
|
+
|
|
153
|
+
df = pd.read_csv("players.csv")
|
|
154
|
+
resolver = bb.resolver(df.columns) # basketball vocabulary supplied
|
|
155
|
+
|
|
156
|
+
df = bb.draft.add_prospect_features(df, resolver=resolver)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`add_prospect_features` skips any feature whose inputs it can't resolve
|
|
160
|
+
rather than raising, so it's safe to run on a dataset you haven't audited
|
|
161
|
+
yet. For real-world data, though, run it after the fuller pipeline below,
|
|
162
|
+
since features computed on mixed units or unreconstructed counts will be
|
|
163
|
+
wrong.
|
|
164
|
+
|
|
165
|
+
### Recommended pipeline order
|
|
166
|
+
|
|
167
|
+
Run these steps in order, since later ones depend on earlier ones: units
|
|
168
|
+
first, then reconstruction, then everything else.
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
import pandas as pd
|
|
172
|
+
from mclovin import ComputationReport, units, quality
|
|
173
|
+
import mclovin.basketball as bb
|
|
174
|
+
from mclovin.basketball import box_metrics, draft, reconstruct
|
|
175
|
+
|
|
176
|
+
df = pd.read_csv("players.csv")
|
|
177
|
+
df = df.drop_duplicates(subset=["pid", "year"])
|
|
178
|
+
|
|
179
|
+
r = bb.resolver(df.columns)
|
|
180
|
+
report = ComputationReport()
|
|
181
|
+
|
|
182
|
+
# 1. Audit what this dataset supports
|
|
183
|
+
print(r.coverage().query("available"))
|
|
184
|
+
print("team context:", r.has_team_context())
|
|
185
|
+
|
|
186
|
+
# 2. Fix units FIRST. Nothing downstream is valid without this
|
|
187
|
+
detection = units.detect_stat_units(df, resolver=r)
|
|
188
|
+
print(detection.evidence)
|
|
189
|
+
df = units.harmonize_units(df, resolver=r, target="season_total",
|
|
190
|
+
detection=detection)
|
|
191
|
+
print(units.verify_scoring_identity(df, resolver=r))
|
|
192
|
+
|
|
193
|
+
# 3. Recover missing raw counts
|
|
194
|
+
r = SchemaResolver(df.columns)
|
|
195
|
+
df = reconstruct.reconstruct_counting_stats(df, resolver=r, report=report)
|
|
196
|
+
|
|
197
|
+
# 4. Clean small-sample artifacts
|
|
198
|
+
r = SchemaResolver(df.columns)
|
|
199
|
+
print(quality.find_impossible_rates(df, resolver=r))
|
|
200
|
+
df = quality.clip_rates(df, resolver=r)
|
|
201
|
+
df = quality.add_sample_size_flag(df, resolver=r)
|
|
202
|
+
|
|
203
|
+
# 5. Compute metrics and prospect features
|
|
204
|
+
df = box_metrics.add_box_plus_minus_linear(df, resolver=r, report=report)
|
|
205
|
+
df = draft.add_prospect_features(df, resolver=r, report=report)
|
|
206
|
+
df = draft.add_age_adjusted_production(df, production_col="bpm", report=report)
|
|
207
|
+
|
|
208
|
+
# 6. Audit what you can trust
|
|
209
|
+
print(report.to_frame())
|
|
210
|
+
print("trusted:", report.trusted())
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Small-sample correction
|
|
214
|
+
|
|
215
|
+
Three-point percentage needs roughly **750 attempts** to become reliable
|
|
216
|
+
(Blackport 2014). A college season provides a fraction of that, so nearly
|
|
217
|
+
every college 3P% is under-sampled.
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from mclovin import shrinkage
|
|
221
|
+
|
|
222
|
+
df = shrinkage.empirical_bayes_rate(df, made_col="TPM", attempted_col="TPA")
|
|
223
|
+
df = shrinkage.add_reliability_weight(df, "TPA", stat_key="TP_PCT")
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
A 4-for-7 shooter moves substantially toward the population mean; a
|
|
227
|
+
200-for-500 shooter barely moves.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Testing
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
pip install -e ".[dev]"
|
|
235
|
+
pytest --cov=mclovin
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**91 tests pass**, including:
|
|
239
|
+
|
|
240
|
+
- BPM's steal coefficient is the largest positive weight (Myers)
|
|
241
|
+
- Oliver's weights match the published 40/25/20/15
|
|
242
|
+
- PER normalises to exactly 15.00
|
|
243
|
+
- Turnover recovery round-trips to `rtol=1e-9`
|
|
244
|
+
- Robust z-score resists a single 1072 artifact where classical z-score fails
|
|
245
|
+
- Empirical Bayes shrinks small samples more than large ones
|
|
246
|
+
- Censored careers become `NaN`, not false negatives
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Known limitations
|
|
251
|
+
|
|
252
|
+
- **RAPM is not computable** from box scores. It needs play-by-play lineup
|
|
253
|
+
data. BPM is its box-score approximation, and is treated as such here.
|
|
254
|
+
- **Full BPM 2.0 coefficients are only partially published.** This implements
|
|
255
|
+
Myers' simplified *linear* version; correlation with a full-model reference
|
|
256
|
+
is 0.81, not 1.0.
|
|
257
|
+
- **Team-context metrics need team data.** Usage%, AST%, TRB%, ORtg, PER and
|
|
258
|
+
Win Shares all require team/opponent totals absent from a player-only
|
|
259
|
+
dataset. They raise a clear `KeyError` rather than silently approximating.
|
|
260
|
+
- **PER's `normalise` centres against whatever rows you pass**, which
|
|
261
|
+
equals the true league average only if you pass the full league.
|
|
262
|
+
- **Pelton's 0.5/year age penalty is in WARP units.** Applied to another
|
|
263
|
+
production scale it should be rescaled, e.g. by the ratio of that scale's
|
|
264
|
+
standard deviation to WARP's. This package does not fit the penalty
|
|
265
|
+
against outcome labels; doing so is a modelling decision left to the
|
|
266
|
+
caller, consistent with `mclovin` computing statistics rather than
|
|
267
|
+
training predictive models.
|
|
268
|
+
- **Rate ceilings in `quality` are judgement calls**, not published constants.
|
|
269
|
+
Review them against your own data before relying on the clipping.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Key references
|
|
274
|
+
|
|
275
|
+
- Oliver, D. (2004). *Basketball on Paper*. Potomac Books.
|
|
276
|
+
- Kubatko, J., Oliver, D., Pelton, K., & Rosenbaum, D. (2007). "A Starting
|
|
277
|
+
Point for Analyzing Basketball Statistics." *JQAS* 3(3).
|
|
278
|
+
- Myers, D. (2020). "About Box Plus/Minus (BPM)." Basketball-Reference.
|
|
279
|
+
- NBA.com Advanced Stats glossary. "Performance Impact Estimator (PIE)."
|
|
280
|
+
- Sill, J. (2010). "Improved NBA Adjusted +/− Using Regularization and
|
|
281
|
+
Out-of-Sample Testing." MIT Sloan Sports Analytics Conference.
|
|
282
|
+
- Rosenbaum, D. (2004). "Measuring How NBA Players Help Their Teams Win."
|
|
283
|
+
- Pelton, K. "Explaining Kevin Pelton's NBA draft projection system." ESPN.
|
|
284
|
+
- Vashro, L. (2014). "How Do We Assess 'Potential' Among NBA Draft
|
|
285
|
+
Prospects?" Canis Hoopus.
|
|
286
|
+
- Cheng, C. (2020). "Scouting NBA Three-Point Shooting." Harvard Sports
|
|
287
|
+
Analysis Collective.
|
|
288
|
+
- Blackport, D. (2014). "How Long Does It Take For Three Point Shooting To
|
|
289
|
+
Stabilize?" Nylon Calculus.
|
|
290
|
+
- Vaci, N., Cocić, D., Gula, B., & Bilalić, M. (2019). "Large data and
|
|
291
|
+
Bayesian modeling: aging curves of NBA players." *Behavior Research Methods*
|
|
292
|
+
51(4).
|
|
293
|
+
- Cui, Y., et al. (2019). "Key Anthropometric and Physical Determinants…NBA
|
|
294
|
+
Draft Combine." *Frontiers in Psychology*.
|
|
295
|
+
- Efron, B. & Morris, C. (1975). "Data Analysis Using Stein's Estimator."
|
|
296
|
+
*JASA* 70(350).
|
|
297
|
+
- Casella, G. (1985). "An Introduction to Empirical Bayes Data Analysis."
|
|
298
|
+
*The American Statistician* 39(2), 83-87.
|
|
299
|
+
- Brown, L. D. (2008). "In-season prediction of batting averages: A field
|
|
300
|
+
test of empirical Bayes and Bayes methodologies." *Annals of Applied
|
|
301
|
+
Statistics* 2(1).
|
|
302
|
+
- Gelman, A. & Hill, J. (2007). *Data Analysis Using Regression and
|
|
303
|
+
Multilevel/Hierarchical Models*. Cambridge University Press.
|
|
304
|
+
- Morris, C. N. (1983). "Parametric Empirical Bayes Inference: Theory and
|
|
305
|
+
Applications." *JASA* 78(381), 47-55.
|
|
306
|
+
- Searle, S.R., Casella, G., & McCulloch, C.E. (1992). *Variance
|
|
307
|
+
Components*. Wiley.
|
|
308
|
+
- Jolliffe, I.T. (2002). *Principal Component Analysis* (2nd ed.). Springer
|
|
309
|
+
Series in Statistics.
|
|
310
|
+
- Alagappan, M. (2012). "From 5 to 13: Redefining the Positions in
|
|
311
|
+
Basketball." MIT Sloan Sports Analytics Conference.
|
|
312
|
+
- Dempster, A.P., Laird, N.M. & Rubin, D.B. (1977). "Maximum Likelihood
|
|
313
|
+
from Incomplete Data via the EM Algorithm." *JRSS Series B* 39(1), 1-38.
|
|
314
|
+
- Mahalanobis, P.C. (1936). "On the Generalised Distance in Statistics."
|
|
315
|
+
Proceedings of the National Institute of Sciences of India, 2(1), 49-55.
|
|
316
|
+
- Ledoit, O. & Wolf, M. (2004). "A well-conditioned estimator for
|
|
317
|
+
large-dimensional covariance matrices." Journal of Multivariate
|
|
318
|
+
Analysis, 88(2), 365-411.
|
|
319
|
+
</content>
|
mclovin-1.0.0/README.md
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# mclovin
|
|
2
|
+
|
|
3
|
+
[](https://github.com/SidharthJoly/36120-26SP-group10-25664929-package/actions/workflows/tests.yml)
|
|
4
|
+
[](https://codecov.io/gh/SidharthJoly/36120-26SP-group10-25664929-package)
|
|
5
|
+
[](https://pypi.org/project/mclovin/)
|
|
6
|
+
[](https://test.pypi.org/project/mclovin/)
|
|
7
|
+
[](https://pypi.org/project/mclovin/)
|
|
8
|
+
[](https://pypi.org/project/mclovin/)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](https://mypy-lang.org/)
|
|
11
|
+
|
|
12
|
+
Trustworthy features from messy tabular data: schema resolution, provenance
|
|
13
|
+
tiers, small-sample correction and leakage-safe panel features.
|
|
14
|
+
|
|
15
|
+
It began as basketball code. What survived the move to a general-purpose
|
|
16
|
+
library is the part that was never really about basketball. The basketball
|
|
17
|
+
metrics are still here, in `mclovin.basketball`, as one domain pack among
|
|
18
|
+
others -- and the previous release is still on PyPI as
|
|
19
|
+
[moneyBBall 0.6.2](https://pypi.org/project/moneyBBall/).
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Design principles
|
|
24
|
+
|
|
25
|
+
**1. Schema-flexible.** Functions resolve canonical names (`PTS`, `STL_PCT`,
|
|
26
|
+
`TEMP_MAX`, …) against whatever your DataFrame actually calls them, using a
|
|
27
|
+
`Vocabulary` you point the resolver at. The resolver itself holds no domain
|
|
28
|
+
knowledge, which is what lets the same machinery work on a box score and a
|
|
29
|
+
weather archive without modification.
|
|
30
|
+
|
|
31
|
+
**2. Honest about provenance.** Many derived quantities are *defined* in terms
|
|
32
|
+
of context a given dataset may not carry -- team totals, station baselines,
|
|
33
|
+
cohort aggregates. Rather than quietly approximating, every metric declares
|
|
34
|
+
its inputs and reports a reliability tier
|
|
35
|
+
(`EXACT`, `APPROXIMATED`, `PASSTHROUGH`, `DERIVED`, `UNAVAILABLE`), so you
|
|
36
|
+
know which numbers can bear weight.
|
|
37
|
+
|
|
38
|
+
**3. Leakage-resistant by default.** Rolling windows require a full window,
|
|
39
|
+
horizon joins match on the key rather than on position, and splits are
|
|
40
|
+
chronological. The defaults are the careful choice, not the convenient one.
|
|
41
|
+
|
|
42
|
+
**4. Research-cited.** Domain formulas carry their sources: Oliver's
|
|
43
|
+
*Basketball on Paper*, Kubatko et al. (2007) *JQAS*, Myers' BPM 2.0,
|
|
44
|
+
Hollinger's PER, Sill's RAPM regularisation, Pelton's age adjustment, Vashro
|
|
45
|
+
on steal rate, and Cheng on free-throw percentage as a shooting-touch proxy.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Modules
|
|
50
|
+
|
|
51
|
+
| Module | Purpose |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `schema` | Canonical-name resolution, coverage auditing, context detection |
|
|
54
|
+
| `vocab` | Domain vocabularies: aliases, context markers, rate ceilings |
|
|
55
|
+
| `core` | Provenance system: `MetricSpec`, `ComputationReport`, `Reliability` |
|
|
56
|
+
| `panel` | Lags, deltas, rolling windows, horizon joins, temporal splits |
|
|
57
|
+
| `units` | Detect and harmonise mixed per-period / cumulative columns |
|
|
58
|
+
| `shrinkage` | Empirical-Bayes (flat and hierarchical) and James-Stein small-sample correction |
|
|
59
|
+
| `quality` | Rate validation, artifact detection, robust standardisation |
|
|
60
|
+
| `similarity` | PCA decomposition, GMM soft archetypes, Mahalanobis-distance neighbours |
|
|
61
|
+
| `survival` | Kaplan-Meier, Cox, censoring-aware label construction |
|
|
62
|
+
| `evaluation` | Shared regression and classification scoring helpers |
|
|
63
|
+
| `basketball` | The domain pack: Four Factors, possessions, BPM/PER, draft features, count reconstruction |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Validation
|
|
68
|
+
|
|
69
|
+
BPM reconstruction, unit harmonisation, and rate-artifact detection have
|
|
70
|
+
been checked against a real NCAA box-score dataset and an independent
|
|
71
|
+
reference. See [VALIDATION.md](VALIDATION.md) for methodology, results,
|
|
72
|
+
and the data-quality issues these modules handle.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Installation
|
|
77
|
+
|
|
78
|
+
Published to [PyPI](https://pypi.org/project/mclovin/) and
|
|
79
|
+
[TestPyPI](https://test.pypi.org/project/mclovin/).
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# From PyPI (recommended)
|
|
83
|
+
pip install mclovin
|
|
84
|
+
|
|
85
|
+
# From TestPyPI (dependencies still resolve from PyPI)
|
|
86
|
+
pip install --index-url https://test.pypi.org/simple/ \
|
|
87
|
+
--extra-index-url https://pypi.org/simple/ mclovin
|
|
88
|
+
|
|
89
|
+
# Local development
|
|
90
|
+
pip install -e ".[dev]"
|
|
91
|
+
|
|
92
|
+
# With survival analysis (Cox models)
|
|
93
|
+
pip install -e ".[survival]"
|
|
94
|
+
|
|
95
|
+
# With the scoring helpers (scikit-learn)
|
|
96
|
+
pip install -e ".[models]"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Usage
|
|
102
|
+
|
|
103
|
+
### Quickstart
|
|
104
|
+
|
|
105
|
+
Resolve your schema once, then call any `add_*` function. Each one returns
|
|
106
|
+
your DataFrame with new columns appended.
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import pandas as pd
|
|
110
|
+
import mclovin.basketball as bb
|
|
111
|
+
|
|
112
|
+
df = pd.read_csv("players.csv")
|
|
113
|
+
resolver = bb.resolver(df.columns) # basketball vocabulary supplied
|
|
114
|
+
|
|
115
|
+
df = bb.draft.add_prospect_features(df, resolver=resolver)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`add_prospect_features` skips any feature whose inputs it can't resolve
|
|
119
|
+
rather than raising, so it's safe to run on a dataset you haven't audited
|
|
120
|
+
yet. For real-world data, though, run it after the fuller pipeline below,
|
|
121
|
+
since features computed on mixed units or unreconstructed counts will be
|
|
122
|
+
wrong.
|
|
123
|
+
|
|
124
|
+
### Recommended pipeline order
|
|
125
|
+
|
|
126
|
+
Run these steps in order, since later ones depend on earlier ones: units
|
|
127
|
+
first, then reconstruction, then everything else.
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
import pandas as pd
|
|
131
|
+
from mclovin import ComputationReport, units, quality
|
|
132
|
+
import mclovin.basketball as bb
|
|
133
|
+
from mclovin.basketball import box_metrics, draft, reconstruct
|
|
134
|
+
|
|
135
|
+
df = pd.read_csv("players.csv")
|
|
136
|
+
df = df.drop_duplicates(subset=["pid", "year"])
|
|
137
|
+
|
|
138
|
+
r = bb.resolver(df.columns)
|
|
139
|
+
report = ComputationReport()
|
|
140
|
+
|
|
141
|
+
# 1. Audit what this dataset supports
|
|
142
|
+
print(r.coverage().query("available"))
|
|
143
|
+
print("team context:", r.has_team_context())
|
|
144
|
+
|
|
145
|
+
# 2. Fix units FIRST. Nothing downstream is valid without this
|
|
146
|
+
detection = units.detect_stat_units(df, resolver=r)
|
|
147
|
+
print(detection.evidence)
|
|
148
|
+
df = units.harmonize_units(df, resolver=r, target="season_total",
|
|
149
|
+
detection=detection)
|
|
150
|
+
print(units.verify_scoring_identity(df, resolver=r))
|
|
151
|
+
|
|
152
|
+
# 3. Recover missing raw counts
|
|
153
|
+
r = SchemaResolver(df.columns)
|
|
154
|
+
df = reconstruct.reconstruct_counting_stats(df, resolver=r, report=report)
|
|
155
|
+
|
|
156
|
+
# 4. Clean small-sample artifacts
|
|
157
|
+
r = SchemaResolver(df.columns)
|
|
158
|
+
print(quality.find_impossible_rates(df, resolver=r))
|
|
159
|
+
df = quality.clip_rates(df, resolver=r)
|
|
160
|
+
df = quality.add_sample_size_flag(df, resolver=r)
|
|
161
|
+
|
|
162
|
+
# 5. Compute metrics and prospect features
|
|
163
|
+
df = box_metrics.add_box_plus_minus_linear(df, resolver=r, report=report)
|
|
164
|
+
df = draft.add_prospect_features(df, resolver=r, report=report)
|
|
165
|
+
df = draft.add_age_adjusted_production(df, production_col="bpm", report=report)
|
|
166
|
+
|
|
167
|
+
# 6. Audit what you can trust
|
|
168
|
+
print(report.to_frame())
|
|
169
|
+
print("trusted:", report.trusted())
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Small-sample correction
|
|
173
|
+
|
|
174
|
+
Three-point percentage needs roughly **750 attempts** to become reliable
|
|
175
|
+
(Blackport 2014). A college season provides a fraction of that, so nearly
|
|
176
|
+
every college 3P% is under-sampled.
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from mclovin import shrinkage
|
|
180
|
+
|
|
181
|
+
df = shrinkage.empirical_bayes_rate(df, made_col="TPM", attempted_col="TPA")
|
|
182
|
+
df = shrinkage.add_reliability_weight(df, "TPA", stat_key="TP_PCT")
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
A 4-for-7 shooter moves substantially toward the population mean; a
|
|
186
|
+
200-for-500 shooter barely moves.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Testing
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
pip install -e ".[dev]"
|
|
194
|
+
pytest --cov=mclovin
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**91 tests pass**, including:
|
|
198
|
+
|
|
199
|
+
- BPM's steal coefficient is the largest positive weight (Myers)
|
|
200
|
+
- Oliver's weights match the published 40/25/20/15
|
|
201
|
+
- PER normalises to exactly 15.00
|
|
202
|
+
- Turnover recovery round-trips to `rtol=1e-9`
|
|
203
|
+
- Robust z-score resists a single 1072 artifact where classical z-score fails
|
|
204
|
+
- Empirical Bayes shrinks small samples more than large ones
|
|
205
|
+
- Censored careers become `NaN`, not false negatives
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Known limitations
|
|
210
|
+
|
|
211
|
+
- **RAPM is not computable** from box scores. It needs play-by-play lineup
|
|
212
|
+
data. BPM is its box-score approximation, and is treated as such here.
|
|
213
|
+
- **Full BPM 2.0 coefficients are only partially published.** This implements
|
|
214
|
+
Myers' simplified *linear* version; correlation with a full-model reference
|
|
215
|
+
is 0.81, not 1.0.
|
|
216
|
+
- **Team-context metrics need team data.** Usage%, AST%, TRB%, ORtg, PER and
|
|
217
|
+
Win Shares all require team/opponent totals absent from a player-only
|
|
218
|
+
dataset. They raise a clear `KeyError` rather than silently approximating.
|
|
219
|
+
- **PER's `normalise` centres against whatever rows you pass**, which
|
|
220
|
+
equals the true league average only if you pass the full league.
|
|
221
|
+
- **Pelton's 0.5/year age penalty is in WARP units.** Applied to another
|
|
222
|
+
production scale it should be rescaled, e.g. by the ratio of that scale's
|
|
223
|
+
standard deviation to WARP's. This package does not fit the penalty
|
|
224
|
+
against outcome labels; doing so is a modelling decision left to the
|
|
225
|
+
caller, consistent with `mclovin` computing statistics rather than
|
|
226
|
+
training predictive models.
|
|
227
|
+
- **Rate ceilings in `quality` are judgement calls**, not published constants.
|
|
228
|
+
Review them against your own data before relying on the clipping.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Key references
|
|
233
|
+
|
|
234
|
+
- Oliver, D. (2004). *Basketball on Paper*. Potomac Books.
|
|
235
|
+
- Kubatko, J., Oliver, D., Pelton, K., & Rosenbaum, D. (2007). "A Starting
|
|
236
|
+
Point for Analyzing Basketball Statistics." *JQAS* 3(3).
|
|
237
|
+
- Myers, D. (2020). "About Box Plus/Minus (BPM)." Basketball-Reference.
|
|
238
|
+
- NBA.com Advanced Stats glossary. "Performance Impact Estimator (PIE)."
|
|
239
|
+
- Sill, J. (2010). "Improved NBA Adjusted +/− Using Regularization and
|
|
240
|
+
Out-of-Sample Testing." MIT Sloan Sports Analytics Conference.
|
|
241
|
+
- Rosenbaum, D. (2004). "Measuring How NBA Players Help Their Teams Win."
|
|
242
|
+
- Pelton, K. "Explaining Kevin Pelton's NBA draft projection system." ESPN.
|
|
243
|
+
- Vashro, L. (2014). "How Do We Assess 'Potential' Among NBA Draft
|
|
244
|
+
Prospects?" Canis Hoopus.
|
|
245
|
+
- Cheng, C. (2020). "Scouting NBA Three-Point Shooting." Harvard Sports
|
|
246
|
+
Analysis Collective.
|
|
247
|
+
- Blackport, D. (2014). "How Long Does It Take For Three Point Shooting To
|
|
248
|
+
Stabilize?" Nylon Calculus.
|
|
249
|
+
- Vaci, N., Cocić, D., Gula, B., & Bilalić, M. (2019). "Large data and
|
|
250
|
+
Bayesian modeling: aging curves of NBA players." *Behavior Research Methods*
|
|
251
|
+
51(4).
|
|
252
|
+
- Cui, Y., et al. (2019). "Key Anthropometric and Physical Determinants…NBA
|
|
253
|
+
Draft Combine." *Frontiers in Psychology*.
|
|
254
|
+
- Efron, B. & Morris, C. (1975). "Data Analysis Using Stein's Estimator."
|
|
255
|
+
*JASA* 70(350).
|
|
256
|
+
- Casella, G. (1985). "An Introduction to Empirical Bayes Data Analysis."
|
|
257
|
+
*The American Statistician* 39(2), 83-87.
|
|
258
|
+
- Brown, L. D. (2008). "In-season prediction of batting averages: A field
|
|
259
|
+
test of empirical Bayes and Bayes methodologies." *Annals of Applied
|
|
260
|
+
Statistics* 2(1).
|
|
261
|
+
- Gelman, A. & Hill, J. (2007). *Data Analysis Using Regression and
|
|
262
|
+
Multilevel/Hierarchical Models*. Cambridge University Press.
|
|
263
|
+
- Morris, C. N. (1983). "Parametric Empirical Bayes Inference: Theory and
|
|
264
|
+
Applications." *JASA* 78(381), 47-55.
|
|
265
|
+
- Searle, S.R., Casella, G., & McCulloch, C.E. (1992). *Variance
|
|
266
|
+
Components*. Wiley.
|
|
267
|
+
- Jolliffe, I.T. (2002). *Principal Component Analysis* (2nd ed.). Springer
|
|
268
|
+
Series in Statistics.
|
|
269
|
+
- Alagappan, M. (2012). "From 5 to 13: Redefining the Positions in
|
|
270
|
+
Basketball." MIT Sloan Sports Analytics Conference.
|
|
271
|
+
- Dempster, A.P., Laird, N.M. & Rubin, D.B. (1977). "Maximum Likelihood
|
|
272
|
+
from Incomplete Data via the EM Algorithm." *JRSS Series B* 39(1), 1-38.
|
|
273
|
+
- Mahalanobis, P.C. (1936). "On the Generalised Distance in Statistics."
|
|
274
|
+
Proceedings of the National Institute of Sciences of India, 2(1), 49-55.
|
|
275
|
+
- Ledoit, O. & Wolf, M. (2004). "A well-conditioned estimator for
|
|
276
|
+
large-dimensional covariance matrices." Journal of Multivariate
|
|
277
|
+
Analysis, 88(2), 365-411.
|
|
278
|
+
</content>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mclovin"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Trustworthy features from messy tabular data: schema resolution, provenance tiers, small-sample correction and leakage-safe panel features."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "SidharthJoly" }]
|
|
14
|
+
keywords = [
|
|
15
|
+
"feature-engineering", "data-quality", "panel-data", "time-series",
|
|
16
|
+
"empirical-bayes", "shrinkage", "provenance", "schema-matching",
|
|
17
|
+
"sports-analytics",
|
|
18
|
+
]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 4 - Beta",
|
|
21
|
+
"Intended Audience :: Science/Research",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"pandas>=1.5",
|
|
28
|
+
"numpy>=1.23",
|
|
29
|
+
"scipy>=1.9",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
survival = ["lifelines>=0.27"]
|
|
34
|
+
models = ["scikit-learn>=1.0"]
|
|
35
|
+
dev = ["pytest>=7.0", "pytest-cov>=4.0", "build", "twine", "mypy>=1.0", "pandas-stubs", "scipy-stubs; python_version >= '3.10'", "lifelines>=0.27", "scikit-learn>=1.0"]
|
|
36
|
+
docs = ["sphinx>=7.0", "furo", "myst-parser>=2.0", "sphinx-autodoc-typehints"]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Repository = "https://github.com/SidharthJoly/36120-26SP-group10-25664929-package"
|
|
40
|
+
Documentation = "https://mclovin.readthedocs.io/"
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.packages.find]
|
|
43
|
+
where = ["src"]
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.package-data]
|
|
46
|
+
mclovin = ["py.typed"]
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
testpaths = ["tests"]
|
mclovin-1.0.0/setup.cfg
ADDED