library(tidyMacro)
library(tidyverse)
set_theme(fThemeTidyMacro())
data("BankingDeregulation")
data("DemocracyGrowth")12 Local Projections Difference-in-Differences
Replicating Dube, Girardi, Jordà and Taylor 2025
12.1 Overview
This chapter replicates the two empirical applications of Dube et al. (2025) with fLPDID() and fPlotLPDID(). Shared conventions across the LP engines — formula grammar, ..macros, gap-safe l() / f() / d(), panel_id handling, duplicate-row rejection, output tibble shape — are documented once in the Local Projections syntax primer. Below we focus on what is DiD-specific.
For each horizon \(h\) the long-difference regression \[y_{i,t+h} - y_{i,t-1} \;=\; \beta_h \,\Delta D_{it} \;+\; X_{it}'\gamma_h \;+\; \tau_t \;+\; \varepsilon_{it}\] is fit on a clean-control sample (newly treated units and units that stay untreated over the relevant window). Pre-treatment horizons replace \(y_{i,t+h}\) by \(y_{i,t-j}\) as placebo tests, with \(\beta_{-1}=0\) as the normalization.
Estimator-specific arguments (beyond the shared list; see ?fLPDID for the full API):
post,pre— event-time windows (fLPDID()uses these instead ofhorizons).treat— name of the treatment level column.cluster— cluster column for the CR1 sandwich (default = unit id).nonabsorbing = TRUE— non-absorbing treatment; requires the stabilization windowLand the clean-control flavorccc ∈ {0, 1, 2}.pmd = TRUE— pre-mean-differenced baseline (Stata calendar-time formula, see §5 of the syntax primer).reweight = TRUE— equally-weighted ATT via \(w = 1/(1-p_t)\); with non-empty controls this is not the DDCGteffects raestimator and the function warns accordingly.- Time fixed effects are always absorbed (they are part of the estimator); unit effects are removed by the long difference. Any
| timeyou write is accepted but redundant.
12.3 Variance-weighted vs. equally-weighted ATT
By default the LP-DiD OLS coefficient is a variance-weighted average of clean \(2\times2\) comparisons — efficient, but it overweights treatment cohorts observed when the treated share is small. Setting reweight = TRUE inverts the implicit weights (\(w = 1/(1-p_t)\)) and recovers the equally-weighted ATT.
bank_rw <- fLPDID(lshare ~ bank,
data = BankingDeregulation, panel_id = c("id", "year"),
treat = "bank", post = 9, pre = 9,
reweight = TRUE)fPlotLPDID(`Variance-weighted (OLS)` = bank_fe,
`Equally-weighted ATT` = bank_rw,
ncol = 3, ci = "bars", scales = "fixed",
title = "Weighting and the estimand",
xlab = "Years since deregulation",
ylab = "Labor share")
12.4 Democracy and growth (non-absorbing treatment)
The second application (DGJT, Figure 4) revisits Acemoglu et al. (2019) . Treatment is non-absorbing — countries democratize and revert — and its timing is endogenous to past GDP dynamics, so all specifications control for four lags of GDP (l(lgdp, 1:4)) and clean-control samples are built recursively over a stabilization window of \(L = 20\) years.
Three flavors of the clean-control condition (ccc):
ccc = 0— no restriction beyond the transition definition (the original ANRR local-projection specification);ccc = 1— treated and controls must have a clean past (no democracy transitions within the last \(L\) years);ccc = 2— controls must additionally remain clean up to \(t+h\).
demo_anrr <- fLPDID(lgdp ~ dem + l(lgdp, 1:4),
data = DemocracyGrowth, panel_id = c("id", "year"),
treat = "dem", post = 30, pre = 20,
nonabsorbing = TRUE, L = 20, ccc = 0)
demo_ccc1 <- fLPDID(lgdp ~ dem + l(lgdp, 1:4),
data = DemocracyGrowth, panel_id = c("id", "year"),
treat = "dem", post = 30, pre = 20,
nonabsorbing = TRUE, L = 20, ccc = 1)
demo_ccc2 <- fLPDID(lgdp ~ dem + l(lgdp, 1:4),
data = DemocracyGrowth, panel_id = c("id", "year"),
treat = "dem", post = 30, pre = 20,
nonabsorbing = TRUE, L = 20, ccc = 2)fPlotLPDID(
`ANRR LP specification` = demo_anrr,
`LP-DiD (CCC 1)` = demo_ccc1,
`LP-DiD (CCC 2)` = demo_ccc2,
ncol = 3, scales = 'free',
title = "Democracy and growth (LP-DiD)",
xlab = "Years since democratization",
ylab = "GDP per capita (log x 100)"
) +
ylim(-40, 60)
Democratization raises GDP per capita by roughly 15–25 log points over 25–30 years, with flat pre-trends. Restricting to clean controls (ccc = 1, 2) moderates the ANRR estimates, as contaminated comparisons are removed from the control group.
12.5 Notes on the implementation
Generic behavior — gap-safe operators, duplicate (id, time) rejection, argument validation, PMD baseline formula, reweight + controls warning, direct-C++ boundary checks — is documented in the syntax primer. The two items below are LP-DiD-specific and worth flagging in the empirical context:
- Weighting.
reweight = TRUEimplements the inverse implicit weights of the exogenous/absorbing case. The regression-adjustment (ATET) reweighting DGJT use in the non-absorbing case is not in the C++ engine; the runtime warning triggered byreweight = TRUEwith non-empty controls makes this explicit at the point of use. - Missing-value semantics for clean-control sets. The recursive CCS follow Stata’s conventions at panel boundaries: a missing treatment change counts as “no change” (condition OK); a missing lagged clean-control flag counts as failing the condition. This matters for reproducing the original results observation-for-observation.
