Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Dimensionality reduction: principal component analysis (PCA)

Heinrich Heine University Düsseldorf

PCA

Principal component analysis (PCA) is an unsupervised method that applies a coordinate transformation to the data so that the new coordinate axes are ordered by how much variance the data show along them. A very common application is to project onto the first (few) coordinates after a PCA, reducing the number of dimensions without throwing away too much of the information in the data.

When somebody says “we do a PCA”, they usually mean steps 1–2, often also 1–3:

  1. fit – compute from the data a linear, invertible coordinate transformation – more precisely, an orthonormal basis. The new coordinate axes are called principal components or principal axes, and they are ordered by how much variance the data show along them.

  2. transform – apply the transformation to data. These may well be data other than the ones it was computed from; that is exactly why there are two steps.

  3. dimensionality reduction – project the transformed data onto the first principal components, that is, onto a low-dimensional linear subspace.

In Scikit-Learn the first two steps are called exactly that (PCA().fit(x), pca.transform(x), and fit_transform for both at once), and step 3 lives in the parameter n_components: PCA(n_components=2) throws away all but the first two principal components when transforming. Leave n_components out and you get nothing but a rotation of the coordinate system – and lose nothing.

The two figures show exactly these two cases: in blue the original data in space, in grey their projections onto the coordinate planes, and as an overlay the difference in total variance before and after the projection onto the xyxy plane.

Case (a): z is constantly 1. The projection onto the xy plane has exactly the same total variance as the original data – the third coordinate carries no information.

Figure 1:Case (a): zz is constantly 1. The projection onto the xyxy plane has exactly the same total variance as the original data – the third coordinate carries no information.

Case (b): z \sim \mathcal{N}(1,\ 0.1^2). Now something is lost in the projection, but only 0.01 out of a total variance of 2.28 – less than half a per cent.

Figure 2:Case (b): z∼N(1, 0.12)z \sim \mathcal{N}(1,\ 0.1^2). Now something is lost in the projection, but only 0.01 out of a total variance of 2.28 – less than half a per cent.

Case (b) is the realistic one: a coordinate is never exactly constant, it just varies very little. PCA makes this quantitative – it says not only that you may project, but also how much variance it costs (in Scikit-Learn, pca.explained_variance_ratio_).

A coordinate transformation is nothing but multiplication by an invertible square matrix. Such a matrix can compress or stretch in any direction, rotate about any axis by an angle, or shear. The goal of PCA is to find coordinates in which the first axis, the xx axis, carries the highest variance. Recall: if a matrix is read as a linear map by matrix multiplication, the images of the basis vectors are exactly the columns of the matrix. If we can find a vector pointing in the direction of greatest variance, we can write it into a matrix as the first column and project onto the subspace orthogonal to it (that is, we consider all vectors at a right angle to our first matrix column, which leaves a vector space one dimension smaller). Carrying on inductively, we obtain a matrix MM. The inverse map M−1M^{-1} is then a transformation that maps the direction of greatest variance onto the first standard basis vector, that is, onto the xx axis.

Because we normalise every principal axis and always look for the next one in the orthogonal complement of the previous ones, the columns of MM are normalised and pairwise perpendicular: they form an orthonormal basis. Such a matrix is called orthogonal (also: orthonormal matrix). It satisfies M⊤M=IM^\top M = I (identity matrix), hence M−1=M⊤M^{-1} = M^\top: the back-transformation is simply the transpose, and the inversion comes for free. Why the principal axes come out perpendicular to each other of their own accord is something we shall see at the end of the derivation below.

Unfortunately we have not yet said how to find the direction of greatest variance.

Derivation

These examples suggest asking which matrix MM could have produced the observed data from a standard normal distribution (or at least, for which matrix that would be most plausible). The eigenvectors belonging to the largest eigenvalue of MM are precisely the directions of greatest variance.

Such a covariance matrix can always be set up. If we do that with our real data and could then take a “square root” of it, we would have found the matrix MM. Fortunately, covariance matrices have a special property: they are positive definite and symmetric. (In the chapter The multivariate normal distribution we saw that the covariance matrix describes the ellipses of the density; the eigenvectors we are now computing are exactly the axes of those ellipses.)

There is a very simple way to find this matrix Σ\Sigma if CC is a diagonal matrix with nothing but positive entries: then Σ\Sigma is the diagonal matrix of the respective square roots. If CC is not a diagonal matrix, there are procedures for changing coordinates so as to turn CC into one. Mathematically this means that CC can be replaced by a transformation TT backwards, then a diagonal matrix C′C', then the transformation TT forwards. In formulas: C=TC′T−1C = T C' T^{-1}.

Suppose we have found such a decomposition (also called an eigenspace decomposition). Then we can compute Σ′\Sigma' as a square root of C′C', and Σ=TΣ′T−1\Sigma = T \Sigma' T^{-1} satisfies

