Build a stack

Build a stacked difference-in-differences dataset: one sub-experiment per feasible adoption cohort, aligned by event time, with clean controls and Q-weights for correct aggregation.

R build_stack()   ↔︎   Stata stacked build

Usage

build_stack(
  data, time_var, unit_var, adopt_var,
  kappa_pre = 3L, kappa_post = 2L,
  control_type = c("both", "never_treated", "not_yet_treated"),
  nyt_horizon = NULL, weight_var = NULL,
  data_type = c("panel", "repeated_cross_section"),
  weight_type = c("unit_weights", "population_between", "pop_constant",
    "pop_total_periods", "pop_period_specific", "sample_share"),
  pop_var = NULL, copy = TRUE, overwrite_conflicts = FALSE, duck = FALSE
)

Arguments

Argument Description
data data.frame/data.table long panel (one row per unit-time).
time_var Calendar-time column (numeric, Date, or POSIXt).
unit_var Unit/group identifier column.
adopt_var Adoption-time column; NA for never-treated.
kappa_pre, kappa_post Pre/post event-window periods (default 3, 2).
control_type "both", "never_treated", or "not_yet_treated".
nyt_horizon Cap not-yet-treated controls to units adopting within this many periods after the window.
weight_var Survey sampling-weight column.
data_type "panel" or "repeated_cross_section" (adds event-time level adjustment).
weight_type Q-weight scheme (see below).
pop_var Population column; required by the population weight types.
copy Copy input before modifying (default TRUE).
overwrite_conflicts Overwrite reserved output columns if present.
duck Build inside DuckDB for larger-than-memory data — see DuckDB.
stacked build, time(varname) unit(varname) adopt(varname) ///
    [kpre(#) kpost(#) controltype(str) nythorizon(#) ///
     weightvar(varname) datatype(str) weighttype(str) popvar(varname) replace]

stacked build replaces the data in memory with the stacked dataset.

Options

Option Description
time(varname) Calendar-time variable (numeric or Stata date).
unit(varname) Unit identifier.
adopt(varname) Adoption time; missing for never-treated. Constant within unit.
kpre(#), kpost(#) Event-window bounds (defaults 3, 2).
controltype(str) both (default), never_treated/never, not_yet_treated/notyet.
nythorizon(#) Restrict not-yet-treated controls to units adopting within # periods after the window.
weightvar(varname) Survey/sampling weights.
datatype(str) panel (default) or repeated_cross_section/rcs.
weighttype(str) Q-weight scheme (see below).
popvar(varname) Population column; required by the population weight types.
replace Overwrite existing sub_exp/event_time/treat/post/q_weight.

Weight types (same names both languages): unit_weights (default), population_between, pop_constant, pop_total_periods, pop_period_specific, sample_share. The pop_* and population_between types require the population column. See the kappa trade-offs vignette and the paper’s Appendix 0 for the estimand each targets.

R ↔︎ Stata mapping

R Stata
time_var time()
unit_var unit()
adopt_var adopt()
kappa_pre / kappa_post kpre() / kpost()
control_type controltype()
nyt_horizon nythorizon()
weight_var weightvar()
data_type datatype()
weight_type weighttype()
pop_var popvar()
overwrite_conflicts replace
duck = TRUE parquet() — see DuckDB
copy (none; Stata always modifies data in memory)

Value / Stored results

R returns a data.table with all original columns plus sub_exp (sub-experiment id = adoption time), event_time, treat (0/1), post (0/1), and q_weight. Design settings are stamped in a stacked_meta attribute read by stack_summary().

Stata replaces the data in memory with the same variables and stores r(n_obs), r(n_treated), r(n_control), r(n_sub_exp), r(n_unidentified_sub_exp), r(adoption_times).

Feasible sub-experiments with no eligible control (or treated) units are dropped with a warning before Q-weights are computed — most often the latest cohorts under not_yet_treated with a long post-window.

Example

library(stacked)
data(medicaid)

stack <- build_stack(
  medicaid,
  time_var  = "year",
  unit_var  = "state",
  adopt_var = "adopt_year",
  kappa_pre = 3,
  kappa_post = 2
)

dim(stack)
[1] 600  10
head(stack[, .(state, year, sub_exp, event_time, treat, post, q_weight)])
   state year sub_exp event_time treat post q_weight
1:    AL 2011    2014         -3     0    0 2.888889
2:    AL 2012    2014         -2     0    0 2.888889
3:    AL 2013    2014         -1     0    0 2.888889
4:    AL 2014    2014          0     0    1 2.888889
5:    AL 2015    2014          1     0    1 2.888889
6:    AL 2016    2014          2     0    1 2.888889
stacked use medicaid, clear
stacked build, time(year) unit(state) adopt(adopt_year) kpre(3) kpost(2)

See also