
Note
By default, thefixespackage assumes time is a regularly spaced numeric variable (e.g., year = 1995, 1996, …).
If your time variable is irregular or non-numeric (e.g.,Datetype), settime_transform = TRUEto automatically convert it to a sequential index within each unit.
For unit-specific treatment timing, setstaggered = TRUE.
The fixes package is designed for convenient event study
analysis and plotting, particularly useful for visualizing parallel
trends and dynamic effects in two-way fixed effects (TWFE)
difference-in-differences (DID) research.
Key Functions:
run_es() — Takes a data frame, generates lead/lag
dummies, and fits the event study regression. Supports fixed effects,
covariates, clustering, staggered timing, weights, custom baseline, and
multiple confidence intervals.plot_es() — Plots event study results using
ggplot2 with flexible options: ribbon or error bars, choice
of CI level, and theme customization.Install from CRAN:
install.packages("fixes")Or with pak:
pak::pak("fixes")For the latest development version from GitHub:
pak::pak("yo5uke/fixes")First, load the library.
library(fixes)run_es() expects a panel data frame with at least:
Date)For staggered adoption
(staggered = TRUE), include a variable specifying
unit-specific treatment timing (e.g., “treatment_year”).
Widely used panel datasets include:
did::sim_dt(): simulated panel for DiD tutorialsfixest::base_stagg: a built-in dataset for staggered
adoptiondf1 <- fixest::base_did      # Basic DiD
df2 <- fixest::base_stagg    # Staggered treatment| y | x1 | id | period | post | treat | 
|---|---|---|---|---|---|
| 2.8753063 | 0.5365377 | 1 | 1 | 0 | 1 | 
| 1.8606527 | -3.0431894 | 1 | 2 | 0 | 1 | 
| 0.0941652 | 5.5768439 | 1 | 3 | 0 | 1 | 
| 3.7814749 | -2.8300587 | 1 | 4 | 0 | 1 | 
| -2.5581996 | -5.0443544 | 1 | 5 | 0 | 1 | 
| 1.7287324 | -0.6363849 | 1 | 6 | 1 | 1 | 
| id | year | year_treated | time_to_treatment | treated | treatment_effect_true | x1 | y | |
|---|---|---|---|---|---|---|---|---|
| 2 | 90 | 1 | 2 | -1 | 1 | 0 | -1.0947021 | 0.0172297 | 
| 3 | 89 | 1 | 3 | -2 | 1 | 0 | -3.7100676 | -4.5808453 | 
| 4 | 88 | 1 | 4 | -3 | 1 | 0 | 2.5274402 | 2.7381717 | 
| 5 | 87 | 1 | 5 | -4 | 1 | 0 | -0.7204263 | -0.6510307 | 
| 6 | 86 | 1 | 6 | -5 | 1 | 0 | -3.6711678 | -5.3338166 | 
| 7 | 85 | 1 | 7 | -6 | 1 | 0 | -0.3152137 | 0.4956263 | 
run_es()The main event study function. All key arguments below:
| Argument | Description | 
|---|---|
| data | Data frame to be used. | 
| outcome | Outcome variable. Can be specified as a raw variable or a
transformation (e.g., log(y)). Provide it unquoted. | 
| treatment | Dummy variable indicating the treated units. Provide it unquoted.
Accepts both 0/1andTRUE/FALSE. | 
| time | Time variable. Provide it unquoted. | 
| timing | The time at which the treatment occurs. If staggered = FALSE, this should be a scalar (e.g.,2005). Ifstaggered = TRUE, provide a variable
(column) indicating the treatment time for each unit. | 
| fe | Fixed effects to control for unobserved heterogeneity. Must
be a one-sided formula (e.g., ~ id + year). | 
| lead_range | Number of pre-treatment periods to include (e.g., 3 = lead3,lead2,lead1). Default isNULL, which automatically uses the maximum available lead
range. | 
| lag_range | Number of post-treatment periods to include (e.g., 2 = lag0(the treatment period),lag1,lag2). Default isNULL, which automatically
uses the maximum available lag range. | 
| covariates | Additional covariates to include in the regression. Must be
a one-sided formula (e.g., ~ x1 + x2). | 
| cluster | Specifies clustering for standard errors. Can be a character
vector (e.g., c("id", "year")) or a
formula (e.g.,~ id + year,~ id^year). | 
| weights | Optional weights to be used in the regression. Provide as a
one-sided formula (e.g., ~ weight). | 
| baseline | Relative time value to be used as the reference category. The corresponding dummy is excluded from the regression. Must be within the specified lead/lag range. | 
| interval | Time interval between observations (e.g., 1for yearly
data,5for 5-year intervals). | 
| time_transform | Logical. If TRUE, converts thetimevariable into a sequential index (1, 2, 3, …) within each unit. Useful
for irregular time (e.g., Date). Default isFALSE. | 
| unit | Required if time_transform = TRUE. Specifies the panel
unit identifier (e.g.,firm_id). | 
| staggered | Logical. If TRUE, allows for unit-specific treatment
timing (staggered adoption). Default isFALSE. | 
| conf.level | Numeric vector of confidence levels (e.g., c(0.90, 0.95, 0.99); default:0.95). | 
event_study <- run_es(
  data       = df1,
  outcome    = y,
  treatment  = treat,
  time       = period,
  timing     = 6,
  fe         = ~ id + period,
  lead_range = 5,
  lag_range  = 4,
  cluster    = ~ id,
  baseline   = -1,
  interval   = 1,
  conf.level = c(0.90, 0.95, 0.99)
)fe must be a one-sided formula (e.g.,
~ firm_id + year).cluster can be a one-sided formula or a character
vector.event_study <- run_es(
  data       = df1,
  outcome    = y,
  treatment  = treat,
  time       = period,
  timing     = 6,
  fe         = ~ id + period,
  lead_range = 5,
  lag_range  = 4,
  covariates = ~ cov1 + cov2 + cov3,
  cluster    = ~ id,
  baseline   = -1,
  interval   = 1
)Date), with
time_transformdf_alt <- df1 |>
  dplyr::mutate(
    year = rep(2001:2010, times = 108),
    date = as.Date(paste0(year, "-01-01"))
  )
