Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
4cb0c11
Add Phase 3 PR B: covariates, trends, and extensions for dCDH
igerber Apr 13, 2026
f580dae
Address AI review P1/P2 findings for Phase 3 PR B
igerber Apr 13, 2026
2c6cabf
Fix CI review Round 1: NaN-consistency, overall surface, rank_deficie…
igerber Apr 13, 2026
6f1c99f
Fix CI review Round 2: NaN overall for trends, reject controls+hetero…
igerber Apr 13, 2026
3f7e72c
Fix CI review Round 3: validation guards for het/design2 interactions
igerber Apr 13, 2026
357a551
Fix CI review Round 4: add intercepts to OLS regressions, NaN guard
igerber Apr 13, 2026
bc1dab7
Fix CI review Round 5: partial theta_hat, coarser-than-group, het docs
igerber Apr 13, 2026
000dc46
Fix CI review Round 6: thread set_ids into placebos, small-sample guard
igerber Apr 13, 2026
8fd2e60
Fix CI review Round 7: failed DID^X strata excluded, event study labels
igerber Apr 13, 2026
d087f21
Fix CI review Round 8: exclude failed DID^X strata from N_mat, fix la…
igerber Apr 13, 2026
e3d51db
Fix CI review Round 9: suppress normalized_effects under trends, Inf …
igerber Apr 13, 2026
8a57c5d
Fix CI review Round 10: NaN set validation, design2 raw Y, controls=[]
igerber Apr 13, 2026
4da0841
Fix CI review Round 11: document Design-2 raw-outcome contract
igerber Apr 13, 2026
d20efde
Fix CI review Round 12: document Assumption 14 support-trimming behavior
igerber Apr 13, 2026
cd73917
Fix CI review Round 13: per-period path uses raw Y under trends_linear
igerber Apr 13, 2026
bfed3e6
Address green review P3s: return type, design2 to_dataframe, docstrings
igerber Apr 13, 2026
3e6611f
Update ROADMAP: mark 3i parity tests as shipped
igerber Apr 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,15 +167,15 @@ The dynamic companion paper subsumes the AER 2020 paper: `DID_1 = DID_M`. The si

| Item | Priority | Status |
|------|----------|--------|
| **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 |
| **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 |
| **3c.** State-set-specific trends (`trends_nonparam` option, Web Appendix Section 1.4) | MEDIUM | Not started |
| **3d.** Heterogeneity testing `beta^{het}_l` (Web Appendix Section 1.5) | LOW | Not started |
| **3e.** Design-2 switch-in / switch-out separation (Web Appendix Section 1.6) | LOW | Not started |
| **3f.** Non-binary treatment support (the formula already handles it; this row is documentation + tests) | MEDIUM | Not started |
| **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) |
| **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) |
| **3c.** State-set-specific trends (`trends_nonparam` option, Web Appendix Section 1.4) | MEDIUM | Shipped (PR B) |
| **3d.** Heterogeneity testing `beta^{het}_l` (Web Appendix Section 1.5) | LOW | Shipped (PR B) |
| **3e.** Design-2 switch-in / switch-out separation (Web Appendix Section 1.6) | LOW | Shipped (PR B; convenience wrapper) |
| **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) |
| **3g.** HonestDiD (Rambachan-Roth) integration on `DID^{pl}_l` placebos | MEDIUM | Not started |
| **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 |
| **3i.** Parity tests vs `did_multiplegt_dyn` for covariate and extension specifications | HIGH | Not started |
| **3i.** Parity tests vs `did_multiplegt_dyn` for covariate and extension specifications | HIGH | Shipped (PR B; controls, trends_lin, combined) |

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

Expand Down
94 changes: 94 additions & 0 deletions benchmarks/R/generate_dcdh_dynr_test_values.R
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,100 @@ scenarios$joiners_only_long_multi_horizon <- list(
results = extract_dcdh_multi(res9, n_effects = 5, n_placebos = 5)
)

# ---------------------------------------------------------------------------
# Phase 3: Covariate and linear-trends scenarios
# ---------------------------------------------------------------------------

