learn-causal-inference-with-phoebe / Practitioner session 9 of 10
Learn Causal Inference with Phoebe · Practitioner session 9 of 10

Sensitivity analysis

Present any observational estimate and one objection arrives every time: there could be something you did not measure. It is always true, it is never falsifiable, and as stated it ends the conversation without informing it. The professional move is to replace it with a question that has a number for an answer. At Kestrelmark, the correct adjustment set gives 6.56, and an unmeasured cause would have to be about 1.55 times as strongly associated with both the email and spend as customer tenure is, per standard deviation, to account for that estimate entirely. Tenure is the strongest thing anybody measured. Tonight you compute that number, and then you watch it lie.

🔴 Hardest Data scientists and analysts 45 min
0-3 · Welcome 3-22 · The objection you cannot rule out 22-40 · Build-along: how strong would it have to be 40-45 · Q&A
Part 0

What is left after identification

Sessions 2 and 3 settled which variables belong in the adjustment set and whether the effect is identified at all, given the graph. Both of those answers are conditional on a graph somebody drew, and the arrow most likely to be missing is the one from a cause nobody measured. Sensitivity analysis is what remains: not a way to remove that doubt, but a way to price it.

Live = presented in the 45 minutes Self-study = the full depth, after class 🔍 Every diagram zooms

★ What you walk out with tonight. One sentence you can put under every observational estimate you ever publish, stating the tipping point in units a reader can judge without rerunning anything. The habit of benchmarking that tipping point against the covariates you actually measured, which is what turns an argument about possibility into a comparison. And the trap, measured on the bench: the same sweep run from an adjustment set contaminated by a collider reports a comfortable-looking 1.34 for an estimate that was already 1.30 below the truth before the sweep began.

LiveWhy the objection is unanswerable as stated3 min▶

"There could be an unmeasured confounder" is a statement about the space of possible worlds, and that space is infinite. No dataset refutes it, because the variable is by definition absent from the dataset. No sample size touches it, because it is a bias rather than noise. Nobody can prove it wrong, which means nobody can be argued out of it, which means the sentence has no work to do beyond stopping the meeting.

It is also useless to whoever says it. The objector cannot say how much it would matter, so they cannot distinguish a finding that would collapse under a faint breeze from one that would need a hurricane. Both get the same objection and the same shrug. A room that cannot tell those two apart will end up either believing everything or believing nothing, and both outcomes are expensive.

Real world

Kestrelmark's finance review killed the email programme's budget increase with exactly that sentence, and approved it eleven weeks later on the same analysis plus one line: an unmeasured cause would have to be more than one and a half times as strong as customer tenure, and tenure was the strongest association in the model. Nothing in the estimate changed. What changed was that the objection had been given a size.

Self-studyWhere this sits relative to the rest of the track3 min▶
  • After identification, not instead of it. Session 3 asks whether a valid adjustment set exists in the data. Sensitivity analysis assumes you already believe one does and asks what an unnamed extra cause would have to look like to overturn the result.
  • Not a repair. Nothing here removes bias. It reports how much bias would be needed, which is a different and more honest thing to hand a reader.
  • Not an estimator. Correcting an observational comparison, by reweighting or otherwise, belongs to learn-experimentation-with-phoebe. What is built tonight sits on top of whatever estimate you already have.
  • The published families have names. You will meet them as Rosenbaum bounds, E-values, and covariate-benchmarked partial-association bounds among others. Each assumes a different shape for the unmeasured cause, and that assumption is the first thing to read in any of them.
Part 1 · covers the reframe from possibility to strength

Every observational claim has the same objection

9 min live

The objection cannot be defeated, so it has to be converted. Swap the yes-or-no question for a how-much question, and an unfalsifiable claim becomes a quantity that can be computed, benchmarked, and disagreed with productively.

LiveThe shape of the objection, from both sides3 min▶

Both sides of the argument are wrong in the same way, which is worth saying out loud because it defuses the room. The analyst who says "we controlled for everything relevant" is claiming to have enumerated the causes of human behaviour. The sceptic who says "there could be something else" is claiming that possibility alone is a reason to disbelieve. Neither statement can be checked, and the two of them can be repeated at each other indefinitely.

  • Always true. There is always something unmeasured. Spend at Kestrelmark is caused by things nobody has a column for, and always will be.
  • Always unfalsifiable. A variable you did not measure produces no evidence about itself, in either direction.
  • Therefore useless as stated. A sentence that applies identically to every observational finding ever produced cannot help anyone rank two of them.

