cleverly.sensitivity.BenchmarkResult#

class cleverly.sensitivity.BenchmarkResult(estimand: str, covariates: tuple[str, ...], cf_y: float, cf_d: float, rho: float, delta_psi: float, psi_long: float, psi_short: float, sigma2_long: float, sigma2_short: float, nu2_long: float, nu2_short: float, random_state: int | None = None)[source]#

Confounder strengths calibrated against covariates that were observed.

Interpretation: cf_y and cf_d are the sensitivity parameters an unobserved confounder would need in order to be “as important as” the benchmark covariates, measured by how much dropping those covariates degrades the outcome regression and the Riesz representer. delta_psi is how much the estimate actually moved when they were dropped, and rho is the implied degree of adversity – a value well below 1 says the worst-case rho = 1 is pessimistic for confounders like these.

Parameters:
estimandstr

Alias of the estimand benchmarked.

covariatestuple of str

The observed covariates the strength is calibrated against.

cf_yfloat

Gain in explained outcome variation from adjusting for those covariates, (R^2_long - R^2_short) / (1 - R^2_long), clipped to [0, 1]. The raw value is gain_y.

cf_dfloat

Gain in the Riesz representer’s second moment from adjusting for those covariates, (nu2_long - nu2_short) / nu2_short, clipped to [0, 1]. The raw value is gain_d. Both gains are the benchmark convention of Chernozhukov et al. (arXiv:2112.13398v6, Appendix E.5, Remark 8) and of DoubleML. Remark 8 states that they serve as proxies for the shares that omitted_variable_bounds() takes, so they enter it unchanged. Pass the whole record as benchmark=.

rhofloat

The implied alignment of the two.

delta_psifloat

How far the estimate moved when they were dropped.

psi_longfloat

Estimate with the benchmark covariates adjusted for.

psi_shortfloat

Estimate with them dropped.

sigma2_longfloat

Residual outcome variance with them adjusted for.

sigma2_shortfloat

Residual outcome variance with them dropped.

nu2_longfloat

Riesz second moment with them adjusted for.

nu2_shortfloat

Riesz second moment with them dropped.

random_stateint or None

Seed this benchmark ran under. Pass it back to benchmark() to obtain the benchmark again. None only on a record built without a seed.

Attributes:
gain_yfloat

The unclipped outcome gain, sigma2_short / sigma2_long - 1.

gain_dfloat

The unclipped Riesz gain, nu2_long / nu2_short - 1.

Methods#

BenchmarkResult.summary() → str[source]#

Return a printable summary.

Returns:
str

A printable report, one line per reported quantity.

BenchmarkResult.to_dict() → dict[str, Any][source]#

Return a JSON-compatible representation.

Returns:
dict

A JSON-compatible mapping of every reported field.

Attributes#

BenchmarkResult.gain_d#

The unclipped Riesz gain, nu2_long / nu2_short - 1.

A value of 1 or more gives cf_d = 1, at which the bound is unbounded.

BenchmarkResult.gain_y#

The unclipped outcome gain, sigma2_short / sigma2_long - 1.

A value above 1 says that a confounder as strong as the benchmark covariates would explain all of the remaining outcome variation. cf_y then holds the largest share, 1.

BenchmarkResult.random_state: int | None = None#

Seed the short refit ran under, resolved rather than requested: an explicit seed, else the fit’s own, else one drawn here. A benchmark refits, so an estimator carrying no seed would otherwise give a different answer to the same question and cache the first one.

BenchmarkResult.estimand: str#
BenchmarkResult.covariates: tuple[str, ...]#
BenchmarkResult.cf_y: float#
BenchmarkResult.cf_d: float#
BenchmarkResult.rho: float#
BenchmarkResult.delta_psi: float#
BenchmarkResult.psi_long: float#
BenchmarkResult.psi_short: float#
BenchmarkResult.sigma2_long: float#
BenchmarkResult.sigma2_short: float#
BenchmarkResult.nu2_long: float#
BenchmarkResult.nu2_short: float#