Σ2=TΣ′T−1TΣ′T−1=TΣ′2T−1=TC′T−1=C.\Sigma^2 = T \Sigma' T^{-1} T \Sigma' T^{-1} = T \Sigma'^2 T^{-1} = T C' T^{-1} = C.

The matrix TT we are after satisfies C=TC′T−1C = T C' T^{-1}, or, rearranged, CT=TC′C T = T C'. For every column v=T,iv = T_{,i} of TT and the ii-th diagonal entry αi\alpha_i of C′C' this gives an equation Cv=vαiC v = v \alpha_i: the columns of TT are exactly the eigenvectors of CC, and the diagonal entries of C′C' are exactly the eigenvalues of CC belonging to them.

That eigenvectors belonging to different eigenvalues are perpendicular can be checked in one line: if Cv1=λ1v1C v_1 = \lambda_1 v_1 and Cv2=λ2v2C v_2 = \lambda_2 v_2 with λ1≠λ2\lambda_1 \neq \lambda_2, then, because C=C⊤C = C^\top,

λ1v1⊤v2=(Cv1)⊤v2=v1⊤Cv2=λ2v1⊤v2,\lambda_1 v_1^\top v_2 = (C v_1)^\top v_2 = v_1^\top C v_2 = \lambda_2 v_1^\top v_2,

hence (λ1−λ2) v1⊤v2=0(\lambda_1 - \lambda_2)\, v_1^\top v_2 = 0 and therefore v1⊤v2=0v_1^\top v_2 = 0. If the eigenvectors are normalised as well, then T⊤TT^\top T is the matrix of all inner products vi⊤vjv_i^\top v_j, that is, the identity matrix. This is why the “rotation” of PCA (the matrix MM from the section on coordinate transformations above) is inverted by mere transposition, T−1=T⊤T^{-1} = T^\top – which the code below exploits too.

So we have all the ingredients, provided only that we can somehow find the eigenvectors of the covariance matrix!

A good procedure for finding the eigenvector belonging to the largest eigenvalue: start with a random vector and apply the matrix to it. If it is an eigenvector – good, we note it down. If not, the matrix maps the vector into a direction that, with higher probability, points a little more towards the eigenvector belonging to the largest eigenvalue. So if we apply the matrix repeatedly, the chances are good that we get closer and closer to an eigenvector.

A less efficient procedure is the one you should have learned in linear algebra: first find the eigenvalues as the roots of the characteristic polynomial det⁡(M−XId)\det(M - X Id), then for every eigenvalue α\alpha find the eigenspace Ker(M−αId)Ker(M - \alpha Id) by Gaussian elimination, that is, by solving the equation Mv=αvMv = \alpha v.

