The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
This is a separate feature line from the event-study workflow in Getting started. It supports DCDH and the fect family only; see Why not PanelMatch? below.
The main nonabsdid workflow ([nabs_event_study()] /
[nabs_event_plot()]) collapses every treated cohort onto a single
relative-time axis: one curve per estimator. That is the right summary
most of the time, but it hides which cohorts drive the
average.
The effect matrix keeps the onset cohort as a second dimension. Instead of a curve you get a grid – rows are onset cohorts, columns are relative (or calendar) time, and the fill is the estimated effect – drawn as a heatmap. It is the two-dimensional companion to the event-study overlay, built from the same estimator objects.
Three user-facing pieces mirror the event-study API:
nabs_effect_cells() – fit one estimator and return its
cohort cells.as_nabs_effect_cells() – coerce an existing estimator
object into the cell schema.plot_effect_matrix() – draw one or more cell tables as
heatmaps.set.seed(1)
N <- 120; TT <- 14
panel <- expand.grid(id = 1:N, t = 1:TT)
grp <- panel$id %% 4 # group 0 = never treated
onset <- c(`1` = 4L, `2` = 6L, `3` = 8L)[as.character(grp)]
# a quarter of switchers turn OFF again 3 periods later (non-absorbing)
off <- (panel$id %% 8 == 1) & !is.na(onset) & panel$t >= onset + 3L
panel$d <- as.integer(!is.na(onset) & panel$t >= onset & !off)
panel$y <- rnorm(N, sd = .5)[panel$id] + 0.15 * panel$t +
ifelse(panel$d == 1, 0.4, 0) + rnorm(nrow(panel))nabs_effect_cells()nabs_effect_cells() wires up what a cohort breakdown
needs for each estimator (a unit-level onset cohort for DCDH;
keep.sims = TRUE for fect bootstrap cell SEs), so you only
pass the usual arguments.
res_ife <- nabs_effect_cells(
panel, outcome = "y", treatment = "d", unit = "id", time = "t",
method = "IFE", lags = 4, leads = 6, nboots = 100
)
#> For identification purposes, units whose number of untreated periods <5 are dropped automatically.
#> Cross-validating ...
#> Criterion: Mean Squared Prediction Error
#> Interactive fixed effects model...
#> r = 2; sigma2 = 0.89113; IC = 2.40771; PC = 2.98096; MSPE = 2.70052
#>
#> r* = 2
res_ife$cells
#> # <nabs_effect_cell_tbl>: 19 cells, 3 cohorts, methods: "IFE"
#> # A tibble: 19 × 12
#> cohort event_time calendar_time estimate std.error conf.low conf.high n
#> <int> <int> <int> <dbl> <dbl> <dbl> <dbl> <int>
#> 1 4 0 4 0.938 0.700 -0.848 0.903 15
#> 2 4 1 5 0.580 0.672 -1.91 1.30 15
#> 3 4 2 6 1.06 0.889 -1.34 1.45 15
#> 4 6 0 6 0.260 0.477 -0.801 1.07 30
#> 5 6 1 7 0.388 0.346 -0.771 0.757 30
#> 6 6 2 8 0.0478 0.643 -0.939 1.96 30
#> 7 6 3 9 0.667 0.679 -0.736 2.20 30
#> 8 6 4 10 1.29 1.00 -1.31 2.48 30
#> 9 6 5 11 0.921 0.735 -1.10 1.92 30
#> 10 6 6 12 0.682 0.611 -0.769 1.95 30
#> 11 6 7 13 0.130 0.648 -1.45 0.906 30
#> 12 6 8 14 -0.0850 0.684 -1.08 1.69 30
#> 13 8 0 8 0.278 0.637 -1.17 1.31 30
#> 14 8 1 9 0.680 0.677 -1.10 1.48 30
#> 15 8 2 10 0.800 1.13 -2.69 2.43 30
#> 16 8 3 11 0.778 0.716 -1.15 1.55 30
#> 17 8 4 12 0.655 0.513 -0.431 1.74 30
#> 18 8 5 13 -0.00298 0.800 -1.04 1.17 30
#> 19 8 6 14 -0.280 0.809 -0.901 1.85 30
#> # ℹ 4 more variables: window <chr>, method <chr>, outcome <chr>,
#> # se_method <chr>A single-method call is titled with the method automatically, and
show_se = TRUE prints the standard error (in parentheses)
beneath each estimate. The fect surface only covers
treated cells, so the matrix starts at
event_time = 0 (the first treated period) and has no
pre-period column.
For DCDH, dcdh_strategy = "loop" (the default)
re-estimates the event study once per onset cohort against the
never-treated units; "by" instead issues a single
did_multiplegt_dyn(..., by = cohort) call.
res_dcdh <- nabs_effect_cells(
panel, outcome = "y", treatment = "d", unit = "id", time = "t",
method = "DCDH", lags = 3, leads = 5, dcdh_strategy = "loop"
)
#> ℹ Attached polars for the DCDH backend.
#> The number of placebos which can be estimated is at most 2.The command will therefore try to estimate 2 placebo(s).Unlike fect, DCDH reports placebo (pre-period) cells and a reference
period (normalized to 0 at event_time = -1),
so its matrix spans negative event time too.
The recommended view is one heatmap per method (above): each is titled with its method and stays readable. If you do want them in one figure, passing several cell tables facets them with a shared fill scale and legend:
The faceted view is convenient but gets crowded fast (especially with in-tile labels), which is why per-method plots are the default emphasis.
Either way, read this as triangulation of the
pattern, not as cell-by-cell equality. The two
estimators line up on the same axes – both define the cohort as the
onset period and anchor event_time = 0 at the first treated
period – but they do not target identical quantities:
"loop" strategy
compares each cohort to the never-treated; fect’s counterfactual is
model-based over all controls.event_time cells under both methods, but each handles
carryover differently, so those cells are the least comparable.The fill encodes the point estimate only. Standard errors live in the
std.error column and can be drawn in each tile with
show_se = TRUE ("bootstrap" for fect,
"native" or CI-recovered "ci" for DCDH; see
the schema below). For claims about whether two cells differ, look at
those SEs rather than the colours.
If you already fit an estimator, coerce it with
as_nabs_effect_cells(). For fect you need
imputed_outcomes() (fect >= 2.4.0); for bootstrap cell
SEs the fit must have been run with
se = TRUE, keep.sims = TRUE. For DCDH, pass an object run
with a unit-level cohort by variable.
fit <- fect::fect(y ~ d, data = panel, index = c("id", "t"),
method = "fe", force = "two-way",
se = TRUE, nboots = 100, keep.sims = TRUE)
cells <- as_nabs_effect_cells(fit, method = "FE", outcome = "y")A data-frame escape hatch needs no estimator packages – handy for testing the plot or building cells from numbers you already have:
raw <- expand.grid(cohort = c(4L, 6L, 8L), event_time = -2:5)
raw$estimate <- with(raw, ifelse(event_time < 0, 0, 0.4 + 0.05 * event_time))
raw$std.error <- 0.07
cells <- as_nabs_effect_cells(raw, method = "FE", outcome = "y")
plot_effect_matrix(cells, show_estimates = TRUE, show_se = TRUE)aggregate_effects() averages cells over cohorts and
returns a nabs_event_study_tbl, making explicit that the
event study is the cohort-collapsed view of the same cells.
(Re-aggregated standard errors are not computed, so they come back
NA; use it for a quick overlay, not inference.)
agg <- aggregate_effects(res_ife$cells, by = "event_time")
#> ℹ Aggregated over cohorts; std.error is "NA" (re-aggregated SEs need replicate
#> draws).
nabs_event_plot(agg, xlim = c(0, 6))as_nabs_effect_cells() returns a tibble of class
nabs_effect_cell_tbl:
| column | type | description |
|---|---|---|
cohort |
int | Onset calendar period (first treated period). |
event_time |
int | Relative period; 0 = onset. |
calendar_time |
int | cohort + event_time (may be NA). |
estimate |
num | Cell point estimate. |
std.error |
num | Standard error (may be NA). |
conf.low/high |
num | CI bounds. |
n |
int | Treated cells aggregated (fect only; NA for DCDH). |
window |
chr | "pre" / "post". |
method |
chr | Estimator label. |
outcome |
chr | Outcome name. |
se_method |
chr | "bootstrap" (fect),
"native"/"ci" (DCDH), or
"none". |
The se_method column records how uncertainty was
produced. fect cells use the bootstrap surface
(imputed_outcomes(replicates = TRUE)), re-aggregated within
each replicate; DCDH cells carry the estimator’s own SEs; otherwise SEs
are NA.
A faithful cohort matrix needs cohort-level estimates and
cohort-level uncertainty. For DCDH and fect both fall out of objects the
packages already expose. PanelMatch reports lead-specific ATTs
aggregated over all matched sets; recovering a per-cohort cell means
re-aggregating matched-set effects by switch time and
re-running the matched-set bootstrap on that re-aggregation to get
honest SEs. That is real work and out of scope for this pass, so
PanelMatch is omitted here rather than shipped with naive (wrong)
standard errors. The se_method column is reserved so a
PanelMatch path can slot in later without changing the plotting
code.
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.