Project ยท Machine learning + interactive viz

NBA Rebound Predictor

Sketch a play at the moment of a shot and the model predicts where each player moves and how likely they are to grab the rebound.

How to use it

  • Click the court to drop players in order โ€” PGโ†’C for offense, then the same for defense
  • Or pick a side and drag position bubbles onto the court โ€” 5 each. Repeats are fine.
  • Fill default lineup places all ten at once if you'd rather start from a shape
  • Drag a player to move them; drag one off the court to remove them
  • Choose the shooter when prompted, then hit Run
  • Color runs blue โ†’ red by rebound likelihood; tap a player, or read the ranked list

The demo calls /api/rebound/predict. Running locally? Start the Flask app and set window.REBOUND_CONFIG.endpoint below to http://localhost:8000/predict.

How it works

Two models run in sequence. A movement model (PyTorch) takes the pre-shot positions of all ten players and samples where they end up when the ball comes off the rim โ€” rebounding is about movement toward the ball, not just where you start. A ranker (LightGBM) then scores all ten players together and returns the probability that each one comes down with it.

The ranker scores the ten players jointly rather than one at a time, under a grouped softmax โ€” the model is told from the outset that exactly one player gets the rebound, so the probabilities are a single distribution over the group and already sum to one.

The movement model predicts a distribution over whole scenes, not one position per player. A big man might crash the glass or might leak out, and those are two different futures, not one average future somewhere in between. Each request samples a dozen complete scenes; the demo animates one of them, so re-running the same play gives you a slightly different โ€” and equally plausible โ€” result each time.

Engineered features

Twenty-seven features per player, computed by the pipeline rather than the web app, so the served features cannot drift from the trained ones. They fall into a few families:

Rebuilt in 2026

The original 2017 version was a Keras network that predicted a single post-shot point per player, feeding eight hand-built features into a scikit-learn random forest that scored each player independently; the ten scores were then divided by their sum to make them look like a distribution. Both stages have been replaced. Scoring the players jointly removed the need for that fudge, and sampling whole scenes removed an averaging artifact that parked players at the midpoint of futures they never actually took.

The rewrite also moved every feature definition into the modelling repo. The 2017 web app recomputed its own features at request time, which is exactly the kind of duplication that silently rots โ€” and had, in small ways, by the time it was rebuilt.

How well does it do?

On 5,756 held-out shots, it identifies the actual rebounder 29.7% of the time, with the right player in its top three 66.8% of the time. Ten players on the floor makes 10% the number to beat, so it is about three times better than guessing โ€” which is about right for a genuinely noisy event. A tip-out, a lucky carom or a bounce off a shoulder is not predictable from where everyone stood a second earlier, and no amount of model is going to recover it.

Telling it who is a guard and who is a centre is worth a surprising amount: without positions the same model scores 26.9%, so that one field is worth 2.8 points of top-1. That is why the court asks you to pick positions rather than just placing dots. One caveat on the number โ€” it was measured on real lineups, and a sketched five-guard lineup is not something the training data contains, so treat it as the ceiling rather than a promise. The movement model places players within 4.9 feet on average, against a mean travel of 7.5 feet.

Front-end

The court is a D3.js overlay on an SVG half-court. Clicks build a payload of player coordinates, a POST hits the Flask service, and the JSON response drives the color gradient and the transition animation, outlining the single most likely rebounder.

Read the full write-up โ†’  ยท  Source on GitHub โ†’