# `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