Read the direction and its variance separately
The share of the universe's total variance explained by component is . The variance contribution to a portfolio with weights is
A market-like component from three correlated stocks
Suppose three standardised equity returns have pairwise correlation 0.60. Their first eigenvector is with eigenvalue 2.20. The remaining two eigenvalues are 0.40 each. The first component therefore explains 73.3% of total standardised asset variance. With equal asset volatilities and equal capital weights, the portfolio is exactly aligned with that first component and it accounts for 100% of the portfolio's modelled variance. That distinction matters when using PCA to explain an allocation. The asset universe can have meaningful regional factors even when your chosen weights give them little exposure.
A component's sign is arbitrary. Multiplying the loading vector and every score by minus one leaves the fit unchanged. If you compare neighbouring windows, orient a market-like direction the same way before interpreting a sign flip. With tied or nearly tied eigenvalues, individual vectors can also rotate inside the corresponding subspace. In the three-asset example the two smaller eigenvalues are tied, so it is more meaningful to interpret their joint span than to give the second and third directions separate economic names.
Fit PCA on a fixed training window
import numpy as np
from sklearn.decomposition import PCA
rng = np.random.default_rng(8)
market = rng.normal(size=(1_000, 1))
noise = rng.normal(size=(1_000, 3))
returns = (np.sqrt(0.6) * market + np.sqrt(0.4) * noise)
returns *= np.array([0.010, 0.013, 0.017])
train, test = returns[:750], returns[750:]
center, scale = train.mean(axis=0), train.std(axis=0, ddof=1)
pca = PCA().fit((train - center) / scale)
future_scores = pca.transform((test - center) / scale)
weights = np.array([0.5, 0.3, 0.2])
exposure = pca.components_ @ (scale * weights)
risk_by_component = pca.explained_variance_ * exposure**2
assert np.allclose(risk_by_component.sum(), np.var(train @ weights, ddof=1))
print(pca.explained_variance_ratio_.round(3))
print((risk_by_component / risk_by_component.sum()).round(3))