Linear Algebra for Quantitative Finance › Matrix Decompositions
Quantitative FinanceAll chapters Linear Algebra for Quantitative Finance › Matrix DecompositionsOne section · Sections in this chapter5 / 9 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
Matrix decompositions break a matrix into simpler components, exposing structure that's hidden in the raw numbers. In quantitative finance, decompositions help us study risk directions, enable dimensionality reduction through truncation, and improve numerical stability.
Think of decomposition as a kind of mathematical X-ray. A covariance matrix appears as a dense array of numbers, but its eigendecomposition reveals modes of variation, the basic ways in which assets tend to move together. A return matrix might contain hundreds of thousands of numbers, but its singular value decomposition exposes dominant patterns in the observed variation. These mathematical components can support financial interpretation after their loadings, scores, and stability have been examined; the decomposition itself does not assign economic meaning.
Eigenvalue Decomposition
A diagonalizable square matrix has a complete eigenvector basis and can be expressed as:
where:
- : an matrix whose columns are the eigenvectors
- : a diagonal matrix with eigenvalues on the diagonal
- : the inverse of the eigenvector matrix, which "undoes" the coordinate transformation
This decomposition shows that acts as: (1) changing into the eigenvector coordinate system via , (2) scaling each coordinate by the corresponding eigenvalue via , and (3) changing back via . These changes of basis are rotations or reflections only when is orthogonal, as it can be for a real symmetric covariance matrix. In that covariance setting, the decomposition separates uncorrelated directions of variation.
The power of this decomposition lies in the simplicity of the diagonal matrix . A diagonal matrix just scales each coordinate independently, with no mixing between directions. All the complexity of is absorbed into finding the right coordinate system (the eigenvectors) in which the matrix action becomes simple scaling.
An eigenvector is a nonzero vector satisfying for matrix , where is the eigenvalue. Its span is an invariant line: a nonzero preserves that line, with an orientation reversal when , while maps the eigenvector to the zero vector. A real symmetric matrix has real eigenvalues and admits an orthonormal eigenbasis. Eigenvectors associated with distinct eigenvalues are orthogonal; within a repeated eigenspace, an orthonormal basis can be chosen.
For a covariance matrix, the eigenvectors represent the principal directions of variation in the data, and the eigenvalues represent the variance along each direction. Among unit-length directions , the leading eigenvector maximizes and the maximum equals the largest eigenvalue. This describes the highest-variance direction in the asset universe; the contribution to a particular portfolio's variance also depends on that portfolio's weights.
# Eigendecomposition of the covariance matrix
eigenvalues, eigenvectors = np.linalg.eigh(sample_cov)
# Sort by eigenvalue (descending)
sort_idx = np.argsort(eigenvalues)[::-1]
eigenvalues = eigenvalues[sort_idx]
eigenvectors = eigenvectors[:, sort_idx]
# Calculate variance explained by each eigenvector
total_variance = np.sum(eigenvalues)
variance_explained = eigenvalues / total_variance
cumulative_variance = np.cumsum(variance_explained)Eigenvalues of covariance matrix: λ1 = 0.00063233 (57.0% of variance) λ2 = 0.00036008 (32.5% of variance) λ3 = 0.00011670 (10.5% of variance) Total variance: 0.00110911 First eigenvector (principal direction): [0.43878792 0.32747683 0.83679393] Interpretation: This is the direction in asset-space along which returns vary most. Its economic meaning must be checked in this sample.


