2 IFE-EM & MC
estimator = "ife" (IFE-EM, Gobillon and Magnac (2016)), estimator = "mc" (matrix completion, Athey et al. (2021)), and the legacy EM = TRUE flag are soft-deprecated in gsynth v1.5.0 and will be removed in v2.0.0 (target: late 2026).
Each call to one of these paths emits a one-time-per-session console message. Behavior is unchanged from v1.4.0 — the deprecation is a signal, not a break.
For new code, use fect::fect() directly. The gsynth package is built around the GSC estimator (Xu (2017)); IFE-EM and matrix completion are now documented and maintained in fect. Migration recipes are in Section 2.5 below.
In the difference-in-differences setting (with a large \(N\), and \(N_{tr}\) growing with \(N\)), Gobillon and Magnac (2016) propose an EM algorithm that exploits pre-treatment observations from treated units; Athey et al. (2021) propose a matrix-completion approach that imputes counterfactual outcomes under a low-rank structure. Both methods use information from never-treated controls and not-yet-treated observations of treated units, and conduct inference under a super-population framework.
For a full treatment, see the fect User Manual.
This chapter documents the v1.5.0 wrapper paths for users with existing code that depends on estimator = "ife" or estimator = "mc", and shows how to run a placebo test on the resulting fit. The chapter ends with migration recipes that move existing calls to fect::fect() directly.
2.1 A working IFE-EM fit
out_ife <- gsynth(Y ~ D + X1 + X2, data = simdata,
index = c("id", "time"), estimator = "ife",
force = "two-way", inference = "bootstrap",
se = TRUE, nboots = 500, r = c(0, 5),
CV = TRUE, parallel = TRUE, cores = 16)
print(out_ife)
#> Call:
#> gsynth(formula = Y ~ D + X1 + X2, data = simdata, index = c("id",
#> "time"), force = "two-way", r = c(0, 5), CV = TRUE, estimator = "ife",
#> se = TRUE, nboots = 500, inference = "bootstrap", parallel = TRUE,
#> cores = 16, vartype = "bootstrap")
#>
#> Average Treatment Effect on the Treated:
#> ATT.avg S.E. CI.lower CI.upper p.value
#> [1,] 5.085 0.3874 4.326 5.845 0
#>
#> ~ by Period (including Pre-treatment Periods):
#> ATT S.E. CI.lower CI.upper p.value count
#> -19 0.05230 0.5813 -1.08694 1.19153 9.283e-01 5
#> -18 -0.10596 0.3575 -0.80669 0.59478 7.670e-01 5
#> -17 -0.80153 0.2237 -1.23989 -0.36318 3.386e-04 5
#> -16 1.16458 0.2822 0.61153 1.71763 3.672e-05 5
#> -15 -1.08265 0.4631 -1.99035 -0.17494 1.940e-02 5
#> -14 1.99758 0.5439 0.93160 3.06356 2.399e-04 5
#> -13 -0.16400 0.4306 -1.00803 0.68003 7.033e-01 5
#> -12 0.78696 0.2984 0.20205 1.37187 8.364e-03 5
#> -11 -0.04384 0.6561 -1.32985 1.24217 9.467e-01 5
#> -10 0.76039 0.3970 -0.01767 1.53846 5.544e-02 5
#> -9 0.44816 0.4726 -0.47811 1.37442 3.430e-01 5
#> -8 -0.34174 0.4577 -1.23891 0.55543 4.553e-01 5
#> -7 -0.93095 0.5584 -2.02532 0.16343 9.546e-02 5
#> -6 -0.73271 0.4622 -1.63853 0.17311 1.129e-01 5
#> -5 -1.01939 0.5028 -2.00480 -0.03397 4.261e-02 5
#> -4 -0.15895 0.5117 -1.16186 0.84395 7.561e-01 5
#> -3 0.15752 0.3993 -0.62515 0.94019 6.932e-01 5
#> -2 -0.13031 0.6030 -1.31219 1.05158 8.289e-01 5
#> -1 -1.07844 0.1896 -1.45000 -0.70689 1.279e-08 5
#> 0 1.22297 0.6627 -0.07592 2.52185 6.498e-02 5
#> 1 0.34635 0.7187 -1.06228 1.75498 6.299e-01 5
#> 2 0.15980 1.4058 -2.59543 2.91503 9.095e-01 5
#> 3 3.64150 0.8949 1.88751 5.39549 4.719e-05 5
#> 4 2.75111 0.8208 1.14233 4.35990 8.033e-04 5
#> 5 4.45712 0.7657 2.95628 5.95795 5.863e-09 5
#> 6 5.67151 0.5878 4.51935 6.82367 0.000e+00 5
#> 7 8.17218 0.8900 6.42773 9.91663 0.000e+00 5
#> 8 6.57342 0.9287 4.75315 8.39370 1.464e-12 5
#> 9 9.11684 0.4897 8.15697 10.07671 0.000e+00 5
#> 10 9.96265 0.8358 8.32450 11.60079 0.000e+00 5
#>
#> Coefficients for the Covariates:
#> Coef S.E. CI.lower CI.upper p.value
#> X1 1.455 0.04136 1.374 1.536 0
#> X2 3.508 0.04310 3.424 3.593 0plot(out_ife, main = "Estimated ATT (IFE-EM)")2.2 A working matrix-completion fit
The lambda argument controls the regularization parameter. If left unset, a sequence of candidates is generated and selected via cross-validation. Set the candidate-list length with nlambda, or supply your own sequence via lambda. The number of CV folds is set with k.
plot(out_mc, main = "Estimated ATT (MC)")In v1.4.0, inference = "parametric" combined with estimator = "ife" or "mc" was silently coerced to bootstrap inference with a warning. In v1.5.0 this combination errors instead.
The hard gate matches fect v2.2.0+. The reason is that silent coercion can hide an inferential issue from users who deliberately chose IFE-EM: the parametric bootstrap is designed for the small-\(N_{tr}\) GSC setting and is not a sensible choice once you switch to a super-population estimator. A hard error forces a conscious choice. For the longer discussion, see the fect User Manual.
2.3 Placebo tests for IFE-EM and MC
The basic placebo workflow is in Chapter 1.3. We repeat it here on an IFE-EM fit because the placebo test is especially worth running on these estimators: the EM step blends pre-treatment treated cells into the factor estimates, and the matrix-completion regularizer trades bias for variance, so both can produce non-zero pre-treatment “effects” without a real signal.
plot(out_ife_pb, main = "Placebo test --- IFE-EM on simdata")The numeric output for the placebo region is in out_ife_pb$est.placebo.
A note on what gsynth() does not expose: fect also has a carryoverTest argument that holds out late post-treatment periods to check whether treatment effects persist past the end of the treatment window. That test requires panels with treatment reversals, which the GSC framework does not allow, so gsynth() does not accept carryoverTest. If you have a panel with reversals and want a carryover test, use fect::fect() directly.
2.4 Matrix completion on unbalanced data
Applying matrix completion to the unbalanced turnout data:
2.5 Migration to fect
When v2.0.0 ships (target: late 2026), the IFE-EM and MC paths in gsynth will be removed entirely. Migrating now means moving these calls to fect::fect(). The substitution is mechanical.
2.5.1 IFE-EM migration
## v1.4.0:
gsynth(Y ~ D + X1 + X2, data = simdata,
index = c("id", "time"), estimator = "ife",
inference = "bootstrap", se = TRUE,
nboots = 200, r = c(0, 5), CV = TRUE,
force = "two-way")
## v1.5.0+ (use fect directly):
fect::fect(Y ~ D + X1 + X2, data = simdata,
index = c("id", "time"),
method = "ife",
time.component.from = "notyettreated",
vartype = "bootstrap", se = TRUE,
nboots = 200, r = c(0, 5), CV = TRUE,
force = "two-way")2.5.2 Matrix completion migration
## v1.4.0:
gsynth(Y ~ D + X1 + X2, data = simdata,
index = c("id", "time"), estimator = "mc",
inference = "bootstrap", se = TRUE, nboots = 200,
force = "two-way")
## v1.5.0+ (use fect directly):
fect::fect(Y ~ D + X1 + X2, data = simdata,
index = c("id", "time"),
method = "mc",
time.component.from = "notyettreated",
vartype = "bootstrap", se = TRUE, nboots = 200,
force = "two-way")
2.5.3 EM = TRUE migration
EM = TRUE was an early flag that mapped to IFE-EM. In v1.5.0, the canonical replacement is fect::fect(method = "ife", ...) directly:
## v1.4.0:
gsynth(Y ~ D + X1 + X2, data = simdata,
index = c("id", "time"), EM = TRUE,
se = TRUE, nboots = 200, r = c(0, 5))
## v1.5.0+:
fect::fect(Y ~ D + X1 + X2, data = simdata,
index = c("id", "time"),
method = "ife",
time.component.from = "notyettreated",
vartype = "bootstrap", se = TRUE,
nboots = 200, r = c(0, 5))2.5.4 Argument mapping
gsynth() arg |
fect::fect() equivalent |
|---|---|
inference = "parametric" |
vartype = "parametric" |
inference = "nonparametric" |
vartype = "bootstrap" |
inference = "bootstrap" |
vartype = "bootstrap" |
inference = "jackknife" |
vartype = "jackknife" |
weight = "col" |
W = "col" |
EM = TRUE |
method = "ife", time.component.from = "notyettreated"
|
estimator = "ife" |
method = "ife", time.component.from = "notyettreated"
|
estimator = "mc" |
method = "mc", time.component.from = "notyettreated"
|
estimator = "gsynth" |
method = "ife" (or "gsynth"), time.component.from = "nevertreated"
|
Most other arguments (CV, r, force, nboots, parallel, cores, seed, min.T0, alpha, normalize, tol) carry over by name.
If you maintain a replication archive that depends on gsynth(..., estimator = "ife") or gsynth(..., estimator = "mc"), plan for one of two paths:
-
Pin to gsynth 1.4.0 via the CRAN archive:
remotes::install_version("gsynth", "1.4.0"). -
Migrate the calls to
fect::fect()using the recipes above.
After v2.0.0, bug fixes and feature work for IFE-EM and MC happen exclusively in fect.