nonabsdid: Visualize Heterogeneity-Robust DID Event Studies with Non-Absorbing Binary Treatments

R-CMD-check r-universe version Lifecycle: experimental

nonabsdid is an R package for visualizing and comparing heterogeneity-robust staggered DID event-study estimates under non-absorbing binary treatment. Its example output plot is as follows.

Comparison of heterogeneity-robust estimators vs naive TWFE

It covers existing multiple estimators and runs analysis via their own packages, then puts their output on the same time axis, the same tidy schema, and the same ggplot2 panel so you can compare them at a glance.

Supported estimators:

Note: The DCDH estimator depends on DIDmultiplegtDYN, which in turn requires polars. polars is not on CRAN, so install it from R-multiverse:

Sys.setenv(NOT_CRAN = "true")
install.packages("polars",
  repos = c("https://community.r-multiverse.org", "https://cloud.r-project.org"))

Plus an optional naive TWFE reference series (via fixest) drawn in a neutral color so you can see what the heterogeneity-robust estimators are correcting against.

Installation

# Development version from GitHub:
# install.packages("pak")
pak::pak("takuma1102/nonabsdid")

You can install this package through r-universe.

install.packages(
  "nonabsdid",
  repos = c("https://takuma1102.r-universe.dev", getOption("repos"))
)

The estimator packages themselves (DIDmultiplegtDYN, PanelMatch, fect, fixest) are listed in Suggests, so install the ones you plan to use.

First-pass exploratory analysis

At the early stage of an analysis, use nabs_event_study_simple() to get an initial sense of how the event-study estimates look across estimators. It runs the heterogeneity-robust estimators with reasonable defaults, fits a naive TWFE reference, and gives you a single overlay plot to inspect before moving on to estimator-specific tuning and robustness checks.

library(nonabsdid)

res <- nabs_event_study_simple(
  mydata,
  outcome   = "y",
  treatment = "d",
  unit      = "id",
  time      = "t"
)

res$plot       # the figure
res$tidy       # combined tidy tibble across methods
res$per_method # per-method tidy tibbles
res$fits       # the native estimator objects, for diagnostics

If a particular estimator’s package is not installed, that estimator is skipped with a message, and the remaining methods still produce output.

Careful runs

For publication-ready work, switch to the full wrapper or to the underlying packages directly. The unified wrapper:

res_dcdh <- nabs_event_study(mydata,
                             outcome = "y", treatment = "d",
                             unit = "id", time = "t",
                             method = "DCDH",
                             lags = 6, leads = 8,
                             controls = c("x1", "x2"))

res_pm   <- nabs_event_study(mydata, ..., method = "PanelMatch")
res_ife  <- nabs_event_study(mydata, ..., method = "IFE")
res_fe   <- nabs_event_study(mydata, ..., method = "FE")
res_mc   <- nabs_event_study(mydata, ..., method = "MC")

Or call estimators directly and tidy their output:

fit <- DIDmultiplegtDYN::did_multiplegt_dyn(
  df = mydata, outcome = "y", group = "id", time = "t",
  treatment = "d", effects = 9, placebo = 6
)
tidy_dcdh <- as_nabs_event_study(fit, outcome = "y")

# Naive TWFE reference for the plot:
ref <- naive_twfe(mydata, outcome = "y", treatment = "d",
                  unit = "id", time = "t",
                  lags = 6, leads = 8)

# Overlay everything:
nabs_event_plot(
  res_dcdh$tidy, res_pm$tidy, res_ife$tidy,
  reference = ref,
  xlim = c(-6, 8), ylim = c(-2, 2),
  ylab = "Effect on outcome"
)

Plot styles

nabs_event_plot() offers two ways to encode the pre/post distinction, plus an option to join point estimates with a thin line. Both arguments also flow through nabs_event_study_simple() via ....

By default (style = "prepost_color"), each method gets its own color with separate shades for pre- and post-treatment periods:

Comparison of heterogeneity-robust estimators vs naive TWFE
nabs_event_plot(res_dcdh$tidy, res_pm$tidy, res_ife$tidy, reference = ref)

With style = "method_shape", color encodes the method only, and the pre/post distinction is carried by the marker shape (hollow circles for pre, filled triangles for post). This reads cleanly in grayscale:

Comparison of heterogeneity-robust estimators vs naive TWFE
nabs_event_plot(res_dcdh$tidy, res_pm$tidy, res_ife$tidy, reference = ref,
                style = "method_shape")

Set connect = TRUE (works with either style) to join each series’ point estimates with a thin line, drawn through the full path (pre and post are connected, including across the treatment boundary):

Comparison of heterogeneity-robust estimators vs naive TWFE
nabs_event_plot(res_dcdh$tidy, res_pm$tidy, res_ife$tidy, reference = ref,
                style = "method_shape", connect = TRUE)

Working from existing results

If you have already run supported estimators, you can convert their result objects into the common nabs_event_study_tbl schema with as_nabs_event_study().

tidy_one <- as_nabs_event_study(fit_dcdh, outcome = "y")

tidy_all <- as_nabs_event_study(
  list(fit_dcdh, fit_panelmatch, fit_ife),
  outcome = "y"
)

Results returned by nabs_event_study() and nabs_event_study_simple() can also be passed back to as_nabs_event_study():

res <- nabs_event_study(...)
tidy_res <- as_nabs_event_study(res)

res_simple <- nabs_event_study_simple(...)
tidy_simple <- as_nabs_event_study(res_simple)

Tidy schema

All tidiers return a tibble of class nabs_event_study_tbl with these columns:

column type description
time int Relative period (0 = treatment onset/switch).
estimate num Point estimate.
std.error num Standard error (may be NA).
conf.low num Lower CI bound.
conf.high num Upper CI bound.
window chr "pre" if time < 0, else "post".
method chr "DCDH", "PanelMatch", "IFE", "FE", "MC", "TWFE", …
outcome chr Outcome variable name.

All methods share this convention (time = 0 = treatment onset / the period of a treatment switch, time = -1 = reference). DCDH’s native output anchors the reference at 0, so it is shifted by one period internally to line up.

Anything coercible to a data frame with at least time and estimate columns also flows through as_nabs_event_study(). Adding a new estimator later means writing a one-line method that pulls the right slots — the plotting code keeps working.

Status

This package is experimental. The output schema is intended to be stable, but the upstream estimator packages occasionally rearrange their internal structures, so please pin versions in production code.

Citation

If you use nonabsdid in your work, please kindly cite the package:

Takuma Iwasaki (2026). nonabsdid: Heterogeneity-Robust DID Event Studies with Non-Absorbing Binary Treatments. R package version X.Y.Z. https://github.com/takuma1102/nonabsdid

Please also cite whichever underlying estimator(s) you actually used.