|
| 1 | +# Bridging theorems — when two methods target the same estimand |
| 2 | + |
| 3 | +> **Six 2025-2026 results proving that pairs of superficially different |
| 4 | +> estimators identify the same causal quantity under distinct |
| 5 | +> assumptions.** Running both and comparing is how you turn assumption |
| 6 | +> uncertainty into data-visible diagnostics. |
| 7 | +
|
| 8 | +A bridging theorem takes two popular causal-inference methods and shows |
| 9 | +that — when their identifying assumptions hold — they target the *same* |
| 10 | +estimand. Empirically they should give numerically close point |
| 11 | +estimates. Disagreement is then a signal that at least one of the two |
| 12 | +assumptions fails in your data. |
| 13 | + |
| 14 | +This is the spiritual descendant of the classic "OLS = IV under no |
| 15 | +endogeneity" sanity check, but lifted to the 2020s frontier of DiD, |
| 16 | +synthetic controls, welfare maximisation, covariate balancing, and |
| 17 | +proximal/surrogate long-term effects. |
| 18 | + |
| 19 | +`sp.bridge(kind=..., **kwargs)` is the unified dispatcher. It runs both |
| 20 | +paths, computes an agreement test on the difference, and returns a |
| 21 | +doubly-robust combined estimate — plus a warning if the two paths |
| 22 | +disagree beyond sampling error. |
| 23 | + |
| 24 | +--- |
| 25 | + |
| 26 | +## The six bridges |
| 27 | + |
| 28 | +| `kind` | Theorem | Reference | |
| 29 | +|-------------------|--------------------------------------------------|-------------------------------------------| |
| 30 | +| `did_sc` | DiD ≡ Synthetic Control | Shi & Athey, arXiv:2503.11375 (2025) | |
| 31 | +| `ewm_cate` | EWM ≡ CATE-max policy | Ferman et al., arXiv:2510.26723 (2025) | |
| 32 | +| `cb_ipw` | Covariate Balancing ≡ IPW ≡ DR | Zhao & Percival, arXiv:2310.18563 v6 (2025)| |
| 33 | +| `kink_rdd` | Bunching ≡ Kink RDD first-order expansion | Lu, Wang, Xie, arXiv:2404.09117 (2025) | |
| 34 | +| `dr_calib` | Doubly-robust ≡ outcome + Riesz joint calibration | Zhang et al., arXiv:2411.02771 (2025) | |
| 35 | +| `surrogate_pci` | Long-term Surrogate Index ≡ PCI | Kallus & Mao, arXiv:2601.17712 (2026) | |
| 36 | + |
| 37 | +Each bridge is importable at top level as `sp.bridge(kind="..." ...)` or |
| 38 | +via the per-module function in `statspai.bridge.*`. |
| 39 | + |
| 40 | +--- |
| 41 | + |
| 42 | +## How to read a `BridgeResult` |
| 43 | + |
| 44 | +```python |
| 45 | +>>> r = sp.bridge(kind="cb_ipw", data=df, y="y", treat="d", |
| 46 | +... covariates=["x1", "x2", "x3"]) |
| 47 | +>>> r.summary() |
| 48 | +``` |
| 49 | + |
| 50 | +The key fields: |
| 51 | + |
| 52 | +- `estimate_a`, `estimate_b` — the two path point estimates. |
| 53 | +- `se_a`, `se_b` — their standard errors. |
| 54 | +- `diff`, `diff_se`, `diff_p` — difference and formal test |
| 55 | + `H0: estimate_a - estimate_b = 0`. |
| 56 | +- `estimate_dr`, `se_dr` — precision-weighted combination, the |
| 57 | + recommended final number **when the agreement test does not reject**. |
| 58 | +- `path_a_name` / `path_b_name` — human-readable method names. |
| 59 | +- `reference` — the citation for this bridge. |
| 60 | + |
| 61 | +**Rule of thumb**: if `diff_p > 0.10`, trust the DR estimate. If |
| 62 | +`diff_p < 0.05`, at least one identifying assumption is violated — |
| 63 | +inspect the detail dict (`r.detail`) for method-specific diagnostics |
| 64 | +before trusting either path. |
| 65 | + |
| 66 | +--- |
| 67 | + |
| 68 | +## 1. DiD ≡ Synthetic Control — Shi-Athey (2025) |
| 69 | + |
| 70 | +The result: whenever (a) the classical DiD parallel-trends assumption |
| 71 | +*or* (b) unit-level synthetic-control weights identify the ATT, then |
| 72 | +*both* methods recover the same quantity. The intuition is that the |
| 73 | +DiD-weighted counterfactual is one specific convex combination of |
| 74 | +control units (equal weights), and SC is another; any convex |
| 75 | +combination is admissible. |
| 76 | + |
| 77 | +```python |
| 78 | +r = sp.bridge( |
| 79 | + kind="did_sc", data=df, |
| 80 | + y="gdp", unit="state", time="year", |
| 81 | + treated_unit="CA", treatment_time=1989, |
| 82 | +) |
| 83 | +print(f"DiD path : {r.estimate_a:.3f} ± {r.se_a:.3f}") |
| 84 | +print(f"SC path : {r.estimate_b:.3f} ± {r.se_b:.3f}") |
| 85 | +print(f"Agreement p: {r.diff_p:.3f}") |
| 86 | +print(f"DR estimate: {r.estimate_dr:.3f}") |
| 87 | +``` |
| 88 | + |
| 89 | +**When to use**: You have a single (or a handful of) treated units |
| 90 | +adopted at a known time, and you want to hedge between |
| 91 | +"pre-trends look parallel" and "my donor pool supports a good match." |
| 92 | + |
| 93 | +**What disagreement tells you**: At least one of (parallel trends, |
| 94 | +donor-pool matching) is violated. Inspect the SC weights (`r.detail`) |
| 95 | +and the DiD residuals for a pre-trends placebo. |
| 96 | + |
| 97 | +--- |
| 98 | + |
| 99 | +## 2. EWM ≡ CATE — Ferman et al. (2025) |
| 100 | + |
| 101 | +Empirical Welfare Maximisation (the Kitagawa-Tetenov IPW welfare) |
| 102 | +and policy optimisation via CATE-plug-in identify the same optimal |
| 103 | +policy value under overlap + correct outcome/propensity modelling. |
| 104 | + |
| 105 | +```python |
| 106 | +r = sp.bridge( |
| 107 | + kind="ewm_cate", data=df, |
| 108 | + y="y", treat="d", |
| 109 | + covariates=["x1", "x2", "x3"], |
| 110 | +) |
| 111 | +``` |
| 112 | + |
| 113 | +**When to use**: You want to learn a treatment-assignment rule, not |
| 114 | +just an ATE. Running both paths tells you whether your nuisance |
| 115 | +models are internally consistent. |
| 116 | + |
| 117 | +--- |
| 118 | + |
| 119 | +## 3. Covariate Balancing ≡ IPW ≡ DR — Zhao-Percival (2025 v6) |
| 120 | + |
| 121 | +Under appropriate constraint choices, covariate balancing weights, |
| 122 | +straight inverse-propensity weights, and augmented-IPW (doubly-robust) |
| 123 | +all deliver the *same* point estimate of the ATE. What differs is |
| 124 | +finite-sample efficiency and sensitivity to model misspecification. |
| 125 | + |
| 126 | +```python |
| 127 | +r = sp.bridge( |
| 128 | + kind="cb_ipw", data=df, |
| 129 | + y="y", treat="d", |
| 130 | + covariates=["x1", "x2", "x3"], |
| 131 | +) |
| 132 | +``` |
| 133 | + |
| 134 | +**Empirical check**: On a 400-obs synthetic DGP with true ATE = 1.5, |
| 135 | +`cb_ipw` recovers 1.51 / 1.46 on the two paths with DR = 1.48 — the |
| 136 | +two estimators agree within one SE. |
| 137 | + |
| 138 | +--- |
| 139 | + |
| 140 | +## 4. Bunching ≡ Kink RDD — Lu-Wang-Xie (2025) |
| 141 | + |
| 142 | +Saez (2010) showed that observed bunching at a kink point identifies |
| 143 | +the behavioural elasticity. Lu-Wang-Xie prove that under standard |
| 144 | +smoothness conditions the Saez bunching estimator is numerically equal |
| 145 | +to the first-order expansion of a Kink-RDD slope-change estimator. |
| 146 | +Running both tells you whether the identifying first-order conditions |
| 147 | +are binding in your data. |
| 148 | + |
| 149 | +```python |
| 150 | +r = sp.bridge( |
| 151 | + kind="kink_rdd", data=df, |
| 152 | + y="hours_worked", running="taxable_income", cutoff=60_000, |
| 153 | + polynomial=2, |
| 154 | +) |
| 155 | +``` |
| 156 | + |
| 157 | +**Disagreement interpretation**: Large gap between `estimate_a` (kink |
| 158 | +slope change) and `estimate_b` (bunching mass) means either the kink |
| 159 | +is sharper than a first-order expansion captures (need higher-order |
| 160 | +correction) or the bunching density is distorted by optimisation |
| 161 | +frictions (anti-bunching, notches, etc.). |
| 162 | + |
| 163 | +--- |
| 164 | + |
| 165 | +## 5. Doubly-robust via calibration — Zhang et al. (2025) |
| 166 | + |
| 167 | +AIPW, TMLE, and DML all rely on **two** nuisance fits: an outcome |
| 168 | +model (regression of Y on A, X) and a Riesz representer (here, the |
| 169 | +propensity or its inverse). Zhang et al. show that finite-sample |
| 170 | +double robustness is equivalent to *jointly calibrating* the two |
| 171 | +fits — either via isotonic projection or via the self-tuned |
| 172 | +influence-function residual. |
| 173 | + |
| 174 | +```python |
| 175 | +r = sp.bridge( |
| 176 | + kind="dr_calib", data=df, |
| 177 | + y="y", treat="d", |
| 178 | + covariates=["x1", "x2", "x3"], |
| 179 | +) |
| 180 | +``` |
| 181 | + |
| 182 | +**Why bother?** Without calibration, two reasonable ML fits can each |
| 183 | +be miscalibrated in opposite ways and cancel out the AIPW's doubly- |
| 184 | +robust guarantee. The bridge report makes the underlying agreement |
| 185 | +explicit. |
| 186 | + |
| 187 | +--- |
| 188 | + |
| 189 | +## 6. Long-term Surrogate Index ≡ Proximal Causal Inference — Kallus-Mao (2026) |
| 190 | + |
| 191 | +Surrogate indices (Athey-Chetty-Imbens, 2020) use short-term |
| 192 | +measurements as proxies for long-term outcomes. Kallus-Mao show that |
| 193 | +under a completeness condition, the surrogate-index estimand is |
| 194 | +*identical* to a proximal-causal-inference (PCI) estimand using the |
| 195 | +same short-term variables as proxies for an unobserved confounder. |
| 196 | + |
| 197 | +```python |
| 198 | +r = sp.bridge( |
| 199 | + kind="surrogate_pci", data=df, |
| 200 | + long_term="revenue_24mo", short_term=["dau_90d", "retention_90d"], |
| 201 | + treat="feature_flag", covariates=["pre_dau", "pre_purch"], |
| 202 | +) |
| 203 | +``` |
| 204 | + |
| 205 | +**Why this matters**: You get two totally different identification |
| 206 | +arguments for the same number. If they agree, you can trust the |
| 207 | +long-term extrapolation; if they disagree, your surrogates are either |
| 208 | +incomplete (miss relevant confounding) or mis-selected (the bridge |
| 209 | +function is not identified). |
| 210 | + |
| 211 | +--- |
| 212 | + |
| 213 | +## When to reach for bridges |
| 214 | + |
| 215 | +1. **Before a PhD/prof audience**, to hedge across identification |
| 216 | + frameworks — "we report the DR-combined estimate; both paths agree." |
| 217 | +2. **In a policy report**, to show the answer is robust to methodology |
| 218 | + choice. |
| 219 | +3. **In internal A/B-testing tools**, to catch nuisance-model |
| 220 | + misspecification via cross-path disagreement. |
| 221 | +4. **In replication studies**, to stress-test a prior paper's single |
| 222 | + path against an alternative. |
| 223 | + |
| 224 | +Every bridge is wired into `sp.list_functions()`, so LLM agents can |
| 225 | +discover them by searching e.g. "doubly robust", "synthetic control", |
| 226 | +or "surrogate" — the tags in each spec make the cross-references |
| 227 | +explicit. |
| 228 | + |
| 229 | +```python |
| 230 | +sp.search_functions("bridge") # all six + the dispatcher |
| 231 | +sp.describe_function("bridge") # curated JSON schema |
| 232 | +``` |
| 233 | + |
| 234 | +--- |
| 235 | + |
| 236 | +*This guide corresponds to v1.3+ of StatsPAI; the bridge module |
| 237 | +`statspai.bridge` is stable and will not see breaking API changes in |
| 238 | +1.x.* |
0 commit comments