Constructs the bias-adjusted bounds on the target parameter implied by benchmarking against observed covariates, together with one-sided confidence bounds that propagate the estimation uncertainty of the benchmarked gains. Both the fixed-gain and the propagated bounds are returned so the two can be compared.

benchmark_bounds(
  model,
  benchmark,
  kY = 1,
  kD = 1,
  rho2 = NULL,
  level = 0.95,
  combine.method = c("median", "mean"),
  na.rm = TRUE
)

# S3 method for class 'dml_benchmark_bounds'
print(x, digits = 4, ...)

Arguments

model

the dml fit used for the benchmark. Must be fit with a single target ("ate"/"att"/"atu").

benchmark

either a dml_benchmark object (from dml_benchmark, re-used without refitting) or the benchmark_covariates to pass to dml_benchmark.

kY, kD

relative-strength multipliers \(k_{Y,j}\), \(k_{D,j}\) of the latent-vs-observed gains. Either a single number or a vector with one entry per benchmark covariate (in the order of benchmark). Default 1 (a latent confounder as strong as the observed covariate).

rho2

optional fixed squared alignment \(\rho^2\) to use in place of the benchmarked \(\hat\rho_j\). NULL (default) uses covariate \(j\)'s estimated (signed) alignment and, in the propagated CI, its sampling uncertainty. A value in \([0, 1]\).

level

confidence level for the one-sided bounds. Default 0.95 (critical value \(\Phi^{-1}(level)\)).

combine.method

how to combine cross-fitting repetitions, "median" (default) or "mean". Each repetition's bound and its influence-function SE are combined with the same rule as confidence_bounds, so the reported SE folds in the across-repetition spread of the estimate.

na.rm

logical. Should repetitions with a missing benchmark gain be dropped before the repetitions are combined? Default is TRUE. With FALSE, a single failed repetition makes the combined gain NA.

x

an object of class dml_benchmark_bounds.

digits

number of digits to print.

...

ignored.

Value

An object of class dml_benchmark_bounds (a data frame, one row per benchmark covariate) with the bias factor BF, the point bounds theta.minus/theta.plus, the fixed-gain confidence bounds lwr.fixed/upr.fixed (\(=\)confidence_bounds), the uncertainty-propagated confidence bounds lwr/upr, and the propagated bound SEs se.minus/se.plus. Covariates whose implied \(k_D \hat G_{D,j} \ge 1\) (cf.d reaches 1, so the bound diverges) return -Inf/Inf bounds with a warning.

References

Chernozhukov, V., Cinelli, C., Newey, W., Sharma, A., and Syrgkanis, V. (2026). "Long Story Short: Omitted Variable Bias in Causal Machine Learning." Review of Economics and Statistics (Appendix E).

Wang, J., Sant'Anna, P. H. C., Chernozhukov, V., and Cinelli, C. (2026). "Omitted Variable Bias in Difference-in-Differences Designs." Working Paper. (Benchmarking for the conditional ATT/ATU.)

Examples

data("pension")
set.seed(3); idx <- sample(nrow(pension), 2000)
y <- pension$net_tfa[idx]
d <- pension$e401[idx]
x <- model.matrix(~ -1 + age + inc + educ + fsize + marr + twoearn + pira + hown,
                  data = pension[idx, ])
fit <- dml(y, d, x, model = "plm", cf.folds = 2, cf.seed = 3, verbose = FALSE)

# bounds implied by a latent variable as strong as income
bench <- dml_benchmark(fit, benchmark_covariates = "inc")
#> 
#> === Computing benchmarks using covariate: inc  ===
#> 
benchmark_bounds(fit, bench, kY = 1, kD = 1)
#> Covariate benchmark bounds  (kY = 1, kD = 1, rho2 = benchmarked;  95% one-sided)
#> theta.s = 12966.1442,  S = 118534.2640
#> 
#>        BF theta.minus theta.plus lwr.fixed upr.fixed      lwr      upr
#> inc 0.027    9768.652   16163.64  4468.396  22025.71 3890.724 21788.42
#> 
#>  [theta.minus, theta.plus] point estimates of the bounds
#>  [lwr.fixed, upr.fixed]    confidence bounds treating benchmark as fixed
#>  [lwr, upr]                confidence bounds accounting for benchmark uncertainty