knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE )
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)) ) }))
task <- SyntheticControlTask$new( treated_data = treated_data, donor_data = donor_data, treatment_time = 61 ) print(task) #> SyntheticControlTask #> Donors: 10 #> Periods: 100 #> Pre-treat: 60 #> Post-treat: 40 #> Treatment: 61 #> Estimated: FALSE
Two optimization methods are available:
Uses quadprog::solve.QP() for exact quadratic programming. Requires the quadprog package.
task <- estimate_synthetic_control(task, method = "quadprog")
Uses stats::optim() with a softmax reparameterization. No extra dependencies.
task <- estimate_synthetic_control(task, method = "optim")
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:
plot_synthetic_control(task, type = "trajectory")
Visualize the treatment effect (gap between treated and synthetic):
plot_synthetic_control(task, type = "gap")
After running the placebo test, overlay placebo gaps:
task <- sc_placebo_test(task) plot_synthetic_control(task, type = "placebo")
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_ratios
You can limit the number of placebo units for faster computation:
task <- sc_placebo_test(task, n_placebo = 5)
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.