event_study_alt <- run_es(
  data           = df_alt,
  outcome        = y,
  treatment      = treat,
  time           = date,
  timing         = 9,  # Use index, not the original Date
  fe             = ~ id + period,
  lead_range     = 3,
  lag_range      = 3,
  cluster        = ~ id,
  baseline       = -1,
  time_transform = TRUE,
  unit           = id
)Note:
Whentime_transform = TRUE, specifytimingas an index (e.g., 9 = 9th observation in unit).
Currently,time_transform = TRUEcannot be combined withstaggered = TRUE(future versions may support this).
plot_es()plot_es() visualizes results using ggplot2.
By default, it plots a ribbon for the 95% CI, but supports error bars,
CI level selection, and multiple themes.
| Argument | Description | 
|---|---|
| data | Data frame from run_es() | 
| ci_level | Confidence interval (default: 0.95) | 
| type | “ribbon” (default) or “errorbar” | 
| vline_val | X for vertical line (default: 0) | 
| vline_color | Color for vline (default: “#000”) | 
| hline_val | Y for horizontal line (default: 0) | 
| hline_color | Color for hline (default: “#000”) | 
| linewidth | Line width (default: 1) | 
| pointsize | Point size (default: 2) | 
| alpha | Ribbon transparency (default: 0.2) | 
| barwidth | Errorbar width (default: 0.2) | 
| color | Point/line color (default: “#B25D91FF”) | 
| fill | Ribbon color (default: “#B25D91FF”) | 
| theme_style | Theme: “bw” (default), “minimal”, “classic” | 
plot_es(event_study)
plot_es(event_study, type = "errorbar")
plot_es(event_study, type = "ribbon", ci_level = 0.9, theme_style = "minimal")
plot_es(event_study, type = "errorbar", ci_level = 0.99) + ggplot2::ggtitle("Event Study, 99% CI")Further customization with ggplot2 is fully
supported:
plot_es(event_study, type = "errorbar") + 
  ggplot2::scale_x_continuous(breaks = seq(-5, 5, by = 1)) + 
  ggplot2::ggtitle("Result of Event Study")staggered = TRUE with
time_transform = TRUEtiming to accept original time values (e.g.,
Date), not just indexIf you find an issue or want to contribute, please use the GitHub Issues page.
Happy analyzing!🥂