# Helper: add a covariate column to a panel. The covariate is correlated with
# switch timing (confounding) but the true effect is constant.
add_covariate <- function(df, seed = 42, x_effect = 1.5) {
set.seed(seed)
n <- nrow(df)
groups <- unique(df$group)
# Group-level base value (correlated with which groups switch)
x_base <- setNames(rnorm(length(groups), 0, 1), groups)
# Time-varying component
df$X1 <- x_base[as.character(df$group)] + 0.3 * df$period + rnorm(n, 0, 0.2)
# Add covariate effect to outcome
df$outcome <- df$outcome + x_effect * df$X1
df
}

# Scenario 10: joiners_only with controls (L_max=2)
cat(" Scenario 10: joiners_only_controls\n")
d10 <- gen_reversible(n_groups = N_GOLDEN, n_periods = 8,
pattern = "joiners_only", seed = 110)
d10 <- add_covariate(d10, seed = 210, x_effect = 1.5)
res10 <- did_multiplegt_dyn(
df = d10, outcome = "outcome", group = "group", time = "period",
treatment = "treatment", effects = 2, placebo = 1, ci_level = 95,
controls = "X1"
)
scenarios$joiners_only_controls <- list(
data = list(
group = as.numeric(d10$group),
period = as.numeric(d10$period),
treatment = as.numeric(d10$treatment),
outcome = as.numeric(d10$outcome),
X1 = as.numeric(d10$X1)
),
params = list(pattern = "joiners_only", n_groups = N_GOLDEN, n_periods = 8,
seed = 110, effects = 2, placebo = 1, ci_level = 95,
controls = "X1"),
results = extract_dcdh_multi(res10, n_effects = 2, n_placebos = 1)
)

# Scenario 11: joiners_only with trends_lin (L_max=2)
cat(" Scenario 11: joiners_only_trends_lin\n")
d11 <- gen_reversible(n_groups = N_GOLDEN, n_periods = 8,
pattern = "joiners_only", seed = 111)
# Add group-specific linear trends to outcome
set.seed(311)
groups11 <- unique(d11$group)
g_trends <- setNames(rnorm(length(groups11), 0, 0.5), groups11)
d11$outcome <- d11$outcome + g_trends[as.character(d11$group)] * d11$period
res11 <- did_multiplegt_dyn(
df = d11, outcome = "outcome", group = "group", time = "period",
treatment = "treatment", effects = 2, placebo = 1, ci_level = 95,
trends_lin = TRUE
)
scenarios$joiners_only_trends_lin <- list(
data = export_data(d11),
params = list(pattern = "joiners_only", n_groups = N_GOLDEN, n_periods = 8,
seed = 111, effects = 2, placebo = 1, ci_level = 95,
trends_lin = TRUE),
results = extract_dcdh_multi(res11, n_effects = 2, n_placebos = 1)
)

# Scenario 12: joiners_only with both controls and trends_lin (L_max=2)
cat(" Scenario 12: joiners_only_controls_trends_lin\n")
d12 <- gen_reversible(n_groups = N_GOLDEN, n_periods = 8,
pattern = "joiners_only", seed = 112)
d12 <- add_covariate(d12, seed = 212, x_effect = 1.5)
# Add group-specific linear trends
set.seed(312)
groups12 <- unique(d12$group)
g_trends12 <- setNames(rnorm(length(groups12), 0, 0.5), groups12)
d12$outcome <- d12$outcome + g_trends12[as.character(d12$group)] * d12$period
res12 <- did_multiplegt_dyn(
df = d12, outcome = "outcome", group = "group", time = "period",
treatment = "treatment", effects = 2, placebo = 1, ci_level = 95,
controls = "X1", trends_lin = TRUE
)
scenarios$joiners_only_controls_trends_lin <- list(
data = list(
group = as.numeric(d12$group),
period = as.numeric(d12$period),
treatment = as.numeric(d12$treatment),
outcome = as.numeric(d12$outcome),
X1 = as.numeric(d12$X1)
),
params = list(pattern = "joiners_only", n_groups = N_GOLDEN, n_periods = 8,
seed = 112, effects = 2, placebo = 1, ci_level = 95,
controls = "X1", trends_lin = TRUE),
results = extract_dcdh_multi(res12, n_effects = 2, n_placebos = 1)
)

# ---------------------------------------------------------------------------
# Write output
# ---------------------------------------------------------------------------
Expand Down
135 changes: 135 additions & 0 deletions benchmarks/data/dcdh_dynr_golden_values.json

Large diffs are not rendered by default.

Loading
Loading