Synthetic control methods (Abadie and Gardeazabal, 2003; Abadie, Diamond, and Hainmueller, 2010) provide a data-driven approach to construct a counterfactual for a single treated unit from a weighted combination of donor (control) units. This is particularly useful when:
The EventStudy package implements synthetic control estimation with two optimization methods and includes a placebo test for inference.
Synthetic control requires: - Treated unit: time series of the outcome for the treated unit - Donor pool: time series of outcomes for potential control units - Treatment time: when the intervention occurs
library(EventStudy)
library(tibble)
# Treated unit
treated_data <- tibble(
time = 1:100,
outcome = cumsum(rnorm(100, mean = 0.1, sd = 0.5))
)
# Inject treatment effect after period 60
treated_data$outcome[61:100] <- treated_data$outcome[61:100] + 5
# Donor pool (long format)
donor_data <- do.call(rbind, lapply(paste0("D", 1:10), function(u) {
tibble(
unit = u,
time = 1:100,
outcome = cumsum(rnorm(100, mean = 0.1, sd = 0.5))
)
}))Two optimization methods are available:
Uses quadprog::solve.QP() for exact quadratic
programming. Requires the quadprog package.
After estimation, the task contains:
# Donor weights (sum to 1, all >= 0)
task$results$weights
#> D1 D2 D3 D4 D5 ...
#> 0.312 0.000 0.188 0.000 0.500 ...
# Average treatment effect on the treated
task$results$att
#> [1] 4.87
# Pre- and post-treatment mean squared prediction error
task$results$pre_mspe
task$results$post_mspe
# Full trajectory
task$results$trajectory
#> # A tibble: 100 x 4
#> time treated synthetic gap
#> <int> <dbl> <dbl> <dbl>
#> 1 1 0.234 0.289 -0.0547
#> ...Three plot types are available:
Compare the treated unit to its synthetic counterpart:
Visualize the treatment effect (gap between treated and synthetic):
The placebo test re-estimates the synthetic control for each donor unit as a pseudo-treated unit. The treated unit’s RMSPE ratio is ranked among all units to produce a p-value:
\[p = \frac{\text{rank of treated ratio}}{N_{donors} + 1}\]
where the RMSPE ratio is \(\frac{RMSPE_{post}}{RMSPE_{pre}}\).
task <- sc_placebo_test(task)
# p-value
task$results$placebo$p_value
# RMSPE ratios for all donor units
task$results$placebo$rmspe_ratiosYou can limit the number of placebo units for faster computation: