Causal Inference 05: Measuring One Experiment
Reading a lift test honestly is a skill, not a formality. Notebooks 00–04 pushed
observational modeling as far as it goes and hit the same wall every time — a residual
the data cannot settle, culminating in the
latent-confounder wall of notebook 04.
The cure is an experiment: randomization severs every back-door by construction.
But an experiment only buys truth if it's read honestly — and the field is
littered with lift tests that weren't. This page mirrors
nbs/causal/causal_05_measuring_one_experiment.ipynb: Veranda Home, a
national home & garden retailer, has ten test markets and wants to measure
Search — the channel every observational rung kept over-crediting.
Before a single dollar moves, three pieces of homework, all on pre-period data,
all pure pandas — no MCMC, no MMM.
The three failure modes this page is built to prevent: treatment markets picked by convenience (the big ones), not by matching; significance declared with a t-test whose assumptions the data violates; and estimators chosen after seeing the data, because one of them "found" lift. Every claim below is graded — the synthetic world's answer key stays sealed until the notebook's end.
1 — Ten markets, one question
The brand at DMA grain (the geo_heterogeneous world from
src/mmm_framework/synth/): 10 markets × 130 weeks of
weekly sales, each market with its own baseline, budget share, and — because this
world is honest about geography — its own true regional effectiveness. The question:
what is Search's incremental ROAS, really?
2 — Design: matched pairs, then a coin flip
geo_lift_design builds the treatment geometry a referee would accept:
- Matched pairs — markets paired by pre-period KPI similarity (residualized against spend), so each treated market carries its own counterfactual twin;
- Randomization inside each pair — a coin flip decides which twin gets treated, severing any lingering association between assignment and demand;
- Placebo-calibrated power — the design's minimum detectable effect comes from re-running the analysis on 123 pre-period placebo windows, not from a textbook variance formula.
The measured design: 5 matched randomized pairs (Boston–Atlanta, Dallas–Minneapolis, Denver–Chicago, Seattle–Portland, Miami–Phoenix), an 8-week holdout, design MDE ≈ 0.27 ROAS at a readout SE of 0.095.
from mmm_framework.planning.design import design_options, geo_lift_design
from mmm_framework.planning.simulation import (
build_sim_panel, build_geo_assignment, run_aa_simulation,
run_ab_simulation, fixed_lift_injector, methodology_leaderboard,
pooled_did_estimator, per_pair_did_estimator, regadj_geo_estimator,
)
# 1. Design: matched randomized pairs + placebo-calibrated MDE
opts = design_options("veranda_geo.csv", "Sales", "Search")
design = geo_lift_design("veranda_geo.csv", "Sales", "Search",
design="holdout", duration=8, seed=42)
# 2. A/A: each estimator's REAL false-positive rate, measured on null windows
panel = build_sim_panel("veranda_geo.csv", "Sales", "Search")
assignment = build_geo_assignment(panel, seed=42)
aa = run_aa_simulation(panel, regadj_geo_estimator, assignment,
duration=8, seed=42, name="regression-adjusted")
# 3. A/B: inject known lifts into real history, measure empirical power
injector = fixed_lift_injector(panel, assignment, duration=8,
pct_of_baseline=0.10)
ab = run_ab_simulation(panel, pooled_did_estimator, assignment, injector,
duration=8, aa_result=aa, seed=42, name="pooled DiD")
# 4. One honest recommendation: validity first, then power, then cost
lb = methodology_leaderboard("veranda_geo.csv", "Sales", "Search",
duration=8, seed=42)
3 — The A/A discipline: test your test on nothing
Before trusting any estimator with a real experiment, run it on null data: 115 rolling windows in which no experiment happened, asking how often it cries "significant!" anyway. Weekly KPI series are autocorrelated — adjacent weeks share trend, season, and demand shocks — so the effective sample size is far below the nominal one, and naive standard errors are too small. Three candidate estimators, same frozen assignment. The DiD estimand for a treated–control pair:
\[ \hat{\tau} \;=\; \bigl(\bar{Y}_{T,\text{test}} - \bar{Y}_{T,\text{pre}}\bigr) \;-\; \bigl(\bar{Y}_{C,\text{test}} - \bar{Y}_{C,\text{pre}}\bigr) \]
Measured — nbs/artifacts/causal_05_methodology.json,
115 null windows per estimator. Naive t-test false-positive rates: pooled DiD
0.9%, per-pair DiD 8.7%, regression-adjusted
26.1% — the most "sophisticated" of the three is the one that lies,
at more than five times the nominal 5%. The design-calibrated critical value (the
empirical 95th percentile of |placebo estimates|) restores 5.2% for
every estimator, by construction.
⚠️ Sophistication is not validity
The regression-adjusted estimator is flagged inflated and
invalid on this panel. Nothing about its extra covariates makes
it wrong in general — but on this data, its naive decision rule
false-positives ~26% of the time at nominal 5%. Calibrate on placebo history,
pre-register the critical value, then run the test.
4 — Power against injected truth
The A/A test protects against false positives; the A/B simulation measures power against false negatives. Take real history, inject a known lift into the treated markets across a grid of effect sizes, and count how often each (calibrated) estimator detects it. Because the injected lift is known, this is the estimator graded against a sealed key — the same epistemics as the rest of the series, at simulation speed.
Measured — injected-truth power curves for the two calibrated DiD estimators (their curves coincide on this panel). At a quarter of the expected effect, power is 69%; from half the expected effect upward, both detect the planted lift every time. At the expected effect size — a 10%-of-baseline lift — power = 100% for both, comfortably past the 80% bar.
5 — The leaderboard: one honest recommendation
methodology_leaderboard runs the full A/A + A/B battery for every
estimator the data supports, on one frozen assignment, and ranks by
validity first (honest false-positive rate), then power, then cost.
The data supports three designs (geo_lift,
matched_market_did, national_flighting; recommended:
geo_lift), and the estimator ranking lands:
| estimator | valid size? | naive FPR (null data) | power at expected effect | verdict |
|---|---|---|---|---|
| pooled DiD | ✓ | 0.9% | 100% | recommended |
| per-pair DiD | ✓ | 8.7% | 100% | valid runner-up |
| regression-adjusted | ✗ | 26.1% | — | disqualified on A/A size |
Two caveats the leaderboard itself reports: sliding placebo windows overlap, so the rates are indicative rather than independent Bernoulli trials; and the false-positive rate is measured against the analytic decision rule — the calibrated critical value is what restores nominal size.
6 — What a pre-registration now says
The homework above compresses to five lines, written before the test:
Estimand: incremental Search ROAS, 8-week window, treated DMAs.
Design: 5 matched randomized pairs (holdout).
Estimator: pooled DiD.
Decision rule: effect declared iff |estimate| > the placebo-calibrated 95th-percentile critical value.
Power: >80% at the expected effect; MDE 0.27 ROAS as designed.
Every choice was made on pre-period data — nothing was picked because it "found lift". That discipline is what makes the number an experiment produces worth folding into a model, which is exactly what notebook 06 does next.
Check your understanding
Commit to an answer before opening each one.
Why does the most sophisticated estimator have the worst false-positive rate on null data?
Weekly KPI series are autocorrelated — adjacent weeks share trend, season, and demand shocks — so the effective sample size is far below the nominal one and naive standard errors come out too small. The regression-adjusted estimator compounds this: its extra structure gives it more ways to fit noise in any particular window, and its analytic decision rule doesn't know that. Measured on 115 null windows it declares a nonexistent effect 26.1% of the time at nominal 5%, versus 0.9% for pooled DiD. Sophistication is not validity — the A/A test is the only way to find out which is which on your panel.
What does the placebo-calibrated critical value do that a textbook variance formula cannot?
It replaces an assumption with a measurement. Instead of deriving the null distribution from independence assumptions the data violates, it re-runs the estimator on hundreds of pre-period windows where no experiment happened and takes the empirical 95th percentile of |placebo estimates| as the bar. That makes the test honest by construction, whatever the panel's autocorrelation structure — measured here, it restores a 5.2% false-positive rate for all three estimators, including the one whose naive rate was 26%.
Why must the estimator be chosen before the experiment runs?
If three estimators are available and you pick the one that "found lift" after the fact, your true false-positive rate is roughly the union of the three individual rates — even three honest 5% tests give you far more than 5%, and with an invalid 26% member in the menu the post-hoc pick is nearly guaranteed to find something. Pre-registering the design, estimator, and critical value on pre-period data is what makes the eventual readout an experiment rather than a story.
💡 Run it yourself
This page mirrors
nbs/causal/causal_05_measuring_one_experiment.ipynb
— notebook 5 of 11 in the Causal Inference in Practice series (brand:
Veranda Home; synthetic worlds with sealed answer keys; every claim graded by an
assert). All numbers from nbs/artifacts/causal_05_methodology.json,
produced by nbs/causal/causal_05_measuring_one_experiment.ipynb.
Previous: 04 · Latent Confounders.
Next: 06 · Calibrating the
Model, where the readout this design produces meets the confounded MMM.
Source: github.com/redam94/mmm-framework.