Principal Component Analysis (PCA)
PCA uses eigendecomposition to transform correlated variables into uncorrelated principal components. The full projection preserves the original dimension; dimensionality reduction occurs only when we retain leading components. Whether a small number explains most variation depends on the assets, sample period, return frequency, scaling, and preprocessing.
The principal components are projections of the data onto the eigenvectors:
where:
- : the full matrix of principal component scores; retaining its first columns gives a reduced representation
- : the centered data matrix (each row is an observation, each column is a variable minus its mean)
- : the matrix of eigenvectors (principal component loadings)
Each column of represents a principal component, a new synthetic variable that captures a specific pattern of co-movement in the original data. The first principal component captures the most variance, the second captures the most remaining variance while being uncorrelated with the first, and so on. Analysts may assign economic labels such as market, sector, or style factors after inspecting the loadings and scores, but PCA does not guarantee that a component has a clean economic interpretation.
Why does this step produce uncorrelated components? Let be the covariance matrix of the centered data and let the columns of be its orthonormal eigenvectors. Then
Because is diagonal, distinct principal-component scores have zero sample covariance. Orthogonal axes alone would not guarantee this for arbitrary data; the result holds because PCA chooses axes that diagonalize . The eigenvalues give the variances along those axes, so ordering them places the main variance direction first.
# Apply PCA to return data
from sklearn.decomposition import PCA
# Center the returns
centered_returns = returns_data - returns_data.mean(axis=0)
# Fit PCA
pca = PCA()
principal_components = pca.fit_transform(centered_returns)
reduced_scores = principal_components[:, :2]
# Examine results
pc_variance = pca.explained_variance_ratio_
loadings = pca.components_ # Each row is an eigenvectorPCA Results:
--------------------------------------------------
PC1: 57.0% variance explained
PC2: 32.5% variance explained
PC3: 10.5% variance explained
Cumulative variance: [0.57012852 0.8947838 1. ]
Reduced score shape: (252, 2); variance retained: 89.5%
Factor loadings (how each PC relates to original assets):
Asset1 Asset2 Asset3
PC1: +0.4388 +0.3275 +0.8368
PC2: +0.7356 +0.4040 -0.5438
PC3: -0.5162 +0.8541 -0.0636
In this seeded sample, the first principal component has same-sign loadings and can be read as a broad co-movement direction. The later components have mixed signs and contrast assets. These interpretations are sample-dependent, and multiplying any loading vector by -1 describes the same principal axis, so loading signs are a convention rather than an economic direction by themselves.
PCA in Practice: Interest Rate Curves
PCA is often applied to yield-curve movements because rates at different maturities tend to co-move. In the synthetic example below, the first three components explain 92.3% of sample variation and their loading shapes resemble three commonly used descriptions:
- Level (PC1): A broad same-direction shift resembling a level change; loading magnitudes need not be equal across maturities.
- Slope (PC2): Steepening or flattening. Short and long rates move in opposite directions.
- Curvature (PC3): Bending. The middle of the curve moves relative to the ends.
Retaining the first three components below produces a score matrix instead of the original data matrix. This reduces the dimension, but the variance retained and the economic labels depend on the sample and preprocessing. A fixed-income risk analysis can track these component exposures alongside maturity-specific risks that the reduced representation leaves out.
The synthetic daily yield changes and their marginal standard deviations are measured in percentage points: 0.05 percentage points is 5 basis points. This fixes the financial scale without changing the PCA loading directions or explained-variance ratios.
# Simulate yield curve data (simplified example)
np.random.seed(456)
n_days = 500
maturities = [1, 2, 3, 5, 7, 10, 20, 30] # Years
# Generate correlated yield changes using an illustrative index-distance kernel
# Correlation decays equally with the number of maturity-grid steps between rates
n_rates = len(maturities)
base_corr = np.eye(n_rates)
for i in range(n_rates):
for j in range(n_rates):
base_corr[i, j] = np.exp(-0.15 * abs(i - j))
# Daily yield-change standard deviations in percentage points (0.05 pp = 5 bp)
# These stipulated volatilities decrease slightly with maturity
vols = 0.05 * np.array([1.0, 0.95, 0.9, 0.85, 0.8, 0.75, 0.7, 0.65])
cov_matrix = np.outer(vols, vols) * base_corr
# Generate yield changes
yield_changes = np.random.multivariate_normal(np.zeros(n_rates), cov_matrix, n_days)
# Apply PCA
pca_yields = PCA()
yield_reduced_scores = pca_yields.fit_transform(yield_changes)[:, :3]Yield Curve PCA Results: -------------------------------------------------- Variance explained by each component: PC1: 72.8% PC2: 14.4% PC3: 5.2% PC4: 2.7% PC5: 1.8% First 3 PCs explain 92.3% of variance; reduced score shape: (500, 3)

