| Title: | Event Studies and Inference for Difference-in-Differences |
|---|---|
| Description: | Provides tools for event-study analysis and inference in difference-in-differences (DiD) designs with panel data, from the classic two-way fixed-effects (TWFE) event study to estimators for staggered adoption. Includes event_study() for event-study curves (dynamic effects by relative time), att() for aggregated ATT estimation (overall, by cohort, by calendar time), did() for basic two-way fixed-effects DiD, and honest_sensitivity() for sensitivity analysis under violations of parallel trends (Rambachan and Roth, 2023), all running on an internal C++ fixed-effects OLS engine. Supports multiple modern estimators: Callaway-Sant'Anna (2021), Sun-Abraham (2021), Borusyak-Jaravel-Spiess (2024), Wooldridge TWM, and Deb et al. FLEX. Results plot via base plot() methods. |
| Authors: | Yosuke Abe [aut, cre] |
| Maintainer: | Yosuke Abe <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 1.0.0 |
| Built: | 2026-10-21 12:23:30 UTC |
| Source: | https://github.com/yo5uke/fixes |
Estimates average treatment effects on the treated (ATT) using either the
Callaway-Sant'Anna (2021) or Borusyak-Jaravel-Spiess (2024) estimator,
aggregated to a single summary effect ("simple"), per-cohort effects
("by_cohort"), or per calendar-time effects ("by_time").
att( formula, data, time, timing = NULL, unit = NULL, estimator = c("cs", "bjs"), aggregation = c("simple", "by_cohort", "by_time"), control_group = c("nevertreated", "notyettreated"), anticipation = 0L, conf_level = 0.95, interval = 1, time_transform = FALSE, outcome )att( formula, data, time, timing = NULL, unit = NULL, estimator = c("cs", "bjs"), aggregation = c("simple", "by_cohort", "by_time"), control_group = c("nevertreated", "notyettreated"), anticipation = 0L, conf_level = 0.95, interval = 1, time_transform = FALSE, outcome )
formula |
A model formula |
data |
A data.frame containing panel data. |
time |
Unquoted calendar time variable (numeric). |
timing |
Treatment timing: an unquoted column giving each unit's
first treatment period ( |
unit |
Unquoted unit identifier; taken from the fixed effects when
they are |
estimator |
Estimation strategy: |
aggregation |
Aggregation type: |
control_group |
For |
anticipation |
For |
conf_level |
Numeric confidence level(s) (default |
interval |
Numeric time spacing (default |
time_transform |
Logical; if |
outcome |
|
This function complements event_study(): use event_study() when you
want a full event-study curve (dynamic effects by relative time), and
att() when you want aggregated ATT estimates that collapse the time
dimension. Both take the same formula, and treatment is given in the same
three ways (see event_study(), Specifying treatment):
att(y ~ D | id + year, data = df, time = year) # D column att(y ~ 1 | id + year, data = df, time = year, timing = g) # timing column att(y ~ treated | id + year, data = df, time = year, timing = 2005)
The result has broom::tidy() and broom::glance() methods, so it can be
passed to modelsummary::modelsummary() (rendered with tinytable) to build
a publication table — including stacking several estimators (e.g. did()
TWFE, CS, and BJS) side by side in one table.
att() supersedes calc_att() as of fixes 1.0.0.
A data.frame of class "att_result" with columns:
groupCohort or calendar time (NA for "simple").
estimateATT point estimate.
std.errorStandard error.
statistict-statistic (estimate / std.error).
p.valueTwo-sided p-value (normal approximation).
conf_low_XX, conf_high_XX
CI bounds for each conf.level.
Attributes: aggregation, estimator, conf.level, N, N_units,
N_treated, N_nevertreated, unit_var, time_var, control_group
(CS only), att_gt (CS raw ATT(g,t) table), tau_it (BJS unit-time
effects table).
simple:
where is the mean over post-treatment periods.
by_cohort: per cohort.
by_time:
with .
Rows with a missing or non-finite outcome, time, or unit are dropped and
the panel is balanced by dropping units not observed in every period, as
did::att_gt() does by default; both steps warn, and N, N_units,
N_treated and N_nevertreated describe the balanced sample.
For "cs", the SE of each aggregate is computed from its unit-level
influence function, which carries the covariance across the ATT(g,t)
it averages and the estimation of the cohort weights (the same SEs as
did::aggte()).
For "bjs", the SE is the conservative cluster-robust SE of Borusyak et
al. (2024, Theorem 3), clustered by unit, including the uncertainty from
the imputation regression. It matches didimputation::did_imputation().
event_study() for event-study (dynamic) estimates, and
calc_att() for the deprecated verb-style predecessor.
## Not run: att(y ~ 1 | id + year, data = df, time = year, timing = g) att(y ~ D | id + year, data = df, time = year, estimator = "bjs", aggregation = "by_cohort") ## End(Not run)## Not run: att(y ~ 1 | id + year, data = df, time = year, timing = g) att(y ~ D | id + year, data = df, time = year, estimator = "bjs", aggregation = "by_cohort") ## End(Not run)
Returns the cohort-by-period ATT(g,t) table computed by
event_study(estimator = "cs") or att(estimator = "cs") as a real
att_gt_result object with its own plot() and
autoplot() methods. Bootstrap metadata (when the originating call used
bootstrap = TRUE) is carried along so simultaneous-inference overlays
keep working.
att_gt(x)att_gt(x)
x |
An |
A data frame of class c("att_gt_result", "data.frame") with
columns g (cohort), t (calendar time), estimate, and std_error.
plot.att_gt_result(), event_study(), att()
## Not run: res <- event_study(y ~ 1 | id + year, data = df, time = year, timing = g, estimator = "cs") gt <- att_gt(res) plot(gt) # heatmap plot(gt, type = "facet") # one panel per cohort ## End(Not run)## Not run: res <- event_study(y ~ 1 | id + year, data = df, time = year, timing = g, estimator = "cs") gt <- att_gt(res) plot(gt) # heatmap plot(gt, type = "facet") # one panel per cohort ## End(Not run)
S3 method that plots an es_result (from event_study()).
It forwards arguments to plot.es_result().
## S3 method for class 'es_result' autoplot( object, ci_level = 0.95, type = c("errorbar", "ribbon"), time_axis = c("relative", "calendar"), ... )## S3 method for class 'es_result' autoplot( object, ci_level = 0.95, type = c("errorbar", "ribbon"), time_axis = c("relative", "calendar"), ... )
object |
An |
ci_level |
Confidence level (numeric, e.g., 0.95). |
type |
Plot type: |
time_axis |
|
... |
Additional styling arguments; see |
A ggplot object.
# res <- event_study(...) # ggplot2::autoplot(res, ci_level = 0.95, type = "ribbon")# res <- event_study(...) # ggplot2::autoplot(res, ci_level = 0.95, type = "ribbon")
Estimates the contamination weights that
decompose each TWFE event-study coefficient into a
linear combination of cohort-specific ATTs (CATTs):
(Sun and Abraham 2021, Equation 20).
The weights are obtained via the OVB auxiliary regression (Eq. 12): for
each cohort-period CATT cell , regress the cohort-specific
indicator on all cohort-aggregated
relative-time indicators and two-way fixed effects.
The resulting regression coefficient on is
.
contamination_weights(data, time, timing, unit, fe = NULL, baseline = -1L)contamination_weights(data, time, timing, unit, fe = NULL, baseline = -1L)
data |
A data.frame with one row per unit-period (balanced panel). |
time |
Unquoted name of the calendar time variable (numeric). |
timing |
Unquoted name of the first-treatment-period variable;
|
unit |
Unquoted name of the unit identifier. |
fe |
One-sided fixed-effects formula, e.g. |
baseline |
Integer reference (baseline) period excluded from the TWFE
specification (default |
An object of class c("sa_contamination_weights", "data.frame")
with one row per (catt_cohort, catt_period, twfe_period) triple,
and columns:
catt_cohortCohort (first treatment period).
catt_periodRelative event time of the CATT.
twfe_periodRelative event time of the TWFE
coefficient being decomposed.
weightContamination weight .
is_ownLogical; TRUE when catt_period == twfe_period.
Attributes: baseline, cohorts, cohort_sizes, incl_periods.
Own-period cell (catt_period == twfe_period):
represents the weight the TWFE estimator
places on . Under the SA IW estimator these equal
the cohort-size weights .
Cross-period cell (catt_period != twfe_period):
Any non-zero weight indicates contamination: the TWFE coefficient
also picks up treatment effects from period
.
Verification: the OVB identity (property iii) holds
exactly, so up to
floating-point precision.
Sun, L. and Abraham, S. (2021). Estimating dynamic treatment effects in event studies with heterogeneous treatment effects. Journal of Econometrics, 225(2), 175–199.
plot.sa_contamination_weights(), event_study(), and
compute_contamination_weights() for the deprecated predecessor.
## Not run: # Estimate contamination weights cw <- contamination_weights( data = panel_data, time = year, timing = first_treat, unit = id, fe = ~ id + year, baseline = -1L ) print(cw) plot(cw) ## End(Not run)## Not run: # Estimate contamination weights cw <- contamination_weights( data = panel_data, time = year, timing = first_treat, unit = id, fe = ~ id + year, baseline = -1L ) print(cw) plot(cw) ## End(Not run)
Estimates a classic difference-in-differences model of the form
outcome ~ D + covariates | fixed effects on the package's internal
fixed-effects OLS engine (numerically equivalent to fixest::feols()).
The formula is the one used by event_study() and att(): swap the
function to get effects by event time or aggregated staggered ATTs.
did() supersedes run_did() as of fixes 1.0.0.
Treatment is given in one of three ways, as in event_study():
# D: 1 for treated units in post-treatment periods did(y ~ D | id + year, data = df) # each unit's first treated period (NA = never treated) did(y ~ 1 | id + year, data = df, time = year, timing = g) # a treated-group dummy and one treatment date did(y ~ treated | id + year, data = df, time = year, timing = 2006)
With timing, D = treatment * (time >= timing) is built internally.
For staggered-adoption settings see att(); for dynamic effects see
event_study().
did( formula, data, time = NULL, timing = NULL, unit = NULL, cluster = NULL, weights = NULL, conf_level = 0.95, vcov = "HC1", vcov_args = list(), outcome, treatment, fe = NULL, covariates = NULL )did( formula, data, time = NULL, timing = NULL, unit = NULL, cluster = NULL, weights = NULL, conf_level = 0.95, vcov = "HC1", vcov_args = list(), outcome, treatment, fe = NULL, covariates = NULL )
formula |
A model formula
|
data |
A data.frame (panel format). |
time |
Unquoted time variable. Required with |
timing |
Treatment timing: an unquoted column giving each unit's
first treatment period ( |
unit |
Unquoted unit identifier column (for metadata). Taken from the fixed effects when possible. |
cluster |
Clustering specification: a one-sided formula ( |
weights |
Observation weights (formula or numeric vector). |
conf_level |
Confidence level(s) for CIs. Scalar or vector
(e.g., |
vcov |
VCOV type string passed to |
vcov_args |
Named list of additional arguments forwarded to
|
outcome, treatment, fe, covariates
|
|
A did_result object (named list) with elements:
estimatesData frame with the treatment coefficient:
term, estimate, std.error, statistic, p.value, and
conf_low_XX/conf_high_XX for each conf.level entry.
coeftableData frame with all kept coefficients (treatment
and covariates): term, estimate, std.error, statistic,
p.value.
vcovFull coefficient covariance matrix.
fit_statsModel-level statistics (nobs, R-squareds, sigma,
logLik, AIC, BIC) used by broom::glance().
df.tDegrees of freedom of the t inference.
Attributes: call, formula_str, outcome, treatment, timing,
fe, vcov_type, cluster_vars, conf.level, N, N_units,
N_treated, unit, time (and timing_var with a timing column).
N, N_units, and N_treated describe the estimation sample (after
rows with missing values are dropped), matching broom::glance(), not
nrow(data).
event_study() for dynamic effects, att() for staggered
aggregation, and run_did() for the deprecated verb-style predecessor.
## Not run: res <- did(y ~ D | id + year, data = df) # Built from a treated-group dummy and the treatment date res <- did(y ~ treated | id + year, data = df, time = year, timing = 2006) # Covariates and cluster-robust SE res <- did(y ~ D + x1 | id + year, data = df, cluster = ~ id) print(res) broom::tidy(res) broom::glance(res) # modelsummary::modelsummary(res) ## End(Not run)## Not run: res <- did(y ~ D | id + year, data = df) # Built from a treated-group dummy and the treatment date res <- did(y ~ treated | id + year, data = df, time = year, timing = 2006) # Covariates and cluster-robust SE res <- did(y ~ D + x1 | id + year, data = df, cluster = ~ id) print(res) broom::tidy(res) broom::glance(res) # modelsummary::modelsummary(res) ## End(Not run)
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.
event_study( formula, data, time, timing = NULL, unit = NULL, estimator = c("twfe", "cs", "sa", "bjs", "twm", "flex"), cluster = NULL, weights = NULL, baseline = -1L, lead_range = NULL, lag_range = NULL, conf_level = 0.95, vcov = "HC1", vcov_args = list(), control_group = c("nevertreated", "notyettreated"), anticipation = 0L, bootstrap = FALSE, boot_reps = 999L, boot_alpha = 0.05, boot_seed = NULL, group = NULL, trends = FALSE, interval = 1, time_transform = FALSE, rel_time = NULL, staggered = NULL, outcome, treatment = NULL, fe = NULL, covariates = NULL )event_study( formula, data, time, timing = NULL, unit = NULL, estimator = c("twfe", "cs", "sa", "bjs", "twm", "flex"), cluster = NULL, weights = NULL, baseline = -1L, lead_range = NULL, lag_range = NULL, conf_level = 0.95, vcov = "HC1", vcov_args = list(), control_group = c("nevertreated", "notyettreated"), anticipation = 0L, bootstrap = FALSE, boot_reps = 999L, boot_alpha = 0.05, boot_seed = NULL, group = NULL, trends = FALSE, interval = 1, time_transform = FALSE, rel_time = NULL, staggered = NULL, outcome, treatment = NULL, fe = NULL, covariates = NULL )
The model is a formula, outcome ~ treatment + covariates | fixed effects,
as in fixest::feols(); the same formula works in did() and att().
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 |
a timing column, a scalar timing, or a D column |
| 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.
With a D column, a unit's start is the first period in which D = 1 is
observed.
Missing values and unbalanced panels. With estimator = "cs", rows
with a missing or non-finite outcome, time, or unit are dropped, and the
panel is then balanced by dropping every unit not observed in all periods,
as did::att_gt() does by default (allow_unbalanced_panel = FALSE).
Both steps warn, and N, N_units, N_treated and N_nevertreated
describe the balanced sample. The regression-based estimators drop rows
with missing values from the fit as fixest::feols() does; with
estimator = "sa" or "twm" the cohort shares used to aggregate
cohort-specific effects count only units that remain in the estimation
sample (Sun and Abraham 2021, Section 4.1).
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,
unit_var, time_var.
Describe treatment in whichever way matches your data:
| Your data has | Write |
| D (1 for treated units in post-treatment periods) | y ~ D + x | id + year, time = year |
each unit's first treated period (NA = never treated) |
y ~ x | id + year, time = year, timing = g |
| a treated-group dummy and one treatment date | y ~ treated + x | id + year, time = year, timing = 2005
|
D column (no timing): the first right-hand-side term is D. A
unit's treatment starts in the first period with D = 1 and must stay
on from then on (staggered adoption). Rows with a missing D are dropped.
A single start date across units gives the classic universal design,
several give the staggered one. With time_transform = TRUE the start
is read on the time index, so no index arithmetic is needed.
Timing column (timing = g): g defines treatment, so the
right-hand side holds only covariates (write y ~ 1 | id + year when
there are none). D or the treated-group dummy must not appear in the
formula.
Treated-group dummy and a date (timing = 2005): the first term is
the treated-group dummy (constant within units), and treatment starts at
timing for every treated unit.
A pre-built event-time column goes in rel_time, with the treated-group
dummy as the first term: y ~ treated | id + year, time = year, rel_time = ttt.
time is required. unit is taken from the fixed effects when they are
plain columns made of time and exactly one other column (| id + year
with time = year, in either order); a message then reports the unit and
time used. Otherwise pass unit: it is needed for a D column and for
estimator = "cs", "sa", "bjs" and "twm". print() shows the
identifiers of every result.
"twfe", "sa" and "twm" use the fixed effects as written. "cs",
"bjs" and "flex" absorb the unit (group) and time effects themselves:
those fixed effects are accepted silently, any others are ignored with a
warning. Covariates are supported by "twfe", "twm" and "flex", and
may be expressions such as log(x) or factor(region); "cs", "sa"
and "bjs" stop with an error when the formula contains covariates.
One formula for the model (outcome, treatment, covariates, fixed
effects), shared with did() and att().
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().
plot.es_result() for visualization, att() for aggregated ATT
estimation, and run_es() for the deprecated verb-style predecessor.
## Not run: # D column: 1 for treated units in post-treatment periods res <- event_study(y ~ D | id + year, data = df, time = year) plot(res) # Timing column (NA = never treated), Callaway-Sant'Anna res <- event_study(y ~ 1 | id + year, data = df, time = year, timing = g, estimator = "cs") # Treated-group dummy and a single treatment date, clustered by unit res <- event_study(y ~ treated + x1 | id + year, data = df, time = year, timing = 2005, cluster = ~ id) ## End(Not run)## Not run: # D column: 1 for treated units in post-treatment periods res <- event_study(y ~ D | id + year, data = df, time = year) plot(res) # Timing column (NA = never treated), Callaway-Sant'Anna res <- event_study(y ~ 1 | id + year, data = df, time = year, timing = g, estimator = "cs") # Treated-group dummy and a single treatment date, clustered by unit res <- event_study(y ~ treated + x1 | id + year, data = df, time = year, timing = 2005, cluster = ~ id) ## End(Not run)
att_result objectReturns a one-row summary of an att() result for the goodness-of-fit
block of a modelsummary::modelsummary() table.
glance.att_result(x, ...)glance.att_result(x, ...)
x |
An |
... |
Unused; for S3 generic compatibility. |
A one-row data frame with nobs, n.units, n.treated,
estimator, and aggregation.
## Not run: res <- att(y ~ D | id + year, data = df, time = year) broom::glance(res) ## End(Not run)## Not run: res <- att(y ~ D | id + year, data = df, time = year) broom::glance(res) ## End(Not run)
did_result objectReturns a single-row summary of model-level statistics from a did()
result: r.squared, adj.r.squared, within.r.squared, sigma,
nobs, AIC, BIC, and logLik, in the column layout of
broom::glance() on a fixest model.
glance.did_result(x, ...)glance.did_result(x, ...)
x |
A |
... |
Unused; kept for compatibility with the |
A one-row tibble of model-level statistics.
## Not run: res <- did(y ~ D | id + year, data = df) broom::glance(res) ## End(Not run)## Not run: res <- did(y ~ D | id + year, data = df) broom::glance(res) ## End(Not run)
es_result objectReturns a one-row summary of an event_study() result: sample sizes,
estimator, and VCOV type.
glance.es_result(x, ...)glance.es_result(x, ...)
x |
An |
... |
Unused; kept for compatibility with the |
A one-row tibble with columns nobs, n_units, n_treated,
n_nevertreated, estimator, and vcov_type.
## Not run: res <- event_study(y ~ 1 | id + year, data = df, time = year, timing = g, estimator = "cs") broom::glance(res) ## End(Not run)## Not run: res <- event_study(y ~ 1 | id + year, data = df, time = year, timing = g, estimator = "cs") broom::glance(res) ## End(Not run)
Robust inference and sensitivity analysis for event-study / DiD designs following Rambachan and Roth (2023). Instead of assuming parallel trends holds exactly, it asks how large a violation of parallel trends would have to be before the causal conclusion changes, returning confidence sets for a post-treatment effect under a sequence of restrictions on the possible difference in trends.
Two restriction families are supported:
"relative_magnitude" (): post-treatment
violations are at most times the largest pre-treatment
violation (Rambachan & Roth 2023, Section 2.4.1).
"smoothness" (): the difference in trends
deviates from linearity by at most per period (Section 2.4.3).
Inference uses the Andrews-Roth-Pakes (ARP) conditional moment-inequality test (Section 3.2.1), which is uniformly valid and recommended for general restriction sets.
honest_sensitivity( object = NULL, type = c("relative_magnitude", "smoothness"), m_grid = NULL, l_vec = NULL, alpha = 0.05, grid_points = 1000L, betahat = NULL, sigma = NULL, n_pre_periods = NULL, n_post_periods = NULL, Mvec = lifecycle::deprecated(), gridPoints = lifecycle::deprecated(), numPrePeriods = lifecycle::deprecated(), numPostPeriods = lifecycle::deprecated() )honest_sensitivity( object = NULL, type = c("relative_magnitude", "smoothness"), m_grid = NULL, l_vec = NULL, alpha = 0.05, grid_points = 1000L, betahat = NULL, sigma = NULL, n_pre_periods = NULL, n_post_periods = NULL, Mvec = lifecycle::deprecated(), gridPoints = lifecycle::deprecated(), numPrePeriods = lifecycle::deprecated(), numPostPeriods = lifecycle::deprecated() )
object |
An |
type |
Restriction family: |
m_grid |
Numeric vector of restriction parameters. For
|
l_vec |
Numeric weight vector over post-treatment periods defining the
target |
alpha |
Significance level (default |
grid_points |
Number of grid points for test inversion (default
|
betahat |
Optional event-study coefficient vector (pre then post,
excluding the reference period), ordered by relative time. Required when
|
sigma |
Optional covariance matrix of |
n_pre_periods, n_post_periods
|
Optional integer counts; inferred from
|
Mvec |
|
gridPoints |
|
numPrePeriods, numPostPeriods
|
A data.frame of class "honest_result" with one row per
restriction value plus the original (parallel-trends) confidence interval,
with columns M, lb, ub, method, and type. The breakdown value
(largest restriction at which the robust CI still excludes 0) is stored in
attr(., "breakdown").
Rambachan, A. and Roth, J. (2023). A More Credible Approach to Parallel Trends. Review of Economic Studies, 90(5), 2555-2591.
event_study(), plot.honest_result()
Visualises the full cohort-by-period ATT(g,t) matrix stored in the
att_gt attribute of an es_result object produced by
run_es(estimator = "cs"). Two display styles are available:
"heatmap": a tile plot with calendar time on the
x-axis and cohort on the y-axis, colour-filled by the point
estimate. Cells whose pointwise confidence interval excludes zero are
marked with a filled dot; cells that are simultaneously significant
(when bootstrap data are available) are additionally marked with an open
diamond.
"facet": one panel per cohort showing ATT(g,t) over
calendar time with a pointwise confidence ribbon, mirroring
the style of plot_es(). A lighter simultaneous CI ribbon
is overlaid when bootstrap data are available.
Both types draw a vertical dashed line at (treatment onset)
for each cohort.
## S3 method for class 'att_gt_result' plot( x, type = c("heatmap", "facet"), ci_level = 0.95, zero_line = TRUE, theme = c("bw", "minimal", "classic"), color = NULL, fill = NULL, alpha = NULL, ... ) ## S3 method for class 'att_gt_result' autoplot(object, ...)## S3 method for class 'att_gt_result' plot( x, type = c("heatmap", "facet"), ci_level = 0.95, zero_line = TRUE, theme = c("bw", "minimal", "classic"), color = NULL, fill = NULL, alpha = NULL, ... ) ## S3 method for class 'att_gt_result' autoplot(object, ...)
x |
An |
type |
|
ci_level |
Confidence level for pointwise intervals (default 0.95). |
zero_line |
Logical; draw a horizontal reference line at zero in the
|
theme |
One of |
color |
Line and point colour used in the |
fill |
Ribbon fill colour in the |
alpha |
Ribbon transparency in the |
... |
Passed on to the plotting engine. |
object |
An |
A ggplot2::ggplot() object.
When attr(x, "bootstrap") is present (i.e., run_es() was
called with bootstrap = TRUE), both plot types add simultaneous
inference overlays sourced from the (g,t)-level bootstrap object.
## Not run: cs_result <- event_study(y ~ 1 | id + year, data = mydata, time = year, timing = g, estimator = "cs") plot(att_gt(cs_result)) plot(att_gt(cs_result), type = "facet") ## End(Not run)## Not run: cs_result <- event_study(y ~ 1 | id + year, data = mydata, time = year, timing = g, estimator = "cs") plot(att_gt(cs_result)) plot(att_gt(cs_result), type = "facet") ## End(Not run)
Base plot() method for att_result objects returned by att() (or the
deprecated calc_att()). Draws point estimates with confidence-interval
bars: a single point for aggregation = "simple", one point per cohort
for "by_cohort", and one per calendar period for "by_time".
## S3 method for class 'att_result' plot( x, ci_level = 0.95, color = NULL, zero_line = TRUE, theme_style = c("bw", "minimal", "classic"), ... ) ## S3 method for class 'att_result' autoplot(object, ...)## S3 method for class 'att_result' plot( x, ci_level = 0.95, color = NULL, zero_line = TRUE, theme_style = c("bw", "minimal", "classic"), ... ) ## S3 method for class 'att_result' autoplot(object, ...)
x |
An |
ci_level |
Confidence level to display (default |
color |
Point and interval colour (default black). |
zero_line |
Logical; draw a dashed reference line at zero
(default |
theme_style |
One of |
... |
Unused. |
object |
An |
A ggplot object.
## Not run: res <- att(y ~ 1 | id + year, data = df, time = year, timing = g, aggregation = "by_cohort") plot(res) ## End(Not run)## Not run: res <- att(y ~ 1 | id + year, data = df, time = year, timing = g, aggregation = "by_cohort") plot(res) ## End(Not run)
Base plot() method for es_result objects returned by event_study()
(or the deprecated run_es()). Draws the event-study curve with pointwise
confidence bands as a static ggplot, or — with interactive = TRUE — an
interactive plotly chart with hover tooltips (requires the suggested
{plotly} package).
## S3 method for class 'es_result' plot( x, ci_level = 0.95, type = c("errorbar", "ribbon"), time_axis = c("relative", "calendar"), interactive = FALSE, show_simultaneous = FALSE, ... )## S3 method for class 'es_result' plot( x, ci_level = 0.95, type = c("errorbar", "ribbon"), time_axis = c("relative", "calendar"), interactive = FALSE, show_simultaneous = FALSE, ... )
x |
An |
ci_level |
Confidence level to display (default |
type |
|
time_axis |
|
interactive |
Logical; if |
show_simultaneous |
Logical; overlay the simultaneous bootstrap CI
band (requires |
... |
Further styling arguments: for static plots
|
The default display is black error bars. type = "ribbon" switches to a
line with a shaded band and, unless color/fill say otherwise, to the
package's muted steel-blue palette. In either display color sets the
points (and the line), while errorbar_color can recolour the bars on
their own.
time_axis controls what the horizontal axis shows. The default
"relative" plots event time (periods since treatment) and labels the axis
"Relative Time to Treatment". "calendar" instead plots the original time
values (e.g. 2010, or a Date), labels the axis with the time column's
name, and moves the dashed vertical reference line to the treatment period.
It is available whenever every treated unit adopts in the same period —
universal timing, or a staggered design that happens to have a single
cohort — and errors otherwise, because with several cohorts one relative
period corresponds to several calendar periods.
A ggplot object, or a plotly object when
interactive = TRUE.
event_study(), autoplot.es_result()
## Not run: res <- event_study(y ~ 1 | id + year, data = df, time = year, timing = g, estimator = "cs") plot(res) plot(res, type = "ribbon", ci_level = 0.9) plot(res, color = "#1B4965", errorbar_color = "grey50") plot(res, interactive = TRUE) # Universal timing: label the axis with calendar years instead uni <- event_study(y ~ d | id + year, data = df, time = year, timing = 2010) plot(uni, time_axis = "calendar") ## End(Not run)## Not run: res <- event_study(y ~ 1 | id + year, data = df, time = year, timing = g, estimator = "cs") plot(res) plot(res, type = "ribbon", ci_level = 0.9) plot(res, color = "#1B4965", errorbar_color = "grey50") plot(res, interactive = TRUE) # Universal timing: label the axis with calendar years instead uni <- event_study(y ~ d | id + year, data = df, time = year, timing = 2010) plot(uni, time_axis = "calendar") ## End(Not run)
Visualises the output of honest_sensitivity(): robust confidence intervals
for the target effect under progressively weaker parallel-trends restrictions
(increasing or ), alongside the original confidence
interval that assumes parallel trends holds exactly. This is the "top-down"
sensitivity plot of Rambachan and Roth (2023).
## S3 method for class 'honest_result' plot(x, ...) ## S3 method for class 'honest_result' autoplot(object, ...)## S3 method for class 'honest_result' plot(x, ...) ## S3 method for class 'honest_result' autoplot(object, ...)
x |
A |
... |
Unused. |
object |
A |
A ggplot object.
Base plot() method for the contamination weights returned by
contamination_weights(): a ggplot2 tile heatmap.
Each cell at position (twfe_period, catt_label) shows the
weight : how much of
leaks into the TWFE coefficient .
Diagonal cells (catt_period == twfe_period): own-period
weights (sum across cohorts should be ).
Off-diagonal cells: cross-period contamination (ideally close to zero under treatment effect homogeneity).
## S3 method for class 'sa_contamination_weights' plot( x, limit_abs = NULL, midpoint = 0, low = "#2166AC", mid = "white", high = "#B2182B", theme = c("bw", "minimal", "classic"), show_values = FALSE, value_digits = 2L, ... )## S3 method for class 'sa_contamination_weights' plot( x, limit_abs = NULL, midpoint = 0, low = "#2166AC", mid = "white", high = "#B2182B", theme = c("bw", "minimal", "classic"), show_values = FALSE, value_digits = 2L, ... )
x |
An |
limit_abs |
Numeric; symmetric colour scale limit |
midpoint |
Numeric; midpoint of the diverging colour scale (default 0). |
low |
Colour for negative weights (default |
mid |
Colour for zero weight (default |
high |
Colour for positive weights (default |
theme |
Character; |
show_values |
Logical; overlay weight values in each tile (default
|
value_digits |
Integer; decimal digits when |
... |
Unused. |
A ggplot2::ggplot() object.
att_result objectReturns a tidy data frame of the aggregated ATT estimates from a
att() result, in the column format expected by
modelsummary::modelsummary() (and therefore renderable with
tinytable::tt()). This makes it easy to put overall, by-cohort, or
by-time ATTs into a publication table, and to stack several estimators
(e.g. did() TWFE, CS, and BJS) side by side in one table.
The term column is derived from the aggregation type so that rows align
across models:
aggregation = "simple" -> "ATT"
aggregation = "by_cohort" -> "Cohort <g>"
aggregation = "by_time" -> "Time <t>"
## S3 method for class 'att_result' tidy(x, conf.int = FALSE, conf.level = 0.95, ...)## S3 method for class 'att_result' tidy(x, conf.int = FALSE, conf.level = 0.95, ...)
x |
An |
conf.int |
Logical; add |
conf.level |
Confidence level for |
... |
Unused; for S3 generic compatibility. |
A data frame with columns term, estimate, std.error,
statistic, p.value (and optionally conf.low, conf.high).
## Not run: res <- att(y ~ D | id + year, data = df, time = year, estimator = "cs", aggregation = "by_cohort") broom::tidy(res) # Compare estimators in one table: twfe <- did(y ~ D | id + year, data = df) cs <- att(y ~ D | id + year, data = df, time = year, estimator = "cs") bjs <- att(y ~ D | id + year, data = df, time = year, estimator = "bjs") modelsummary::modelsummary(list(TWFE = twfe, CS = cs, BJS = bjs)) ## End(Not run)## Not run: res <- att(y ~ D | id + year, data = df, time = year, estimator = "cs", aggregation = "by_cohort") broom::tidy(res) # Compare estimators in one table: twfe <- did(y ~ D | id + year, data = df) cs <- att(y ~ D | id + year, data = df, time = year, estimator = "cs") bjs <- att(y ~ D | id + year, data = df, time = year, estimator = "bjs") modelsummary::modelsummary(list(TWFE = twfe, CS = cs, BJS = bjs)) ## End(Not run)
did_result objectReturns a tidy data frame of model coefficients from a did() result:
all regressors (treatment and covariates), in the format expected by
modelsummary::modelsummary(). The table is read from the fit stored at
estimation time; the VCOV type chosen in did() (vcov / cluster)
is already reflected in the standard errors.
## S3 method for class 'did_result' tidy(x, conf.int = FALSE, conf.level = 0.95, ...)## S3 method for class 'did_result' tidy(x, conf.int = FALSE, conf.level = 0.95, ...)
x |
A |
conf.int |
Logical; add |
conf.level |
Confidence level for |
... |
Unused; kept for compatibility with the |
A tibble with columns term, estimate, std.error,
statistic, p.value (and optionally conf.low, conf.high).
## Not run: res <- did(y ~ D | id + year, data = df) broom::tidy(res) broom::tidy(res, conf.int = TRUE) ## End(Not run)## Not run: res <- did(y ~ D | id + year, data = df) broom::tidy(res) broom::tidy(res, conf.int = TRUE) ## End(Not run)
es_result objectReturns the event-study coefficient table of an event_study() result as
a plain tibble — the format expected by modelsummary::modelsummary()
and other broom-aware tooling. The baseline row (zero by construction)
is included; drop it with x[!x$is_baseline, ] beforehand if undesired.
## S3 method for class 'es_result' tidy(x, ...)## S3 method for class 'es_result' tidy(x, ...)
x |
An |
... |
Unused; kept for compatibility with the |
A tibble with columns term, estimate, std.error,
statistic, p.value, relative_time, is_baseline, and the
conf_low_XX/conf_high_XX pairs of the originating call.
## Not run: res <- event_study(y ~ 1 | id + year, data = df, time = year, timing = g, estimator = "cs") broom::tidy(res) ## End(Not run)## Not run: res <- event_study(y ~ 1 | id + year, data = df, time = year, timing = g, estimator = "cs") broom::tidy(res) ## End(Not run)