Causal Inference 07: Many Experiments

Evidence composes — and a portfolio needs bookkeeping. Notebook 06 repaired one channel with one experiment. A real measurement program accumulates a portfolio: readouts of different channels, windows, designs and vintages — plus the occasional inherited "study" of dubious provenance. This page mirrors nbs/causal/causal_07_many_experiments.ipynb: three very different pieces of evidence folded into Veranda Home's realistic seven-channel model, each one graded against the sealed key. Three lessons: experiments compose; a test on one twin of a collinear pair resolves the split but not the sum; and the portfolio's most dangerous member is the confident wrong one.

−74%
Radio 90% interval width after one go-dark; point error −36% → −10%
−56%
Print point error after the Radio test (was −16%) — the untested twin inherits the pair-sum's bias
−54%
Display interval width from an off-panel holdout the model's window never saw
z = 2.8
tension between the legacy Search claim and the posterior — merged anyway, Search goes +8% → +26%

1 — The baseline and its honest ignorance

The realistic seven-channel world's causal fit, reloaded from cache. The ridge: Radio and Print flighted in lockstep (spend correlation ≈ 0.996), so the data pins their combined effect while each channel's own interval stays enormous — 90% interval widths of 3,871 (Radio) and 4,034 (Print) KPI units, against true contributions of 3,301 and 2,097. Points: Radio −36%, Print −16%, both covered by their wide intervals. And a detail that matters later: the pair-sum itself is under-read — combined estimate 3,853 vs true 5,398, −29%. The observational data got the total wrong, not just the split.

2 — Experiment 1: the Radio go-dark

Ten dark weeks for Radio (window 2022-09-26 → 2022-11-28), read with notebook 05's discipline: window ROAS 0.94 ± 0.10 (the world's true window ROAS: 0.94 — a well-run test). Folded in via the likelihood route and re-graded:

Measurednbs/artifacts/causal_07_portfolio.json. The tested channel is repaired: Radio lands at −10% (from −36%) and its interval collapses by 74%. The twin moves too — Print's interval shrinks 43%, because resolving Radio resolves the split of the pinned pair-sum. But the honest part most case studies omit: Print's point estimate got worse, −16% → −56%. The data had pinned the pair-sum ~30% low; the experiment fixed Radio's share of it, so the whole sum deficit now lands on Print — with more confidence.

⚠️ One test resolves a collinear pair's SPLIT, not its SUM

The pair-sum stays whatever the observational data said it was — here, ~30% under-read. The untested twin just became the program's top re-test priority. Budget the second test, or design one that moves both channels (notebook 08).

3 — Experiment 2: off-panel Display

The Display holdout ran after the modeling window closed — spring 2024, while the panel ends in late 2023. Classic grounds for "sorry, the test doesn't overlap the data, we can't use it." With off-panel calibration you can: declare the test's spend level (eval_spend, per period), duration (eval_periods), and adstock regime (cold_start — the channel ramps from zero in the test markets), and the likelihood attaches to the channel's global response curve evaluated at that spend. No training rows involved, so window overlap is unnecessary.

from mmm_framework.calibration import ExperimentEstimand, ExperimentMeasurement

radio_dark = ExperimentMeasurement(
    channel="Radio", test_period=("2022-09-26", "2022-11-28"),
    value=0.94, se=0.10,
    estimand=ExperimentEstimand.ROAS, distribution="normal",
    name="radio_dark_2022",
)
display_offpanel = ExperimentMeasurement(
    channel="Display", test_period=("2024-03-04", "2024-04-22"),
    value=0.59, se=0.08,
    estimand=ExperimentEstimand.ROAS, distribution="normal",
    eval_spend=45.0, eval_periods=8, eval_units=1,   # $45k/week, 8 weeks, 1 cell
    adstock_state="cold_start",                      # ramps from zero in test markets
    name="display_offpanel_2024",
)
model.add_experiment_calibration([radio_dark, display_offpanel])
results = model.fit(draws=1000, tune=1000)

Measured: at $45k/week (cold start) the world's true off-panel ROAS is 0.56; the simulated readout comes in at 0.59 ± 0.08. After the merge, Display's interval width drops 4,113 → 1,898 (−54%) and its point estimate doesn't degrade (+19% → +16%). The assumption you buy it with, stated out loud: structural stationarity — the response curve (saturation, carryover, effectiveness) is the same in spring 2024 as in the fitted window. Time-varying worlds break this; pricing evidence decay is notebook 09's job.

4 — Experiment 3: the confident wrong certificate