Notice what the objection is not. It is not a general complaint about causal claims from data: a randomised assignment answers it by design, which is the reason randomisation is worth its cost. It is specifically about what happens when the assignment was made by the world instead.

LiveThe reframe that turns it into a real question4 min▶

Do not ask whether an unmeasured confounder could exist. Ask this instead:

★ The question to ask instead How strongly would an unmeasured cause have to be associated with BOTH the treatment and the outcome to move this estimate to zero, and is anything that strong plausible in this domain?

Three things change at once. The question now has a numerical answer, so it can be computed rather than asserted. It requires the confounder to do two jobs at once, associating with the treatment and with the outcome, which is a much harder bar than doing either alone and is where most speculative confounders fail. And the last clause, is anything that strong plausible, hands the judgement to whoever knows the domain, which is usually not the analyst and is exactly the person whose objection started this.

Both associations, not one. A candidate confounder that predicts spend brilliantly but has nothing to do with who received the email cannot bias the estimate at all, and neither can the reverse. In practice the bias scales with the product of the two associations, which is why doubling a confounder's overall strength does roughly four times the damage, and why a long list of one-sided candidates is not a threat.

LiveWhy the reframe is the professional standard3 min▶

Because it converts an argument about possibility into a comparison against variables you HAVE measured. Possibility has no scale and therefore no stopping rule. Measured covariates have both: they are real numbers, computed from the same data, on the same footing, and everyone in the room already accepts them.

That comparison is what a domain expert can actually rule on. Nobody has a well-formed opinion about whether an association of 0.31 is large. Plenty of people have a strong opinion about whether there is a driver of subscription spend more than one and a half times as important as how long somebody has been a customer, and if they say no, the finding has survived something. If they say yes and can name it, the finding has failed usefully, because now you know what to go and measure.

Real world

The version that goes wrong is a tipping point reported with no benchmark: "the estimate reaches zero at a confounding strength of 1.55". Nobody knows whether 1.55 is a lot. Attach it to a covariate everyone recognises and the same number becomes a sentence anyone in the business can argue with, which is the entire objective.

Part 2 · covers benchmarking, reporting, and the limits

The benchmark that makes the number meaningful

8 min live

A tipping point on its own is a number without a scale. Expressed as a multiple of the strongest thing you actually measured, it becomes a claim a domain expert can accept or refute, and it is the form the sentence should always take.

LiveBenchmarking against observed covariates3 min▶

Take the covariates already in the adjustment set and measure, for each, how strongly it is associated with the treatment and with the outcome. Those pairs are the ruler. Then express the tipping point as a multiple of the largest of them. If an unmeasured cause would need to be three times as strongly associated with spend as season is, and season is the strongest thing measured, the finding is robust: something bigger than the biggest known driver, hiding in plain sight, is a specific and unlikely object. If it only needs to be half as strong as tenure, it is not robust, because things that size are common and one of them is probably missing.

  • Compare per standard deviation, not per raw unit. At Kestrelmark, season enters spend with a coefficient of 6.0 and tenure with 5.0, so season looks stronger. Season is binary and half the customers are in winter, so one standard deviation of it moves spend by 3.0 against tenure's 5.0. Per standard deviation, tenure is the stronger benchmark, and picking the wrong one here inflates the robustness claim by more than half.
  • Benchmark on both associations at once. Tenure's pair at Kestrelmark is 0.7 into the email and 5.0 into spend. A candidate that matches tenure on one and is trivial on the other is not a tenure-strength confounder.
  • Benchmark against what is IN the model. The point of the ruler is that the reader already believes it. A covariate you excluded proves nothing about a covariate you never had.
LiveThe reporting habit: one sentence, next to the estimate3 min▶

State the tipping point alongside the estimate, in the same paragraph, in one sentence, so a reader can judge the claim without rerunning anything or opening an appendix. It costs a line and it removes the single most common reason an observational finding gets waved away.

