Shot Taxonomy Profiles
The shot-taxonomy mart turns canonical play-by-play attempts into a stable historical player-season profile. It is a reusable data contract for player bios, future shot-ecosystem context models, and profile-token neural models; it is not itself a player-value model.
Canonical Families
The NBA's detailed event_subtype vocabulary changes across sources and eras.
The first release deliberately uses only three robust families:
| Family | Canonical definition |
|---|---|
rim |
Two-point subtype contains layup, dunk, tip, finger roll, or alley-oop. |
non_rim_two |
Every other two-point event, including jumpers, hooks, and floaters. |
three |
Every canonical three-point event. |
No shot is discarded because its detailed subtype is unfamiliar: a two-point
event that does not match the rim rule belongs to non_rim_two. The output
also retains a season-by-subtype coverage table so the mapping remains
auditable as future source data is added.
Player-Season Profiles
For player \(i\), family \(k\), and on-court possession count \(n_i\), raw attempt volume is recorded as \(A_{ik}\). The stabilized attempt rate is:
where \(\overline a_k\) is the completed season's possession-weighted league rate. This prevents a low-exposure player from having an extreme rate solely because of a tiny denominator while preserving raw attempts for inspection.
Finishing uses a 75-attempt beta-binomial equivalent shrinkage:
The raw field-goal percentage remains nullable when a player has no attempts; the shrunk percentage is always available. All rates use reconstructed on-court possessions, never minutes or per-36 scaling.
Forward Use
The mart is historical. A target-season model uses the immediately preceding player-season row as its profile. It never reads target-season shot outcomes. Players without a prior season are explicitly visible through missing prior rows; a later cold-start profile model will decide how to blend draft/cohort and replacement profiles.
Storage
data/analytical/shot_taxonomy/
_manifest.json
player_season_shot_profiles.parquet
season_shot_taxonomy_coverage.parquet
subtype_shot_taxonomy_coverage.parquet
league_shot_taxonomy_references.parquet
The manifest hashes the validated player-season panel and every season's curated event manifest, as well as the output files and smoothing contract.
Current Historical Build
The current build covers 30 regular seasons from 1996-97 through 2025-26, with 5,773,713 source shot events and 14,562 player-season rows. Outcome and player attribution coverage are effectively complete:
| Check | Coverage |
|---|---|
| Final made/missed result recorded | 99.9943% |
| Final-result shots with a player ID | 100.0000% |
| Player-attributed shots matched to the player-season panel | 99.9997% |
The 331 source shots without a final result are all from 2019-20 and are excluded from attempt and make totals rather than silently counted as misses. Nineteen 1996-97 player-attributed shots do not match a panel row; every other season has complete player-panel matching.
The broad taxonomy remains stable despite detailed subtype drift:
| Season | Rim shots | Non-rim twos | Threes |
|---|---|---|---|
| 1996-97 | 65,938 | 78,496 | 38,798 |
| 2015-16 | 62,310 | 85,693 | 58,877 |
| 2025-26 | 71,259 | 56,931 | 90,968 |
For example, modern V3 rows are mostly Layup, DUNK, Jump Shot, and
Hook, while older data distinguishes driving, reverse, running, and other
specific variants. The same canonical family mapping handles both forms.
Build
uv run nba-build-shot-taxonomy
See Build shot taxonomy for the command and validation workflow.