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: usemspe_ratio_pval()orconformal_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
# }
