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_irm_sensitivity_elements

Compute DoubleML-style sensitivity elements for binary-treatment IRM.

compute_bias_aware_ci

Compute bias-aware confidence intervals.

format_bias_aware_summary

Render a single, unified bias-aware summary string.

get_sensitivity_summary

Render a single, unified bias-aware summary string.

sensitivity_benchmark

Benchmark confounders one by one by refitting a short IRM that excludes each requested confounder from the supplied CausalData.

sensitivity_analysis

Compute bias-aware components and cache them.

interpret_sensitivity_analysis

Run sensitivity analysis and return a structured interpretation.

Data

__all__

API

causalis.scenarios.unconfoundedness.refutation.unconfoundedness.sensitivity.__all__

[‘sensitivity_analysis’, ‘sensitivity_benchmark’, ‘get_sensitivity_summary’, ‘interpret_sensitivity_…

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_summary for 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 by refitting a short IRM that excludes each requested confounder from the supplied CausalData.

This function intentionally performs a genuine short-model refit for every benchmarked confounder because theta_short and delta are defined by that re-estimation step. The residual-based strengths alone are not enough to recover those values.

Parameters

effect_estimation : dict or Any Estimate/model container exposing a fitted IRM-like model. data : CausalData The causal dataset used for benchmarking. It must match the fitted long model on treatment, outcome, confounders, and row order. benchmarking_set : list[str] or “all” Confounders to benchmark one by one. Passing "all" benchmarks every confounder in data.confounders in that order. fit_args : dict, optional Additional keyword arguments passed to IRM.estimate(...) on each short model. If score is omitted, ATE/ATTE is inferred from the supplied estimate/model, and defaults to ATE. If store_diagnostics or legacy diagnostic_data is omitted, the short benchmark refits use store_diagnostics=False by default.

Returns

pandas.DataFrame A long-form DataFrame with one row per benchmarked confounder and columns benchmark_confounder, r2_y, r2_d, rho, theta_long, theta_short, and delta.

Notes

Benchmarking allows the user to judge the plausibility of unobserved confounding by comparing it to the strength of observed confounders. For each confounder $X_k$, we calculate:

  • $R^2_{Y \sim X_k | D, X_{-k}}$: The partial $R^2$ of the outcome on $X_k$.

  • $R^2_{D \sim X_k | X_{-k}}$: The partial $R^2$ of the treatment on $X_k$.

These values can then be used as $R^2_Y$ and $R^2_D$ in sensitivity_analysis.

Examples

from causalis.dgp import obs_linear_26_dataset from causalis.scenarios.unconfoundedness.model import IRM from causalis.scenarios.unconfoundedness.refutation import sensitivity_benchmark

1. Fit a model

data = obs_linear_26_dataset(n=1000, seed=42, return_causal_data=True) irm = IRM(data=data).fit() estimate = irm.estimate()

2. Benchmark specific confounders

benchmarks = sensitivity_benchmark(estimate, data, benchmarking_set=[‘x1’, ‘x2’]) # doctest: +SKIP print(benchmarks[[‘benchmark_confounder’, ‘r2_y’, ‘r2_d’]]) # doctest: +SKIP

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_y controls how much residual outcome variation an omitted confounder could explain, :math:r2_d does the same for treatment assignment, and :math:\rho sets 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 interpretation

Notes

This function wraps sensitivity_analysis and 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