Skip to contents

Traces how the two pre-treatment imbalances of partially pooled SCM trade off as the pooling parameter nu varies: q_pool, the imbalance of the average of the treated units, which the aggregate ATT rests on, and q_sep, the root mean square of the per-block imbalances, which the block-level effects rest on. Both are the cohort-level measures of Ben-Michael, Feller & Rothstein (2022, Appendix A.2), in which a block enters through the sum over its treated units, so a block of \(n_g\) units counts \(n_g\) times towards q_pool and \(n_g^2\) times towards q_sep. Supplying weights to scm_fit() replaces the unit count with the mass it gives them, and the frontier is traced on that same objective.

Usage

scm_balance_frontier(fit, nu_grid = seq(0, 1, length.out = 11L))

Arguments

fit

A staggered coresynth object from scm_fit() with method = "scm".

nu_grid

Values of nu to trace.

Value

A data.frame of class scm_frontier, one row per nu, with nu, q_sep, q_pool, their values normalised by the separate solution (q_sep_rel, q_pool_rel), estimate, and heuristic marking the row closest to the fit's own nu. A fit made with cov_method = "balance" trades off a covariate imbalance as well, so its frontier carries q_cov_sep and q_cov_pool too, and the two normalisation constants are then the combined measure of the paper's footnote 7 rather than the outcome imbalance alone – which is why q_sep_rel and q_pool_rel are not 1 at nu = 0 there. Plot it with plot().

Details

Ben-Michael, Feller & Rothstein (2022) call this plot "an important tool for using partially pooled SCM in practice" (S.4.2): the curve is strongly convex, so a little pooling usually buys a large reduction in one imbalance for a small rise in the other, and where that bargain stops being worth it is a judgement the data can inform but not settle. Their nu heuristic, which scm_fit() uses by default, is one point on this curve.

A fit made with cov_method = "balance" is traced on the objective that includes its covariate term, and the frontier reports that term's two measures in q_cov_sep and q_cov_pool. The plot still draws the outcome imbalances, which are then only part of what the fit trades off.

References

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

Examples

set.seed(1)
panel <- expand.grid(unit = 1:15, year = 1:20)
adopt <- c(`1` = 12, `2` = 15, `3` = 17)
panel$treated <- as.integer(!is.na(adopt[as.character(panel$unit)]) &
  panel$year >= adopt[as.character(panel$unit)])
panel$treated[is.na(panel$treated)] <- 0L
panel$gdp <- panel$unit + 0.4 * panel$year +
  rnorm(nrow(panel)) + 2 * panel$treated
fit <- scm_fit(gdp ~ treated | unit + year, data = panel, method = "scm")
fr <- scm_balance_frontier(fit, nu_grid = c(0, 0.5, 1))
fr
#> === SCM balance possibility frontier ===
#> nu used by the fit: 0.5635 
#> q_pool: imbalance of the treated average (drives the ATT)
#> q_sep : RMS of the per-block imbalances (drives block-level effects)
#> 
#>   nu  q_sep q_pool estimate q_sep_rel q_pool_rel heuristic
#>  0.0 0.6310 0.3243   1.8017    1.0000     1.0000          
#>  0.5 0.6833 0.2193   1.8730    1.0829     0.6762 <- nu_hat
#>  1.0 0.8579 0.1810   1.8449    1.3596     0.5582