Causal / Uplift — CUPED and Individual Treatment Effects preview
Situation
A two-sample t-test ignores the pre-period: experiments ask for more traffic than needed and retention offers go to everyone
Task
Implement CUPED and uplift modeling and prove neither is biased where the true answer is known in advance
Action
CUPED on a pre-period covariate and T-/S-learners on LightGBM; checked against ground truth with AUUC and QINI
Result
CUPED cuts the standard error 26% (5.5k users per arm instead of 10k), and a pre-registered break-even threshold of 0.05 ships the campaign to new users only.
Stack
PythonLightGBMscikit-learnpandas / NumPymatplotlibpytestuv
Source View on GitHub → Updated: Nov 1, 2026
On this page
  1. Situation
  2. Task
  3. Actions
  4. Run
  5. Result
  6. Limitations
  7. Documentation

Causal / Uplift — CUPED and Individual Treatment Effects

▶ Data-flow diagram — self-contained HTML, opens in a browser — no install, no build

Situation

A standard two-sample t-test answers only “does the treatment work on average” and ignores the pre-period. Experiments therefore ask for more traffic than they need, and retention offers go to everyone while only part of the audience responds.

The project closes both gaps: CUPED reduces variance using a pre-period covariate, and uplift modeling estimates the individual treatment effect (ITE). Both run on a synthetic randomized experiment with a known heterogeneous effect, so the estimates can be checked against ground truth.

Task

I needed the experiment to read both the average effect and who actually responds, so I owned the variance reduction and the uplift estimation, and checked both against a synthetic run with known ground truth.

Actions

Synthetic, deterministic data (seed = 42), 20,000 users in a randomized experiment:

  • segment — new (30%) / returning (70%) → heterogeneous treatment effect;
  • x_pre — a pre-period covariate correlated with the outcome (ρ ≈ 0.67);
  • treatment — 50/50, independent of everything (clean randomization);
  • tau_true — the ground-truth individual treatment effect (eval only);
  • y_cont — continuous outcome; y_bin — binary conversion.

CUPED — same ATE estimate, less variance:

Method ATE SE 95% CI
Naive 0.270 0.019 [0.232, 0.308]
CUPED 0.276 0.014 [0.247, 0.304]

Uplift — individual effects, T- and S-learners:

Model AUUC QINI uplift@20% corr(τ)
T-learner 0.0043 0.0014 0.072 0.50
S-learner 0.0057 0.0029 0.041 0.66
Random 0.0037 0.0008 0.022 −0.007

Run

uv run --with pandas --with numpy python data/generate_data.py
uv run --with pandas --with numpy --with scikit-learn --with lightgbm --with matplotlib python run.py
uv run --with pandas --with numpy --with scikit-learn --with lightgbm --with matplotlib --with pytest pytest -q

Outputs: reports/metrics.json + reports/uplift.png (QINI curves + segment uplift vs ground truth).

Result

CUPED buys power for free: no new experiment design is needed, only a better estimator on data you already collected — provided a pre-period covariate exists. The point estimate is unchanged (0.270 → 0.276, within noise), the standard error shrinks by ~26%, and the CI narrows 1.35x — an experiment that needed 10k users per arm now needs ~5.5k.

Uplift answers a different question than A/B testing: not “does the treatment work on average” but “who does it work on”. At n = 6000 the curve metrics are noise-dominated: the T-learner sits 0.81σ from the null baseline (AUUC +0.00293 ± 0.00164), the S-learner 1.72σ, and even the oracle ranking by the known true τ reaches only 2.25σ, while the random scorer sits at 0.46σ — where an independent baseline belongs, which leaves the oracle as the ceiling. What is clean is not AUUC or QINI but the segment recovery and corr(τ) = 0.50–0.66 against −0.01 for randomness. The models recovered the shape of the effect: “new” users respond 6.3–6.8x more than “returning” users (the reference gap is 8.4x) — the targeting signal a discount campaign would act on.

  • Ship rule (pre-registered): a break-even threshold of 0.05 — the campaign ships to new users only and is not shipped to returning.
Segment Truth (binary uplift) T-learner S-learner
new 0.110 0.126 0.120
returning 0.013 0.018 0.019

Limitations

The latent ground-truth τ is 0.60 (new) / 0.08 (returning), but the conversion uplift is ~0.11 / 0.01 because the sigmoid at a high baseline conversion (~71%) damps large latent effects. Comparing predicted binary uplift to latent τ would be a scale mismatch, so the report uses rank correlation (scale-free) and per-segment empirical recovery.

On real data the ITE is never observed — synthetic data with a true heterogeneous effect is the only reason recovery can be checked at all; that is the fundamental problem of causal inference, not a flaw of the method.

Documentation

Need this reasoning applied to your data?

Tell me the task — I'll come back with a plan: what data it needs, what we test, and how long it takes. I reply within a day.

Discuss a task →