Skip to contents

For a fitted object from fetwfe(), etwfe(), or betwfe(), computes the pooled event-time treatment-effect estimates tau_E(e), defined as cohort-weighted averages of the cell-level treatment-effect estimates at each post-treatment event time e = t - g (where t is calendar time and g is the cohort's first-treated calendar time). Weights are sample-cohort-size weights (matching did::aggte(type = "dynamic") convention).

Standard errors combine two terms, mirroring the package's existing overall-ATT SE machinery: var_1(e) from regression-coefficient noise (computed via the same gram_inv machinery the package uses for cohort SEs, or the cluster-robust sandwich under se_type = "cluster"), and var_2(e) from cohort-probability noise (analog of the existing getSecondVarTermOLS / getSecondVarTermDataApp machinery, with the multinomial Jacobian restricted to cohorts valid at event time e). Combined as sqrt(var_1 + var_2) by default (asymptotically exact under paper Theorem te.asym.norm.thm(\(c'\)) / Assumption (\(\Psi\)-IF), which the package's default cohort-sample-proportions estimator satisfies); the conservative Cauchy-Schwarz bound sqrt(var_1 + var_2 + 2 sqrt(var_1 * var_2)) is available via se_type = "conservative" (for users with non-(\(\Psi\)-IF) propensity-score estimators). When indep_counts was supplied at fit time, the tight formula applies regardless of se_type (two-sample regime, Theorem (b)).

Usage

eventStudy(x, alpha = NULL, ci_type = NULL)

Arguments

x

A fitted object of class "fetwfe", "etwfe", or "betwfe".

alpha

(Optional) Significance level for confidence intervals. Defaults to x$alpha (the alpha used at fit time).

ci_type

(Optional) Character; one of "simultaneous" or "pointwise", or NULL (default). Controls whether the returned ci_low / ci_high are the event-study-family simultaneous (family-wise, uniform) band or the per-event-time pointwise Wald intervals. NULL inherits the fit's stored ci_type (so a fit made with the default ci_type = "simultaneous" yields simultaneous event-study bands, which print / summary / plot then display); for objects fitted before version 1.16.0 (no ci_type slot) NULL falls back to "pointwise". The se column is identical under both settings; the interval bounds, their critical-value multiplier, and the p_value differ (under "simultaneous" the p_value is the single-step max-T multiplicity- adjusted dual of the band, under "pointwise" the per-effect Wald p-value; #200). Degenerate event times (no valid contributing cohorts) carry NA bounds and NA p_value under both settings.

High-dimensional (p >= NT): the "simultaneous" band here is the analytic post-selection band on the selected support (or NA bounds on a gls = FALSE fit), not the uniformly-valid desparsified band. For the valid high-dimensional event-study band call simultaneousCIs() with family = "event_study", method = "bootstrap" (Theorem debiased.highdim.joint.thm, validated near-nominally in Faletto 2025). Such a call emits a warning; see Details.

Value

A data frame with class c("eventStudy", "data.frame") and columns:

event_time

Integer; event time e = t - g, ranging from 0 to T - 2.

n_cohorts

Integer; number of cohorts contributing to the pooled estimate at event time e.

estimate

Numeric; the pooled event-time ATT estimate.

se

Numeric; combined standard error.

ci_low

Numeric; lower bound of the (1 - alpha) Wald CI.

ci_high

Numeric; upper bound of the (1 - alpha) Wald CI.

p_value

Numeric; follows the fit's ci_type. Under "pointwise", the two-sided Wald p-value (2 * pnorm(-|estimate / se|)); under "simultaneous" (the default), the single-step max-T multiplicity-adjusted (family-wise) p-value matching the simultaneous band (#200). NA when se is 0 or NA.

The returned frame also carries the logical attribute attr(., "band_applied") (#460), TRUE when its ci_low / ci_high ARE the event-study-family simultaneous band and FALSE when they are the pointwise Wald bounds — because the fit asked for pointwise intervals, because standard errors were unavailable on the selected support, or because the band construction degraded. It is not the same question as the fit's ci_type, which records what was requested, nor as the fit's catt_band_applied slot, which answers it for the cohort family: the two families are built from different contrast matrices and fail independently, so one can be applied while the other is not. It is an attribute rather than a slot because no slot could hold it. This band is recomputed per call with that call's arguments, so one fit answers differently for different calls: on a fit carrying catt_band_applied = TRUE, attr(eventStudy(fit), "band_applied") is TRUE while attr(eventStudy(fit, ci_type = "pointwise"), "band_applied") is FALSE. Like the slot, it records only that the band was applied: TRUE does not imply the bounds are wider than the pointwise ones, and does not imply every row is informative — degenerate event times are masked to NA while the band is still reported as applied. Only post-treatment event times (e >= 0) are included; pre-treatment placebo periods would require an extended regression specification and are out of scope for this initial release.

Details

High-dimensional (p >= NT) bands warn. When the returned "simultaneous" bounds are the p >= NT post-selection fallback rather than a uniformly-valid band – which is every high-dimensional fit that reaches the band at all, since this accessor always takes the analytic route – the call emits a warning naming the remedy (#433). A high-dimensional fit under ci_type = "pointwise", or one whose standard errors are unavailable, never builds a simultaneous band and so does not warn. A #308 coverage study measures the fallback band at roughly 0.09 against 0.95 nominal, even when the selected support is low-dimensional. The remedy is simultaneousCIs() with family = "event_study" and method = "bootstrap" on a fetwfe() fit; no valid high-dimensional band exists for the other estimators.

The warning is the same classed condition simultaneousCIs() signals, "fetwfe_highdim_postselection_band", so it can be caught or muffled precisely:


withCallingHandlers(
  eventStudy(fit),
  fetwfe_highdim_postselection_band = function(w) invokeRestart("muffleWarning")
)

print(), summary(), and plot() call this function internally and muffle that condition, so they do not repeat it on every render; print() and summary() render a two-line caveat under their CATT preview instead, and plot() renders none.

See also

cohortStudy() for the parallel per-cohort accessor; cohortTimeATTs() for the fully disaggregated per-(cohort, time) accessor.

Examples

if (FALSE) { # \dontrun{
  coefs <- genCoefs(G = 3, T = 6, d = 2, density = 0.5, eff_size = 2)
  dat <- simulateData(coefs, N = 120, sig_eps_sq = 1, sig_eps_c_sq = 0.5, seed = 123)
  res <- fetwfeWithSimulatedData(dat)
  eventStudy(res)
} # }