Mathematical Derivation: SVD Whitening Implementation

2 minute read

Published:

This document explains the SVD Whitening method in SVD.R. While mathematically equivalent to PCA whitening for Symmetric Positive Definite (SPD) matrices, this method is framed in SVD notation; in eegwhiten it shares the same eigensolver as PCA.R.

1. Core Concept

The Singular Value Decomposition (SVD) generalizes eigendecomposition. For a covariance matrix (which is symmetric), the Singular Values are identical to the Eigenvalues, and Left/Right Singular Vectors are identical to Eigenvectors.

\(\operatorname{Cov}(\mathbf z)\) of the points shown
1.000.000.001.00

Each dot is a sample, coloured by its position in the raw cloud so you can follow it. For a covariance matrix the SVD and the eigendecomposition coincide, so this is the same rotation-then-scaling path as PCA whitening; the output is expressed in the singular-vector frame (note the turned colour wheel).

2. Mathematical Formulation

Step 1: Decomposition

Mathematically, we want the SVD of the covariance matrix $\Sigma$:

\[\Sigma = U D V^\top\]

Since $\Sigma$ is symmetric and positive definite:

  • $U = V$ (Left and Right singular vectors are the same).
  • $D$ contains positive singular values (identical to eigenvalues $\lambda$).

Because the two decompositions coincide, SVD.R does not call svd(). It obtains $U$ and $D$ from the package’s shared SPD eigensolver .eigen_spd(), the same one used by PCA.R:

  • By default this is base R’s eigen(Sigma, symmetric = TRUE).
  • When only the leading n_comp components are requested (fewer than ncol(Sigma)) and the RSpectra package is available, it switches to the iterative RSpectra::eigs_sym() (selectable via eig_method).
  • The singular values are returned in decreasing order, and the decomposition is truncated to the first n_comp components if requested.

Finally, the sign of each singular vector is fixed so that results are reproducible across runs: its largest-magnitude entry is made positive, or it is aligned with sign_ref when one is supplied.

R Code Correspondence:

dec <- .eigen_spd(Sigma, k = k, method = eig_method, fast = fast, precomp = precomp)
U <- dec$vectors   # singular vectors (= eigenvectors), one column per component
D <- dec$values    # singular values (= eigenvalues), decreasing

if (any(D <= 0)) stop("Sigma must be positive-definite.")

U <- .fix_component_sign(U, sign_ref = sign_ref)

Step 2: Constructing the Whitening Matrix

With $U$ and $D$ in hand, the whitening matrix takes the same form as in PCA whitening:

\[W_{\text{SVD}} = D^{-1/2} U^\top\]

so that $Z = X W^\top$ satisfies $\text{Cov}(Z) = D^{-1/2} U^\top \Sigma \, U D^{-1/2} = D^{-1/2} D \, D^{-1/2} = I$.

R Code Correspondence:

# W = D^-1/2 * U^T
W <- sweep(t(U), 1L, 1 / sqrt(D), "*")