efa_procrustes: Rotate a loading matrix to a target using Procrustes...

View source: R/efa_procrustes.R

efa_procrustesR Documentation

Rotate a loading matrix to a target using Procrustes alignment

Description

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.

Usage

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
)

Arguments

A

Numeric loading matrix to be aligned.

Target

Numeric target matrix with the same dimensions as A.

rotation

Character string, either "orthogonal" or "oblique".

S

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.

T_init

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.

oblique_eps

Positive convergence tolerance for the projected-gradient norm in the oblique solver.

oblique_maxit

Non-negative integer. Maximum number of projected-gradient updates in the full oblique solver.

oblique_max_line_search

Non-negative integer. Maximum number of step-halving attempts after the initial line-search step.

oblique_step0

Positive initial step size for the oblique solver.

oblique_normalize

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)).

oblique_random_starts

Non-negative integer. Number of additional random starts used by the oblique solver.

oblique_screen_keep

Non-negative integer. Number of random starts retained after cheap objective screening and sent to triage optimization.

oblique_triage_maxit

Non-negative integer. Number of short optimization iterations used in the triage stage.

oblique_triage_improve_tol

Non-negative scalar. Relative improvement required for a triaged start to be promoted to full optimization.

Details

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.

Value

A list. Every path returns the following components:

loadings

Aligned loading matrix.

T

Transformation matrix.

Phi

Factor intercorrelation matrix; the identity for orthogonal and one-factor alignment.

value

Target criterion at the returned solution.

convergence

Logical; TRUE for the closed-form orthogonal solution.

valid

Logical; whether the transformation defines an admissible Phi.

iterations

Number of solver iterations; 0 for the closed-form orthogonal solution.

kappa_T

Condition number of T; a constant 1 on the orthogonal path.

Table

Iteration history with columns iter, f, log10_s, and step; a single placeholder row on the orthogonal path.

method

"orthogonal_procrustes", "oblique_procrustes", or "single_factor_procrustes" for a one-factor oblique request.

line_search_failed

Logical line-search diagnostic.

best_start_index, all_start_indices, all_values, all_converged, all_iterations

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).

See Also

Other factor rotation: efa_schmid_leiman()

Examples

## 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")


EFAtools documentation built on Aug. 21, 2026, 5:16 p.m.