Changelog
v2.4.5
(2026-05-30) CRAN release.
- New
group.feargument onfect(): absorb additive fixed effects at a coarsening of the unit identifier (e.g., state FE on county-level data when treatment varies at the state level). - Fix:
method = "cfe"withforce = "time"/"unit"previously errored withIndex out of bounds: [index='alpha']/[index='xi']. - Removed the vestigial public
sfeargument and the orphan internalR/polynomial.R(no live code reached either).
v2.4.4
(2026-05-19)
-
fect()now returns$sample, a logicalT x Nmatrix (same dims as$Y.dat) marking cells the estimator used in any part of the procedure (main fit, placebo / carryover / balance tests). Compatible withpanelView::panelview(sample = ...).
v2.4.3
(2026-05-14)
- Fix
future.globals.maxSizeoverrun in parallel bootstrap:quiet_nonparawrapper no longer capturesfect_boot()’s full frame. - Raise
future.globals.maxSizeto 2 GiB locally inside the parallel block.
v2.4.2
(2026-05-02)
- New
test = c("none", "placebo", "carryover")argument onestimand()closes issue #131. - New
ci.method = c("normal", "basic")argument onfect(). Default"normal"is byte-equivalent to the v2.4.1 default."basic"is the reflected pivot CI (Davison-Hinkley 1997 §5.2.1;boot::boot.ci(type = "basic")). All CIs in fect’sest.*slots use the requested method uniformly. Legacyquantile.CIis soft-deprecated — both legacy values still work (mapped toci.methodwith a one-time warning). - New
ci.methodvalues"bc","bca","normal"onestimand(); per-type defaults updated.att.cumudefault is"basic"(the reflected pivot CI recommended by Davison-Hinkley 1997 §5.2.1 and used byboot::boot.ci(type = "basic")); the raw-quantile"percentile"option is preserved for replication. The full 5-method surface lives onestimand(); fect’s built-in CI machinery covers normal / basic only. - New
para.errorargument forvartype = "parametric"selects the residual-error model:"auto","ar","empirical","wild". - Tighter EM convergence defaults:
tolfrom1e-3to1e-5,max.iterationfrom1000to5000, plus awarning()when EM hitsmax.iterationwithout converging. IFE/CFE point estimates shift a few percent (up to 40% on factor-heavy CFE) closer to the EM’s converged minimum. Passtol = 1e-3, max.iteration = 1000explicitly to reproduce pre-v2.4.2 numerical output. -
estimand()warns when a tail-quantile CI method ("basic","percentile","bc","bca") is requested on a fit with fewer than 1000 bootstrap replicates, recommending refit atnboots = 1000. -
vartype = "jackknife"withNco > 1000emits a fit-time warning recommendingvartype = "bootstrap"for tractability. - Various bug fixes.
v2.4.1
(2026-04-30) CRAN release.
Enhancement. estimand()’s vartype argument now accepts "parametric" in addition to "bootstrap", "jackknife", and "none". Works for all four type values when the fit was produced with fect(..., vartype = "parametric", keep.sims = TRUE).
Bug fix. estimand(fit, "att.cumu", ...) now populates n_cells on both event-time and overall paths, so esplot(..., Count = "n_cells") draws the bottom-bar treated-cell panel for cumulative plots.
v2.4.0
(2026-04-29)
New: post-hoc estimands API. Two new public functions and one new dedicated chapter in the user manual.
-
estimand(fit, type, by, ...)typed dispatcher returns a tidy data frame with consistent columns regardless of estimand type. Shipped types:-
"att"— default per-event-time ATT; byte-identical tofit$est.att. -
"att.cumu"— cumulative ATT; replaceseffect(). -
"aptt"— average proportional treatment effect on the treated (Chen and Roth 2024 QJE). -
"log.att"— mean log-scale treatment effect.
-
-
imputed_outcomes(fit, ...)low-level accessor returns the cell-level potential-outcome surface as a long-form data frame for custom estimands. Pipe to dplyr / data.table for arbitrary aggregation. -
cells =filter (logical / formula) on both functions, withwindow = c(L, R)sugar onestimand()for the common event-time-window case. -
direction = c("on", "off")selects the event-time grid for reversal panels. - New chapter on post-hoc estimands in the user manual walks through all six worked examples plus the migration table.
Soft-deprecation. effect() and att.cumu() emit a one-time-per-session message pointing at estimand(). They continue to work byte-identically; removal not before v3.0.0. Numerical equality with estimand() is asserted by package tests.
Internal slot reservation. eff_debias slot on the fit object is now reserved (NULL for plain imputation estimators); populated by future doubly-robust estimators so DR scores can be added without breaking the long-form schema.
v2.3.3
(2026-04-28)
Bug fixes.
-
diagtest()no longer crashes with “incorrect number of dimensions” when only one bootstrap column survives the all-non-NA filter (drop = FALSEat both filter sites inR/diagtest.R). - Bootstrap PSOCK cluster init now uses the shared
rscript_libs = .libPaths()helper, wrapped in a 3-attempt retry-with-backoff. Eliminates the intermittent PSOCK race that surfaced in mixed CV+boot test runs; falls back to sequential bootstrap ifdoParallelalso fails. -
carryover.rmis now stored on the fit object.plot.fect()reads it from the slot rather than re-parsingx$call; correct underdo.call(), programmatic wrappers, and any call-rewriting code path.
Documentation. Chapter 2 §Other estimands gains a worked example for post-hoc estimands derived from the imputed potential-outcome surface, showing APTT (Chen and Roth 2024) with bootstrap CIs from the existing fit slots.
v2.3.2
(2026-04-28)
Modern visual defaults for plot.fect() (visual breaking change):
- All 14 plot types now render with a publication-ready recipe: white panel, plain left-aligned title, dashed treatment-onset vline, peach highlight rectangle behind placebo / carryover periods, pre/post lightness contrast, and compact legends.
- Pass
legacy.style = TRUEfor byte-identical pre-2.3.1 reproduction.theme.bw = FALSEis soft-deprecated (removal in v2.5.0). - Placebo and carryover periods now render as a single accent glyph (orange triangle / blue diamond) per period, sized so the glyph reads as the same visual weight as the surrounding circles. Background rectangle behind each highlight period is opt-in via
highlight.fill = TRUE; default is glyph only, which keeps figures clean for print and grayscale. -
highlightargument now also accepts a character subset ofc("placebo", "carryover", "carryover.rm")for per-test-type selective highlighting (e.g.,highlight = "placebo"on a fit with both tests renders carryover periods as plain circles). - Stats-annotation block (placebo / carryover / F / equivalence p-values) sits at the top-left panel corner with symmetric inset and enough top padding to never graze the leftmost CI. Sized so it does not overpower the title.
- Internal: migrated off the ggplot2 4.0 deprecated
fatten/lwdarguments to thesize/linewidthaesthetics.
v2.3.1
(2026-04-27)
Consistent ATT surface when W is supplied. fit$est.att, fit$est.avg, plot(fit), and print(fit) now all report the same W-weighted aggregation. The redundant *.W parallel slots are no longer attached to the fit object. To see the unweighted view, refit with W = NULL.
New W.est and W.agg arguments. Both default to NULL and fall back to W. Use W.est alone when the weight should enter the outcome-model fit only, or W.agg alone when it should enter the aggregation only. A clean fect-internal solution for inverse-probability weights for confounding adjustment is under development for v3.0.
Deprecations. The weight argument to plot.fect() is now a no-op (the canonical slots are already W-weighted when W is supplied). It emits a one-time warning and is slated for removal in v2.5.0.
v2.3.0
(2026-04-25)
Rolling-window cross-validation:
- New
cv.method = "rolling"in the mainfect()CV dispatcher (also exposed as standaloner.cv.rolling()) implements the standard time-series rolling-window CV design adapted to panel data. Recommended default for serially correlated panels; closes the AR-leakage channel that causes block CV to over-selectr(often pegging atr.max). Supportsmethodin {"ife","gsynth","cfe","mc","both"}. New parametercv.buffer(default 1, past-side buffer). Empirical validation: on a Xu (2017) DGP with \(r_{\text{true}} = 2\) and AR(1) \(\rho = 0.8\) (K = 200 reps), rolling CV recovers truth in 56% vs block CV’s 15% (meanr.cv= 1.58 vs 3.34).
1-SE rule is the new default for r selection (Hastie/Tibshirani/Friedman 2009 §7.10):
-
cv.rule = "1se"(default) picks the smallestrwithin one fold-SE of the minimum-CV-errorr. Legacycv.rule = "1pct"remains available for byte-identical reproducibility of pre-2.3.0 fits. - Default flip: existing
CV = TRUEcalls will produce differentr.cvon the same data. Setcv.rule = "1pct"to recover prior behavior.
Bounded factor loadings for GSC:
- New
loading.bound = "simplex"(default"none") constrains treated-unit loadings to the convex hull of control loadings via an entropy-regularized simplex projection. Companion arguments:gamma.loading(regularization strength;NULLtriggers 5-fold CV) andgamma.loading.grid. Diagnostic outputs:wgt.implied(row-stochastic simplex weights),loading.proj.resid(extrapolation flag per treated unit). Currentlymethod = "ife"/"gsynth"withtime.component.from = "nevertreated"only. - Caveat: percentile-bootstrap intervals may under-cover when the constraint binds; boundary-corrected inference is deferred.
- Companion plot:
plot(..., type = "loading.overlap")shows treated loadings vs the control convex hull (\(r \geq 2\)) or mirror histogram with control-range band (\(r = 1\)).
Other changes:
-
cv.methoddefault flipped from"all_units"(or"treated_units"infect_nevertreated) to"rolling"across all CV entry points. Existing block-CV behavior remains available via the newcv.method = "block"value (renamed from"all_units"); the legacy values"all_units"and"treated_units"are still accepted but emit a one-time deprecation message and will be removed in v2.4.0 (replaced by the unified(cv.method, cv.units)API). - GSC path now populates
Y.ct.full[, co]from \(F \hat\lambda_{co}\) at masked control positions (wasNA); enables rolling CV scoring formethod = "gsynth". ATT/gap/est.avg unchanged. - CV default flips paired for stable iid recovery:
cv.prop = 0.1andk = 20across all CV entry points (fect,fect_cv,fect_binary_cv,fect_nevertreated,fect_mspe,r.cv.rolling). The(cv.prop = 0.1, k = 20)pair gives ~2× coverage of every eligible unit across folds while keeping per-fold mask size moderate. Sharpens the MSPE curve enough that the 1-SE rule recoversr_truereliably on iid panels with at-least-moderate factor signal-to-noise. -
cv.donutdefault raised from 0 to 1 to matchcv.buffer = 1for rolling CV. -
simdataregenerated with the latent factor contribution scaled by 2 (vs the original Liu-Wang-Xu 2024 DGP). The doubling raises factor signal-to-noise from 2.7 to 10.9, which makes the latent factor structure clearly recoverable by cross-validated rank selection on this dataset (the original simdata’s signal was comparable to noise, so MSPE-based rank selection landed within the 1-SE band of the truth without converging on it). All other DGP components (covariates, fixed effects, treatment effect, error) are unchanged. Existing user code that loadssimdatawill see different numerical Y values. -
r.cv.rolling()extended tomethod = "cfe". CFE-specific args (Z,gamma,Q,Q.type,kappa, extra index columns) are forwarded via...and held fixed; rolling CV varies onlyr. -
fect_mspe()acceptscv.method = "rolling"for AR-leakage-resistant model comparison. - PSOCK worker
.libPaths()propagation fix:parallelly::makeClusterPSOCK(rscript_libs = .libPaths())used whereverfuture::plan(future::multisession, ...)previously appeared. Resolves “no package called ‘fect’” errors during Quarto book render and intermittent CRAN-check failures. - Phase A bootstrap (
R/boot.R::draw.error) migrated fromforeach %dopar%tofuture.apply::future_lapply; removes a staledoFuture::registerDoFuture()re-registration that caused an ~8× slowdown on variant (iii) parametric bootstraps in multi-fit sessions.
v2.2.1
(2026-04-22)
Parametric-bootstrap fixes (se = TRUE, vartype = "parametric"):
- Parametric bootstrap on unbalanced panels now correctly reflects within-unit serial correlation. Prior versions used a diagonal residual covariance in the Gaussian draw, under-estimating ATT standard errors on serially correlated data by ≈ \(\sqrt{(1+\rho)/(1-\rho)}\) at AR(1) coefficient \(\rho\). Balanced panels and
vartype∈ {"bootstrap","jackknife"} are unaffected. - Fixed
Unsupported bootstrap method: fecrash whenmethod = "gsynth"or"cfe"and CV selected \(r_{\text{cv}} = 0\). Reported against thegsynthwrapper. - Fixed
one.nonparadispatcher soife+notyettreated,cfe+nevertreated, andcfe+notyettreatedbootstraps route to the correct Loop-2 estimator (previously all three produced incorrect SEs). - Added hard gate that errors on
ife + notyettreated + parametric— a coverage simulation showed ~80% vs 95% nominal. Usetime.component.from = "nevertreated"or a non-parametricvartype.
Parallel cross-validation:
- IFE, MC, and CFE cross-validation now run in parallel via
future_lapply, dispatching(r, fold)(or(lambda, fold)) flat across workers. Auto-engages above \(N_{co} \times T \geq 20{,}000\) for IFE/MC and \(\geq 60{,}000\) for CFE. - The
parallelargument now accepts five forms:TRUE,FALSE,"cv","boot",c("cv", "boot"). Scalar forms are backward-compatible; string forms bypass the auto-threshold. - MC-only tradeoff:
break_checkshort-circuit applies in serial mode only; parallel MC evaluates all candidate lambdas. Preferparallel = FALSEfor MC if the search typically terminates early. - Internal: extracted
(rank, fold)logic intoR/cv-helpers.R;fect_nevertreated.Rparallel CV migrated fromforeach %dopar%(fold-only) tofuture_lapply(flatr×k), which also resolves a latent worker-visibility issue in the priorforeachpath. - Fix: vector
parallel = c("cv", "boot")no longer errors infect_boot,fit_test,permutation,fect_sens, ordid_wrapper— five sites had legacy scalarparallel == TRUE/if (parallel)checks.
v2.2.0
(2026-03-27) CRAN release.
- Added
time.component.fromparameter:"notyettreated"(default) or"nevertreated"controls which units provide the time-varying model components (time fixed effects, latent factors, and temporal dynamics). Replacesmethod = "gsynth"withmethod = "ife", time.component.from = "nevertreated". - CFE estimator now supports
time.component.from = "nevertreated", enabling full CFE model components (additional FEs, \(Z/\gamma\), \(Q/\kappa\), latent factors) with never-treated estimation. - Added
fect_mspe()for out-of-sample model comparison (MSPE, RMSE, MAD) across specifications. - Unified cross-validation: replaced
cv.treat+mask.methodwith singlecv.methodparameter; addedcv.samplek-fold CV forfect_nevertreated. - Improved EM convergence conditioning: R-level centering + C++ component-wise convergence (up to 2000x better component accuracy).
- Removed deprecated methods:
polynomial,bspline,cfe_old. Renamed internalfect_gsynthtofect_nevertreated. - Plot system overhaul: pre/post color distinction, highlight shapes (triangles for placebo, diamonds for carryover), new
esplot()parameters (xangle,yangle,xbreaks,ybreaks,legendOff). - Fixed parallel bootstrap
.exportlists for CFE + nevertreated; fixedfect_mspe+CV=TRUEinteraction; fixedstart0coloring; fixed.as_mask()dimension bug. - Renamed datasets for clarity:
simdata1→sim_base,simdata2→sim_trend,simgsynth→sim_gsynth. Addedsim_regionandsim_linear.simdatakept for backward compatibility. - Fixed ggplot2 deprecation warnings:
size→linewidthin allgeom_hline,geom_line, and bound line aesthetics. - Fixed
GGally::ggpairsloadings plot warnings by applying color/fill scales inside sub-panel functions. - Exit plot legends now read “Under treatment” / “Out of treatment” instead of “Pre-treatment” / “Post-treatment”.
- CRAN compliance: resolved codoc mismatches in
esplot.Rd,fect_mspe.Rd,plot.fect.Rd; removed duplicate data source (fect.RData); addedglobalVariablesdeclarations. - 590 tests (up from 131), including 98 book-derived behavioral tests and 36 CFE bifurcation tests.
v2.1.1
- Added
fect_mspe()for evaluating out-of-sample prediction accuracy across model specifications. - Optimized CFE estimator performance: memory recycling, sparse data handling, reduced C++ deep copies.
- Fixed
sigma2normalization crash and division-by-zero guards in bootstrap. - Fixed bootstrap NA and multicollinearity abort issues.
- Fixed gsynth weight calculation and
r=0grand mean issue. - Fixed
Q.typerouting in CFE and handling of time periods with all units under control. - Switched parallel backend from
futuretoparallelly. - Added CFE tutorial chapter to Quarto book.
- Added
codetoolsandtailimports required by new functions.
v2.1.0
(2025-10-27)
- Added CFE (Complex Fixed Effects) estimator as
method = "cfe": additional group FEs viaindex, time-invariant covariates with time-varying coefficients (Z/gamma), unit-specific loadings on known time bases (Q.type/Q/kappa), and latent factors (r). - Added B-spline option to
Q.typefor flexible nonlinear time trend approximation. - Added
return.data = TRUEoption toplot()for extracting plot data. - Added
doRNGfor reproducible parallel bootstrap results. - Switched parallel backend from
futuretoparallelly. - Aligned gsynth bootstrap values and standard errors with the original gsynth package.
- Reduced memory usage by removing unnecessary C++ deep copies.
- Added
min.T0restrictions to prevent estimation with too few control observations. - Fixed various R CMD check issues for CRAN compliance.
v2.0.5
(2025-08-25) CRAN release.
- Fixed various bugs due to changes in dependencies
- Added limit to
coresby default in parallel computing - Add new plot
type = "hte" - Added R-CMD-check for merging
v2.0.2
(2025-05-07)
- Fixed various bugs
- Updated counterfactual plot
- Added sensitivity analysis
- Added
did_wrapper()andesplot() - Added various plotting options
- Improved documentation
v2.0.0
(2025-01-17) CRAN release.
- Changed to new syntax
- Fixed various bugs
- Added
get_cohort() - Merged in gsynth
v1.0.0
- First CRAN version
- Fixed bugs
v0.6.5
- Replace fastplm with fixest for fixed effects estimation
- Added plots for heterogeneous treatment effects
- Fixed bugs
v0.4.1
- Added a
NEWS.mdfile to track changes to the package.