Skip to contents

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, see staggered). 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): NA in the timing column means the unit is never treated and will be used as a control. This follows the same convention as did::att_gt(), fixest::sunab(), and didimputation. If NA instead represents missing treatment timing for an otherwise treated unit, those observations will be silently absorbed into the control group, which is almost certainly wrong. For estimator = "twfe" with staggered timing, a warning is emitted when units with treatment = 1 also have timing = NA.

fe

One-sided fixed-effects formula, e.g., ~ id + year. Can be NULL for no fixed effects. Ignored when estimator = "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 length nrow(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_Treatment produced by paneltools/fect's get.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 from time and timing. Never-treated / control rows should be NA. Requires estimator = "twfe"; cannot be combined with staggered = TRUE or time_transform = TRUE. This normalises the older time = event_time, timing = 0 idiom. See Details for how this differs from the calendar-difference and time_transform conventions.

unit

Unit identifier variable (required when estimator = "cs" or time_transform = TRUE); also used for metadata when supplied.

staggered

Logical or NULL (default). Only consulted by estimator = "twfe": TRUE means timing is a column of unit-specific adoption times; FALSE means timing is a single universal value. With the default NULL the design is inferred — when timing names a column of data the 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, default 0L).

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 to fixest::vcov() when the optional {fixest} package is installed.

vcov_args

List of additional arguments forwarded to fixest::vcov() (requires {fixest}).

bootstrap

Logical; if TRUE and estimator = "cs", compute simultaneous confidence bands via the multiplier bootstrap (Algorithm 1, Callaway and Sant'Anna 2021). Adds conf_low_sim and conf_high_sim columns to the result and stores the full (g,t)-level bootstrap object as attr(result, "bootstrap"). Default FALSE.

boot_reps

Integer number of bootstrap draws (default 999). Used only when bootstrap = TRUE and estimator = "cs".

boot_alpha

Significance level for the simultaneous band (default 0.05). Note: this is independent of conf_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 of timing (or NA for never-treated groups). Not used by other estimators.

Logical; for estimator = "twm" only. When TRUE, 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. Default FALSE.

Value

A data.frame of class "es_result" with columns:

  • term, estimate, std.error, statistic, p.value

  • conf_low_XX, conf_high_XX (for each requested conf.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:

MethodDefinitionHow to invoke
Calendar difference (default)(time - timing) / intervalstaggered = TRUE (or scalar timing)
Within-unit rankdense_rank(time) within each unittime_transform = TRUE
Pre-built event timesupplied column, used verbatimrel_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 with plot(), print(), and autoplot().

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)
} # }