
Compare two loading matrices after Procrustes alignment
Source:R/rotate-loadings.R
compare_loadings.RdWhen two latent-variable fits use the same traits, the loading matrices are
only identified up to rotation and sign. Procrustes alignment finds the
orthogonal transform that brings one matrix as close as possible to the
other, then reports the residual disagreement. This is mainly a validation
helper for comparing gllvmTMB() with another implementation.
Value
A list with the optimal rotation R, the rotated Lambda_a_rot
(rows in the order of Lambda_b when rows were matched by name),
the Frobenius distance after alignment (frobenius), and the
correlation between each column of Lambda_a_rot and the matching
column of Lambda_b (cor_per_factor).
Details
When both matrices have row names, rows are matched by trait name:
Lambda_a is reordered to the row order of Lambda_b, and the function
stops if the two row-name sets differ (or a name is repeated). If either
matrix has no row names, rows are matched by position, so both must list
the traits in the same order; a different order then shows up as a larger
distance rather than as better or worse agreement of the loadings.
Examples
set.seed(1)
# Simulate six traits, each measured twice on 100 units, from known
# loadings: traits 2-3 share one axis, traits 4-6 a second, and
# trait_1 loads on both.
Lambda_true <- cbind(c(0.7, 1.0, 0.9, 0.0, 0.0, 0.0),
c(0.7, 0.0, 0.0, 1.0, 0.9, 0.8))
sim <- simulate_unit_trait(n_units = 100, n_obs_per_unit = 2,
n_traits = 6, Lambda_B = Lambda_true,
psi_B = 0.4, sigma2_eps = 0.3)
fit <- gllvmTMB(value ~ 0 + trait +
latent(0 + trait | unit, d = 2, unique = TRUE),
data = sim$data, trait = "trait", unit = "unit")
L_hat <- extract_ordination(fit, "unit")$loadings
# The fitted axes may be rotated or sign-flipped relative to the true
# ones, so compare_loadings() first rotates the fitted loadings to
# match the truth as closely as possible, then measures the gap.
cmp <- compare_loadings(L_hat, Lambda_true)
# Fitted loadings after alignment; compare them with Lambda_true.
round(cmp$Lambda_a_rot, 2)
#> [,1] [,2]
#> trait_1 0.68 0.57
#> trait_2 0.94 0.01
#> trait_3 1.01 0.09
#> trait_4 -0.07 0.97
#> trait_5 0.01 0.84
#> trait_6 0.09 0.88
# Overall distance after alignment, and per-axis correlation.
round(cmp$frobenius, 3)
#> [1] 0.255
round(cmp$cor_per_factor, 3)
#> [1] 0.988 0.984
# Raw and varimax-rotated loadings from one fit differ only by a
# rotation, so once aligned they match exactly (distance 0).
L_rot <- rotate_loadings(fit, level = "unit")$Lambda
round(compare_loadings(L_rot, L_hat)$frobenius, 6)
#> [1] 0