Estimates dynamic treatment effects by relative time (an event-study
curve) on panel data. Supports the classic TWFE design (universal or
staggered timing) plus modern staggered-robust estimators —
Callaway-Sant'Anna, Sun-Abraham, Borusyak-Jaravel-Spiess, Wooldridge
two-way Mundlak, and Deb et al. FLEX — selected via estimator=.
Estimation runs on the package's internal fixed-effects OLS engine;
results are returned as a tidy table with rich metadata.
Usage
event_study(
data,
outcome,
treatment = NULL,
time,
timing,
fe = NULL,
lead_range = NULL,
lag_range = NULL,
covariates = NULL,
cluster = NULL,
weights = NULL,
baseline = -1L,
interval = 1,
time_transform = FALSE,
rel_time = NULL,
unit = NULL,
staggered = NULL,
estimator = c("twfe", "cs", "sa", "bjs", "twm", "flex"),
control_group = c("nevertreated", "notyettreated"),
anticipation = 0L,
conf_level = 0.95,
vcov = "HC1",
vcov_args = list(),
bootstrap = FALSE,
boot_reps = 999L,
boot_alpha = 0.05,
boot_seed = NULL,
group = NULL,
trends = FALSE
)Arguments
- data
A data.frame containing panel data.
- outcome
Unquoted outcome (name or expression, e.g.,
log(y)).- treatment
Unquoted treatment indicator (0/1 or logical). Used only when
estimator = "twfe".- time
Unquoted time variable (numeric or Date).
- timing
For
estimator = "twfe": a numeric/Date scalar (universal timing) or an unquoted column with unit-specific adoption times (staggered; detected automatically, seestaggered). For the staggered estimators ("cs","sa","bjs","twm","flex"): unquoted column giving each unit's first treatment period (NA= never treated). Convention for staggered designs (cs, sa, bjs, twm, flex, twfe with staggered timing):NAin thetimingcolumn means the unit is never treated and will be used as a control. This follows the same convention asdid::att_gt(),fixest::sunab(), anddidimputation. IfNAinstead represents missing treatment timing for an otherwise treated unit, those observations will be silently absorbed into the control group, which is almost certainly wrong. Forestimator = "twfe"with staggered timing, a warning is emitted when units withtreatment = 1also havetiming = NA.- fe
One-sided fixed-effects formula, e.g.,
~ id + year. Can beNULLfor no fixed effects. Ignored whenestimator = "cs".- lead_range, lag_range
Integers for pre/post windows. If
NULL, determined automatically.- covariates
One-sided formula of additional controls, e.g.,
~ x1 + log(x2).- cluster
Cluster specification (one-sided formula like
~ id + year, a single character column name, or a vector of lengthnrow(data)).- weights
Observation weights (a name/one-sided formula or a numeric vector of length
nrow(data)).- baseline
Integer baseline period (default
-1); the reference period excluded from the estimation results.- interval
Numeric spacing of the time variable (default
1; ignored internally for Dates).- time_transform
Logical; if
TRUE, creates consecutive integer time within unit.- rel_time
Optional unquoted column holding a pre-built event time (time relative to treatment), e.g. the
Time_to_Treatmentproduced bypaneltools/fect'sget.cohort(). When supplied,event_study()uses this column verbatim as the event-study factor —i(rel_time, treatment, ref = baseline)— instead of computing relative time internally fromtimeandtiming. Never-treated / control rows should beNA. Requiresestimator = "twfe"; cannot be combined withstaggered = TRUEortime_transform = TRUE. This normalises the oldertime = event_time, timing = 0idiom. See Details for how this differs from the calendar-difference andtime_transformconventions.- unit
Unit identifier variable (required when
estimator = "cs"ortime_transform = TRUE); also used for metadata when supplied.- staggered
Logical or
NULL(default). Only consulted byestimator = "twfe":TRUEmeanstimingis a column of unit-specific adoption times;FALSEmeanstimingis a single universal value. With the defaultNULLthe design is inferred — whentimingnames a column ofdatathe design is staggered, otherwise it is universal. Pass an explicit value to override the inference.- estimator
Estimation strategy:
"twfe"(default) for the classic TWFE event study,"cs"(Callaway-Sant'Anna 2021),"sa"(Sun-Abraham 2021 interaction-weighted),"bjs"(Borusyak-Jaravel-Spiess 2024 imputation),"twm"(Wooldridge two-way Mundlak), or"flex"(Deb et al. 2024, repeated cross-sections).- control_group
For
estimator = "cs": comparison group, either"nevertreated"(default) or"notyettreated".- anticipation
For
estimator = "cs": number of anticipation periods before treatment (non-negative integer, default0L).- conf_level
Numeric vector of confidence levels (default
0.95).- vcov
VCOV type:
"HC1"(default),"iid", or"hetero"on the internal engine; other types are delegated tofixest::vcov()when the optional {fixest} package is installed.- vcov_args
List of additional arguments forwarded to
fixest::vcov()(requires {fixest}).- bootstrap
Logical; if
TRUEandestimator = "cs", compute simultaneous confidence bands via the multiplier bootstrap (Algorithm 1, Callaway and Sant'Anna 2021). Addsconf_low_simandconf_high_simcolumns to the result and stores the full (g,t)-level bootstrap object asattr(result, "bootstrap"). DefaultFALSE.- boot_reps
Integer number of bootstrap draws (default
999). Used only whenbootstrap = TRUEandestimator = "cs".- boot_alpha
Significance level for the simultaneous band (default
0.05). Note: this is independent ofconf_level, which governs the pointwise delta-method CIs.- boot_seed
Integer seed for the bootstrap RNG;
NULL(default) does not set a seed. Pass an integer for reproducible results.- group
Unquoted group identifier for
estimator = "flex"only. Identifies which treatment group (cohort) each observation belongs to in a repeated cross-section design (\(R_{ig}\) in Deb et al. 2024). Each group must map to exactly one value oftiming(orNAfor never-treated groups). Not used by other estimators.- trends
Logical; for
estimator = "twm"only. WhenTRUE, adds cohort-specific linear trend regressors \(d_g \cdot t\) to the Procedure 5.1 regression (Wooldridge 2025, Section 8), allowing each cohort's counterfactual trend to deviate linearly from the common time trend. Requires at least 2 pre-treatment periods per cohort. DefaultFALSE.
Value
A data.frame of class "es_result" with columns:
term,estimate,std.error,statistic,p.valueconf_low_XX,conf_high_XX(for each requestedconf.level)relative_time(integer; 0 = event),is_baseline(logical; marks the reference period)
Attributes include: lead_range, lag_range, baseline, interval, call, model_formula, conf.level,
N, N_units, N_treated, N_nevertreated, fe, vcov_type, cluster_vars, staggered, sunab_used.
Details
event_study() supersedes run_es() as of fixes 1.0.0.
How relative (event) time is defined. event_study() can build the
event-time axis in three different ways, and in sparse or unbalanced panels
(e.g. monthly surveys observed only a few times a year) they do not
coincide:
| Method | Definition | How to invoke |
| Calendar difference (default) | (time - timing) / interval | staggered = TRUE (or scalar timing) |
| Within-unit rank | dense_rank(time) within each unit | time_transform = TRUE |
| Pre-built event time | supplied column, used verbatim | rel_time = <col> |
In a balanced panel all three agree. When observation spacing is irregular
they diverge: for a unit observed at periods {1, 5, 9, 20} treated at 20,
the calendar difference puts period 9 at 9 - 20 = -11, whereas a global
observed-period grid (as used by paneltools/fect's get.cohort()) places
it two observed waves before treatment (-2). To reproduce an analysis built
on such a grid, pass that grid's event-time column via rel_time so the
design matches fixest::feols(y ~ i(rel_time, treatment, ref) | fe) exactly.
att() uses the same calendar-difference convention as the default here.
Key Features
One-step event study: specify outcome, treatment, time, timing, fixed effects, and (optionally) covariates.
Six estimators behind one interface:
"twfe","cs","sa","bjs","twm","flex".Flexible clustering, weights, and VCOV choices (e.g.,
vcov = "HC1" | "iid" | "hetero" ...).Automatic lead/lag window detection and customizable baseline period.
Returns an
"es_result"object compatible withplot(),print(), andautoplot().
See also
plot.es_result() for visualization, att() for aggregated ATT
estimation, and run_es() for the deprecated verb-style predecessor.
Examples
if (FALSE) { # \dontrun{
# Staggered adoption, Callaway-Sant'Anna
res <- event_study(df, outcome = y, time = year, timing = g, unit = id,
estimator = "cs")
plot(res)
# Classic TWFE with universal timing (timing inferred as universal)
res <- event_study(df, outcome = y, treatment = treated, time = year,
timing = 2005, fe = ~ id + year, cluster = ~ id)
} # }