An inherited deck claims Search ROAS 0.95 ± 0.10 ("agency lift study, 2021"). Before merging anything, run the tension check — compare the claimed value against the current posterior for the same estimand, combining both uncertainties:

\[ z \;=\; \frac{v_{\text{claim}} - \mu_{\text{model}}} {\sqrt{\text{SE}_{\text{claim}}^2 + \sigma_{\text{model}}^2}} \;=\; \frac{0.95 - 0.55}{\sqrt{0.10^2 + 0.11^2}} \;\approx\; 2.8 \]

A >2σ gap before merging. Policy: investigate provenance — was the estimand matched? the estimator calibrated? the market representative? Park it in the ledger as disputed. The notebook merges it anyway, as narrator, to price the mistake:

Measured — the bad certificate drags a healthy estimate away from truth: Search relative error +8% → +26%, truth pushed outside the interval — and the interval tightens while it happens (width 2,752 → 1,741). Confidently wrong, by injection. A likelihood commits — including to lies.

5 — The portfolio ledger

Every readout, its vintage, estimand, SE, and what it did to the posterior — the bookkeeping a planning engine consumes. The legacy study is excluded from the final model; it stays in the ledger as disputed.

name channel estimand vintage value ± SE status origin
radio_dark_2022 Radio avg ROAS (window) 2022-11 0.94 ± 0.10 merged pre-registered geo holdout (nb05 discipline)
display_offpanel_2024 Display avg ROAS (off-panel, cold start) 2024-04 0.59 ± 0.08 merged post-window national holdout
legacy_search_2021 Search avg ROAS (claimed) 2021-08 0.95 ± 0.10 disputed (tension z = 2.8) inherited agency deck

Measured — what each honest piece of evidence bought, as 90% interval width reduction: Radio −74% (its own go-dark), Print −43% (spillover — resolving the twin resolves the split), Display −54% (the off-panel holdout). Each merged readout repaired its target and tightened its neighborhood.

6 — Portfolio discipline

  1. Evidence composes through the likelihood — each honest readout repaired its target and tightened its neighborhood.
  2. Collinear pairs need two answers. A test on one twin resolves the split; the pair-sum stays whatever the observational data said it was. Budget the second test, or design one that moves both.
  3. Tension-check before merging. A >2σ gap between a claim and the current posterior is a provenance investigation, not a data point.
  4. Vintage matters. The 2021 study wouldn't be trustworthy even if honest — evidence decays as the world drifts. Pricing that decay is notebook 09's job.

Check your understanding

Commit to an answer before opening each one.

The Radio test was honest and accurate (readout 0.94, truth 0.94). Why did Print's point estimate get worse after merging it?

Because the observational data had pinned two things: the Radio+Print sum (about 30% below truth) and almost nothing about the split. The experiment nailed Radio's share, so the posterior kept its beliefs about the sum and reassigned the entire deficit to the untested twin: Print went −16% → −56%, with a tighter interval. Nothing malfunctioned — the model combined its evidence correctly; the observational component of that evidence was simply wrong about the sum, and no Radio test can fix a Print bias. That is why the untested twin becomes the top re-test priority.

What exactly does off-panel calibration attach the likelihood to, and what assumption does that purchase run on?

Not to any training row — the test window doesn't overlap the panel. The likelihood evaluates the channel's global response curve (the fitted saturation and adstock machinery) at the declared spend level (eval_spend) over the declared duration (eval_periods), under the declared adstock regime (cold_start). The purchase price is structural stationarity: the curve must be the same in spring 2024 as in the fitted window. State it, and revisit it when the world drifts — evidence-decay pricing is notebook 09.

The legacy study made the Search interval tighter. Why is that the alarming part?

Because width is the model's own statement of confidence. An honest merge buys narrower intervals around a better point; a poisoned merge buys narrower intervals around a worse one — Search moved +8% → +26% while its width fell 2,752 → 1,741, so the model became more sure of a more wrong number, and the truth left the interval. A likelihood commits, including to lies. The z = 2.8 tension check was the smoke detector: fired before the merge, it routes the claim to provenance review instead of the graph.

💡 Run it yourself

This page mirrors nbs/causal/causal_07_many_experiments.ipynb — notebook 7 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_07_portfolio.json, produced by nbs/causal/causal_07_many_experiments.ipynb. Previous: 06 · Calibrating the Model. Next: 08 · Designing the Next Experiment — stop reading tests and start choosing them: model-anchored power, the short-term cost of learning, and a Pareto frontier of designs. Source: github.com/redam94/mmm-framework.