| Title: | GMM Estimation of Treatment Effects Under Staggered Adoption |
| Version: | 0.1.0 |
| Description: | Estimates cohort-by-time average treatment effects under staggered treatment adoption by the generalized method of moments. Three weighting schemes are provided, corresponding to a pooled stationary covariance, a cohort-specific stationary covariance, and an unrestricted within-cohort covariance. Optional adjustment for baseline covariates by outcome regression, and a serial-correlation robust over-identification test of parallel trends and no anticipation, are also supported. The methods are described in Arora and Bijani (2026) <doi:10.2139/ssrn.6558759>. |
| License: | MIT + file LICENSE |
| Encoding: | UTF-8 |
| Depends: | R (≥ 4.1.0) |
| Imports: | fixest, MASS, stats, utils |
| Suggests: | covr, data.table, knitr, rmarkdown, testthat (≥ 3.1.5) |
| Config/testthat/edition: | 3 |
| VignetteBuilder: | knitr |
| LazyData: | true |
| URL: | https://github.com/RishabhBijani/staggeredGMM |
| BugReports: | https://github.com/RishabhBijani/staggeredGMM/issues |
| Config/roxygen2/version: | 8.0.0 |
| NeedsCompilation: | no |
| Packaged: | 2026-09-06 17:00:22 UTC; rishabhbijani |
| Author: | Rishabh Bijani |
| Maintainer: | Rishabh Bijani <rishabhbijani@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-09-15 11:00:10 UTC |
staggeredGMM: GMM Estimation of Treatment Effects Under Staggered Adoption
Description
Estimates cohort-by-time average treatment effects (CATTs) under staggered treatment adoption by the generalized method of moments, following Arora and Bijani (2026).
Details
The single estimation entry point is gmm_staggered(). The
over-identification test of parallel trends and no anticipation is
gmm_j_test().
Data coding conventions
gmm_staggered() expects one row per unit-period. The cohort column gives
each unit's first treated period, with 0 reserved for units that are
never treated. Time must be integer-valued, and the set of periods present
in the data (pooled across units) must be consecutive. Individual units may
be missing individual periods; see vignette("staggeredGMM") for the
unbalanced-panel policy. Full details, including every condition that
raises an error, are in gmm_staggered().
Author(s)
Maintainer: Rishabh Bijani rishabhbijani@gmail.com (ORCID) [copyright holder]
Authors:
Rishabh Bijani rishabhbijani@gmail.com (ORCID) [copyright holder]
Parush Arora (ORCID) [copyright holder]
References
Arora, P. and Bijani, R. (2026). "Estimating Treatment Effects under Staggered Timing and Non-Spherical Errors." doi:10.2139/ssrn.6558759
See Also
Useful links:
Report bugs at https://github.com/RishabhBijani/staggeredGMM/issues
State-level panel: bank branch deregulation and income inequality
Description
A panel of 49 US states over 1976–2006, used to study intrastate bank branching deregulation and the distribution of income.
Usage
beck_banks
Format
A data frame with 1519 rows and 8 variables:
- state
Two-letter state abbreviation.
- state_name
State name.
- statefip
State FIPS code.
- wrkyr
Calendar year, 1976–2006.
- gini
Income Gini coefficient.
- branch_reform
Year the state deregulated intrastate bank branching.
1960marks states already deregulated before the sample begins.- ln_gini
Natural log of
gini.- D_branch
Treatment indicator.
Always-treated states
This dataset is a worked example of a
coding pitfall as much as of a design. Thirteen of the 49 states
deregulated at or before 1976, the first observed year: AK, AZ, CA, DC,
ID, MD, ME, NC, NV, NY, RI, SC and VT, ten of them carrying
branch_reform == 1960. These units have no pre-treatment period, so
none of their effects is identified. No state is coded 0, so the
dataset also contains no never-treated group.
Passed to gmm_staggered() as it stands, the always-treated states
raise an error naming them. That is deliberate: silently including them
would return a plausible-looking aggregate contaminated by cells that
are not estimated at all. Restrict to states adopting inside the
window before estimating:
dat <- beck_banks[beck_banks$branch_reform > 1976, ]
fit <- gmm_staggered(dat, yname = "ln_gini", tname = "wrkyr",
idname = "state", gname = "branch_reform")
This leaves 36 states across cohorts 1977–1999, identified from not-yet-treated controls alone.
Source
Beck, T., Levine, R. and Levkov, A. (2010). "Big Bad Banks? The Winners and Losers from Bank Deregulation in the United States." The Journal of Finance 65(5), 1637–1667. doi:10.1111/j.1540-6261.2010.01589.x
Replication data deposited by the authors at DataverseNL, doi:10.34894/B1K9OU, and made available under the Creative Commons Attribution 4.0 International licence, https://creativecommons.org/licenses/by/4.0/.
Modified from the deposited data: a subset of columns was selected and
the remainder discarded. No values were altered, recoded or derived. The
full licence note is installed with the package; see
system.file("LICENSE.note", package = "staggeredGMM").
Extract cohort-by-time effect estimates
Description
Extract cohort-by-time effect estimates
Usage
## S3 method for class 'staggered_gmm'
coef(object, ...)
Arguments
object |
A |
... |
Ignored. |
Value
A named numeric vector of cohort-by-time effects, one per cell,
named "g<cohort>:t<period>". Cells with no clean comparison are NA.
Examples
fit <- gmm_staggered(sim_panel, yname = "y", tname = "year",
idname = "unit_id", gname = "cohort")
head(coef(fit))
Confidence intervals for cohort-by-time effects
Description
Pointwise normal-approximation intervals. They are not adjusted for multiplicity, so reading a whole event-study path off them overstates confidence.
Usage
## S3 method for class 'staggered_gmm'
confint(object, parm, level = 0.95, ...)
Arguments
object |
A |
parm |
Optional character vector of coefficient names. Defaults to all of them. |
level |
Confidence level, strictly between 0 and 1. |
... |
Ignored. |
Value
A two-column matrix of lower and upper limits, with one row per
requested coefficient. Unidentified cells are NA.
Examples
fit <- gmm_staggered(sim_panel, yname = "y", tname = "year",
idname = "unit_id", gname = "cohort")
head(confint(fit))
Specification test for parallel trends and no anticipation
Description
A serial-correlation robust joint test of the identifying assumptions, following Section 4.4 of Arora and Bijani (2026).
Usage
gmm_j_test(object, type = c("local", "full"), window = 3L)
Arguments
object |
A fitted |
type |
Character. |
window |
Integer. Width of the local pre-window, in periods. Ignored
when |
Details
The test is built on the never-treated event-study basis of Eq. (27) of
the paper. Fixing a base pre-period t_0, each treated cohort
g and period t \ne t_0 defines
\Delta_{g,t} = (\bar{Y}_{g,t} - \bar{Y}_{g,t_0}) -
(\bar{Y}_{\infty,t} - \bar{Y}_{\infty,t_0}).
Those with t \ge g identify the cohort-by-time effects. Those with
t < g are placebo restrictions whose expectation is zero only under
parallel trends and no anticipation. Collecting a set of J placebo
moments into \hat{p} with covariance \hat\Omega_p implied by
the fitted serial-correlation model,
J = \hat{p}' \hat\Omega_p^{-1} \hat{p} \;\to\; \chi^2_J
under the null. Because the placebo moments do not involve the treatment effects, no degrees of freedom are subtracted for estimated parameters.
The base period is t_0 = g_{\min} - 1, the last period before any
cohort adopts, so that every moment has an untreated reference.
A large number of restrictions degrades the chi-squared approximation in
small panels. type = "local" therefore uses only the placebo moments
within window periods of each cohort's adoption, which holds its
nominal size across sample sizes at essentially no cost in power, since a
differential trend or an anticipation effect is already visible just
before treatment. type = "full" uses every placebo moment and is
well-sized only when the number of units is comfortably large.
Value
An object of class staggered_gmm_jtest, a list with components
statistic, df, p_value, type, window, base_period,
n_moments and moments (the placebo moment table).
References
Arora, P. and Bijani, R. (2026). "Estimating Treatment Effects under Staggered Timing and Non-Spherical Errors." doi:10.2139/ssrn.6558759
Examples
fit <- gmm_staggered(sim_panel, yname = "y", tname = "year",
idname = "unit_id", gname = "cohort")
gmm_j_test(fit)
GMM estimation of treatment effects under staggered adoption
Description
Estimates cohort-by-time average treatment effects (CATTs) under staggered treatment adoption by the generalized method of moments, following Arora and Bijani (2026).
Usage
gmm_staggered(
data,
yname,
tname,
idname,
gname,
weighting = c("pooled_toeplitz", "cohort_toeplitz", "unrestricted"),
never_treated = NULL,
covar = NULL,
max_iter = 100L,
tol = 1e-06
)
Arguments
data |
A data frame with one row per unit-period. |
yname |
Character. Name of the outcome column. Must be numeric; factor, character and logical outcomes are rejected rather than coerced. |
tname |
Character. Name of the calendar-time column. Must be
integer-valued, and the set of periods present in |
idname |
Character. Name of the unit identifier column. |
gname |
Character. Name of the cohort column, giving each unit's
first treated period, with |
weighting |
Character. The covariance model used to form the optimal
GMM weight. One of |
never_treated |
Logical or |
covar |
Optional character vector of baseline (pre-treatment,
time-invariant) covariate columns. When supplied, the outcome-regression
adjustment of Section 4.5 of the paper is applied, so that parallel
trends need hold only conditional on |
max_iter |
Integer. Maximum number of iterated-GMM reweighting steps. |
tol |
Numeric. Convergence tolerance on the largest absolute change in any CATT estimate between successive iterations. |
Value
An object of class staggered_gmm: a list with components
call, weighting, coefficients, vcov, catt, aggregate,
weights, convergence, design, controls and validation. See
summary.staggered_gmm() and vignette("staggeredGMM").
Data requirements
The following conditions are checked before any estimation, and each raises an error naming the offending rows, units, periods or cohorts:
-
datais a data frame and every named column exists. The outcome is numeric; time and cohort are integer-valued.
No missing unit id, period or cohort. Missing outcomes are permitted.
Each unit-period combination appears exactly once.
The cohort is constant within unit.
No period is absent from the whole panel. Individual units may be missing individual periods.
No unit is treated at or before the first observed period. Such units have no pre-treatment period, so none of their effects is identified, and silently including them contaminates the aggregate.
At least one cohort adopts strictly inside the observed window.
Units whose cohort falls after the last observed period are never treated within the window and are therefore pooled with the never-treated group as clean controls, rather than discarded.
Unbalanced panels
Missing outcomes are supported. Group-period means are taken over the units observed at that period; autocovariances are divided by the number of residual pairs actually observed at each lag; and the variance of a group-period mean accounts for the overlap between the unit sets contributing at each pair of periods. When the panel is balanced all three reduce exactly to the complete-data formulas. Validity requires that missingness be independent of the outcome given group and period; selection on outcomes is not addressed by any weighting scheme.
Partial identification
A cohort-by-time effect with no clean comparison is not estimable. Such
cells are reported with estimate = NA and identified = FALSE. Where an
unidentified cell carries positive
aggregation weight the corresponding aggregate is NA, and an
identified-subset aggregate renormalised over the estimable cells is
reported alongside it, with the share of weight it covers.
Convergence
Three states are reported separately. converged is TRUE only when the
largest change in any CATT fell below tol. solve_ok records the weaker
fact that at least one weighted solve succeeded. termination gives the
reason the iteration stopped.
When solve_ok is FALSE, no weighted step was ever completed and the
returned estimates are the identity-weighted seed rather than a GMM
estimate under the requested weighting. That always raises a warning.
When solve_ok is TRUE but converged is FALSE, the iteration ran
and did not settle; the estimates are the last valid iterate. That warns
for the two Toeplitz weightings but not for "unrestricted", where slow
convergence is routine.
References
Arora, P. and Bijani, R. (2026). "Estimating Treatment Effects under Staggered Timing and Non-Spherical Errors." doi:10.2139/ssrn.6558759
See Also
gmm_j_test() for the over-identification test of parallel
trends and no anticipation.
Examples
fit <- gmm_staggered(sim_panel, yname = "y", tname = "year",
idname = "unit_id", gname = "cohort")
fit
Print a fitted staggered GMM model
Description
Print a fitted staggered GMM model
Usage
## S3 method for class 'staggered_gmm'
print(x, ...)
Arguments
x |
A |
... |
Ignored. |
Value
x, invisibly. Called for the side effect of printing.
Examples
fit <- gmm_staggered(sim_panel, yname = "y", tname = "year",
idname = "unit_id", gname = "cohort")
print(fit)
Print a specification test result
Description
Print a specification test result
Usage
## S3 method for class 'staggered_gmm_jtest'
print(x, ...)
Arguments
x |
A |
... |
Ignored. |
Value
x, invisibly. Called for the side effect of printing.
Examples
fit <- gmm_staggered(sim_panel, yname = "y", tname = "year",
idname = "unit_id", gname = "cohort")
print(gmm_j_test(fit))
Simulated staggered adoption panel
Description
A balanced synthetic panel used in the examples, the vignette and the
tests: 60 units observed over 33 periods, with five treatment cohorts
(first treated in periods 10, 13, 16, 19 or 22; 10 units each) and a
never-treated group of 10 units. True effects are deterministic given
(cohort, period), so both aggregate targets are known exactly:
ATT_CW = -16.79375 under treated-observation weighting and
ATT_EW = -15.82020 under cohort-equal weighting. The two differ because
earlier cohorts are observed for more post-treatment periods; see
gmm_staggered() for the definitions.
Usage
sim_panel
Format
A data frame with 1980 rows and 6 variables:
- unit_id
Unit identifier, integer 1–60.
- year
Period, integer 1–33.
- cohort
0 = never treated; 10, 13, 16, 19 or 22 = first treated period.
- y
Observed outcome.
- x1
Continuous baseline covariate.
- x2
Binary baseline covariate.
Details
The data-generating process, for unit i in cohort
g_i at period t:
y_{it} = \alpha_i + \lambda_t + \tau_{it} + x_{1i}\theta_{1t} +
x_{2i}\theta_{2t} + \varepsilon_{it}
-
\alpha_i \sim N(0, 1): unit fixed effect. -
\lambda_t \sim N(0, 1): period fixed effect. -
\tau_{it} = \beta_{g_i}(1 + r_{g_i})^{t - g_i}fort \ge g_iand 0 otherwise, with\beta = (-16, -12, -10, -9, -2)andr = (0.01, 0.04, 0.08, 0.10, 0.07)for cohorts(10, 13, 16, 19, 22). -
\varepsilon_{it}: AR(1) within unit,\rho = 0.5. -
x_{1i},x_{2i}: baseline covariates with cohort-correlated means and linear-in-time loadings (\theta_{1t},\theta_{2t}), so an unconditional difference-in-differences is genuinely confounded by them.
set.seed(312844) throughout. See data-raw/sim_panel.R for the exact
implementation.
This is the design of Appendix E of the paper, so the specification test has 70 full-set and 14 local-window restrictions on it.
Source
Simulated for Arora, P. and Bijani, R. (2026). "Estimating Treatment Effects under Staggered Timing and Non-Spherical Errors." doi:10.2139/ssrn.6558759
Summarise a fitted staggered GMM model
Description
Prints everything print.staggered_gmm() shows, followed by the full
table of cohort-by-time effects and the covariate-adjustment status.
Usage
## S3 method for class 'staggered_gmm'
summary(object, ...)
Arguments
object |
A |
... |
Ignored. |
Value
object, invisibly. Called for the side effect of printing.
Examples
fit <- gmm_staggered(sim_panel, yname = "y", tname = "year",
idname = "unit_id", gname = "cohort")
summary(fit)
Covariance matrix of the estimated cohort-by-time effects
Description
Covariance matrix of the estimated cohort-by-time effects
Usage
## S3 method for class 'staggered_gmm'
vcov(object, ...)
Arguments
object |
A |
... |
Ignored. |
Value
A square numeric matrix covering the identified cells only, with
row and column names matching the corresponding entries of coef().
Examples
fit <- gmm_staggered(sim_panel, yname = "y", tname = "year",
idname = "unit_id", gname = "cohort")
dim(vcov(fit))