Skip to content

Commit d801cc1

Browse files
authored
Merge pull request #302 from igerber/dcdh-phase3b-covariates-trends
Add Phase 3 PR B: covariates, trends, and extensions for dCDH
2 parents 0028102 + 3e6611f commit d801cc1

File tree

8 files changed

+2087
-89
lines changed

8 files changed

+2087
-89
lines changed

ROADMAP.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -167,15 +167,15 @@ The dynamic companion paper subsumes the AER 2020 paper: `DID_1 = DID_M`. The si
167167

168168
| Item | Priority | Status |
169169
|------|----------|--------|
170-
| **3a.** Residualization-style covariate adjustment `DID^X` (Web Appendix Section 1.2 of dynamic paper). **Note:** NOT doubly-robust, NOT IPW, NOT Callaway-Sant'Anna-style. | HIGH | Not started |
171-
| **3b.** Group-specific linear trends `DID^{fd}` (Web Appendix Section 1.3, Lemma 6) — second-difference estimator with cumulation for level effects | MEDIUM | Not started |
172-
| **3c.** State-set-specific trends (`trends_nonparam` option, Web Appendix Section 1.4) | MEDIUM | Not started |
173-
| **3d.** Heterogeneity testing `beta^{het}_l` (Web Appendix Section 1.5) | LOW | Not started |
174-
| **3e.** Design-2 switch-in / switch-out separation (Web Appendix Section 1.6) | LOW | Not started |
175-
| **3f.** Non-binary treatment support (the formula already handles it; this row is documentation + tests) | MEDIUM | Not started |
170+
| **3a.** Residualization-style covariate adjustment `DID^X` (Web Appendix Section 1.2 of dynamic paper). **Note:** NOT doubly-robust, NOT IPW, NOT Callaway-Sant'Anna-style. | HIGH | Shipped (PR B) |
171+
| **3b.** Group-specific linear trends `DID^{fd}` (Web Appendix Section 1.3, Lemma 6) — second-difference estimator with cumulation for level effects | MEDIUM | Shipped (PR B) |
172+
| **3c.** State-set-specific trends (`trends_nonparam` option, Web Appendix Section 1.4) | MEDIUM | Shipped (PR B) |
173+
| **3d.** Heterogeneity testing `beta^{het}_l` (Web Appendix Section 1.5) | LOW | Shipped (PR B) |
174+
| **3e.** Design-2 switch-in / switch-out separation (Web Appendix Section 1.6) | LOW | Shipped (PR B; convenience wrapper) |
175+
| **3f.** Non-binary treatment support (the formula already handles it; this row is documentation + tests) | MEDIUM | Shipped (PR #300; also ships placebo SE, L_max=1 per-group path, parity SE assertions) |
176176
| **3g.** HonestDiD (Rambachan-Roth) integration on `DID^{pl}_l` placebos | MEDIUM | Not started |
177177
| **3h.** **Single comprehensive tutorial notebook** covering all three phases — Favara-Imbs (2015) banking deregulation replication as the headline application, with comparison plots vs LP / TWFE | HIGH | Not started |
178-
| **3i.** Parity tests vs `did_multiplegt_dyn` for covariate and extension specifications | HIGH | Not started |
178+
| **3i.** Parity tests vs `did_multiplegt_dyn` for covariate and extension specifications | HIGH | Shipped (PR B; controls, trends_lin, combined) |
179179

180180
### Out of scope for the dCDH single-class evolution
181181

benchmarks/R/generate_dcdh_dynr_test_values.R

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,100 @@ scenarios$joiners_only_long_multi_horizon <- list(
388388
results = extract_dcdh_multi(res9, n_effects = 5, n_placebos = 5)
389389
)
390390

391+
# ---------------------------------------------------------------------------
392+
# Phase 3: Covariate and linear-trends scenarios
393+
# ---------------------------------------------------------------------------
394+
395+
# Helper: add a covariate column to a panel. The covariate is correlated with
396+
# switch timing (confounding) but the true effect is constant.
397+
add_covariate <- function(df, seed = 42, x_effect = 1.5) {
398+
set.seed(seed)
399+
n <- nrow(df)
400+
groups <- unique(df$group)
401+
# Group-level base value (correlated with which groups switch)
402+
x_base <- setNames(rnorm(length(groups), 0, 1), groups)
403+
# Time-varying component
404+
df$X1 <- x_base[as.character(df$group)] + 0.3 * df$period + rnorm(n, 0, 0.2)
405+
# Add covariate effect to outcome
406+
df$outcome <- df$outcome + x_effect * df$X1
407+
df
408+
}
409+
410+
# Scenario 10: joiners_only with controls (L_max=2)
411+
cat(" Scenario 10: joiners_only_controls\n")
412+
d10 <- gen_reversible(n_groups = N_GOLDEN, n_periods = 8,
413+
pattern = "joiners_only", seed = 110)
414+
d10 <- add_covariate(d10, seed = 210, x_effect = 1.5)
415+
res10 <- did_multiplegt_dyn(
416+
df = d10, outcome = "outcome", group = "group", time = "period",
417+
treatment = "treatment", effects = 2, placebo = 1, ci_level = 95,
418+
controls = "X1"
419+
)
420+
scenarios$joiners_only_controls <- list(
421+
data = list(
422+
group = as.numeric(d10$group),
423+
period = as.numeric(d10$period),
424+
treatment = as.numeric(d10$treatment),
425+
outcome = as.numeric(d10$outcome),
426+
X1 = as.numeric(d10$X1)
427+
),
428+
params = list(pattern = "joiners_only", n_groups = N_GOLDEN, n_periods = 8,
429+
seed = 110, effects = 2, placebo = 1, ci_level = 95,
430+
controls = "X1"),
431+
results = extract_dcdh_multi(res10, n_effects = 2, n_placebos = 1)
432+
)
433+
434+
# Scenario 11: joiners_only with trends_lin (L_max=2)
435+
cat(" Scenario 11: joiners_only_trends_lin\n")
436+
d11 <- gen_reversible(n_groups = N_GOLDEN, n_periods = 8,
437+
pattern = "joiners_only", seed = 111)
438+
# Add group-specific linear trends to outcome
439+
set.seed(311)
440+
groups11 <- unique(d11$group)
441+
g_trends <- setNames(rnorm(length(groups11), 0, 0.5), groups11)
442+
d11$outcome <- d11$outcome + g_trends[as.character(d11$group)] * d11$period
443+
res11 <- did_multiplegt_dyn(
444+
df = d11, outcome = "outcome", group = "group", time = "period",
445+
treatment = "treatment", effects = 2, placebo = 1, ci_level = 95,
446+
trends_lin = TRUE
447+
)
448+
scenarios$joiners_only_trends_lin <- list(
449+
data = export_data(d11),
450+
params = list(pattern = "joiners_only", n_groups = N_GOLDEN, n_periods = 8,
451+
seed = 111, effects = 2, placebo = 1, ci_level = 95,
452+
trends_lin = TRUE),
453+
results = extract_dcdh_multi(res11, n_effects = 2, n_placebos = 1)
454+
)
455+
456+
# Scenario 12: joiners_only with both controls and trends_lin (L_max=2)
457+
cat(" Scenario 12: joiners_only_controls_trends_lin\n")
458+
d12 <- gen_reversible(n_groups = N_GOLDEN, n_periods = 8,
459+
pattern = "joiners_only", seed = 112)
460+
d12 <- add_covariate(d12, seed = 212, x_effect = 1.5)
461+
# Add group-specific linear trends
462+
set.seed(312)
463+
groups12 <- unique(d12$group)
464+
g_trends12 <- setNames(rnorm(length(groups12), 0, 0.5), groups12)
465+
d12$outcome <- d12$outcome + g_trends12[as.character(d12$group)] * d12$period
466+
res12 <- did_multiplegt_dyn(
467+
df = d12, outcome = "outcome", group = "group", time = "period",
468+
treatment = "treatment", effects = 2, placebo = 1, ci_level = 95,
469+
controls = "X1", trends_lin = TRUE
470+
)
471+
scenarios$joiners_only_controls_trends_lin <- list(
472+
data = list(
473+
group = as.numeric(d12$group),
474+
period = as.numeric(d12$period),
475+
treatment = as.numeric(d12$treatment),
476+
outcome = as.numeric(d12$outcome),
477+
X1 = as.numeric(d12$X1)
478+
),
479+
params = list(pattern = "joiners_only", n_groups = N_GOLDEN, n_periods = 8,
480+
seed = 112, effects = 2, placebo = 1, ci_level = 95,
481+
controls = "X1", trends_lin = TRUE),
482+
results = extract_dcdh_multi(res12, n_effects = 2, n_placebos = 1)
483+
)
484+
391485
# ---------------------------------------------------------------------------
392486
# Write output
393487
# ---------------------------------------------------------------------------

benchmarks/data/dcdh_dynr_golden_values.json

Lines changed: 135 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)