★ The sentence, filled in for Kestrelmark Adjusting for season and tenure, receiving the re-engagement email is associated with 6.56 units of additional spend. For that estimate to reach zero, an unmeasured cause would have to be about 1.55 times as strongly associated with BOTH the email and spend as customer tenure is, per standard deviation, or 2.49 times as strongly as season. Tenure is the strongest association in the model.

Read what the sentence does not claim. It does not say no such cause exists. It does not say the estimate is correct. It says precisely how large the missing thing would have to be, in a unit the reader already has intuition for, and then it stops and lets them decide. That restraint is why it survives review.

LiveThe honest limits, and the one that matters most4 min▶
  • It assumes a shape. Every sensitivity method fixes a form for the unmeasured cause: one variable rather than several, additive rather than interacting, the same strength for everybody. The tipping point is correct for that shape and silent about others. Two methods disagreeing on the same data usually means they assumed different shapes, not that one is broken.
  • It cannot rule anything out. A tipping point of 1.55 tenures does not mean no such confounder exists. It means one would have to be that big, and somebody who knows the domain still has to say whether that is plausible.
  • It says nothing about the arrows you drew wrong. This is the one that costs people their conclusions. Sensitivity analysis answers a question about missing confounding. It has no opinion about a variable that should never have been in the model at all.
  • A collider you conditioned on is not a confounding problem. Conditioning on a common effect creates association rather than failing to remove it, and no sensitivity analysis will find it. Kestrelmark's loyalty score costs 1.30 units of bias while every sweep run on top of it reports a comfortable number. That is why sessions 2 and 3 come first: sensitivity analysis is the last check, never the first, and it cannot rescue a graph that was wrong.

The order is not negotiable. Draw the graph, choose the adjustment set from the graph, check identification, estimate, then run the sensitivity. Run it earlier and you get a robustness statement about a number that was measuring something else, which reads exactly like a robustness statement about the right one.

Self-studyWhen "not identified" plus a tipping point is the deliverable3 min▶

Leader session 4 makes the case that "this effect is not identified from the data we have" is a legitimate finding rather than a failure to deliver, and a tipping point is what makes that finding actionable instead of merely discouraging. The pairing is the deliverable: here is the best estimate available, here is the honest statement that a back-door path runs through something unmeasured, and here is how strong that unmeasured thing would have to be for the decision to flip.

  • Sometimes the tipping point is enormous and the decision can be made anyway. A finding that would need a confounder five times the size of anything known is good enough for a reversible, low-cost decision, and saying so is more useful than another month of modelling.
  • Sometimes it is tiny and the honest output is a measurement request. Name the variable you would need, say what it would cost to collect, and hand back a design rather than an estimate.
  • Either way the decision-maker gets a size. That is the difference between a caveat and information, and it is the reason this sentence belongs in the summary rather than the appendix.
Build-along

How strong would it have to be

★ 18 min · everyone builds

Start from the correct adjustment set, {season, tenure}, and the canon estimate of 6.56 against a true effect of 6.23. Inject an unmeasured cause with a tunable strength, sweep it, and find where the estimate crosses zero. Then do the whole thing again from the wrong starting point.

Fix the benchmark before you sweep. Measure each observed covariate's association with the email and with spend, per standard deviation. Tenure's pair is 0.7 and 5.0; season's, once scaled to one standard deviation, is 0.45 and 3.0. Tenure is the ruler.

Define one strength knob, in tenure units. A simulated cause at strength s enters the email with 0.7 times s and spend with 5.0 times s, so s equal to 1 reproduces a second tenure exactly. One knob, one axis, one sentence at the end.

Regenerate the treatment and the outcome at each strength. The unmeasured cause has to feed both, or it is not a confounder and the sweep will show a flat line, which is a useful thing to see once by mistake.

Refit the correct adjustment set at every step. Season and tenure stay in the model; the simulated cause never does. That gap between what is in the model and what is in the world is the entire mechanism being measured.

Sweep and find the crossing. Mind the direction first: a cause positive on both arms inflates what you observe, so the question is how much of the 6.56 it could be supplying. Take that contribution back out and what remains falls from 6.56 and crosses zero at s equal to 1.55. The fall is quadratic rather than linear, because the contribution grows with the product of two associations that are both scaling.

Convert the crossing into season units and write the sentence. 1.55 tenures is 2.49 seasons, and both go in the sentence so nobody has to trust a single ruler.