In this synthetic sample, PC1 has same-sign loadings across maturities and resembles a level shift. PC2 contrasts short and long maturities, while PC3 contrasts the middle with the ends and resembles curvature. The signs could all be reversed without changing the components. These loading patterns can be used to describe and hedge component exposures, subject to the fit and stability of the estimated PCA model.
Singular Value Decomposition (SVD)
While eigendecomposition only applies to square matrices, Singular Value Decomposition works for any matrix. For an matrix :
where:
- : an orthogonal matrix whose columns are the left singular vectors. The vectors associated with non-zero singular values span the column space of ; for a time-by-asset return matrix, their coordinates run across observations such as days.
- : an diagonal matrix with non-negative singular values on the diagonal
- : an orthogonal matrix whose columns are the right singular vectors. The vectors associated with non-zero singular values span the row space of ; for a time-by-asset return matrix, their coordinates run across assets.
The code below requests NumPy's reduced SVD. With , full_matrices=False returns U with shape , singular_values as a length- vector, and Vt with shape . NumPy returns the diagonal entries rather than an explicit matrix; U @ np.diag(singular_values) @ Vt reconstructs .
The singular values measure the "importance" of each component. For a return matrix with days as rows and assets as columns, shows when certain patterns occurred, shows which assets participated in each pattern, and shows how strong each pattern was.
The strength of SVD lies in its universality and interpretability. Any real matrix, not just square symmetric ones, can be decomposed into orthogonal changes of basis, including rotations or reflections, and a rectangular diagonal map with nonnegative scalings. The singular values are always non-negative and ordered by magnitude, and they provide a natural ranking by component strength. Truncating the SVD by keeping only the largest singular values gives the best low-rank approximation to the original matrix in terms of Frobenius norm, a result known as the Eckart-Young theorem. This makes SVD the mathematical foundation for dimensionality reduction and data compression.
SVD is useful for handling non-square return matrices (different numbers of days vs. assets) and for computing the pseudoinverse used in least squares solutions.
# SVD of return matrix
U, singular_values, Vt = np.linalg.svd(centered_returns, full_matrices=False)
# Singular values relate to eigenvalues of covariance matrix
# For centered data X (T×N), T>1, covariance eigenvalues are σ²/(T-1)
# Add zero eigenvalues if the reduced SVD returns fewer than N singular values
n_obs = centered_returns.shape[0]
eigenvalue_estimate = singular_values**2 / (n_obs - 1)Singular values of return matrix: σ1 = 0.3984 σ2 = 0.3006 σ3 = 0.1711 Relationship to eigenvalues: Eigenvalues of cov matrix: [0.00063233 0.00036008 0.0001167 ] σ²/(T-1): [0.00063233 0.00036008 0.0001167 ]
The close match between eigenvalues and σ²/(T-1) confirms the mathematical relationship between SVD and eigendecomposition. The largest singular value corresponds to the dominant variance pattern in the return matrix. That pattern may be interpreted as a market factor only after inspecting and validating its asset loadings and time scores; SVD alone does not assign an economic label. This relationship is why PCA can be computed efficiently via SVD, which is numerically more stable than computing the eigendecomposition of the covariance matrix directly.
Cholesky Decomposition
For positive definite matrices, including covariance matrices that are strictly positive definite, Cholesky decomposition provides a "square root":
where:
- : a lower triangular matrix (all entries above the diagonal are zero)
- : the transpose of (an upper triangular matrix)
For a dense matrix, Cholesky factorization requires about floating-point operations. Dense LU factorization requires about , so both are but Cholesky has roughly half the leading operation count when its positive-definite assumptions hold.
When the diagonal entries of are required to be positive, this lower-triangular Cholesky factor is unique. The factor can be applied efficiently to independent draws to simulate correlated variables, a common step in Monte Carlo derivatives-pricing and risk workflows.
The lower triangular structure of gives a sequential construction relative to the chosen variable ordering. The first simulated component uses one independent shock, the second mixes that shock with a second one, and the third mixes the first two shocks with a third. This is a computational representation of the target covariance, not a causal claim that later-listed assets depend on earlier-listed assets. Reordering the assets generally changes the factor while preserving the appropriately reordered covariance matrix; permutation-invariant cases, such as the identity matrix, can leave the factor unchanged.
# Cholesky decomposition for generating correlated random samples
L = np.linalg.cholesky(sample_cov)
# Generate uncorrelated standard normal samples
n_simulations = 10000
uncorrelated = np.random.standard_normal((n_simulations, 3))
# Transform to correlated samples
correlated = uncorrelated @ L.T
# Verify the correlation structure
simulated_cov = np.cov(correlated, rowvar=False)Original covariance matrix (×10,000): [[3.4765 1.4642 0.9198] [1.4642 2.1172 0.8783] [0.9198 0.8783 5.4973]] Simulated covariance matrix (×10,000): [[3.5015 1.4471 0.8961] [1.4471 2.1432 0.8691] [0.8961 0.8691 5.6059]] Cholesky factor L (lower triangular, ×100): [[1.8645 0. 0. ] [0.7853 1.225 0. ] [0.4933 0.4008 2.2569]]



The simulated covariance matrix closely matches the original, confirming that our Cholesky-based sampling reproduces the target covariance up to Monte Carlo error. For the chosen asset order, the lower triangular factor mixes successive independent shocks into each simulated component. This triangular construction is efficient, but it does not imply a causal ordering among the assets.
The Cholesky decomposition provides one efficient way to generate correlated random returns for Monte Carlo pricing and risk calculations, including some path-dependent derivative and Value-at-Risk workflows. It is not required: alternative matrix square roots, copulas, historical simulation, and other scenario methods can produce dependence in different ways.