Skip to contents

Confidence intervals and p-values for the aggregate ATT of a staggered SCM fit via the weighted multiplier (wild) bootstrap of Ben-Michael, Feller & Rothstein (2022, Section 5.3), adapting Otsu & Rai (2017). The aggregate ATT is written as a weighted average of per-treated-unit effect contributions; each bootstrap draw perturbs those contributions with independent golden-ratio two-point multipliers (mean 0, variance 1) while donor weights and outcomes are kept fixed.

Usage

scm_inference(
  fit,
  method = "wild_bootstrap",
  n_boot = 1000L,
  level = 0.95,
  alternative = c("two.sided", "greater", "less"),
  seed = NULL
)

Arguments

fit

A staggered SCM fit from scm_fit() (method = "scm" on a panel with multiple adoption times). Sharp fits are rejected: use mspe_ratio_pval() or conformal_inference() instead.

method

Only "wild_bootstrap" is available.

n_boot

Number of bootstrap draws. Default 1000.

level

Confidence level. Default 0.95.

alternative

Direction of the alternative hypothesis for the p-value: "two.sided" (default), "greater", or "less".

seed

Optional RNG seed.

Value

A coresynth_inference object with the standard fields (estimate, se, p_value, ci_lower, ci_upper, method, staggered, n_controls, alternative, boot_ests), compatible with tidy.coresynth_inference() and glance.coresynth_inference(). n_treated additionally records the number of treated units resampled by the multipliers.

Details

Works with both staggered SCM paths (nu = NULL legacy and the partially pooled path) and honours the cohort aggregation weights N_treated x T_post. For intercept-shifted fits (fixedeff = TRUE) the per-unit contributions are computed in difference-in-differences form, i.e. each treated unit is demeaned by its own pre-treatment mean.

With very few treated units the multiplier distribution has few atoms, so the bootstrap is unreliable; a warning is issued below 5 treated units.

References

Ben-Michael, E., Feller, A., & Rothstein, J. (2022). Synthetic controls with staggered adoption. JRSS-B, 84(2), 351-381.

Examples

# \donttest{
set.seed(1)
dat <- expand.grid(time = 1:20, id = paste0("u", 1:12))
dat$y <- rnorm(nrow(dat)) + as.numeric(factor(dat$id))
dat$d <- as.integer(
  (dat$id == "u1" & dat$time > 10) | (dat$id == "u2" & dat$time > 14)
)
fit <- scm_fit(y ~ d | id + time, data = dat, method = "scm")
scm_inference(fit, n_boot = 200, seed = 1)
#> Warning: Wild bootstrap with fewer than 5 treated units is unreliable; interpret the interval with caution.
#> Inference (method=wild_bootstrap, alternative=two.sided, staggered)
#>   Estimate  : -1.2358 
#>   SE        : 0.129 
#>   p-value   : 0.005 
#>   CI        : [-1.4321, -1.0395]
#>   Controls  : 11 
# }