See 'Synthetic Difference in Differences' by Arkhangelsky et al. This implements Algorithm 1.

synthdid_estimate(
  Y,
  N0,
  T0,
  X = array(dim = c(dim(Y), 0)),
  noise.level = sd(apply(Y[1:N0, 1:T0], 1, diff)),
  eta.omega = ((nrow(Y) - N0) * (ncol(Y) - T0))^(1/4),
  eta.lambda = 1e-06,
  zeta.omega = eta.omega * noise.level,
  zeta.lambda = eta.lambda * noise.level,
  omega.intercept = TRUE,
  lambda.intercept = TRUE,
  weights = list(omega = NULL, lambda = NULL),
  update.omega = is.null(weights$omega),
  update.lambda = is.null(weights$lambda),
  min.decrease = 1e-05 * noise.level,
  max.iter = 10000,
  sparsify = sparsify_function,
  max.iter.pre.sparsify = 100
)

Arguments

Y

the observation matrix.

N0

the number of control units (N_co in the paper). Rows 1-N0 of Y correspond to the control units.

T0

the number of pre-treatment time steps (T_pre in the paper). Columns 1-T0 of Y correspond to pre-treatment time steps.

X

an optional 3-D array of time-varying covariates. Shape should be N X T X C for C covariates.

noise.level,

an estimate of the noise standard deviation sigma. Defaults to the standard deviation of first differences of Y.

eta.omega

determines the tuning parameter zeta.omega = eta.omega * noise.level. Defaults to the value (N_tr T_post)^(1/4).

eta.lambda

analogous for lambda. Defaults to an 'infinitesimal' value 1e-6.

zeta.omega

if passed, overrides the default zeta.omega = eta.omega * noise.level. Deprecated.

zeta.lambda

analogous for lambda.

omega.intercept

Binary. Use an intercept when estimating omega.

lambda.intercept

Binary. Use an intercept when estimating lambda.

weights

a list with fields lambda and omega. If non-null weights$lambda is passed, we use them instead of estimating lambda weights. Same for weights$omega.

update.omega

If true, solve for omega using the passed value of weights$omega only as an initialization. If false, use it exactly as passed. Defaults to false if a non-null value of weights$omega is passed.

update.lambda

Analogous.

min.decrease

Tunes a stopping criterion for our weight estimator. Stop after an iteration results in a decrease in penalized MSE smaller than min.decrease^2.

max.iter

A fallback stopping criterion for our weight estimator. Stop after this number of iterations.

sparsify

A function mapping a numeric vector to a (presumably sparser) numeric vector of the same shape, which must sum to one. If not null, we try to estimate sparse weights via a second round of Frank-Wolfe optimization initialized at sparsify( the solution to the first round ).

max.iter.pre.sparsify

Analogous to max.iter, but for the pre-sparsification first-round of optimization. Not used if sparsify=NULL.

Value

An average treatment effect estimate with 'weights' and 'setup' attached as attributes. 'weights' contains the estimated weights lambda and omega and corresponding intercepts, as well as regression coefficients beta if X is passed. 'setup' is a list describing the problem passed in: Y, N0, T0, X.