Numpy and Scikit-Learn fall back on LAPACK, which is written in Fortran and carries out a singular value decomposition (the decomposition C=TC′T−1C = TC' T^{-1}) in two steps. We shall not rack our brains over this any further; it belongs in a linear algebra or numerical analysis course. We do, of course, still want the computer’s help:

[2.75 1.5 ]
[[-0.89442719  0.4472136 ]
 [-0.4472136  -0.89442719]]
in this example the transformation T is:
[[-0.89442719 -0.4472136 ]
 [-0.4472136   0.89442719]]

The eigenvalues are the variances of the data along the corresponding eigenvectors, the principal components. Their share of the total variance tr⁡(C)=2.5+1.75=4.25\operatorname{tr}(C) = 2.5 + 1.75 = 4.25 – here 2.75/4.25≈65%2.75/4.25 \approx 65\% and 1.5/4.25≈35%1.5/4.25 \approx 35\% – is called the explained variance of the respective component. This is the quantity you later use to decide how many components to keep.

Dimensionality reduction in five steps

From a dataset x1,…,xN∈Rdx_1, \dots, x_N \in \mathbb{R}^d, PCA makes a dataset in Rk\mathbb{R}^k with k<dk < d:

  1. Centring: subtract its mean from every feature, so that the dataset has its centre of mass at 0 (usually with scaling as well, see below).

  2. Compute the covariance matrix CC of the centred data.

  3. Eigendecomposition: determine the eigenvalues λ1≥⋯≥λd\lambda_1 \geq \dots \geq \lambda_d and the corresponding normalised eigenvectors v1,…,vdv_1, \dots, v_d of CC.

  4. Selecting: keep the kk eigenvectors belonging to the largest eigenvalues – they explain the fraction (λ1+⋯+λk)/tr⁡(C)(\lambda_1 + \dots + \lambda_k) / \operatorname{tr}(C) of the total variance.

  5. Projecting: map every centred data point xx to its new coordinates (⟨x,v1⟩,…,⟨x,vk⟩)\big(\langle x, v_1 \rangle, \dots, \langle x, v_k \rangle\big).

eigenvalues: [8. 2.]  explained variance of the 1st PC: 0.8
new coordinates: [2.82842712 0.        ]

Now, instead of the made-up covariance matrix CC, we look at a real sample xx, distort it with a coordinate transformation, and then ‘undo’ that transformation with the method just discussed:

[[45.35696351 12.19917054]
 [12.19917054  6.1101751 ]]
<Figure size 900x300 with 3 Axes>

Look at the plots very closely. Have we undone the transformation completely? And either way, how can you tell?

Computation

In just the same way we can, of course, proceed with data we did not transform ourselves – data that may never have been sampled from a standard normal distribution in the first place.

[[ 0.68569351 -0.042434    1.27431544  0.51627069]
 [-0.042434    0.18997942 -0.32965638 -0.12163937]
 [ 1.27431544 -0.32965638  3.11627785  1.2956094 ]
 [ 0.51627069 -0.12163937  1.2956094   0.58100626]]
<Figure size 640x480 with 1 Axes>
<Figure size 700x700 with 1 Axes>

Computation with Scikit-learn

We do not even have to compute the singular value decomposition or the eigenvectors of the covariance matrix ourselves – Scikit-learn can do that for us too:

<Figure size 700x700 with 1 Axes>

A completely different approach to finding the direction of maximal variance is to minimise the sum of the squared distances of the points from a straight line.

Up to the factor 1n\frac1n, the sum of the squared distances is a mean squared error. And for the mse\texttt{mse} we already know the best constant model: it is the mean (chapter Expected value and variance, section “Squared deviation”). In this sense the first principal component is the best linear model – the straight line through the mean that minimises the mean squared distance – and the animation shows that this is exactly the direction of greatest variance.

One easily missed detail distinguishes PCA from linear regression: here it is the perpendicular distance to the line that is minimised, whereas in regression it is the vertical distance, in the direction of the target variable. The two lines are therefore in general different.

Here is a picture of how this works:

The line rotates until the sum of the squared distances is minimal – and that is the direction of greatest variance.

Figure 3:The line rotates until the sum of the squared distances is minimal – and that is the direction of greatest variance.

Application example: MNIST

So far we have reduced the dimension only for plotting – but many algorithms need the dimension kept at least small enough that the program does not run for weeks. A dataset with rather more than 4 variables is the MNIST dataset of handwritten digits.

<Figure size 1000x100 with 10 Axes>
0.7250791394789893
0.7437984495348842
0.8572992273791208

How many components do you need? Scikit-learn gives the explained variance of every component directly (explained_variance_ratio_: the eigenvalues of the covariance matrix divided by their sum). The cumulative curve shows how much variance the first nn components explain together:

<Figure size 640x480 with 1 Axes>
components for 90% of the variance: 21 out of 64
<Figure size 640x480 with 1 Axes>

As the graph shows, the performance of 10-means as a classifier only suffers once we are down from the original 64 dimensions to no more than 10 and keep reducing.

In the Iris data we have already seen that the covariance matrices within the groups come out slightly different. Strictly speaking, for an optimal separation of the groups we do not want to favour the directions of greatest variance at all; we would much rather have the direction that separates best. To look at that, however, you have to know the classes already – which makes it supervised learning. One such method is linear discriminant analysis (LDA).

Differences in scale

Things become problematic when the data do not all lie on the same scale, as they did in the examples so far (metres in one place, centimetres in another), or when the scales are even unknown or incompatible (seconds, metres).

Then the variables with large numbers dominate, which is a distortion of the coordinate transformation we were actually after. To avoid this, you rescale every variable by its standard deviation, and likewise shift every variable so that its mean becomes 0. This is called ‘standardisation’. It is particularly sensible when the variable is approximately normally distributed.

A ‘normalisation’ is a rescaling onto the interval [0,1][0,1]. It is particularly sensible when the variable is not normally distributed.

Any such rescaling is called ‘feature scaling’.

In Python, Scikit-Learn does this with a Scaler object:

<Figure size 800x800 with 2 Axes>

We now look at how scaling affects PCA and the k-means clustering that follows it, because instead of simply putting everything through the StandardScaler every time, you should always look at what difference it makes:

<Figure size 1000x1000 with 4 Axes>

The top row clearly shows that k-means works well on the unscaled data. The bottom row shows that with standard scaling the principal components lie differently (which is why the picture looks rotated), but also that k-means has not divided the clusters up well.

This is ultimately an incarnation of Simpson’s paradox: the “right” way to scale the data here would make the clusters as close to standard normally distributed as possible. To achieve that, however, you would first have to cluster, then compute the standard scaling of the clusters, and then scale all the data together by a suitable mean of those. Clustering after that yields a different picture again – and the procedure can be iterated. In this setting it pays not to use k-means at all, but to go straight to a method that does not assume standard normally distributed clusters (Gaussian mixture models, for example).

Application example: Wine

Finally we look at a ‘success story’ for standard scaling:

<Figure size 1000x1000 with 4 Axes>

Rather than going by the picture alone, it is better to compute a summary measure as well. As before, we use an entropy-based measure: the mutual information I(X;Y)I(X;Y) of the two class variables (chapter Relative entropy), normalised by the mean of their two entropies:

The scaled version, with a similarity of 0.865642 to the 'true' class, is better than the unscaled version with a similarity of only 0.423309