Implements the Generalised Least-Squares Matrix Decomposition of Allen, Grosenick & Taylor (2014) for data observed in a row inner-product space M and a column inner-product space A. Setting M = I_n, A = I_p recovers ordinary PCA.
Usage
genpca(
X,
A = NULL,
M = NULL,
ncomp = NULL,
method = c("eigen", "auto", "spectra", "randomized", "deflation"),
constraints_remedy = c("error", "ridge", "clip", "identity"),
preproc = multivarious::pass(),
threshold = 1e-06,
maxit_deflation = 500L,
use_cpp = TRUE,
maxeig = 5000,
warn_approx = TRUE,
maxit_spectra = 1000,
tol_spectra = 1e-09,
rank_rtol = 1e-06,
oversample = 20L,
n_power = 1L,
n_polish = 0L,
jitter_metric = 1e-10,
seed_randomized = 1234L,
tol_polish_randomized = 1e-04,
verbose = FALSE
)Arguments
- X
Numeric matrix n x p.
- A
Column constraint: vector (implies diagonal), dense matrix, or sparse symmetric p x p PSD matrix. If
NULL, defaults to identity.- M
Row constraint: vector (implies diagonal), dense matrix, or sparse symmetric n x n PSD matrix. If
NULL, defaults to identity.- ncomp
Number of components to extract. Defaults to
min(dim(X)). Must be positive.- method
Character string specifying the computation method. One of
"eigen"(default, usesgmdLA),"auto"(heuristic choice among"eigen","spectra", and"randomized"),"spectra"(iterative partial SVD of the metric-whitened data via eigencore,gmd_spectra),"randomized"(approximate randomized block solvergmd_randomized), or"deflation"(usesgmd_deflationRorgmd_deflation_cpp).- constraints_remedy
Character string specifying what to do with a supplied
AorMthat is not positive semi-definite (within a relative tolerance ofsqrt(.Machine$double.eps)). Default"error": reject the input. The alternatives repair it and emit a warning of classgenpca_metric_repairedwhosereportfield (seerepair_metric) records the minimum eigenvalue before and after, the shift applied, the rank and the condition number:"ridge"(Gershgorin diagonal shift: add the smallest diagonal loading that restores positive definiteness, falling back toMatrix::nearPD()for small dense matrices),"clip"(spectral clip to the PSD cone by zeroing negative eigenvalues; this densifies the matrix and refuses sparse input larger than 2000 rows/cols, where"ridge"should be used instead), or"identity"(replace the matrix with the identity). An asymmetric metric is an error under every setting. Singular PSD metrics are valid input and are never repaired.- preproc
Pre-processing transformer object from the multivarious package (default
multivarious::pass()). Usemultivarious::center()for centered GPCA. See?multivarious::prepfor options.- threshold
Convergence tolerance for the
"deflation"method's inner loop. Default1e-6. Cutoffs are relative to the scale of the problem (the norm/singular-value floors scale with \(\sqrt{\mathrm{tr}(X'MXA)}\)), so results are invariant to rescalingX. The convergence check is on a squared step difference, so the resulting singular-vector accuracy scales like \(\sqrt{\code{threshold}}\), notthresholditself.- maxit_deflation
Maximum iterations per component for the
"deflation"method. Default500.- use_cpp
Logical. If
TRUE(default) and package was compiled with C++ support, use faster C++ implementation formethod = "deflation". Fallback to R otherwise. (Ignored formethod = "eigen"andmethod = "spectra").- maxeig
For
method = "eigen"andmethod = "spectra": a positive definite general metric is factored exactly by Cholesky at any size, but a singular general metric (e.g. a graph Laplacian) needs a dense eigendecomposition of the metric, which is refused when the metric has more thanmaxeigrows. The error names the alternatives (method = "deflation", which only multiplies by the metric, or raisingmaxeig);method = "auto"routes such cases to deflation. Results are never approximated. Default5000.- warn_approx
Deprecated and ignored:
method = "eigen"no longer approximates anything.- maxit_spectra
Retained for compatibility and currently unused: the eigencore partial SVD used by
method = "spectra"is controlled bytol_spectraalone.- tol_spectra
Convergence tolerance of the iterative solver when
method = "spectra". Default1e-9. This governs iteration only; rank decisions userank_rtol.- rank_rtol
Relative cutoff for component acceptance, on the scale of the singular values: component \(j\) is dropped when
d_j <= rank_rtol * d_1. Applied by every method (for the eigen paths on \(d_j^2\)), so the number of components returned does not change whenXis rescaled. Default1e-6. Metric validation uses a separate relative tolerance,sqrt(.Machine$double.eps), for positive semi-definiteness and null-space detection for general metric eigendecompositions. Every strictly positive diagonal weight is retained in both the forward and inverse factors.- oversample
Oversampling for
method = "randomized"(sketch size =ncomp + oversample). Default20.- n_power
Number of power iterations for
method = "randomized". Default1.- n_polish
Number of optional block-polish iterations for
method = "randomized". Default0.- jitter_metric
Relative Gram jitter for the candidate Cholesky preconditioner in
method = "randomized". The basis is checked in the original metric; a failed check uses rank-revealing orthonormalization instead. Default1e-10.- seed_randomized
Optional seed for
method = "randomized". Default1234. This fully determines the randomized backend's random stream: the C++ kernel seeds its own generator from this value rather than from R'sset.seed()/.Random.seed, and callinggenpca()withmethod = "randomized"does not alter the caller's.Random.seed. To reproduce a randomized fit, fixseed_randomized, not the R seed.- tol_polish_randomized
Relative tolerance used for early stopping of polish iterations in
method = "randomized". Set0to disable early stop. Default1e-4.- verbose
Logical. If
TRUE, print progress messages. DefaultFALSE.
Value
An object of class c("genpca", "bi_projector") inheriting from multivarious::bi_projector,
with slots including:
- u,v
Left/right singular vectors scaled by the constraint metrics (MU, AV). These correspond to components in the original space's geometry. Use
components(fit).- ou,ov
Orthonormal singular vectors in the constraint metric (U, V such that UT M U = I, VT AV = I). These are the core mathematical factors.
- sdev
Generalised singular values d_k. Note these are singular values of the metric-whitened data matrix, not standard deviations: with identity metrics and centering,
sdev = prcomp(X)$sdev * sqrt(nrow(X) - 1).- s
Scores: the generalised principal components
z_k = X A ov_k = ou_k d_k(Allen et al. 2014, Section 2.4). Identical toproject(fit, X)on the training data. Usescores(fit).- preproc
The
multivariouspre-processing object used.- A, M
The constraint matrices used (potentially after coercion to sparse format).
- propv
Proportion of generalized variance explained by each component.
- cumv
Cumulative proportion of generalized variance explained.
Method
We compute the rank-ncomp factors UDVT that minimise
$$ \|X - UDV^\top\|_{M,A}^2
= \mathrm{tr}\!\bigl(M\, (X-UDV^\top)\,A\,(X-UDV^\top)^\top\bigr) $$
subject to UT M U = I, VT AV = I. (Allen et al., 2014).
Five methods are available via the method argument:
"eigen"(Default): Uses a one-shot eigen decomposition strategy based ongmdLA. It explicitly forms and decomposes a \(p \times p\) or \(n \times n\) matrix (depending onnvsp)."auto": Chooses among"eigen","spectra", and"randomized"using heuristics on shape, rank ratio (ncomp / min(n,p)), and constraint structure."spectra": Computes the top-k singular triplets of the metric-whitened data \(F_M' X F_A\) (with \(M = F_M F_M'\), \(A = F_A F_A'\)) as an implicit operator via the eigencore package, without forming the large intermediate matrix. Generally faster and uses less memory for largenorpwhen few components are requested."randomized": Uses a randomized block range finder and small projected eigendecomposition. This is an approximate low-pass method that is often much faster for wide dense matrices with sparse metrics when only top components are needed."deflation": Uses an iterative power/deflation algorithm. Can be slower but potentially uses less memory than"eigen"for very large dense problems wherencompis small.
Backend Guidance
The default is method = "eigen"; "auto" is opt-in, not the
default.
Use
"eigen"(the default) when you need a stable reference solution on small/medium problems.Use
"auto"to let a heuristic pick among"eigen","spectra", and"randomized"based on problem shape and constraint structure.Use
"spectra"for larger matrix-free iterative solves where memory pressure is a concern.Use
"randomized"for wide low-rank settings (p >> n) with sparse metrics when throughput matters most.Use
"deflation"when you only need a few components and can tolerate iterative convergence behavior.
For pre-computed covariance matrices C = X'MX, see genpca_cov which
performs GPCA directly on C with column constraint R (equivalent to A).
References
Allen, G. I., Grosenick, L., & Taylor, J. (2014). A Generalized Least-Squares Matrix Decomposition. Journal of the American Statistical Association, 109(505), 145-159. arXiv:1102.3074.
See also
genpca_cov for GPCA on pre-computed covariance matrices,
truncate.genpca, reconstruct.genpca,
multivarious::bi_projector, multivarious::project, multivarious::scores,
multivarious::components, multivarious::reconstruct.
Examples
if (requireNamespace("multivarious", quietly = TRUE)) {
set.seed(123)
X <- matrix(stats::rnorm(200 * 100), 200, 100)
rownames(X) <- paste0("R", 1:200)
colnames(X) <- paste0("C", 1:100)
# Standard PCA (A=I, M=I, centered) - using default method="eigen"
gpca_std_eigen <- genpca(X, ncomp = 5, preproc = multivarious::center(), verbose = FALSE)
# Standard PCA using Spectra method (requires C++ build)
# gpca_std_spectra <- try(genpca(X, ncomp = 5,
# preproc = multivarious::center(),
# method = "spectra", verbose = TRUE))
# if (!inherits(gpca_std_spectra, "try-error")) {
# print(head(gpca_std_spectra$sdev))
# }
# Compare singular values with prcomp
pr_std <- stats::prcomp(X, center = TRUE, scale. = FALSE)
print("Eigen Method Sdev:")
print(head(gpca_std_eigen$sdev))
print("prcomp Sdev:")
print(head(pr_std$sdev))
print(paste("Total Var Explained (Eigen):",
round(sum(gpca_std_eigen$propv) * 100), "%"))
# Weighted column PCA (diagonal A, no centering)
col_weights <- stats::runif(100, 0.5, 1.5)
gpca_weighted <- genpca(X, A = col_weights, ncomp = 3,
preproc = multivarious::pass(), verbose = FALSE)
print("Weighted GPCA Sdev:")
print(gpca_weighted$sdev)
print(head(components(gpca_weighted)))
}
#> [1] "Eigen Method Sdev:"
#> [1] 23.38656 23.16950 23.00393 22.56341 22.20862
#> [1] "prcomp Sdev:"
#> [1] 1.657829 1.642442 1.630705 1.599478 1.574327 1.543299
#> [1] "Total Var Explained (Eigen): 13 %"
#> [1] "Weighted GPCA Sdev:"
#> [1] 24.79929 24.23576 23.57776
#> PC1 PC2 PC3
#> C1 0.02654255 -0.04444090 6.316309e-02
#> C2 -0.38534643 0.13459026 2.188201e-05
#> C3 0.06053140 -0.07895713 9.358779e-02
#> C4 -0.04387758 0.01422663 -3.160916e-03
#> C5 -0.06087768 -0.01772540 7.120077e-02
#> C6 0.00294182 0.08799976 1.743206e-01