Who draws the whistle? An item-response model of NBA foul calls
Difficulty
★★★★★
Time
5-6 hours
Data
NBA Last Two Minute reports 2015-2021: 46,861 reviewed plays with the player who (may have) committed a foul, the player who was disadvantaged, and the league's verdict
Skills
Item-response (Rasch) models · crossed random effects with ~1,600 parameters · identifiability and location invariance · centred vs non-centred geometry · coords/dims with large index arrays · nutpie · group-level predictors · stratified posterior predictive checks · shrinkage · decisions about ranks
Close NBA games are decided at the free-throw line. A team's analytics group is preparing
for the trade deadline and asks two questions:
Which players are best at drawing fouls late in close games, and which defenders are
best at not being whistled?
Both answers must be separated from the matchup: a guard who is always defended by
foul-prone opponents looks good for free, and a centre who only ever guards stars looks
bad for free.
Their intern ranked players by raw foul rate. The top of the list is a set of players
nobody has heard of, each at 100%. They would like something better - and they want to know
how far to trust any ranking at all.
Since 2015 the league has reviewed every material play in the last two minutes of close
games and published its verdict. Each row of the data is one reviewed play.
Each task states what to deliver, not how. Write your code in the YOUR CODE HERE cells.
Stuck? h.hint("task2") reveals hints one level at a time: nudge → approach → code skeleton.
Try to get by on nudges.
h.check("task2", sigma_theta=...) compares your numbers with the reference solution.
A full worked solution lives in notebooks/solutions/. Open it only when you are done (or truly stuck).
The models here have about 1,600 parameters and 47,000 observations.
pm.sample(nuts_sampler="nutpie") fits each in roughly a minute; budget accordingly.
nba_fouls: Foul calls / non-calls in close games, with committing and disadvantaged player.
source: NBA Last Two Minute reports 2015-2021, via pymc-examples.
url: https://raw.githubusercontent.com/pymc-devs/pymc-examples/main/examples/data/item_response_nba.csv
committing
disadvantaged
decision
committing_position
disadvantaged_position
play_id
1
Avery Bradley
Stephen Curry
CNC
G
G
3
Isaiah Thomas
Andre Iguodala
CNC
G
G-F
4
Jae Crowder
Harrison Barnes
INC
F
F
6
Draymond Green
Isaiah Thomas
CC
F
G
7
Avery Bradley
Stephen Curry
CC
G
G
fouls.decision.value_counts()
decision
CNC 33401
CC 10498
INC 2540
IC 422
Name: count, dtype: int64
#Task 0 · Define the outcome, then look at raw rates
Deliver
A binary outcome built from decision, with a justification: the four codes allow more
than one defensible definition - which one answers the team's question?
The intern's table: the ten players with the highest raw rate of drawing fouls, with the
number of plays each rate is based on.
A plot of raw foul-drawing rate against number of plays for every player, and one
sentence on why ranking by raw rate is a bad idea.
Item-response theory was built for exams: the probability that student $i$ answers
question $j$ correctly depends on the student's ability minus the question's difficulty.
Here the "student" is the disadvantaged player and the "question" is the committing player:
This model, fitted to all plays, with named dimensions for the two sets of players.
Diagnostics for the five top-level parameters. Something is badly wrong. Show with a
plot which direction in parameter space the sampler cannot resolve.
Find a function of $\mu, \mu_\theta, \mu_b$ that is well determined - report its
posterior mean and r_hat - and explain in two sentences what the data can and cannot
tell you in this model.
An identified version of the model, with a justification of the constraint you chose
(there is more than one), and a prior predictive check on the probability scale.
Fits of both a centred and a non-centred parameterisation of the player effects.
Compare them on divergences and on the effective sample size of $\mu$, $\sigma_\theta$
and $\sigma_b$, and choose one.
An explanation of the difference in terms of the data: which players favour which
parameterisation, and how many of each are there? Show the relevant posterior geometry
for one player of each kind.
Guards handle the ball and attack the rim; centres set screens and protect it. Give both
skills a group-level predictor: a player's effect is drawn around the mean of his
position.
Deliver
The model with position means for both $\theta$ and $b$, identified, with clean
diagnostics.
The posterior of the difference between guards (G) and centres (C) in foul-drawing
skill, and the same for $b$. Do guards draw more fouls than centres?
What happens to $\sigma_\theta$ and $\sigma_b$ compared with Task 2, and what does that
tell you? Be careful about what a play is in this dataset before you interpret the
position effects on $b$.
The model is additive on the logit scale: a guard's skill is the same whether a guard or a
centre is defending him. That is a strong claim with 49 position pairings to test it on.
Deliver a posterior predictive check of the foul-call rate for every pair of
(disadvantaged position, committing position), for the models of Task 2 and Task 3, with
a verdict: is either model adequate for the team's purpose? Where, if anywhere, does
additivity fail?
For every player, the model's estimate of his foul-drawing rate against an average
defender, plotted against his raw rate, with the number of plays visible in the plot.
The top ten by raw rate next to the top ten by posterior mean. How many plays does the
least-observed player in each list have?
Two sentences for the intern on what the model did to the 100% players, and why that is
not "throwing data away".
A posterior mean is still a point estimate, and the front office will read a ranked list as
if rank 3 were better than rank 7.
Deliver
For the leading candidates: the posterior probability of being among the ten best
foul-drawers in the league, and a 94% interval for each player's rank. Is anybody
in the top ten with probability above one half?
The same treatment, briefly, for defenders who avoid the whistle.
A specific matchup: James Harden has the ball and Rudy Gobert is defending. The
posterior of P(foul called) with an interval - and the same with an average centre in
place of Gobert, to show what the defender is worth.
Three sentences for the front office on how to use (and not use) the list.
One player, two skills. Most players appear in both roles. Give each player a
bivariate effect $(\theta_i, b_i)$ with an LKJCholeskyCov prior. Are good foul-drawers
also whistle-prone defenders?
Matchup interactions. Task 4 tests additivity only at the position level. Add a
position-pair interaction (ZeroSumNormal over two dims) and compare with LOO. Is it
worth 49 more parameters?
Referee error instead of whistles. Refit with the outcome "the call was wrong"
(IC or INC). Do some players get the benefit of the doubt?
Does skill drift? The file has no dates, but the reports do. Get the season for each
play from the original L2M data and let $\theta$ follow a random walk across seasons.
h.progress()
C10: 0/21 hints used, 11/11 checks passed.
Reference solution
A complete, executed solution with commentary on every task — including the ways the obvious model fails. Try the hint ladder first.