causalis.scenarios.unconfoundedness.refutation.unconfoundedness.sensitivity¶
Sensitivity analysis and benchmarking for unconfoundedness.
This module provides tools to assess the robustness of causal estimates to potential unobserved confounding. It implements the sensitivity framework based on partial $R^2$ as described in Cinelli & Hazlett (2020), adapted for non-linear and semi-parametric models via influence functions.
The framework assumes the existence of an unobserved confounder $U$ and quantifies the bias as a function of:
$R^2_{Y \sim U | D, X}$: How much of the outcome variance $U$ explains.
$R^2_{D \sim U | X}$: How much of the treatment variance $U$ explains.
The bias-aware estimate is then:
.. math::
\hat{\tau}_{adj} = \hat{\tau} \pm \text{bias}(R^2_{Y \sim U}, R^2_{D \sim U})
Module Contents¶
Functions¶
Compute DoubleML-style sensitivity elements for binary-treatment IRM. |
|
Compute bias-aware confidence intervals. |
|
Render a single, unified bias-aware summary string. |
|
Render a single, unified bias-aware summary string. |
|
Benchmark confounders one by one using genuine short-model IRM refits. |
|
Benchmark a group of confounders using one genuine short-model IRM refit. |
|
Compute bias-aware components and cache them. |
|
Run sensitivity analysis and return a structured interpretation. |
|
Run a binary, benchmark-calibrated sensitivity decision protocol. |
Data¶
API¶
- causalis.scenarios.unconfoundedness.refutation.unconfoundedness.sensitivity.__all__¶
[‘sensitivity_analysis’, ‘sensitivity_benchmark’, ‘sensitivity_benchmark_group’, ‘run_sensitivity_pr…
- causalis.scenarios.unconfoundedness.refutation.unconfoundedness.sensitivity.compute_irm_sensitivity_elements(*, model: Any, y: numpy.ndarray, d: numpy.ndarray, g0: numpy.ndarray, g1: numpy.ndarray, m_hat: numpy.ndarray, w: Optional[numpy.ndarray] = None, w_bar: Optional[numpy.ndarray] = None, psi: Optional[numpy.ndarray] = None, inv_m: Optional[numpy.ndarray] = None, inv_1m: Optional[numpy.ndarray] = None, score: Any = 'ATE') dict[str, Any]¶
Compute DoubleML-style sensitivity elements for binary-treatment IRM.
- causalis.scenarios.unconfoundedness.refutation.unconfoundedness.sensitivity.compute_bias_aware_ci(effect_estimation: Dict[str, Any] | Any, *, r2_y: float, r2_d: float, rho: float = 1.0, H0: float = 0.0, alpha: float = 0.05, use_signed_rr: bool = False) Dict[str, Any]¶
Compute bias-aware confidence intervals.
Returns a dict with:
theta, se, alpha, z
sampling_ci
theta_bounds_cofounding = [theta_lower, theta_upper] = theta ± bound_width
bias_aware_ci = [theta - (bound_width + zse), theta + (bound_width + zse)]
max_bias_base, max_bias, bound_width and components (sigma2, nu2)
Parameters
effect_estimation : Dict[str, Any] or Any The effect estimation object. r2_y : float Sensitivity parameter for the outcome (R^2 form, R_Y^2). r2_d : float Sensitivity parameter for the treatment (R^2 form, R_D^2). rho : float, default 1.0 Correlation parameter. H0 : float, default 0.0 Null hypothesis for robustness values. alpha : float, default 0.05 Significance level. use_signed_rr : bool, default False Whether to use signed rr in the quadratic combination of sensitivity components. If True and m_alpha/rr are available, the bias bound is computed via the per-unit quadratic form and RV/RVa are not reported.
Returns
dict Dictionary with bias-aware results.
- causalis.scenarios.unconfoundedness.refutation.unconfoundedness.sensitivity.format_bias_aware_summary(res: Dict[str, Any], label: str | None = None) str¶
Render a single, unified bias-aware summary string.
Parameters
res : Dict[str, Any] The result dictionary from compute_bias_aware_ci. label : str, optional, default None The label for the estimand.
Returns
str Formatted summary string.
- causalis.scenarios.unconfoundedness.refutation.unconfoundedness.sensitivity.get_sensitivity_summary(effect_estimation: Dict[str, Any] | Any, *, label: Optional[str] = None) Optional[str]¶
Render a single, unified bias-aware summary string.
If bias-aware components are missing, shows a sampling-only variant with max_bias=0 and then formats via
format_bias_aware_summaryfor consistency.Parameters
effect_estimation : Dict[str, Any] or Any The effect estimation object. label : str, optional, default None The label for the estimand.
Returns
Optional[str] Formatted summary string or None if extraction fails.
Notes
The summary reports the point estimate, standard error, and confidence intervals under both the null (no unobserved confounding) and the assumed level of confounding ($R^2_Y, R^2_D$).
It also includes the Robustness Value (RV), which is the minimum strength of confounding ($R^2_Y = R^2_D$) required to change the conclusion (e.g., make the estimate non-significant or zero).
Examples
from causalis.scenarios.unconfoundedness.refutation.unconfoundedness import sensitivity_analysis, get_sensitivity_summary
Assuming ‘estimate’ is a fitted CausalEstimate from IRM
res = sensitivity_analysis(estimate, r2_y=0.05, r2_d=0.05) # doctest: +SKIP summary = get_sensitivity_summary(estimate) # doctest: +SKIP print(summary) # doctest: +SKIP
- causalis.scenarios.unconfoundedness.refutation.unconfoundedness.sensitivity.sensitivity_benchmark(effect_estimation: Dict[str, Any] | Any, data: causalis.dgp.causaldata.CausalData, benchmarking_set: List[str] | Literal[all], fit_args: Optional[Dict[str, Any]] = None) pandas.DataFrame¶
Benchmark confounders one by one using genuine short-model IRM refits.
Each requested confounder is excluded separately. The returned DataFrame therefore contains one row and one short-model estimate per confounder. Pass
"all"to benchmark all confounders indata.confoundersorder.See Also
sensitivity_benchmark_group Exclude several confounders together in one short-model refit.
- causalis.scenarios.unconfoundedness.refutation.unconfoundedness.sensitivity.sensitivity_benchmark_group(effect_estimation: Dict[str, Any] | Any, data: causalis.dgp.causaldata.CausalData, benchmarking_group: List[str], fit_args: Optional[Dict[str, Any]] = None) pandas.DataFrame¶
Benchmark a group of confounders using one genuine short-model IRM refit.
All features in
benchmarking_groupare excluded together. Group-level confounding strengths andrhoare calibrated jointly from long/short IRM sensitivity elements and the actual effect-estimate shift.Parameters
effect_estimation : dict or Any Estimate/model container exposing a fitted IRM-like model. data : CausalData The exact causal dataset and row order used to fit the long model. benchmarking_group : list[str] Non-empty group of confounders to exclude together. Duplicate names are ignored while preserving their first occurrence. fit_args : dict, optional Additional keyword arguments passed to
IRM.estimate(...)for the short model. Short-model diagnostics are disabled by default.Returns
pandas.DataFrame One-row DataFrame retaining
benchmark_group,r2_y,r2_d,rho,theta_long,theta_short, anddeltaand appending element-based gain statistics and calibration audit fields. The group is stored as a tuple inbenchmark_group.Notes
At least one long-model confounder must remain after removing the group. Runtime is normally dominated by the single short-model refit.
- causalis.scenarios.unconfoundedness.refutation.unconfoundedness.sensitivity.sensitivity_analysis(effect_estimation: Dict[str, Any] | Any, *, r2_y: float, r2_d: float, rho: float = 1.0, H0: float = 0.0, alpha: float = 0.05, use_signed_rr: bool = False) Dict[str, Any]¶
Compute bias-aware components and cache them.
This function turns a fitted estimate into a simple hidden-confounding stress test. In the default mode, the bound width is
.. math::
|\rho| \cdot \sqrt{\sigma^2 \nu^2} \cdot \sqrt{\frac{r2_y}{1-r2_y}\frac{r2_d}{1-r2_d}},so the reported confounding interval is
.. math::
[\theta - \text{bound\_width}, \theta + \text{bound\_width}].Here :math:
r2_ycontrols how much residual outcome variation an omitted confounder could explain, :math:r2_ddoes the same for treatment assignment, and :math:\rhosets the sign and strength alignment between the two channels.Parameters
effect_estimation : Dict[str, Any] or Any The effect estimation object. r2_y : float Sensitivity parameter for the outcome (R^2 form, R_Y^2; converted to odds form internally). r2_d : float Sensitivity parameter for the treatment (R^2 form, R_D^2). rho : float, default 1.0 Correlation parameter. H0 : float, default 0.0 Null hypothesis for robustness values. alpha : float, default 0.05 Significance level. use_signed_rr : bool, default False Whether to use signed rr in the quadratic combination of sensitivity components. If True and m_alpha/rr are available, the bias bound is computed via the per-unit quadratic form and RV/RVa are not reported.
Returns
dict Dictionary with bias-aware results: - theta, se, alpha, z - sampling_ci - theta_bounds_cofounding = (theta - bound_width, theta + bound_width) - bias_aware_ci = faithful CI for the bounds - max_bias and components (sigma2, nu2) - params (r2_y, r2_d, rho, use_signed_rr)
Examples
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor from causalis.dgp import obs_linear_26_dataset from causalis.scenarios.unconfoundedness.model import IRM data = obs_linear_26_dataset( … n=1000, … seed=3141, … include_oracle=False, … return_causal_data=True, … ) irm = IRM( … data=data, … ml_g=RandomForestRegressor( … n_estimators=200, … max_depth=6, … min_samples_leaf=5, … random_state=3141, … ), … ml_m=RandomForestClassifier( … n_estimators=200, … max_depth=6, … min_samples_leaf=5, … random_state=3141, … ), … n_folds=3, … random_state=3141, … ) estimate = irm.fit().estimate(score=”ATE”) out = sensitivity_analysis(estimate, r2_y=0.02, r2_d=0.02, rho=1.0) out[“theta_bounds_cofounding”] # doctest: +SKIP out[“bias_aware_ci”] # doctest: +SKIP
- causalis.scenarios.unconfoundedness.refutation.unconfoundedness.sensitivity.interpret_sensitivity_analysis(effect_estimation: Dict[str, Any] | Any, *, r2_y: float, r2_d: float, rho: float = 1.0, H0: float = 0.0, alpha: float = 0.05, use_signed_rr: bool = False) Dict[str, Any]¶
Run sensitivity analysis and return a structured interpretation.
Parameters
effect_estimation : Dict[str, Any] or Any The effect estimation object. r2_y : float Sensitivity parameter for outcome residual confounding strength. r2_d : float Sensitivity parameter for treatment residual confounding strength. rho : float, default 1.0 Correlation parameter for unobserved confounding. H0 : float, default 0.0 Null hypothesis used for significance checks. alpha : float, default 0.05 Significance level. use_signed_rr : bool, default False Whether to use signed rr in the quadratic sensitivity combination.
Returns
Dict[str, Any] Dictionary with: - raw: the output of
sensitivity_analysis(...)- interpretation: machine-readable interpretation fields - summary: compact human-readable interpretationNotes
This function wraps
sensitivity_analysisand provides a textual interpretation of the results, including whether the estimate remains significant under the assumed confounding.Examples
from causalis.scenarios.unconfoundedness.refutation.unconfoundedness import interpret_sensitivity_analysis
Assuming ‘estimate’ is a fitted CausalEstimate
interpretation = interpret_sensitivity_analysis(estimate, r2_y=0.01, r2_d=0.01) # doctest: +SKIP print(interpretation[“summary”]) # doctest: +SKIP
- causalis.scenarios.unconfoundedness.refutation.unconfoundedness.sensitivity.run_sensitivity_protocol(effect_estimation: Dict[str, Any] | Any, data: causalis.dgp.causaldata.CausalData, *, benchmark_groups: Mapping[str, List[str]], decision_threshold: float, direction: Literal[positive, negative], alpha: float = 0.05, stress_multiplier: float = 2.0, preconditions_passed: bool = True, fit_args: Optional[Dict[str, Any]] = None) Dict[str, Any]¶
Run a binary, benchmark-calibrated sensitivity decision protocol.
The primary decision uses one-times benchmark strength and the benchmark’s estimated
rho. Every primary benchmark must pass. A benchmark passes when its bias-aware confidence interval lies strictly beyond the pre-specified practical threshold indirection. The two-times andrho=1adversarial scenarios are reported as stress tests and do not change the primary PASS/FAIL decision.Parameters
effect_estimation : dict or Any Fitted IRM-like estimate accepted by :func:
sensitivity_analysis. data : CausalData Exact data and row order used to fit the long model. benchmark_groups : mapping[str, list[str]] Non-empty mapping from meaningful benchmark labels to groups of observed pre-treatment confounders. Each group is omitted in its own genuine short-model refit. decision_threshold : float Pre-specified practically meaningful effect boundary. For a positive claim, the lower bias-aware CI endpoint must exceed this value. For a negative claim, the upper endpoint must be below it. direction : {“positive”, “negative”} Direction of the practically meaningful causal claim. alpha : float, default 0.05 Two-sided significance level used for bias-aware confidence intervals. stress_multiplier : float, default 2.0 Strength multiplier for the secondary stress scenario. Each partial R-squared is multiplied on the odds/Cohen-f-squared scale. preconditions_passed : bool, default True External design gate covering the causal adjustment set, overlap, nuisance-model quality, and estimator stability.Falseforces the overall result to FAIL while still returning sensitivity diagnostics. fit_args : dict, optional Additional keyword arguments for each short-model estimate.Returns
dict A report with
status("PASS"or"FAIL"),passed, benchmark and scenario DataFrames, raw scenario details, warnings, and a standard human-readable conclusion.primarydetermines status;stressandadversarialdescribe the robustness margin.Notes
The robustness values
rvandrvaare reported but are not used as universal pass/fail cutoffs. Benchmark selection anddecision_thresholdmust be justified before inspecting the effect estimate.rhofor both singleton and multi-feature benchmarks is calibrated from long/short sensitivity elements. A value of+/-1in primary or stress can mean either thatrho_rawsaturated at the correlation boundary or that DoubleML’s non-positive-gain fallback was used. Only the adversarial scenario forcesrho=1independently of the benchmark effect shift.DoubleML-compatible boundary calibration clips non-positive raw gains to zero and keeps the scenario numerically evaluable. Raw values and boundary flags remain available in
benchmarksfor audit. Missing/non-finite sensitivity elements and an upper-boundcf_d=1still fail closed, because Causalis sensitivity bounds require finite inputs below one.The current IRM estimator supports
n_rep=1only. Repeated cross-fitting stability must therefore be assessed outside this function until IRM adds repeated-split support.Examples
report = run_sensitivity_protocol( # doctest: +SKIP … estimate, … data, … benchmark_groups={“engagement”: [“sessions”, “activity_days”]}, … decision_threshold=0.0, … direction=”positive”, … ) report[“status”] # doctest: +SKIP ‘PASS’ report[“primary”][[“benchmark”, “ci_lower”, “ci_upper”, “passed”]] # doctest: +SKIP