etwfe(..., wvar = <wvar>) argument, following Wooldridge
(2026). Each cohort-period cell gets
its own slope on wvar, which emfx() then aggregates as usual. Requires
unit fixed effects (ivar) and a linear model.
emfx() reports the slope (effect per unit of wvar). The new
emfx(..., wlevels = ...) argument instead returns effects at each unit's
observed treatment level ("observed"), or at fixed treatment levels (a
numeric vector, or a function such as fivenum, a la
marginaleffects::datagrid()), relative to no treatment. Fixed levels are
plotted as separate (dodged) groups.cgroup = "never")
are also supported, with binary pre-treatment leads for testing
pre-trends.walmart dataset of Walmart store openings and county retail employment
(1977-1999), as used in Wooldridge
(2026). This is a subset of the data
originally constructed by Brown and Butts
(2025), and is included with
their kind permission.etwfe(..., trend = TRUE) argument for including cohort-specific linear
trends, for both binary and non-binary treatments. This relaxes the parallel
trends assumption, which can matter when pre-trend tests suggest that it is
violated. See the vignette for examples. (Not available with
cgroup = "never", and requires at least two pre-treatment periods for each
treated cohort.)emfx() now reports "unconditional" standard errors by default, via
marginaleffects::vcovUnconditional(). These account for sampling variation
in the covariate distribution over which treatment effects are averaged, and
automatically inherit any one-way clustering from the original etwfe()
model. Exceptions (compressed data, multiway clustering) fall back to the
model's conditional variance-covariance matrix. Use emfx(..., vcov = TRUE)
to recover the previous default. Requires a recent version of
marginaleffects. Thanks to @pedrohcgs for the upstream implementation and
@snhansen for theoretical guidance. (#48)emfx() now aggregates using the regression weights of the original
etwfe() model (e.g., etwfe(..., weights = ~pop)). Previously, these
weights were used for estimation, but ignored when averaging the resulting
treatment effects, so that emfx() reported unweighted ATTs. Results for
unweighted models are unaffected.emfx(), thanks to upstream marginaleffects
support for analytic Jacobians (vincentarelbundock/marginaleffects#1769).
Moreover, standard errors gain improved precision due to their exact
analytic derivations.
emfx(..., compress = "auto") now falls back to FALSE (i.e., no data
compression). Data compression was mainly aimed at reducing computation
costs, which the analytic solution largely obviates. Furthermore, it
cannot support the new default of unconditional standard errors.base_stagg dataset from fixest, and run as
part of the package checks. The introductory vignette continues to use
mpdta from did. The "Performance tips" sections have been replaced by a
brief "Performance" note, since the old workarounds (turning off standard
errors, compressing the data) are no longer needed.plot.emfx() no longer errors for objects without standard errors (e.g.,
emfx(..., vcov = FALSE)), and plots point estimates only instead.
With unit-level fixed effects (ivar) and fe = "vs", the control
variables now get cohort-specific (rather than unit-specific) slopes,
matching the Wooldridge specification and the equivalent fe = "feo" model.
The previous unit-specific slopes were redundant for time-constant controls,
but over-controlled for time-varying controls and could change the
treatment effect estimates.
The default fe for linear models is now "vs" (varying slopes), as
documented. Previously, an internal typo meant that the default silently
resolved to "feo" instead. Treatment effect estimates are unaffected, but
the returned model object contains fewer nuisance parameters.
Improved automatic selection of the reference (never-treated) cohort. If
several gvar values fall outside the range of tvar (e.g., 0 and
2009 for a 2003–2007 panel), etwfe() now picks the value farthest from
the panel and warns about the ambiguity. Previously, it always preferred the
value above the range, which could mistake late-treated units for the
never-treated group. Thanks to @umor for the report and fix. (#89)
etwfe() now warns if any cohort has no pre-treatment observations. Their
effects are not identified from within-cohort variation, but are still
included in aggregated ATTs. (#89)
Missing values (e.g., in the outcome) no longer contaminate results. etwfe()
now drops incomplete cases (including missing formula weights) before
demeaning any controls, and emfx() aggregates over the estimation sample
only. Previously, emfx() averaged over all rows of the original data,
which yielded incorrect ATTs and caused the default unconditional standard
errors to error. (#91)
emfx() now throws an informative error for models estimated with
lean = TRUE, which were never supported. (#92)
xvar would affect
treatment effect estimates. Thanks to @umor for the detailed report and PR.
(#72, #73)fixest::i as part of the package namespace to avoid edge case where
the estimation would fail with boolean covariates. Thanks to @SMasa11 for the
report in #70. (#78)emfx estimations fail due to a name collision between arguments
and variables (e.g., if the input datasets has a column called gvar). Thanks
to @chase-eck for the report in #79 and fix in #80.vcov (now always "iid"), which
may affect etwfe results if the user did not explicitly provide a vcov
argument.etwfe v0.6.0 returns to CRAN after addressing some failures due to upstream dependency changes. Apologies for any inconvenience caused.
etwfe(..., fe = <string>) argument now hinges on
the type of model family. For Gaussian models, we use the same "vs" (varying
slopes) option as before. But for non-linear families like Poission, we now
default to "none". This is to preserve the correct VCOV behaviour of these
models once they are passed to emfx(), thus matching the new upstream logic
of marginaleffects. (Briefly: the standard errors of these non-linear
families cannot be computed correctly with derivative methods on estimations
where the fixed-effects have been abstracted away; see
marginaleffects#1487
for more details.) Technically this is a breaking change, but it requires no
changes on the user side and the end result should be identical in most cases.
The only change you might see is that the standard errors from estimations
with non-linear families will be slightly different/corrected. (#67)emfx(..., window = <numeric>) argument allows users to restrict the
temporal window around the treatment event, thereby narrowing the
consideration horizon. This may prove helpful for cases with a large number of
pre- and/or post-treatment periods. Thanks to @fhollenbach for the initial
suggestion and implementation. (#46)emfx(..., compress = TRUE) to fail. Thanks to @umor for
reporting in #66.emfx arguments:
emfx(..., predict = c("response", "link")), where the latter
allows for obtaining the linear prediction for non-linear models. Internally
passed as marginaleffects::slopes(..., type = predict), thus avoiding a clash
with the topline emfx(..., type = <aggregration_type>) argument. (#49)emfx(..., lean = <logical>). Default value is FALSE, but switching to
TRUE will ensure a light return object that strips away data-heavy attributes
(e.g., copies of the original model). These attributes are unlikely to be needed
as part of the emfx() workflow, so we may change the default to lean = TRUE
in a future version of etwfe. (#51, #58)plot.emfx() method (via a tinyplot backend) for visualizing
emfx objects. (#54)collapse argument in emfx() is superseded by compress. The older
argument is retained as an alias for backwards compatibility, but will now
trigger a message, nudging users to switch to compress instead. The end result
will be identical, though. This cosmetic change was motivated by a desire to be
more consistent with the phrasing used in the literature (i.e., on
performance-boosting within group compression and weighting). See Wong et al.
(2021), for example. (#57)cgroup = "never) due
to incorrect subsetting prior to recovering the marginal effects. Thanks to
@paulofelipe for reporting (#37).Fixed internal centering procedure and handling of multiple covariate levels
(#30, #31). These fixes should have no impact on the main ATT estimates (i.e.,
typical use of the package). But it may lead to differences in the heterogeneous
ATTs---i.e., via the xvar arg---which were incorrectly estimated in some
cases. Thanks to @PhilipCarthy for flagging and to @frederickluser for helpful
discussions
Fixed internal and upstream bug which was causing model offsets to error (#28, thanks @mariofiorini for the initial report and several others for helpful discussion). Note that this fix requires insight >= 0.19.1.8, which is the development version at the time of writing. More information available here: https://github.com/easystats/insight/pull/759
emfx().\dontrun to avoid triggering CRAN NOTEs on
Windows for exceeding 5 seconds execution time. Note that the package homepage
still runs these Examples if users want to inspect the output online.Support for estimating heterogeneous treatment effects via the new
etwfe(..., xvar = <xvar> argument (#16, thanks to @frederickluser).
Automatically extends to emfx() via the latter's by_xvar argument (#21).
More details are provided in the dedicated "Heterogeneous treatment effects"
section of the vignette and help documentation
The new emfx(..., collapse = TRUE) argument can substantially reduce
estimation times for large datasets (#19, thanks @frederickluser). This
performance boost does trade off against a loss in estimate accuracy. But
testing suggests that the difference is relatively minor for typical use cases
(i.e., results are equivalent up to the 1st or 2nd significant decimal place,
and sometimes even better). Please let us know if you find edge cases where this
is not true. More details are available in the dedicated "Performance tips"
section of the vignette and help documentation, including advice for combining
collapsing with emfx(..., vcov = FALSE) (which yields an even more dramatic
speed boost but at a cost of not reporting any standard errors).
Users can now use a 1 on the fml RHS to indicate that there are no control variables
as part of the etwfe call, e.g. etwfe(y ~ 1, ...). This provides a second
way of indicating no controls, alongside the existing 0 option, e.g. etwfe(y ~ 0, ...)
summary() on emfx objects for pretty printing, and that the (former) "dydx"
column of the resulting object is now named "estimate". These changes are
reflected in the updated documentation.Various documentation improvements. For example, the aforementioned sections on Heterogeneous TEs and Performance tips. I have also removed some warnings about the use of time-varying controls (#17). In truth, I can't quite recall why I included these warnings in the first place and testing confirms that it does not appear to pose a problem for the ETWFE framework. Thanks to Felix Pretis for prompting me to revisit this implicit restriction, including forwarding some relevant correspondence with Prof. Wooldridge.
data.table is added to Imports and thus becomes a direct dependency. It was already an indirect dependency through marginaleffects.
It's now possible to install the development version of the package from R-universe. Details are provided in the README.
The .Dtreat indicator variable created during the etwfe call is now
logical instead of integer (#14). This fix yields slightly different effect
sizes for emfx output when applied to non-linear model families (e.g.,
etwfe(..., family = "poisson"). The reason is that we are now implicitly
calling marginaleffects::comparisons under the hood rather than
marginaleffects::marginaleffects. Note that the main etwfe coefficients (for
any family) are unaffected, and the same is also true for emfx when applied to
a linear model (i.e., the default).
The (optional) ivar argument of etwfe() has been moved down the argument
order list from second position to fifth (i.e., after the data argument). This
means that the four required arguments of function now occupy the top positions,
which could enable shorter, unnamed notation like
etwfe(y ~ x, year, cohort, dat).
emfx now allows (time-invariant) interacted control variables on the fml RHS.
emfx now has a post_only logical argument, which may be useful for plotting
aesthetics (but not inference). See the example in the introductory vignette.
Various improvements to the documentation (restructuring, fixed typos, etc.)