statspai.bridge¶
bridge ¶
Bridging theorems for causal inference (StatsPAI v0.10).
Each "bridging theorem" pairs two seemingly different estimators on the same target parameter and proves that — under appropriate conditions — they identify the same quantity. Reporting both estimates side-by-side gives doubly-robust identification: if either path's assumption holds, the estimate is consistent.
Six bridges shipped (per arXiv 2503.11375 / 2510.26723 / 2310.18563 v6 / 2404.09117 / 2411.02771 / 2202.07234, 2022-2025):
did_sc— DiD ≡ Synthetic Control (Sun-Xie-Zhang 2025)ewm_cate— EWM ≡ CATE → policy (Kato 2025)cb_ipw— Covariate Balancing ≡ IPW × DR (Słoczyński-Uysal-Wooldridge 2023)kink_rdd— Kink-Bunching ≡ RDD (Lu-Wang-Xie 2025)dr_calib— Doubly Robust via Calibration (van der Laan-Luedtke-Carone 2024)surrogate_pci— Long-term Surrogate ≡ PCI (Imbens-Kallus-Mao-Wang 2025, JRSS-B)
The unified entry point is sp.bridge(data, kind=..., **kwargs),
returning a :class:BridgeResult reporting the two path estimates,
their agreement test, and the recommended doubly-robust point estimate.
BridgeResult
dataclass
¶
Bases: ResultProtocolMixin
Result of a bridging-theorem comparison.
Attributes:
| Name | Type | Description |
|---|---|---|
kind |
str
|
Which bridge was applied ( |
path_a_name, path_b_name |
str
|
Human-readable names of the two estimation paths. |
estimate_a, estimate_b |
float
|
Point estimates from each path. |
se_a, se_b |
float
|
Standard errors from each path. |
diff, diff_se, diff_p |
float
|
Difference, its SE (assuming independence; conservative when the two paths share data), and the two-sided p-value for H0: paths agree. |
estimate_dr |
float
|
Doubly-robust combined estimate (precision-weighted average
when agreement_p > 0.05; falls back to |
se_dr |
float
|
SE of |
n_obs |
int
|
|
detail |
dict
|
Extra path-specific metadata. |
reference |
str
|
|
Examples:
>>> import statspai as sp
>>> import numpy as np, pandas as pd
>>> rng = np.random.default_rng(0)
>>> units = [f"u{i}" for i in range(6)] + ["CA"]
>>> rows = []
>>> for u in units:
... base = rng.normal(10, 1)
... for yr in range(1985, 1995):
... eff = 2.0 if (u == "CA" and yr >= 1990) else 0.0
... rows.append({"state": u, "year": yr,
... "gdp": base + 0.1 * (yr - 1985) + eff
... + rng.normal(0, 0.2)})
>>> df = pd.DataFrame(rows)
>>> res = sp.bridge(kind="did_sc", data=df, y="gdp", unit="state",
... time="year", treated_unit="CA", treatment_time=1990)
>>> type(res).__name__
'BridgeResult'
>>> res.kind
'did_sc'
>>> bool(res.agreement) # True when the two paths concur
True
bridge ¶
bridge(kind: str, **kwargs: Any) -> BridgeResult
Run a bridging-theorem comparison.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
{'did_sc', 'ewm_cate', 'cb_ipw', 'kink_rdd',
|
Which bridge to run. |
required |
**kwargs
|
Any
|
Bridge-specific keyword arguments. See the per-bridge
implementations in :mod: |
{}
|
Returns:
| Type | Description |
|---|---|
BridgeResult
|
Two path estimates + agreement test + DR combined estimate. |
Examples:
DID vs. Synthetic Control on a panel where one unit gets treated at time T:
>>> import statspai as sp
>>> import numpy as np, pandas as pd
>>> rng = np.random.default_rng(0)
>>> units = [f"u{i}" for i in range(6)] + ["CA"]
>>> rows = []
>>> for u in units:
... base = rng.normal(10, 1)
... for yr in range(1985, 1995):
... eff = 2.0 if (u == "CA" and yr >= 1990) else 0.0
... rows.append({"state": u, "year": yr,
... "gdp": base + 0.1 * (yr - 1985) + eff
... + rng.normal(0, 0.2)})
>>> df = pd.DataFrame(rows)
>>> result = sp.bridge(
... kind="did_sc", data=df,
... y="gdp", unit="state", time="year",
... treated_unit="CA", treatment_time=1990,
... )
>>> result.kind
'did_sc'
>>> bool(result.agreement)
True