Don’t Condition on a Collider
You add an upsell to checkout. Revenue per buyer jumps +$14 — exactly the win you hoped for. But more than half of that jump is an illusion created by who stopped buying, and revenue per visitor actually fell. You’ve conditioned on an in-experiment collider, and the metric is lying to you.
You A/B-test a checkout with an upsell step (treatment) against the plain checkout (control), splitting visitors at random. You expect a tradeoff: the extra step adds friction, so conversion dips, but the people who do buy add the upsell, so revenue per buyer rises. The bet is that the per-buyer gain outweighs the conversion loss. To check, you do the natural thing — compare revenue per buyer across the two arms. That comparison conditions on “purchased,” a collider, and it doesn’t just measure your upsell: it quietly changes who is in each group.
Why it breaks the analysis
A shopper’s basket intent (B) drives both whether they
buy and how much they spend. The upsell fires two arrows at once: it
genuinely lifts each buyer’s basket (a real positive effect on order
value) and it cuts conversion (friction, a negative effect on
“purchased”) — and the friction scares off the small-basket,
price-sensitive shoppers first. So the buyers it drives away are the
low spenders, which means the buyers left in the treatment arm
are a richer crowd than control’s. Condition on “purchased” — restrict to
buyers — and you open a path that randomization had severed
(T → S ← B, where S is the collider). The observed gap stops
being the upsell’s effect alone:
revenue-per-buyer(upsell | bought) − revenue-per-buyer(plain | bought)
= real upsell effect + ( basket(upsell | bought) − basket(plain | bought) )
└──────────────── selection bias ───────────────┘ In the demo the real per-buyer lift is +$7, but the gap among purchasers reads +$14 — the extra ~$7 is pure selection bias from the low spenders bailing out. Meanwhile the metric you actually care about, revenue per visitor (which counts the non-buyers as $0 and is collider-free), has slipped below zero. Worse, this is bias, not noise: more traffic just tightens the confidence interval around the wrong number.
Drag the slider below to set the upsell’s friction — the share of would-be buyers it scares off. At 0% it adds no friction, so the revenue-per-buyer gap is the honest +$7. Push it up and conversion falls, the small baskets drop out, the per-buyer gap inflates, and revenue per visitor goes red.
How to catch it
Judge the test on revenue per visitor over all randomized visitors (non-buyers counted as $0) — it integrates the conversion-versus-revenue-per-buyer tradeoff and conditions on nothing, so the collider can’t touch it. Run a Sample Ratio Mismatch (SRM) test on the “buyers” filter: the upsell changed the share of visitors who became buyers, which flags the segment as post-randomization and any within-segment comparison as invalid. And only ever segment on attributes fixed before assignment.
Sources
This pattern, and the survey-response example it generalizes, are drawn from Causal Inference in Python (M. Facure, “Conditioning on a Collider”); Trustworthy Online Controlled Experiments (Kohavi, Tang & Xu — survivorship bias and intention-to-treat); and Metric Interpretation Pitfalls in Online Controlled Experiments (KDD — segment only on conditions not affected by the treatment, verified with a per-segment SRM test).