Now rerun the sweep from the wrong adjustment set, the one that adds the loyalty score. Loyalty is a pre-treatment collider, the estimate starts at 5.26 rather than 6.56, and the crossing moves to 1.34.

Put the two sensitivity statements side by side and try to tell them apart. 1.55 against 1.34 is a 14 percent difference in a robustness claim. The estimates they describe differ by 1.30 units, which is 21 percent of the true effect. That is the trap.

★ Try it now - the sweep, in tenure units import numpy as np, statsmodels.api as sm from kestrelmark import generate # the written model from session 10, one fixed seed TENURE = {"email": 0.7, "spend": 5.0} # the ruler: strongest observed pair, per SD SEASON = {"email": 0.45, "spend": 3.0} # binary, so scaled to one standard deviation def observed(adjust, s): """Regenerate with an unmeasured cause u of strength s wired into BOTH arms, then fit WITHOUT it, which is what you would actually see.""" d = generate(confounder=s, u_email=TENURE["email"], u_spend=TENURE["spend"]) X = sm.add_constant(d[["email"] + adjust]) return sm.OLS(d.spend, X).fit().params["email"] def remaining(adjust, s, base): """A cause positive on both arms INFLATES what you see. Take its contribution back out and this is the effect that would be left if it were real.""" return base - (observed(adjust, s) - base) def tipping_point(sw): a = [r for r in sw if r[1] > 0][-1] b = [r for r in sw if r[1] <= 0][0] return a[0] + (b[0] - a[0]) * a[1] / (a[1] - b[1]) grid = np.arange(0.0, 2.01, 0.02) base = observed(["season", "tenure"], 0.0) correct = [(s, remaining(["season", "tenure"], s, base)) for s in grid] print(round(base, 2)) # 6.56, the canon estimate print(round(tipping_point(correct), 2)) # 1.55 tenure-strengths ratio = np.sqrt((TENURE["email"] * TENURE["spend"]) / (SEASON["email"] * SEASON["spend"])) print(round(1.55 * ratio, 2)) # 2.49 season-strengths

The correct set, swept. Estimate against unmeasured-confounder strength, in tenure units:

Strength, in tenuresEstimateBias against 6.23Reading
0.006.56+0.33The canon estimate, no unmeasured cause
0.505.82-0.41Half a tenure barely dents it
0.754.69-1.54The fall is quadratic, and it has started
1.003.60-2.63A cause as strong as tenure costs nearly half
1.252.09-4.14Still positive, and the direction is unchanged
1.550.00-6.23The tipping point
2.00-3.40-9.63Two tenures would reverse the sign
Effect remaining against unmeasured-confounder strength. Start 6.56, zero at 1.55. estimate 6 4 2 0 -2 -4 0 0.5 1.0 1.5 2.0 season tenure tipping point: 1.55 tenures or 2.49 seasons, per SD unmeasured confounder strength, in tenure units Sensitivity says how strong it would have to be. It cannot say whether one exists.
🔍 Click to zoom - the crossing sits well to the right of both observed benchmarks.
★ Try it now - the same sweep from the wrong adjustment set base_w = observed(["season", "tenure", "loyalty"], 0.0) # loyalty is a COLLIDER wrong = [(s, remaining(["season", "tenure", "loyalty"], s, base_w)) for s in grid] print(round(base_w, 2)) # 5.26, biased by -1.30 before anything is injected print(round(tipping_point(wrong), 2)) # 1.34 tenures. Still over one. Still reassuring. # Side by side, the two robustness statements are nearly identical: # correct set estimate 6.56 tipping point 1.55 # with collider estimate 5.26 tipping point 1.34 a 14 percent difference # # The estimates differ by 1.30 units, 21 percent of the true effect of 6.23. # Nothing anywhere in the sweep reports the collider, because the sweep # only ever asks about MISSING confounding. Run session 3 first.
The same sweep from a contaminated starting point correct set {season, tenure}, starts 6.56 with the loyalty collider, starts 5.26 truth 6.23 6 4 2 0 -2 -4 0 0.5 1.0 1.5 2.0 1.55 1.34 Both sweeps look reassuring. 1.34 against 1.55 is a 14 percent difference. The estimate underneath it is already 21 percent low. unmeasured confounder strength, in tenure units No value anywhere on this sweep reports the collider. Check the graph first.
🔍 Click to zoom - two robustness statements 14 percent apart, on estimates 1.30 units apart.
Real world

