The difference between getting open and finishing
Splitting offensive efficiency into Shot Selection and Shot Making, what every player profile now exposes.
Thesis
TS% is the single number basketball people quote for offensive efficiency. The problem is it answers two questions at once:
- Were the looks good? (Did the player get high-value shots (rim attempts, open corner 3s) or low-value ones (mid-range pull-ups, contested heaves)?)
- Did he finish them? (Given the looks he got, did he make more than expected, or less?)
A 60% TS% scorer who lives at the rim is doing different work than a 60% TS% scorer hitting tough mid-range pull-ups. Same number; opposite skills. We built a model that splits them.
The model
We trained a gradient-boosted classifier on every NBA shot we've ingested. Features:
- Shot location (x, y)
- Distance to rim
- The play-type bucket ESPN tags (Catch & Shoot, Pullup, Driving, Cutting, Post, Putback, Other)
- Geometric zone (16 court regions)
- 2PT vs 3PT
The classifier outputs P(made | features). Multiply by the shot's point value (2 or 3) and you get xPTS: expected points per attempt.
The features we're missing (defender distance, shot clock, dribbles before the shot, touch time) would make the model better. Those live in tracking data we don't have public access to. What we *do* have captures the dominant signal: where the shot was taken and how it was created. That's enough to do the decomposition.
The decomposition
For every player, we aggregate:
Shot Selection = mean(xPTS) # "are the looks good?"
Shot Making = mean(PTS) − xPTS # "is he finishing them?"
Both end up on the savant percentile panel as their own category. Shot Selection is in [0.8, 1.4] PPS-equivalent; Shot Making is signed (+ for over-performance, − for under-performance).
What it tells you
The split disambiguates four archetypes that all look identical on TS%:
| Archetype | TS% | Shot Selection | Shot Making | What they actually are |
|---|---|---|---|---|
| Rim-living slasher | 60% | high (1.20) | ~0 | Gets to elite spots; finishes about as expected |
| Mid-range pull-up artist | 60% | low (1.00) | high (+0.15) | Tough looks, beats them |
| Stretch-5 off-the-catch | 60% | high (1.15) | low (−0.05) | Open looks, doesn't finish all of them |
| Decoy spacer | 60% | medium (1.05) | medium (+0.05) | League-average everything |
The four players read identical on the box score. The model separates them.
When the decomposition disagrees with TS%
A player can be 90th percentile Selection AND 90th percentile Making (peak Jokic; peak Curry from the 2015-2018 stretch, both elite at *every* layer of the shot-creation chain). They can also be 90th Selection and 30th Making, a young Wemby in his first season, getting great looks because of his height advantage but still adjusting his finishing release.
These are the most useful split cases:
- Sustainable scorer: high Selection, mid-or-high Making. The looks are good, the finishing is real. Stable across seasons.
- Bound-to-regress scorer: low Selection, high Making. Hitting tough shots at unsustainable rates. The Selection slider says it: those looks aren't typically that valuable, and his elite-but-fragile finishing won't hold.
- Bound-to-improve scorer: high Selection, low Making. Gets elite looks, isn't finishing them yet. Often young or learning a new role. Likely to gain TS% next season just by regressing to his Selection mean.
- System dependent: high Selection, low Making, AND low usage. Quality role player whose offense flows from a creator (most catch-and-shoot specialists land here).
These four buckets do something TS% can't: tell you *whether* a hot-shooting player will keep being a hot-shooting player.
What it doesn't capture
The model is honest about its blind spots. Without tracking data:
- A wide-open corner 3 and a heavily contested corner 3 land in the same zone, so they get the same Selection. They shouldn't.
- An end-of-shot-clock heave is treated identically to an early-clock pull-up.
- A shot at the rim with help defenders rotated vs. no help looks the same to the model.
That's fine for the decomposition's main job, *separating* Selection from Making at the player level. The errors are mostly random-noise: they don't systematically advantage one type of player over another.
When public NBA tracking data opens up, the model gets defender distance and shot clock as features. Selection becomes meaningfully sharper, and contested-vs-open Making splits become possible. For now, what we have is the cleanest TS% decomposition you can build on play-by-play alone.
What's next
Two follow-ups make sense on top of this:
- Team Shot Quality Allowed. Flip the model around. For each defending team, aggregate xPTS against. Subtract from actual PPS allowed → defensive efficiency over expected. Lets you separate teams who defend the right *spots* (low Selection allowed) from teams whose opponents miss open looks (negative Making allowed). The model itself doesn't change; only the aggregation.
- Per-game shot quality lines. Same xPTS predictions, summed per game. Surfaces "Luka took 5 fewer xPTS shots than usual tonight and made up the difference with hot Shot Making," the kind of split that explains box-score-vs-eye-test divergences in real time.
The infrastructure for both already exists. The model is trained per-season, the predictions land on the season-stats row, and the percentile panel reads them like any other rate. Adding team-level or per-game views is now scaffolding on top of a model that's already in production.