Who needs the second drug? Hidden subgroups with their own regressions
Difficulty
★★★★★
Time
5-6 hours
Data
AIDS Clinical Trials Group Study 175 (Hammer et al. 1996): 1054 HIV-infected adults randomised to zidovudine alone or zidovudine + didanosine, CD4 counts at baseline and 20 weeks
Skills
Mixtures of regressions · truncated Dirichlet-process (stick-breaking) priors on subgroups · marginalised assignments · label-free diagnostics · spotting an analysis that peeks at the outcome · covariate-dependent mixture weights (logistic stick-breaking) · what LOO can and cannot see · conditional treatment effects and a treatment rule with uncertainty
It is 1996. ACTG 175 has just shown that adding didanosine (ddI) to zidovudine (ZDV)
slows HIV disease better than ZDV alone. But ddI is not free: it causes pancreatitis and
painful peripheral neuropathy in a minority of patients, and a clinic has to decide whom
to put on the combination. The HIV clinic you work for asks:
"Does everybody benefit, or is there a subgroup of patients for whom ddI does little and
who could be spared the toxicity? Our rule of thumb: ddI is worth it for a patient if it
raises their CD4 count after 20 weeks by at least 15%, and we want to be at least
80% sure of that."
A statistician on the team has already fitted a "clustering regression" and says it found
responders and non-responders. Your job: build the model properly - a regression whose
coefficients may differ between hidden subgroups, with a Dirichlet-process prior so that
the data decide how many subgroups there are - find out what it can and cannot say about
who benefits, and turn it into a rule the clinic can use.
This is the regression sequel to example E26 (Dirichlet-process mixtures of densities).
Do E26 first: this challenge assumes you know stick-breaking, truncation checks and why
component labels are meaningless.
Each task states what to deliver, not how. Write your code in the YOUR CODE HERE cells.
Stuck? h.hint("task3") reveals hints one level at a time: nudge → approach → code skeleton.
Try to get by on nudges.
h.check("task2", ate=...) 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).
importloggingimporttimeimportarvizasazimportmatplotlib.pyplotaspltimportnumpyasnpimportpandasaspdimportpymcaspmimportpytensor.tensorasptimportxarrayasxrfromscipyimportstatsfrompymc_challengesimportHints,dataRANDOM_SEED=1996rng=np.random.default_rng(RANDOM_SEED)az.style.use("arviz-variat")logging.getLogger("pymc").setLevel(logging.WARNING)# several fits: no sampler banner per fith=Hints("C11")h.tasks()
C11 - Who needs the second drug? Hidden subgroups with their own regressions
task0 - Look at the trial (2 hints)
task1 - One regression for everybody (3 hints, 2 check(s))
task2 - A Dirichlet-process mixture of regressions (3 hints, 2 check(s))
task3 - The colleague's responder list (3 hints, 2 check(s))
task4 - Let the baseline profile choose the subgroup (3 hints, 2 check(s))
One row per patient. We use the two arms the clinic is choosing between: trt = 0
(zidovudine alone) and trt = 1 (zidovudine + didanosine). CD4 cells are the immune cells
HIV destroys; their count per mm³ of blood is the standard marker of disease progression
(below 200 means AIDS). The trial enrolled patients with 200-500 cells/mm³.
column
meaning
cd40, cd420
CD4 count at baseline and at 20 ± 5 weeks - the outcome is cd420
cd80, cd820
CD8 count at baseline and 20 weeks (do not use cd820: it is post-treatment)
actg175: 2139 HIV-infected adults with 200-500 CD4 cells/mm3, randomised to zidovudine (ZDV, trt 0), ZDV + didanosine (trt 1), ZDV + zalcitabine (trt 2) or didanosine alone (trt 3). One row per patient: baseline covariates (age, wtkg, hemo, homo, drugs, karnof, race, gender, str2 = prior antiretroviral therapy, symptom, ...), CD4/CD8 counts at baseline (cd40, cd80) and at 20 +- 5 weeks (cd420, cd820), and time to a clinical event (time, cid).
source: Hammer et al. (1996, New England Journal of Medicine 335:1081), AIDS Clinical Trials Group Study 175; UCI Machine Learning Repository dataset 890 (doi:10.24432/C5ZG8F), the same data as the R package `speff2trial` (Juraska et al.).
url: https://archive.ics.uci.edu/static/public/890/data.csv
trt
ZDV 532
ZDV + ddI 522
Name: count, dtype: int64
A linear regression of log CD4 at 20 weeks on log baseline CD4, treatment and the other
baseline covariates, with priors you can defend on the log scale, and clean diagnostics.
The treatment effect as a percentage change in CD4, with a 94% interval.
A posterior predictive check, per arm, that shows what this model gets wrong - pick
the statistic that matters for a clinic worried about patients who deteriorate.
Your turn Work this task in the notebook.
#Task 2 · A Dirichlet-process mixture of regressions
Suppose the patients fall into hidden subgroups, each with its own regression: its own
intercept, slope on baseline CD4, treatment effect and residual spread (the other covariates
may keep shared coefficients). Nobody knows how many subgroups there are.
Deliver
That model, with a truncated Dirichlet-process (stick-breaking) prior on the subgroup
weights and the subgroup memberships marginalised. Show what your prior on the
concentration implies for the number of subgroups among these patients, and check that the
truncation does not bite.
Evidence that the sampler worked. Decide which quantities it is meaningful to diagnose.
Did it fix what Task 1 got wrong? The same PPC, and a LOO comparison with Task 1 and
with a single regression that merely has heavier tails. How much of the improvement
needs subgroups?
The average treatment effect, and how different the subgroups' treatment effects are.
The statistician on the team took your Task 2 posterior and computed, for every patient
$i$, "their own treatment effect":
$$\widehat{\text{effect}}_i = \sum_k P(\text{patient } i \in \text{subgroup } k \mid \text{data})\; c_k,$$
averaged over posterior draws. The spread across patients is real, she says: rank them, give
ddI to the top half (the "responders"), and spare the bottom half.
Deliver
Her numbers, for every patient in both arms.
What is wrong with using them to decide who gets ddI. There are two separate problems;
show each with a number or a plot, not only an argument.
What your Task 2 model predicts as the treatment effect for a new patient walking into
the clinic, as a function of their baseline covariates. Explain the result.
Your turn Work this task in the notebook.
#Task 4 · Let the baseline profile choose the subgroup
Deliver
A mixture of regressions whose subgroup probabilities depend on baseline covariates
(and on nothing measured after randomisation). Say which covariates, and why.
Evidence that the quantities you will base decisions on have converged - for every
patient, not just on average - and that your conclusions do not depend on which chain you
happen to look at.
A plot of each patient's conditional average treatment effect (CATE) against baseline CD4,
with uncertainty, next to what Task 2 said.
A LOO comparison with Task 2. What does it tell you - and what can LOO not see here?
Recall the rule: ddI is worth it for a patient if it raises their CD4 count at 20 weeks by
at least 15%, with probability at least 0.8.
Deliver
For every patient, the probability that their expected gain exceeds 15% under your Task 4
model, and the share of patients the rule would put on ddI. Who is left out? Does the
answer depend on the chain?
The same from two plain regressions: one with a treatment × baseline CD4 interaction, one
with treatment × every baseline covariate. Where do the three disagree, and why?
Your recommendation to the clinic in a few sentences, including what the analysis cannot
tell them.
Your turn Work this task in the notebook.
h.progress()
C11: 0/17 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.