This is how a wrong model survives review. Every gate a careful team has built asks about the thing that is missing: a sensitivity statement, an unmeasured-confounder discussion, a robustness appendix. None of them asks whether a variable in the model belongs there. The loyalty score was measured before the email went out, it improved the fit, it made the covariate list look thorough, and it cost 1.30 units. The sweep on top of it was, in its own terms, entirely correct.

Homework

Before session 10

30 min
  1. Take one observational estimate your team has published and compute its tipping point, benchmarked per standard deviation against the strongest covariate already in the model. Write the sentence.
  2. Show the sentence to somebody who knows the domain and not the method. Ask one question: is there anything out there that big? Write down what they say, including "I do not know", which is itself a finding.
  3. Before you trust your own tipping point, go back through the adjustment set and name the graph role of every variable in it. A sweep on a contaminated set is the failure mode tonight was built around.
  4. Bring that covariate list and its roles to session 10, where every one of them gets scored against a truth that is known by construction.
Check yourself

Three questions

An analyst runs a sensitivity sweep on an adjustment set that includes the loyalty score, a pre-treatment collider, and reports a tipping point of 1.34 tenures. What has the sweep told them about the collider?

The estimate was already 1.30 below the truth before the sweep started, and the sweep reports a comfortable-looking number anyway. Collider bias is found by drawing the graph, which is why sessions 2 and 3 come before this one.

A reviewer says "there could be something you did not measure". What is the professional reply?

As stated the objection is always true and unfalsifiable, so it cannot separate a fragile finding from a robust one. Giving it a size, in units of a covariate the reader already accepts, hands the judgement to whoever knows the domain.

At Kestrelmark, season enters spend with a coefficient of 6.0 and tenure with 5.0. Season is binary and split evenly. Which is the right benchmark for a tipping point, and why does it matter?

The tipping point is one quantity expressed in whichever ruler you pick, so picking a weaker ruler makes the same finding sound more robust. Report both, and say the unit out loud.

Sources covered

What tonight maps to

✓ How strong an unmeasured confounder would have to be to overturn the finding The reframe from possibility to strength, the sweep, and the crossing computed at 1.55 tenure-strengths from the correct set's 6.56.
✓ Benchmarking against observed covariates Per standard deviation, on both associations at once, against variables already in the model: 1.55 tenures or 2.49 seasons.
✓ The tipping-point sentence as a reporting habit One sentence beside the estimate, judgeable without rerunning anything, and explicit about what it does not claim.
✓ What sensitivity analysis cannot detect A collider in the adjustment set. The contaminated sweep starts at 5.26, a bias of -1.30, and reports 1.34, which is 14 percent from the honest answer.
◐ Colliders, mediators and M-bias Sessions 2 and 3 own the shapes and the criterion; session 10 prices every one of them on the bench.
◐ "Not identified" plus a tipping point as a deliverable Leader session 4 owns the commissioning argument and what to require in writing.
· Propensity scores, IPW, doubly robust, difference-in-differences, synthetic control, DoWhy Not covered by design, in any session. Correcting an observational comparison belongs to the experimentation course; pricing the doubt that remains after the graph is settled belongs here.

Session 9 cheat sheet

The objection is useless as statedAlways true, never falsifiable, applies identically to every observational finding.
Ask how strong, not whetherHow strongly associated with BOTH the treatment and the outcome, to reach zero.
Both associations, or noneBias scales with the product, so a one-sided candidate is not a threat.
Benchmark per standard deviationSeason's raw 6.0 beats tenure's 5.0; per SD it is 3.0 against 5.0. Tenure is the ruler.
Kestrelmark's tipping point6.56 reaches zero at 1.55 tenures, or 2.49 seasons. Both go in the sentence.
One sentence, next to the estimateNot the appendix. A reader has to be able to judge it without rerunning anything.
It assumes a shapeOne additive cause, constant strength. Correct for that shape, silent about others.
It cannot see a colliderThe contaminated sweep starts at 5.26 and still reports 1.34. Draw the graph first.