efa_compare() now saves print(), format(), and plot() display settings, and reuses them.
efa_compare()'s are_equal now returns NA, not 0, when the compared values differ before the decimal point.
efa_compare()'s elementwise differences are now signed.
efa_compare(reorder = "congruence") now also matches the sign of single-factor solutions.
Fixed efa_compare()'s handling of missing loadings. This previously inflated diff_corres and diff_corres_cross.
Fixed a bug that could trigger spurious warnings from efa_average()'s progress bar.
Fixed two bugs in efa_screen()'s outlier detection. Both could give an incorrect or less robust result.
efa_screen()'s multicollinearity verdict is now based on the condition index alone, following Belsley (1991).
efa_screen() now withholds an uninformative Henze-Zirkler p-value instead of reporting an exact 0 or 1.
Mardia's skewness and kurtosis in efa_screen() now use small-sample corrections.
efa_simulate() gains missing_vars, to restrict missingness to selected variables.
Fixed efa_simulate()'s reported population under marginals = "VM" and force_pd = TRUE.
The type argument of efa_ekc() and ekc_type of efa_retain() no longer have any effect: the empirical Kaiser criterion now always follows Braeken and van Assen (2017). Suggested factor counts can differ from earlier results.
efa_nest() results can now be plotted.
efa_parallel() now shares simulated reference data between "PCA" and "SMC", roughly halving simulation time. Results for a given seed can differ from before.
efa_* arguments with fixed choices are now case-insensitive and accept unambiguous abbreviations.
Correlation matrices supplied as data frames are now recognised and analysed correctly.
Correlation-matrix smoothing now consistently returns a positive-definite matrix.
efa_mi()'s rmsr_upper argument is deprecated and no longer used.
With target_method = "consensus", efa_mi()'s pooled result no longer depends on the order of data_list. Results can differ from earlier versions.
efa_mi()'s pooled RMSEA is now capped at 1, and its confidence interval is withheld when it excludes the point estimate.
An efa_mi() solution now reports the Heywood status of each imputation, in mi_admissibility.
A cor_method = "fiml" fit with analytic standard errors now withholds loading standard errors when the rotation is weakly determined, matching the other standard-error methods.
The "uls" estimator now minimises the full residual, including the diagonal, matching its analytic gradient. Results can shift for over-factored models.
Starting values for the "paf", "ml", and "uls" estimators are now kept within [0, 1], matching psych::smc().
estimate_control() gains fiml_max_iter and fiml_tol, to control the FIML algorithm.
A cor_method = "fiml" fit now reports its convergence status in a new fiml component, and warns if it does not converge.
An RMSEA confidence interval that cannot be computed is now reported as NA, instead of stopping the fit.
Perfectly ordered polychoric or tetrachoric pairs are now reported as 0.9999/-0.9999, with a warning.
Binary pairs with an empty response cell now use a margin-preserving continuity correction.
Unavailable asymptotic variances for polychoric or tetrachoric pairs are now reported as NA, and affected robust standard errors are withheld.
Rare response patterns in strongly correlated pairs are now handled more accurately. They less often block DWLS or robust standard errors.
efa_power() with group > 1 now always solves for a whole-number sample size per group.
In simulation mode, efa_power() now reports NA, not a misleading hit rate, when a retention criterion never gives a suggestion.
In simulation mode, efa_power() now rejects a p that disagrees with the population model, instead of silently replacing it.
Truncated variable names that would collide in loading tables are now abbreviated and numbered, so they stay distinguishable.
print()/format() for Schmid-Leiman loading matrices now honour max_name_length, name_style, sort_loadings, and max_factors_per_block.
efa_reliability() and OMEGA() now compute every omega total from the model's full implied common variance, including cross-loadings and contributions from other group factors. Results can change for solutions with cross-loadings.
The whole-scale row of a correlated-factors solution is now labelled "total", not "g"/"general", which implied a general factor such solutions do not have.
efa_reliability() now recognises several solution types correctly, including Schmid-Leiman tables and single-factor solutions, which were previously misclassified, mislabeled, or refused.
efa_scores() and FACTOR_SCORES() with method = "Bartlett" or "Anderson" now stop on a communality at or above 1, instead of returning unusable weights.
efa_scores() and FACTOR_SCORES() now reject scoring data with a constant, infinite, or too-short variable, instead of silently returning NaN scores.
Oblique rotations, including efa_procrustes(), now refuse a nearly singular transformation matrix at every step. Results can differ for near-degenerate solutions.
A gradient-projection rotation is now reported as converged only when its projected gradient meets tolerance. Convergence flags can differ from before.
efa_fit(se = "sandwich") now withholds standard errors and confidence intervals when a Heywood case occurs.
Bootstrap output now reports the number of usable replicates when fewer than requested are available.
Analytic standard-error output now includes factor and variable names in SE$Phi and vcov_unrot_loadings.
The recommended interface now consists of lowercase efa_* functions, with estimation and rotation settings defined through estimate_control() and rotate_control().
The existing uppercase functions remain available without warnings, so current scripts continue to work, but new features will generally be added only to the efa_* interface.
Estimators, rotations, retention criteria, scoring methods, and presets can now be specified without matching capitalization exactly, while results continue to store and display the standard spelling.
The efa_* functions now report an error for unused, misspelled, or misplaced arguments instead of silently ignoring them.
Estimation and rotation options such as convergence settings, iteration limits, and rotation parameters should now be supplied through estimate_control() or rotate_control().
Factor-retention functions always fit unrotated models and therefore reject rotation settings that would not affect the retention result.
The estimator argument is now named estimator rather than method, and rotation arguments use the names p_type and random_starts rather than P_type and randomStarts.
efa_retain() requires the full argument name max_iter_CD, because the shorter form max_iter is no longer matched automatically.
The second-order estimation preset for efa_schmid_leiman() is now specified with estimate_control(type = ...).
Control objects are validated when they are created, allowing invalid settings to be detected before a model is fitted.
A supplied seed now controls the complete analysis, including random rotation starts, bootstrap samples, group-specific fits, and model averaging where applicable.
efa_average() now has a seed argument and produces the same result regardless of the number of parallel workers.
Seeded results from efa_parallel() and efa_hull() no longer depend on the parallel-processing setup.
Because random-number handling and several calculations were corrected, the same seed may produce different results than in earlier versions for efa_parallel(), efa_hull(), efa_average(), efa_group(), and some efa_simulate() settings.
efa_fit(se = "information") now returns NA with an efa_se_unreliable warning when standard errors for unrotated loadings cannot be estimated reliably, such as for Heywood cases or poorly distinguished factors.
Information-based standard errors are now calculated for correlation matrices rather than covariance matrices, improving their accuracy and agreement with sandwich and bootstrap estimates.
Information-based standard errors are now supported only with Pearson correlations and FIML; use se = "sandwich" for polychoric, tetrachoric, or rank correlations.
FIML-based analytic standard errors now use the same small-sample scaling as the package’s other standard errors and fit statistics.
efa_mi() no longer reports AIC, BIC, or ECVI when these measures are not meaningful for the underlying fitted models.
The pooled unrotated solution from efa_mi() no longer depends on the order of the imputed datasets.
Pooled unrotated loadings from efa_mi() now use the same orientation conventions as a regular efa_fit() result, making the two easier to compare.
These changes can affect unrotated loadings, their standard errors, and statistics derived from them, but rotated solutions and their fit indices remain unchanged.
Consensus results from efa_group() no longer depend on the order in which groups are supplied, so congruences, loading differences, salience classifications, and invariance conclusions are now stable across group orderings.
Fixed a regression in the revised MAP criterion, which could previously suggest an incorrect number of factors.
efa_smt() now rejects polychoric and tetrachoric correlations because its tests and fit measures are not valid for these inputs.
Rotated factors are now labelled F1, F2, and so forth according to the variance they explain, rather than inheriting labels that could change with the random seed.
Rotation diagnostics now distinguish between the total number of starting solutions and the number that were fully optimized; code using n_starts should use n_starts_total or n_optimized instead.
Criterion-based rotations now respect the specified maxit limit during every stage of the optimization.
efa_average() no longer prints NaN% when no models converge and now clearly states which fitted models are included in each reported rate.
Named presets such as "SPSS", "psych", and "EFAtools" now use their intended iteration limits, so models that exceed those limits are marked as non-converged and excluded from the average.
Cudeck–Browne model-error simulations are now reproducible across computing platforms and numerical libraries, although a given seed will produce different populations than in earlier versions.
With marginals = "VM", categorized data now reproduce the requested category proportions more accurately, with a warning when the requested thresholds cannot be transformed safely.
The documentation now clarifies that match = "thresholds" and match = "polychoric" produce the same data for normal marginals, and the selected value is now stored in the result settings.
The documentation now explains the missing-data mechanism produced by missing = "MAR" and the residual bias it may create for analyses based only on the observed data.
The documentation now explains that model_error = "WB" generally produces a larger RMSEA for the generating model than the requested target.
efa_screen() now handles collinear or nearly collinear variables without failing during its outlier checks.
Polychoric covariance calculations now handle sparse or nearly perfectly associated response tables more consistently, allowing DWLS analyses to continue while warning about problematic variable pairs.
Covariance matrices supplied where raw data or a correlation matrix is expected are now rejected with a message suggesting cov2cor().
Polychoric and tetrachoric correlations now require ordered factors or numeric response codes, preventing character or unordered-factor levels from being interpreted in alphabetical order.
efa_power() now clearly documents that N is the total sample size across groups and returns the corresponding sample size per group in N_per_group.
Simulation-based power summaries now distinguish structure-recovery rates from the underlying Tucker congruence values.
The documentation for FACTOR_SCORES() now correctly describes R2 as squared factor-score determinacy.
efa_group() performs multi-group EFA.efa_power() to conduct power analysis (analytic power based on RMSEA and simulation based power analysis).efa_reliability() to calculate various reliability indices.efa_screen() to screen data for multivariate normality and suitability for factor analysis.efa_simulate() to simulate data with various distributions and missing data mechanisms.EFA():
cor_method = "fiml" for raw data with missing values.se = "sandwich" and se = "information".boot.SE, boot.CI, and boot.arrays on
EFA objects to SE, CI, and replicates, as the old names are no longer
accurate given the additional SE implementations.GPArotation package. The rotated solutions
are numerically equivalent but implemented in C++ for speed to allow high
numbers of random starts.EFA() (randomStarts)
has been raised from 10 to 100, making local minima less likely for the
rotation criteria that are prone to them.seed argument and now fits the non-parametric bootstrap replicates
(se = "np-boot") in parallel across replicates via the future framework.summary() method that prints the full, detailed solution
(loadings with communalities and uniquenesses, explained variances, fit
indices, and residuals); print() now gives a more compact overview.EFA_AVERAGE() gains cor_method = "fiml" (passed to EFA()).EFA_POOLED():
boot.MI to MI.target_method = "first_target", which aligns every imputation
to the first imputation's rotated solution with a single Procrustes rotation.
For orthogonal rotations this reaches the same pooled estimate as "consensus"
with substantially less work. "consensus" (Generalized Procrustes Analysis of
the imputation-specific rotated loadings) is still available as an opt-in for
orthogonal rotations, and now raises a classed condition
(efa_consensus_oblique_unsupported) when combined with an oblique rotation,
because the iterated-oblique-Procrustes centroid can drift to degenerate targets.cor_method now accepts "poly" and "tetra" to compute polychoric and tetrachoric
correlations from raw ordinal (respectively binary) data, using a two-step estimator with
no empty-cell continuity correction. Supported by EFA() (including its non-parametric
bootstrap), EFA_AVERAGE(), the suitability tests KMO() and BARTLETT(), and the
retention criteria EKC(), KGC(), MAP(), SCREE(), SMT(), and N_FACTORS(). The
criteria that compare the data against simulated continuous reference data (CD(),
PARALLEL(), NEST(), and HULL()) do not support "poly" / "tetra" and signal an
error.CD(), EKC(), HULL(), KGC(), MAP(), NEST(),
PARALLEL(), SCREE(), and SMT() now return objects of a common efa_retention class
with shared print() and plot() methods.COMPARE() objects now have a plot() method that returns a ggplot object. The plot is
no longer drawn as a side effect of print().print, summary, and format methods) is now produced with the cli
package, and the messages, warnings, and errors emitted across the package are now S3-classed
conditions, which makes them easier to handle programmatically.EFA(), SL(), and EFA_AVERAGE() now accept method = "MINRES" as a synonym for
method = "ULS". Minimum residual and unweighted least squares are two names for the same
estimator and return identical results.EFA() and EFA_POOLED(): The comparative fit index (CFI) now floors the model and
baseline noncentralities at zero before taking their ratio (Bentler, 1990), so it is no
longer deflated toward zero when the baseline (independence) model fits comparatively well.
The value is unchanged for well-fitting models and now matches lavaan. CFI can change for
solutions in which the baseline model does not misfit much.EFA_POOLED(): Corrected and extended the pooled chi-square-based model-fit indices. The D2
average relative increase in variance is now centred on the mean of the square-root
statistics (Li, Meng, Raghunathan & Rubin, 1991), removing a one-sided inflation of the
pooled chi-square, RMSEA, and CFI. Bootstrap/MI confidence intervals for
the pooled fit indices are now the Rubin-Wald multiple-imputation summaries; a miscalibrated
bootstrap-percentile interval (obtained by re-running the pooling algorithm over matched
replicate indices) was removed, as was a mislabeled pooled Fm.EFA_AVERAGE(): When every averaged solution fails (all runs error, fail to converge, or
are Heywood cases), the function now returns an empty (NA) averaged result instead of
erroring or averaging an empty set.EFA() with method = "PAF": the reported number of iterations (iter) is now the
number of iterations actually performed; it was previously one too high. The loadings,
communalities, and convergence status are unchanged.ULS extraction: the minimised objective is now the sum of squared off-diagonal residuals,
consistent with its analytic gradient and the reported minimum (Fm). The diagonal
residuals were previously included in the objective but not in its gradient. The fitted
loadings are unchanged to within optimiser tolerance.NEST() and PARALLEL(): A failed eigendecomposition of a degenerate simulated matrix
now raises a clear error instead of resulting in undefined behaviour.ML and ULS
extraction. For method = "ML" this matches stats::factanal() and psych::fa(); the
small-sample Bartlett correction was previously omitted. For method = "ULS" it is now
a proper chi-square-distributed statistic matching psych::fa(fm = "minres");
previously the least-squares residual sum of squares was multiplied by N - 1 and read
as if it were Wishart-distributed, which produced an invalid p-value. The independence
(baseline) model used for the CFI is rescaled onto the same discrepancy scale.
Consequently the p-value, CFI, RMSEA (and its confidence interval), AIC, and BIC change
for ML and ULS solutions, and the number of factors suggested by SMT() and HULL()
may change for these methods.PARALLEL(): The percentile reference eigenvalues are now computed with
stats::quantile(), correcting a slight off-by-one in the previous indexing.EFA(): For oblique rotations, the factor intercorrelations (Phi), the structure
matrix, and the explained variances are now reflected and reordered consistently with
the rotated loadings. Previously, when a factor was reflected to a positive orientation
the factor intercorrelations were not sign-adjusted (so the structure matrix and
reported correlations did not match the loadings), and with order_type = "ss_factors"
the factor intercorrelations were not reordered at all.EFA(): The returned rotation matrix (rotmat) is now reflected and reordered
consistently with the rotated loadings, for both orthogonal and oblique rotations, so
that the rotated loadings can be reconstructed from the unrotated loadings and rotmat.
Previously the sign reflection was not applied to it and its factors were left in a
different order, so this reconstruction did not hold whenever a factor was reflected or
reordered.HULL(): The convex-hull elimination now tests every triplet of adjacent solutions,
including the one formed by the last interior solution. Previously this final triplet
was skipped, so a solution lying below the line connecting its neighbours could remain
on the hull. This can change the number of factors suggested by HULL() (and hence by
N_FACTORS()).NEST(): When the test accepts every eigenvalue it examines (no empirical eigenvalue
falls at or below its reference within the search range), it now retains that last
accepted model rather than the model with one fewer factor. The search range is also
bounded so that the reference model fitted at each step stays over-identified.PARALLEL(): When every real eigenvalue exceeds its reference, so the decision rule finds
no crossing, the suggested number of factors is now all retained components, reported with
a warning, instead of a silent NA (matching the convention used by EKC()).EFA(se = "np-boot"): The non-parametric bootstrap no longer repeats per-replicate
warnings (about arguments pinned alongside type, and about the iterative fit reaching its
maximum number of iterations) once per replicate. They are now suppressed during
resampling, and non-convergence across replicates is reported once as a summary.COMPARE(): With reorder = "congruence", the columns of y are now matched to those of
x by an optimal one-to-one assignment that maximizes the total Tucker congruence, rather
than by an independent per-factor best match. The greedy match could assign two factors of
x to the same column of y, duplicating that column and dropping another, which corrupted
the reported differences, factor correspondences, and root mean squared distance. The result
is unchanged whenever the greedy match was already one-to-one.EFA(): Added randomStarts argument passed to GPArotation functions, as suggested by Coen Bernaards.FACTOR_SCORES(): Added rho argument, thanks to Andreas Soteriades.EFA_POOLED(): Fixed issue that could lead to averaged Phi not being symmetric.MAP() computes the Velicer MAP criterion (both TR2 and TR4).PROCRUSTES() to perform orthogonal and oblique Procrustes / target rotation.CONSENSUS_PROCRUSTES() to perform Procrustes on a list of targets to obtain a common target.residuals.EFA() to extract and print residuals and, if computed, standardized residuals.EFA_POOLED() to run EFA on a list of multiple imputated datasets and pool the results. Thanks to Andreas Soteriades for the suggestion and first implementation.print.EFA_POOLED(), print method adapted from print.EFA()N_FACTORS() can also compute the MAP criterion.EFA():
print.EFA() now prints more information.OMEGA() that led to incorrect omega, H, and ECV values for lavaan bifactor models. Tnanks to Christopher D. King for bug report and suggested fix.EFA_AVERAGE().EFA().EKC(): Now correctly returns the number of factors based on the first time the eigenvalues drop below the references values.NEST() to perform the Next Eigenvalue Sufficiency Test (Achim, 2017).EKC(): The implementation based on Auerswald and Moshagen (2019) used in previous versions differed from the original implementation by Braeken and van Assen (2017). Now both versions are implemented and can be selected with the new type argument. Thanks to Luis Eduardo Garrido for pointing this out and to Johan Braeken for sharing sample code, based on which the original version is now implemented.N_FACTORS():
ekc_type, alpha_nest, and n_datasets_nest to account for the changes in EKC() and to control NEST().print.EFA():
EFA(): Calculate and return model implied correlation matrix..gof(): Changed some tests to take care of the ATLAS issue when using R-devel on x86_64 Fedora 34 Linux with alternative BLAS/LAPACK.OMEGA() to accommodate changes in the upcoming version of psych::schmid()print.EFA(): Added arguments cutoff, digits and max_name_length that are passed to print.LOADINGS().print.LOADINGS(): New Argument max_name_length to control the maximum length of the displayed variable names (names longer than this will be cut on the right side). Previously, this was fixed to 10 (which is now the default)..gof()) that threw an error when using R-devel on x86_64 Fedora 36 Linux with alternative BLAS/LAPACK.dontrun to examples of EFA_AVERAGE() and its print and plot methods as these were causing issues on R-oldrel which were not directly related to EFAtools and thus could not be fixed from within the package..gof(): Changed the helper function to take care of the MKL issue when using R-devel on x86_64 Fedora 34 Linux with alternative BLAS/LAPACK..is_cormat(): Changed the helper function to better detect wheter a matrix is a correlation matrix.PARALLEL(): Added a check, testing whether N > n_vars and throw an error if this is not the case.EFA(): Changed error to warning when model is underidentified. This allows the Schmid-Leiman transformation to be performed on a two-factor solution.OMEGA(): Added calculation of additional indices of interpretive relevance (H index, explained common variance [ECV], and percent of uncontaminated correlations [PUC]). This is optional and can be avoided by setting add_ind = FALSE.CD(): Added na.omit() to remove missing values from raw data to avoid an error in the comparison-data procedure.EFA():
type = "SPSS" was used with method = "ML" or method = "ULS", or with a rotation other than none, varimax or promax.type = "SPSS" is used.type = "psych" is used, by calling psych::smc().varimax_type = "kaiser" if type = "EFAtools" is used with varimax or promax.EFA_AVERAGE():
future.seed = TRUE to call to future.apply::future_lapply() to prevent warnings.print.EFA(): Fixed test for Heywood cases from testing whether a communality or loading is greater than .998, to only test whether communalities exceed 1 + .Machine$double.epsOMEGA(): Small bugfix when lavaan second-order model is given as inputEFA_AVERAGE() to readme and the EFAtools vignetteOMEGA functionEFA_AVERAGE() and respective print and plot methods, to allow running many EFAs across different implementations to obtain an average solution and test the stability of the results.EFA(): Defaults that were previously set to NULL are now mostly set to NA. This was necessary for EFA_AVERAGE() to work correctly.PARALLEL(): Rewrote the generation of random data based eigenvalues to be more stable when SMCs are used.OMEGA(): Changed expected input for argument factor_corres from vector to matrix. Can now be a logical matrix or a numeric matrix with 0's and 1's of the same dimensions as the matrix of group factor loadings. This is more flexible and allows for cross-loadings.psych and SPSS EFA solutions with EFAtools.FACTOR_SCORES() to calculate factor scores from a solution from EFA(). This is just a wrapper for the psych::factor.scores function.SCREE() that does a scree plot. Also added respective print and plot
methods.CD(): Added check for whether entered data is a tibble, and if so, convert to vanilla data.frame to avoid breaking the procedure.EFA():
varimax_type = "svd"), with type SPSS, the reproduced SPSS varimax implementation is used (varimax_type = "kaiser").kaiser argument (controls if a Kaiser normalization is done or not) into normalize to avoid confusion with the varimax_type argument specifications.ML(): Changed default start method to "psych".N_FACTORS():
criteria argument.OMEGA(): Now also works with a lavaan second-order solution as input. In this case, it does a Schmid-Leiman transformation based on the first- and second-order loadings first and computes omegas based on this Schmid-Leiman solution.SL(): Now also works with a lavaan second-order solution as input (first- and second-order loadings taken directly from lavaan output)..get_compare_matrix(): Fixed a bug that occurred when names of data were longer than n_charCOMPARE(): Fixed a bug that occurred when using reorder = "names".EFA(): RMSEA is now set to 1 if it is > 1.HULL(): Fixed a bug that occurred when no factors are located on the HULLKMO(): Fixed a bug that the inverse of the correlation matrix was not taken anew after smoothing was necessary.PARALLEL():
decision_rule = "percentile"print.COMPARE(): Fixed a bug that occurred when using print_diff = FALSE in COMPARE().print.KMO(): Fixed a bug that printed two statements instead of one, when the KMO value was < .6.OMEGA() and SL(): Added an error message if the entered term in g_name is invalid (i.e., it cannnot be found among the factor names of the entered lavaan solution).PARALLEL() if no solution has been found after 25 tries.Updated different tests
Deleted no longer used packages from Imports and Suggests in DESCRIPTION
PARALLEL(): fixed a bug in indexing if method "EFA" was used.
NEWS.md file to track changes to the package.