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.
efa_* InterfaceThe 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.