--- title: "Policy evaluation: DiD, event studies and staggered adoption" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Policy evaluation: DiD, event studies and staggered adoption} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 6, fig.height = 4) ``` Health policies are often evaluated by comparing the change in an outcome among the units exposed to a policy with the change among units not exposed: difference-in-differences (DiD). This vignette covers: | Design | Tool | |---|---| | One adoption date, average effect | `did()`, two-way fixed effects (TWFE) | | One adoption date, effect over time and pre-trends | `eventstudy()` | | Staggered adoption, heterogeneous effects | `attgt()` (Callaway and Sant'Anna 2021) or `etwfe()` (Wooldridge 2021, 2023) | All functions take the data in long format, `index = c(unit, time)` with a numeric time, and `treat`, the name of a column holding each unit's first treated period (`NA`, `0` or `Inf` for never treated). The examples use small simulated panels and 199 bootstrap draws; the companion book uses real data and more draws. ```{r setup} library(panglm) ``` ## One adoption date: `did()` and `eventstudy()` Half of 200 practices adopt a care pathway in 2019. Visits follow a common upward trend, and the pathway adds 1 visit at adoption and 0.3 more for each year after it. ```{r did-data} set.seed(1) d <- expand.grid(id = 1:200, year = 2015:2022) d$first <- ifelse(d$id <= 100, 2019, NA) a <- rnorm(200) e <- d$year - d$first post <- !is.na(e) & e >= 0 d$visits <- 3 + a[d$id] + 0.1 * (d$year - 2015) + ifelse(post, 1 + 0.3 * e, 0) + rnorm(nrow(d)) d$admitted <- rbinom(nrow(d), 1, plogis(-1 + a[d$id] + 0.05 * (d$year - 2015) + 0.8 * post)) ``` `did()` adds the indicator of being treated, `1(t >= first)`, to a model with unit and year fixed effects. With a single adoption date its coefficient is the average treatment effect on the treated (ATT) under parallel trends. ```{r did} fit <- did(visits ~ 1, data = d, index = c("id", "year"), treat = "first") coef(fit) confint(fit) ``` The pathway adds about 1.7 visits per year on average over the four post-adoption years (true value 1.45). For a binary outcome, `family = "binomial"` fits a conditional (fixed-effects) logit with year effects, so the coefficient is a log odds ratio; `"poisson"` and `"negbin"` are available for counts. ```{r did-binomial} fitb <- did(admitted ~ 1, data = d, index = c("id", "year"), treat = "first", family = "binomial") exp(cbind(OR = coef(fitb), confint(fitb))) ``` The odds of an admission double after adoption (true odds ratio $e^{0.8} = 2.2$). Practices that are never or always admitted carry no information in the conditional likelihood and are dropped. `eventstudy()` replaces the single indicator by one indicator per year relative to adoption, with the year before adoption as reference. The leads (negative event times) test parallel pre-trends; the lags trace the effect over time. ```{r eventstudy, fig.alt = "Event-study estimates with 95% confidence intervals: flat before adoption, positive and rising after"} es <- eventstudy(visits ~ 1, data = d, index = c("id", "year"), treat = "first", leads = 4, lags = 3) es plot(es) ``` The leads are close to zero and jointly not significant (p = 0.74); the effect rises from about 1.1 at adoption to about 1.9 three years later. A non-significant pre-trend test is not proof of parallel trends: it has limited power (Roth 2022). ## Why TWFE fails under staggered adoption When units adopt at different dates and the effect changes with time since adoption, the TWFE coefficient of `did()` is a weighted average of all two-by-two comparisons, including "forbidden" ones in which units treated earlier serve as controls for units treated later. Their growing effects are then subtracted, and some weights can be negative (Goodman-Bacon 2021; de Chaisemartin and D'Haultfoeuille 2020). The estimate can be far from any meaningful average, even with the wrong sign. We simulate 120 provinces joining an insurance scheme in 2016, 2018 or 2020, or not by 2021. The effect on outpatient visits is 2 at adoption and grows by 1 per year. ```{r stag-data} set.seed(1) s <- expand.grid(province = 1:120, year = 2012:2021) s$adoption <- rep(c(2016, 2018, 2020, NA), each = 30)[s$province] s$urban <- rep(runif(120), 10)[s$province] s$pop <- rep(round(runif(120, 50, 500)), 10)[s$province] s$region <- ceiling(s$province / 10) a <- rnorm(120) e <- s$year - s$adoption post <- !is.na(e) & e >= 0 s$visits <- 50 + 5 * a[s$province] + 0.5 * (s$year - 2012) * (1 + s$urban) + ifelse(post, 2 + e, 0) + rnorm(nrow(s)) # true ATT averaged over the treated province-years mean(2 + e[post]) coef(did(visits ~ 1, data = s, index = c("province", "year"), treat = "adoption")) ``` TWFE gives 2.6 against a true average effect of 3.8: it understates the effect by about a third, and a bigger growth of the effect over time would make it worse. ## Group-time effects: `attgt()` The estimator of Callaway and Sant'Anna (2021) computes one ATT for each adoption cohort $g$ and period $t$, ATT$(g, t)$, from a clean two-group, two-period comparison against units never treated (or, with `control = "notyet"`, not yet treated), and then aggregates them. ```{r attgt} cs <- attgt(visits ~ 1, data = s, index = c("province", "year"), treat = "adoption") head(cs$estimates) summary(cs) ``` The overall ATT (the average of the post-adoption ATT$(g, t)$ weighted by cohort size) is 3.5, and its confidence interval covers the true 3.8. `summary()` gives the other aggregations: `"dynamic"` (by time since adoption, an event study free of the TWFE contamination), `"group"` (by cohort) and `"calendar"` (by year). ```{r attgt-agg} summary(cs, type = "dynamic") summary(cs, type = "group")$estimates ``` The event study is flat before adoption and rises after it, from about 2 at adoption to about 6 five years later, as simulated. The early cohort gains most because it has been exposed longest. With covariates, parallel trends holds conditionally on their baseline values; `method = "dr"` (the default) is doubly robust (Sant'Anna and Zhao 2020). `weights` takes sampling or population weights, constant within unit. The multiplier bootstrap (`bstrap = TRUE`) allows clustering at a coarser level, and `cband = TRUE` adds uniform confidence bands, which cover all event times jointly. ```{r attgt-boot, fig.alt = "Dynamic ATT by event time with pointwise intervals and wider uniform bands"} csb <- attgt(visits ~ urban, data = s, index = c("province", "year"), treat = "adoption", control = "notyet", weights = "pop", cluster = "region", cband = TRUE, biters = 199, seed = 1) summary(csb, type = "dynamic", min_e = -3) plot(csb) ``` The uniform bands (grey) are wider than the pointwise intervals; use them to judge the whole event-time profile at once. ## Extended TWFE: `etwfe()` The extended TWFE estimator of Wooldridge (2021) keeps the regression framework but interacts the treatment with every cohort by period cell, so each cohort has its own effect in each period and no forbidden comparison is made. In the linear case it is equivalent to an imputation estimator. `ivar = TRUE` replaces the cohort effects by unit effects (linear model only), which gives the same ATT in a balanced panel. ```{r etwfe} et <- etwfe(visits ~ urban, data = s, index = c("province", "year"), treat = "adoption") summary(et, type = "event") summary(etwfe(visits ~ urban, data = s, index = c("province", "year"), treat = "adoption", ivar = TRUE))$overall ``` The overall ATT is 3.6 and the event-time profile matches that of `attgt()`. Its main advantage is for counts and binary outcomes (Wooldridge 2023): with `family = "poisson"` or `"binomial"`, parallel trends is assumed on the link scale, which respects the nature of the outcome, and the effects are reported on the response scale. `xvar` estimates separate effects by a covariate. ```{r etwfe-pois} s$admissions <- rpois(nrow(s), exp(1 + 0.3 * a[s$province] + 0.03 * (s$year - 2012) + 0.2 * post)) s$rural <- as.numeric(s$urban < 0.5) etp <- etwfe(admissions ~ 1, data = s, index = c("province", "year"), treat = "adoption", family = "poisson", xvar = "rural") summary(etp) ``` The scheme adds about 0.9 admissions per province-year, with similar effects in rural and urban provinces. ## References Callaway, B., and Sant'Anna, P. H. C. (2021). Difference-in-differences with multiple time periods. *Journal of Econometrics*, 225, 200-230. de Chaisemartin, C., and D'Haultfoeuille, X. (2020). Two-way fixed effects estimators with heterogeneous treatment effects. *American Economic Review*, 110, 2964-2996. Goodman-Bacon, A. (2021). Difference-in-differences with variation in treatment timing. *Journal of Econometrics*, 225, 254-277. Roth, J. (2022). Pretest with caution: event-study estimates after testing for parallel trends. *American Economic Review: Insights*, 4, 305-322. Sant'Anna, P. H. C., and Zhao, J. (2020). Doubly robust difference-in-differences estimators. *Journal of Econometrics*, 219, 101-122. Wooldridge, J. M. (2021). Two-way fixed effects, the two-way Mundlak regression, and difference-in-differences estimators. Working paper, SSRN 3906345. Wooldridge, J. M. (2023). Simple approaches to nonlinear difference-in-differences with panel data. *The Econometrics Journal*, 26, C31-C66.