Created
          November 24, 2023 09:58 
        
      - 
      
 - 
        
Save vankesteren/3a603b307e740a2c69d9a68c28202ef8 to your computer and use it in GitHub Desktop.  
    penalized synthetic control estimation (Abadie & L'Hour, 2021)
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | #' Penalized synthetic control estimator | |
| #' | |
| #' Estimate synthetic control with penalization | |
| #' according to Abadie & L'Hour. | |
| #' | |
| #' @param X1 treated unit covariates | |
| #' @param X0 donor units covariates | |
| #' @param v variable weights | |
| #' @param lambda penalization parameter | |
| #' @param ... osqp settings using osqp::osqpSettings() | |
| #' | |
| #' @return list of weights and the osqp optimizer object | |
| #' | |
| #' @export | |
| synthetic_control <- function(X1, X0, v, lambda = 0, opt_pars = osqp::osqpSettings(polish = TRUE)) { | |
| N_donors <- ncol(X0) | |
| X0v <- X0*sqrt(v) | |
| X1v <- X1*sqrt(v) | |
| # components for quadratic program | |
| # see https://github.com/jeremylhour/pensynth/blob/master/functions/wsoll1.R | |
| X0VX0 <- crossprod(X0v) | |
| X1VX0 <- crossprod(X1v, X0v) | |
| Delta <- apply(X0v - c(X1v), 2, crossprod) | |
| # linear constraint matrix | |
| Amat <- rbind(rep(1, N_donors), diag(N_donors)) | |
| lbound <- c(1, rep(0, N_donors)) | |
| ubound <- rep(1, N_donors + 1) | |
| # stop annoying printing with capture.output. | |
| o <- capture.output({ | |
| solver <- osqp::osqp( | |
| P = X0VX0, | |
| q = -X1VX0 + lambda*Delta, | |
| A = Amat, | |
| l = lbound, | |
| u = ubound, | |
| pars = opt_pars | |
| ) | |
| result <- solver$Solve() | |
| }) | |
| return(list(w = result$x, solution = result, optim = solver)) | |
| } | |
| X0 <- matrix( | |
| c(1, 1.3, | |
| 0.5, 1.8, | |
| 1.1, 2.4, | |
| 1.8, 1.8, | |
| 1.3, 1.8), 2) | |
| X1 <- matrix(c(0.8, 1.65), 2) | |
| lam_seq <- seq(0, 12, length.out = 100) | |
| res <- sapply(lam_seq, \(l) synthetic_control(X1, X0, rep(1, 2), lambda = l)$w) | |
| round(res, 3) | |
| library(tidyverse) | |
| data.frame(lam = lam_seq, w = t(res)) |> | |
| filter(lam != 0) |> | |
| pivot_longer(-lam, names_prefix = "w.") |> | |
| ggplot(aes(x = lam, y = value, colour = name)) + | |
| geom_line() + | |
| scale_x_continuous(trans = "log10") + | |
| labs( | |
| x = "Penalty parameter", | |
| y = "Weight", | |
| colour = "Donor unit", | |
| title = "Weights path as a function of lambda" | |
| ) + | |
| theme_minimal() | |
| solpath <- data.frame(lam = lam_seq, x = t(X0%*%res)) | |
| data.frame(id = c("treated", rep("donor", ncol(X0))), x = t(cbind(X1, X0))) |> | |
| ggplot(aes(x = x.1, y = x.2)) + | |
| geom_line(data = solpath, col = "grey", linetype = 1) + | |
| geom_point(aes(fill = id), size = 3, pch = 21, col = "white") + | |
| theme_minimal() + | |
| xlim(0, 2) + | |
| ylim(1, 2.75) | 
      
      
  Author
  
  
      
          
      
      
            vankesteren
  
      
      
      commented 
        Nov 24, 2023 
      
    
  

  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment