efa_procrustes() aligns one loading matrix to a target loading matrix with the
same dimensions. It is used internally by efa_mi(), but can also be used
directly when factor columns must be brought into a common orientation before
averaging or comparing solutions.
efa_procrustes(
A,
Target,
rotation = c("orthogonal", "oblique"),
S = NULL,
T_init = NULL,
oblique_eps = 1e-05,
oblique_maxit = 1000,
oblique_max_line_search = 10,
oblique_step0 = 1,
oblique_normalize = FALSE,
oblique_random_starts = 0,
oblique_screen_keep = 2,
oblique_triage_maxit = 25,
oblique_triage_improve_tol = 0
)A list. Every path returns the following components:
Aligned loading matrix.
Transformation matrix.
Factor intercorrelation matrix; the identity for orthogonal and one-factor alignment.
Target criterion at the returned solution.
Logical; TRUE for the closed-form orthogonal solution.
Logical; whether the transformation defines an admissible Phi.
Number of solver iterations; 0 for the closed-form
orthogonal solution.
Condition number of T; a constant 1 on the orthogonal path.
Iteration history with columns iter, f, log10_s, and step;
a single placeholder row on the orthogonal path.
"orthogonal_procrustes", "oblique_procrustes", or
"single_factor_procrustes" for a one-factor oblique request.
Logical line-search diagnostic.
Multi-start summary of the starts that were fully optimized; each has a single entry when no random starts were used.
The oblique solver additionally returns screen_start_indices and
screen_values (the starts kept by cheap objective screening and their
criterion values) together with the counts n_random_starts, n_screened,
n_triaged, and n_fully_optimized. These six components are absent for
rotation = "orthogonal" and for one-factor models, which are aligned with the
orthogonal solution.
Row and column names are preserved where possible. When
oblique_normalize = TRUE the returned loadings are back-transformed to the
original scale, but value is the criterion on the Kaiser-normalized loadings,
so it is not 0.5 * sum((loadings - Target)^2).
Numeric loading matrix to be aligned.
Numeric target matrix with the same dimensions as A.
Character string, either "orthogonal" or "oblique".
Optional k x k cross-product matrix crossprod(A), kept for
compatibility. It enters both the oblique criterion and its gradient, so any
other matrix would minimize a different criterion: where S is used it is
checked against crossprod(A) and must agree with it up to a relative
tolerance of 1e-8. That check forms crossprod(A) itself, so passing S
no longer avoids any work: omitting it gives the same result for slightly
less. S is used, and therefore checked, only on the oblique path with more
than one factor and oblique_normalize = FALSE; if Kaiser normalization is
requested, the cross-product must be recomputed on the normalized matrix and
S is ignored.
Optional k x k starting transformation matrix for the oblique
solver. Its columns are normalized internally, and the normalized matrix must
be well enough conditioned to define a proper factor correlation matrix: its
smallest singular value must be at least 1e-4, the same floor the solver
applies to every candidate it evaluates. If NULL (the default), the oblique
solver is warm-started from the closed-form orthogonal Procrustes solution.
Positive convergence tolerance for the projected-gradient norm in the oblique solver.
Non-negative integer. Maximum number of projected-gradient updates in the full oblique solver.
Non-negative integer. Maximum number of step-halving attempts after the initial line-search step.
Positive initial step size for the oblique solver.
Logical; if TRUE, apply Kaiser row normalization to
the loadings (only) in the oblique solver and back-transform the aligned
loadings afterwards, leaving Target unnormalized (as in
GPArotation::targetQ(normalize = TRUE)).
Non-negative integer. Number of additional random starts used by the oblique solver.
Non-negative integer. Number of random starts retained after cheap objective screening and sent to triage optimization.
Non-negative integer. Number of short optimization iterations used in the triage stage.
Non-negative scalar. Relative improvement required for a triaged start to be promoted to full optimization.
For rotation = "orthogonal", the function solves the closed-form orthogonal
Procrustes problem
$$\min_T \frac{1}{2}\|A T - B\|_F^2 \quad \textrm{subject to}\quad T'T = I,$$
where A is the loading matrix and B is Target.
For rotation = "oblique", the function calls the compiled
.oblique_procrustes() optimizer. The oblique convention is the same as in
GPArotation::targetQ():
$$L = A T^{-T}, \qquad \Phi = T'T, \qquad diag(\Phi) = 1.$$
By default the oblique solver is warm-started from the closed-form orthogonal
Procrustes solution, which resolves the factor permutation and sign
indeterminacy and avoids the poor local minima an identity start can fall
into. Supply T_init to override this start. Random starts are only used for
oblique alignment. For one-factor models, oblique and orthogonal alignment are
equivalent, so the function uses the stable one-factor orthogonal solution
instead of calling the oblique optimizer.
Other factor rotation:
efa_schmid_leiman()
## Align an estimated loading matrix to a known target pattern: fit an
## unrotated three-factor model, then rotate its loadings toward the true
## population pattern.
efa_mod <- efa_fit(test_models$baseline$cormat, N = 500, n_factors = 3,
estimator = "PAF", rotation = "none")
target <- population_models$loadings$baseline
## Orthogonal target rotation (rigid rotation/reflection):
efa_procrustes(efa_mod$unrot_loadings, target, rotation = "orthogonal")
## Oblique target rotation (lets the aligned factors correlate):
efa_procrustes(efa_mod$unrot_loadings, target, rotation = "oblique")
Run the code above in your browser using DataLab