Challenges · C11 · ★★★★★

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

The brief

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.

How this notebook works

  • 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).
import logging
import time

import arviz as az
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import pymc as pm
import pytensor.tensor as pt
import xarray as xr
from scipy import stats

from pymc_challenges import Hints, data

RANDOM_SEED = 1996
rng = np.random.default_rng(RANDOM_SEED)
az.style.use("arviz-variat")
logging.getLogger("pymc").setLevel(logging.WARNING)  # several fits: no sampler banner per fit

h = 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))
  • task5 - The clinic's rule (3 hints, 3 check(s))

The data

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)
age, wtkg age (years), weight (kg)
str2 prior antiretroviral therapy (1 = experienced, 0 = naive)
symptom 1 = symptomatic HIV infection at baseline
karnof Karnofsky performance score (100 = normal activity)
gender, race 1 = male; 1 = non-white
homo, drugs, hemo homosexual activity, injection drug use, haemophilia (1 = yes)
offtrt, time, cid post-randomisation: stopped treatment, follow-up days, clinical event - do not use
data.describe("actg175")
actg = data.load("actg175")
trial = actg[actg.trt.isin([0, 1])].copy()
print(trial.trt.value_counts().sort_index().rename({0: "ZDV", 1: "ZDV + ddI"}))
trial.describe().T.round(1)
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
count mean std min 25% 50% 75% max
cid 1054.0 0.3 0.4 0.0 0.0 0.0 1.0 1.0
time 1054.0 858.2 302.9 33.0 647.2 989.0 1090.0 1231.0
trt 1054.0 0.5 0.5 0.0 0.0 0.0 1.0 1.0
age 1054.0 35.2 8.8 12.0 29.0 34.0 40.0 70.0
wtkg 1054.0 75.5 13.4 31.0 67.1 74.5 83.0 159.9
hemo 1054.0 0.1 0.3 0.0 0.0 0.0 0.0 1.0
homo 1054.0 0.7 0.5 0.0 0.0 1.0 1.0 1.0
drugs 1054.0 0.1 0.3 0.0 0.0 0.0 0.0 1.0
karnof 1054.0 95.5 5.9 70.0 90.0 100.0 100.0 100.0
oprior 1054.0 0.0 0.2 0.0 0.0 0.0 0.0 1.0
z30 1054.0 0.5 0.5 0.0 0.0 1.0 1.0 1.0
zprior 1054.0 1.0 0.0 1.0 1.0 1.0 1.0 1.0
preanti 1054.0 376.8 468.5 0.0 0.0 123.5 728.0 2500.0
race 1054.0 0.3 0.4 0.0 0.0 0.0 1.0 1.0
gender 1054.0 0.8 0.4 0.0 1.0 1.0 1.0 1.0
str2 1054.0 0.6 0.5 0.0 0.0 1.0 1.0 1.0
strat 1054.0 2.0 0.9 1.0 1.0 2.0 3.0 3.0
symptom 1054.0 0.2 0.4 0.0 0.0 0.0 0.0 1.0
treat 1054.0 0.5 0.5 0.0 0.0 0.0 1.0 1.0
offtrt 1054.0 0.4 0.5 0.0 0.0 0.0 1.0 1.0
cd40 1054.0 351.0 122.3 0.0 264.2 340.0 423.8 1199.0
cd420 1054.0 369.3 147.9 49.0 264.2 352.5 459.0 1119.0
cd80 1054.0 995.7 481.4 40.0 654.0 894.5 1214.5 4255.0
cd820 1054.0 947.9 439.3 124.0 637.0 866.0 1179.8 3407.0

Task 0 · Look at the trial

Deliver

  1. A plot of CD4 at 20 weeks against baseline CD4, by arm, on the scale you will model. Why that scale?
  2. The records that cannot be right, and what you do with them.
  3. The distribution of the change from baseline in each arm: centre, spread, and shape. What would a regression's error distribution have to look like?
Your turn Work this task in the notebook.

Task 1 · One regression for everybody

Deliver

  1. 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.
  2. The treatment effect as a percentage change in CD4, with a 94% interval.
  3. 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

  1. 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.
  2. Evidence that the sampler worked. Decide which quantities it is meaningful to diagnose.
  3. 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?
  4. The average treatment effect, and how different the subgroups' treatment effects are.
Your turn Work this task in the notebook.

Task 3 · The colleague's responder list

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

  1. Her numbers, for every patient in both arms.
  2. 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.
  3. 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

  1. A mixture of regressions whose subgroup probabilities depend on baseline covariates (and on nothing measured after randomisation). Say which covariates, and why.
  2. 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.
  3. A plot of each patient's conditional average treatment effect (CATE) against baseline CD4, with uncertainty, next to what Task 2 said.
  4. A LOO comparison with Task 2. What does it tell you - and what can LOO not see here?
Your turn Work this task in the notebook.

Task 5 · The clinic's rule

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

  1. 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?
  2. 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?
  3. 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.

Open the worked solution →