Contents
Deep Breakdown
What it tells me (Situation Awareness): The trace of a square matrix equals the sum of its eigenvalues: . This connects two different representations of the same matrix: the trace (computable in from the diagonal) and the eigenvalues (requiring eigendecomposition). The identity provides a fast sanity check after eigendecomposition and, more importantly, a direct interpretation of the trace in terms of the spectrum. In DA/DS and ML, this identity is used constantly in PCA: the trace of a covariance matrix equals the total variance in the data, and since , the fraction of variance explained by the top principal components is . It also appears in regularization theory, spectral graph analysis, and the Stein's lemma for risk estimation.
Flowchart
Real-world use
Where it is used
Used in PCA to compute the explained variance ratio for each component, in spectral graph theory where counts closed walks of length , as a fast sanity check after eigendecomposition, in covariance matrix analysis to interpret total data spread, and in trace-norm regularization for matrix completion (collaborative filtering, recommender systems).
Problem it solves
Computing eigenvalues is expensive (). When you only need the sum of eigenvalues (e.g., to get total variance), you get it for free from the trace in . Conversely, when you already have the eigenvalues from a PCA, you can verify your computation by checking without recomputing the trace from scratch.
How it solves the problem
The identity follows from the characteristic polynomial: equals the negative of the coefficient of in , which also equals . More intuitively, in the eigenbasis , so (using the cyclic property of trace).
Example in a business
A data scientist runs PCA on a customer behaviour dataset with features. The covariance matrix eigenvalues are . They compute the number of components needed to explain 90% of variance: the threshold is . Using np.trace(cov_matrix) (fast) for the denominator and the sorted eigenvalues for the numerator, they find components explain 91% of variance, reducing 20 dimensions to 6.
The formula, piece by piece
- : the trace of matrix ; the sum of all diagonal entries ; computed in
- : sum over all eigenvalues, each counted with its algebraic multiplicity
- : the -th eigenvalue of ; possibly complex for non-symmetric ; always real for symmetric positive semi-definite matrices (like covariance matrices)
- The equality: invariant to basis change; both the trace and the eigenvalue sum are spectrum invariants Full formula:
The formula in words
"The sum of the diagonal entries of a matrix equals the sum of its eigenvalues. You don't need to compute eigenvalues to get their sum — the diagonal already encodes it. Conversely, after an eigendecomposition, the eigenvalue sum gives you a free sanity check." Tiny numeric example: . . Eigenvalues: . .
Critical Concepts
- Trace is basis-independent: for any invertible . Changing the coordinate system doesn't change the sum of diagonal entries. This is why it equals the eigenvalue sum (spectrum = invariant).
- Complex eigenvalues come in conjugate pairs for real matrices: is real, so the imaginary parts cancel. The sum of eigenvalues is always real for real .
- Repeated eigenvalues: if has algebraic multiplicity , it contributes to the sum, not just .
- PCA connection: for a covariance matrix , all eigenvalues are non-negative, so is the total variance. Explained variance ratio: .
- Relation to Frobenius norm: (singular values squared), not eigenvalues. Don't confuse eigenvalues with singular values.
- Trace of matrix power: .
In graph theory, counts triangles in the adjacency matrix.
Step-by-step calculation (tiny example)
Step 1: Compute the trace directly from the diagonal . .
Step 2: Find the eigenvalues via the characteristic polynomial . .
Step 3: Verify the trace identity . Note: (trace = eigenvalue sum, det = eigenvalue product).
Code examples
Beginner example (Python)
import numpy as np
# -- tr(A) = sum of eigenvalues: verification and PCA variance check ---
A = np.array([[4.0, 1.0], [3.0, 2.0]])
trace_direct = float(np.trace(A))
eigenvalues, _ = np.linalg.eig(A)
trace_from_eigs = float(np.sum(eigenvalues.real)) # real part (imag ~ 0 for real symm)
print(f"Matrix A:\n{A}")
print(f"\nTrace (diagonal sum): {trace_direct:.6f}")
print(f"Sum of eigenvalues: {trace_from_eigs:.6f}")
print(f"Identity holds: {np.isclose(trace_direct, trace_from_eigs)}")
# Symmetric PSD example: covariance matrix
np.random.seed(42)
cov = np.cov(np.random.randn(5, 50)) # 5x5 sample covariance
eigvals_cov = np.linalg.eigvalsh(cov) # eigvalsh for symmetric: real eigenvalues
print(f"\nCovariance matrix trace: {float(np.trace(cov)):.6f}")
print(f"Sum of eigenvalues: {float(np.sum(eigvals_cov)):.6f}")
print(f"Explained variance ratio per component: {eigvals_cov[::-1] / np.trace(cov)}")Beginner example (R)
# -- tr(A) = sum of eigenvalues: verification and PCA variance check ---
A <- matrix(c(4, 1, 3, 2), nrow = 2, byrow = TRUE)
trace_direct <- sum(diag(A)) # R has no built-in trace(); use sum(diag())
eig_result <- eigen(A)
trace_eigsum <- sum(Re(eig_result$values))
cat(sprintf("Trace (diagonal sum): %.6f\n", trace_direct))
cat(sprintf("Sum of eigenvalues: %.6f\n", trace_eigsum))
cat(sprintf("Identity holds: %s\n", isTRUE(all.equal(trace_direct, trace_eigsum))))
# PCA variance
set.seed(42)
X_data <- matrix(rnorm(50 * 5), nrow = 50, ncol = 5)
cov_mat <- cov(X_data)
eigvals <- eigen(cov_mat, symmetric = TRUE)$values # sorted descending
cat(sprintf("\nCovariance matrix trace: %.6f\n", sum(diag(cov_mat))))
cat(sprintf("Sum of eigenvalues: %.6f\n", sum(eigvals)))
cat(sprintf("Explained variance ratio: %s\n", paste(round(eigvals / sum(diag(cov_mat)), 4), collapse = ", ")))Professional example (Python)
import numpy as np
import pandas as pd
np.random.seed(42)
# -- Business scenario: PCA component selection using trace identity
# Customer behaviour dataset: 20 features.
# Use tr(Sigma) as the total-variance denominator (O(n) cost, no eigendecomp needed).
# Then determine how many PCA components explain >= 90% of variance.
N_OBS = 500
N_FEATURES = 20
VARIANCE_THRESHOLD = 0.90
FEATURE_NAMES = [f"behaviour_{i:02d}" for i in range(1, N_FEATURES + 1)]
# Simulate data with latent structure: first 5 factors drive most variance
latent_factors = np.random.randn(N_OBS, 5)
factor_scales = np.array([2.0, 1.5, 1.2, 0.8, 0.5])
loadings = np.random.randn(5, N_FEATURES) * factor_scales[:, None] # scale each factor's row
noise = np.random.randn(N_OBS, N_FEATURES) * 0.3
X = latent_factors @ loadings + noise
# Center and compute covariance matrix
X_centered = X - X.mean(axis=0)
cov_matrix = np.cov(X_centered.T) # (20 x 20)
# Total variance via trace (O(n), no eigendecomp required)
total_variance = float(np.trace(cov_matrix))
# Eigendecomposition for component analysis
eigenvalues = np.linalg.eigvalsh(cov_matrix)[::-1] # sorted descending
# Verify trace identity
trace_vs_eigsum_match = np.isclose(total_variance, float(np.sum(eigenvalues)))
# Find k components to exceed the variance threshold
cumulative_ratio = np.cumsum(eigenvalues) / total_variance
k_components = int(np.searchsorted(cumulative_ratio, VARIANCE_THRESHOLD)) + 1
# Summary table
component_df = pd.DataFrame({
"component": range(1, N_FEATURES + 1),
"eigenvalue": eigenvalues.round(4),
"explained_pct": (eigenvalues / total_variance * 100).round(2),
"cumulative_pct": (cumulative_ratio * 100).round(2),
})
print(f"Total variance tr(Sigma) = {total_variance:.4f}")
print(f"Sum of eigenvalues = {float(np.sum(eigenvalues)):.4f}")
print(f"Trace identity holds : {trace_vs_eigsum_match}")
print(f"\nComponents needed for {VARIANCE_THRESHOLD*100:.0f}% variance: {k_components}")
print(f"\nPer-component breakdown (top 10):")
print(component_df.head(10).to_string(index=False))
print(f"\nInterpretation: tr(Sigma) = sum(lambda_i) gives the total variance in O(n) without eigendecomposition. Dividing each lambda_i by tr(Sigma) gives the PCA explained variance ratio, enabling component count selection.")Source: MYLA p. 6