Volume 2: Continuous Mathematics · Part VII: Linear Algebra
Chapter 28. Eigenvectors, Bases, and Applications
A matrix moves almost every vector off its original line: it turns it as well as stretching it. But most matrices have a few special directions that they only stretch. Along those directions, a complicated transformation acts like plain multiplication by a number. Those directions are eigenvectors, and the numbers are eigenvalues. Find them, and hard questions become easy: what happens if you apply a matrix a thousand times? Where does a random process settle down? Which web page matters most? Which direction in a cloud of data carries the most information? This chapter builds eigenvectors from that one geometric question, computes them by hand for 2×2 matrices, and then follows them into Fibonacci numbers, Markov chains, PageRank, principal component analysis, and the singular value decomposition that compresses images and finds hidden topics in text.
Why this chapter matters. Many real questions are about what happens when the same step is repeated: next year’s population from this year’s, tomorrow’s weather from today’s, the next vibration of a bridge from the last, the next click of a web surfer. Eigenvectors answer "where does this end up, and how fast?" without simulating every step. The same idea, aimed at a cloud of data instead of a process, finds its most informative directions, which is how faces, genomes and documents get compressed into a few meaningful numbers.
28.1. Directions that only stretch
28.1.1. The problem
Take the matrix
Apply it to \((1, 0)\) and you get \((4, 2)\): the vector has been stretched and turned. Apply it to \((0, 1)\): \((1, 3)\), also turned. But apply it to \((1, 1)\):
No turning at all. Along the line through \((1, 1)\), this matrix does nothing except multiply by 5. Try \((1, -2)\): \(A(1, -2) = (4 - 2, 2 - 6) = (2, -4) = 2(1, -2)\). Another unturned direction, stretched by 2.
Now here’s why this matters. Suppose you want \(A^{100}\mathbf{x}\), the result of applying \(A\) a hundred times. Multiplying out 100 matrices is tedious. But if \(\mathbf{x} = (1, 1)\), the answer is instant: each application multiplies by 5, so \(A^{100}(1, 1) = 5^{100}(1, 1)\). And since \((1, 1)\) and \((1, -2)\) point in different directions, every vector is a combination of them, and we can handle every vector this way. A complicated matrix has become two independent multiplications by numbers.
The questions for this chapter: how do you find such directions systematically, what do they mean, and where does this trick pay off?
28.1.2. Intuition: watch which arrows stay on their lines
Picture the transformation acting on the whole plane. Most directions get dragged around. But a few lines through the origin are mapped onto themselves: vectors on those lines stay on them, just longer, shorter, or flipped. Those are the transformation’s natural axes, its "grain". A shear, a stretch, a projection: each has its own.
|
Eigenvector and eigenvalue
A nonzero vector \(\mathbf{v}\) is an eigenvector of a square matrix \(A\) if
\[A\mathbf{v} = \lambda\mathbf{v}\]
for some scalar \(\lambda\) (Greek "lambda"), called the eigenvalue belonging to \(\mathbf{v}\). In words: \(A\) maps \(\mathbf{v}\) to a multiple of itself. \(\lambda > 1\) stretches, \(0 < \lambda < 1\) shrinks, \(\lambda < 0\) flips (and scales by \(|\lambda|\)), \(\lambda = 0\) crushes \(\mathbf{v}\) to zero. Any nonzero multiple of an eigenvector is also an eigenvector with the same eigenvalue, so really an eigenvector names a direction (a line through the origin). The zero vector is never counted as an eigenvector, since \(A\mathbf{0} = \lambda\mathbf{0}\) for every \(\lambda\) and tells you nothing. |
|
Why "eigen"?
Eigen is German for "own" or "characteristic", as in mein eigenes Auto ("my own car"). An eigenvector is the matrix’s own vector, a direction that belongs to it. David Hilbert popularised the term around 1904 in his work on integral equations. English briefly tried "proper value" and "characteristic value" (the latter survives in "characteristic equation"), but the half-German hybrid won. Physicists use "eigenstate", "eigenfrequency" and "eigenmode"; quantum mechanics is, in a sense, applied eigenvector theory. |
28.1.3. A gallery of eigen-directions
You can often spot eigenvectors from the geometry alone.
-
Scaling \(\begin{bmatrix} 3 & 0 \\ 0 & 2 \end{bmatrix}\): the axes. \(\mathbf{e}_1\) with \(\lambda = 3\), \(\mathbf{e}_2\) with \(\lambda = 2\). A diagonal matrix’s eigenvectors are the coordinate axes and its eigenvalues are the diagonal entries.
-
Reflection in the line \(y = x\): vectors on that line are unchanged (\(\lambda = 1\)); vectors perpendicular to it, along \((1, -1)\), are flipped (\(\lambda = -1\)).
-
Projection onto the x-axis: \(\mathbf{e}_1\) is kept (\(\lambda = 1\)); \(\mathbf{e}_2\) is crushed (\(\lambda = 0\)). An eigenvalue of 0 means the matrix is singular.
-
Rotation by \(90^\circ\): every vector is turned, so there are no real eigenvectors at all. (The eigenvalues turn out to be the complex numbers \(\pm i\); see the failure modes below.)
-
Shear \(\begin{bmatrix} 1 & 1 \\ 0 & 1 \end{bmatrix}\): the x-axis slides along itself unchanged (\(\lambda = 1\)), and nothing else stays on its line. Only one eigen-direction.
28.1.4. Where you’d use this
-
Everyday: why things shake at particular speeds. A washing machine judders at one spin speed and runs smoothly above it; a car’s mirror buzzes at one engine speed; a wine glass rings at one note. Each structure has natural modes of vibration, which are the eigenvectors of its stiffness-and-mass equations, and the eigenvalues give the natural frequencies. Push at one of those frequencies and the motion builds up (resonance).
-
Science and engineering: London’s Millennium Bridge swayed alarmingly on its opening day in 2000 because the footfall of crowds matched a sideways natural mode near 1 Hz; it was closed for nearly two years while dampers were fitted. Structural engineers compute the lowest eigenvalues of a building model to keep its natural frequencies away from earthquake and wind frequencies. In quantum mechanics the allowed energies of an atom are eigenvalues, and the spectral lines astronomers use to identify elements in distant stars are differences between them. A tumbling object (throw a phone or a tennis racket in the air) spins steadily only about the eigenvectors of its inertia matrix, its principal axes.
-
Computing: stability analysis. A numerical simulation, a control system or a recurrent neural network repeatedly multiplies by some matrix; if any eigenvalue has size greater than 1, errors grow without limit. Checking the largest eigenvalue is how engineers tell whether an autopilot, a physics time-step or a training run will settle or blow up (Chapter 32).
28.2. Finding eigenvalues: the characteristic equation
28.2.1. Why it works
We want nonzero \(\mathbf{v}\) with \(A\mathbf{v} = \lambda\mathbf{v}\). Rewrite that with everything on one side. Since \(\lambda\mathbf{v} = \lambda I\mathbf{v}\),
Read it as a transformation: the matrix \(A - \lambda I\) sends the nonzero vector \(\mathbf{v}\) to zero. An invertible matrix can’t do that (it only sends \(\mathbf{0}\) to \(\mathbf{0}\); see Chapter 27). So \(A - \lambda I\) must be singular, which means its determinant is zero:
This is the characteristic equation. It contains only \(\lambda\), not \(\mathbf{v}\), so you can solve it for the eigenvalues first and find the eigenvectors afterwards. For a \(2\times 2\) matrix \(\begin{bmatrix} a & b \\ c & d \end{bmatrix}\),
The coefficients are two old friends: \(a + d\) is the trace (sum of the diagonal) and \(ad - bc\) is the determinant. So for any \(2\times 2\) matrix,
a quadratic, solved with the quadratic formula (Chapter 8). By the sum and product of roots, \(\lambda_1 + \lambda_2 = \operatorname{tr}A\) and \(\lambda_1\lambda_2 = \det A\), two handy checks. The second also makes geometric sense: if \(A\) stretches one direction by \(\lambda_1\) and another by \(\lambda_2\), areas scale by \(\lambda_1\lambda_2\).
|
Characteristic polynomial, trace
|
Input: \(A = \begin{bmatrix} a & b \\ c & d \end{bmatrix}\).
Steps:
-
Form the characteristic equation \(\lambda^2 - (a + d)\lambda + (ad - bc) = 0\).
-
Solve it for \(\lambda_1, \lambda_2\) (factor, or use the quadratic formula). Check: \(\lambda_1 + \lambda_2 = a + d\) and \(\lambda_1\lambda_2 = ad - bc\).
-
For each eigenvalue \(\lambda\), write down \(A - \lambda I\). Its two rows are multiples of each other (it’s singular), so they give a single equation in \((x, y)\).
-
Solve that one equation for a nonzero vector. Shortcut: for a row \((p, q)\) that isn’t all zero, \((-q, p)\) (or \((q, -p)\)) solves \(px + qy = 0\).
-
Check \(A\mathbf{v} = \lambda\mathbf{v}\).
Output: eigenvalues \(\lambda_1, \lambda_2\) and eigenvectors \(\mathbf{v}_1, \mathbf{v}_2\) (each determined up to a nonzero multiple).
Try it: Run the 2×2 procedure.
28.2.2. Worked example: the opening matrix
Find the eigenvalues and eigenvectors of \(A = \begin{bmatrix} 4 & 1 \\ 2 & 3 \end{bmatrix}\). Try it: Watch the eigenvectors stay put; Step through this example.
Step 1: characteristic equation. Trace \(= 4 + 3 = 7\), determinant \(= 4\cdot 3 - 1\cdot 2 = 10\).
Step 2: solve. We need two numbers with product 10 and sum 7: 5 and 2. So \((\lambda - 5)(\lambda - 2) = 0\), giving \(\lambda_1 = 5\) and \(\lambda_2 = 2\). Checks: \(5 + 2 = 7\) ✓, \(5 \times 2 = 10\) ✓.
Step 3–4: eigenvector for \(\lambda_1 = 5\).
The second row is \(-2\) times the first, as promised. The first row says \(-x + y = 0\), i.e. \(y = x\). Take \(\mathbf{v}_1 = (1, 1)\).
Eigenvector for \(\lambda_2 = 2\).
Both rows say \(2x + y = 0\), i.e. \(y = -2x\). Take \(\mathbf{v}_2 = (1, -2)\).
Step 5: check. \(A(1, 1) = (5, 5) = 5(1, 1)\) ✓ and \(A(1, -2) = (2, -4) = 2(1, -2)\) ✓. These are exactly the directions highlighted in the diagram.
28.2.3. Alternative approaches, and choosing between them
-
By hand, 2×2: the characteristic equation, as above. It’s quick and shows the structure.
-
By hand, 3×3: the characteristic polynomial is a cubic, which is unpleasant unless you can spot a root (try small integers, and remember the trace and determinant checks). Exam questions are designed so you can; real matrices aren’t.
-
By computer, any size: never through the characteristic polynomial. Finding polynomial roots is numerically ill-conditioned: tiny rounding errors in the coefficients move the roots a lot. Libraries use the QR algorithm, an iterative process of repeated orthogonal transformations, which is stable and \(O(n^3)\). In NumPy:
np.linalg.eigfor general matrices,np.linalg.eighfor symmetric ones (faster, more accurate, and returns real, sorted, orthonormal results). -
Only the largest eigenvector of a huge sparse matrix: power iteration (Section 28.7), which needs nothing but matrix–vector products.
|
When eigenvectors go missing
|
|
Experiment: Eigenvectors using Python
Verify the worked example, then probe the failure modes.
[5. 2.] [[ 0.70710678 -0.4472136 ] [ 0.70710678 0.89442719]] [3.53553391 3.53553391] [3.53553391 3.53553391] The columns are \((1, 1)/\sqrt{2}\) and \((-1, 2)/\sqrt{5}\), which is \(-(1, -2)/\sqrt{5}\): the same direction as our hand answer, normalised and flipped. Now try these and explain each result geometrically:
|
Try it: Run this in your browser.
28.3. Exercises: finding eigenvectors
-
(Recognition) Without calculating, give the eigenvalues and eigenvectors of \(\begin{bmatrix} -1 & 0 \\ 0 & 7 \end{bmatrix}\) and of the reflection in the x-axis. Try it: Check exercise [1 2; 3 2); Check exercise [2 1; 1 2).
-
(Application) Find the eigenvalues and eigenvectors of \(\begin{bmatrix} 2 & 1 \\ 1 & 2 \end{bmatrix}\).
-
(Application) Find the eigenvalues of \(\begin{bmatrix} 1 & 2 \\ 3 & 2 \end{bmatrix}\) and one eigenvector for each.
-
(Problem solving) A \(2\times 2\) matrix has trace 6 and determinant 9. What can you say about its eigenvalues? Give two different matrices with these properties, one with a single eigen-direction and one where every vector is an eigenvector.
Answers
-
Diagonal: eigenvalues \(-1\) (along \(\mathbf{e}_1\)) and 7 (along \(\mathbf{e}_2\)). Reflection in the x-axis: \(\mathbf{e}_1\) with \(\lambda = 1\), \(\mathbf{e}_2\) with \(\lambda = -1\).
-
Trace 4, determinant 3: \(\lambda^2 - 4\lambda + 3 = (\lambda - 3)(\lambda - 1) = 0\), so \(\lambda = 3, 1\). For 3: \(A - 3I = \begin{bmatrix} -1 & 1 \\ 1 & -1 \end{bmatrix}\), \(y = x\), \(\mathbf{v} = (1, 1)\). For 1: \(A - I = \begin{bmatrix} 1 & 1 \\ 1 & 1 \end{bmatrix}\), \(y = -x\), \(\mathbf{v} = (1, -1)\). (Note: perpendicular.)
-
Trace 3, determinant \(2 - 6 = -4\): \(\lambda^2 - 3\lambda - 4 = (\lambda - 4)(\lambda + 1) = 0\). For 4: \(A - 4I = \begin{bmatrix} -3 & 2 \\ 3 & -2 \end{bmatrix}\), \(-3x + 2y = 0\), \(\mathbf{v} = (2, 3)\). Check: \(A(2, 3) = (2 + 6, 6 + 6) = (8, 12) = 4(2, 3)\) ✓. For \(-1\): \(A + I = \begin{bmatrix} 2 & 2 \\ 3 & 3 \end{bmatrix}\), \(x + y = 0\), \(\mathbf{v} = (1, -1)\). Check: \(A(1, -1) = (-1, 1)\) ✓.
-
\(\lambda^2 - 6\lambda + 9 = (\lambda - 3)^2\): a repeated eigenvalue 3. \(3I\) has every vector as an eigenvector. \(\begin{bmatrix} 3 & 1 \\ 0 & 3 \end{bmatrix}\) has trace 6, determinant 9, and only the eigen-direction \(\mathbf{e}_1\) (since \(A - 3I = \begin{bmatrix} 0 & 1 \\ 0 & 0 \end{bmatrix}\) forces \(y = 0\)).
28.4. Change of basis and diagonalisation
28.4.1. The problem
We found that \(A = \begin{bmatrix} 4 & 1 \\ 2 & 3 \end{bmatrix}\) simply multiplies by 5 along \((1, 1)\) and by 2 along \((1, -2)\). That suggests a strategy: describe vectors in terms of those two directions instead of \(\mathbf{e}_1, \mathbf{e}_2\). In those coordinates, \(A\) should look like the diagonal matrix \(\begin{bmatrix} 5 & 0 \\ 0 & 2 \end{bmatrix}\). How do we switch coordinates, and what does it buy us?
28.4.2. Change of basis
Any two independent vectors form a basis of the plane (Chapter 26). Put the new basis vectors as the columns of a matrix \(P\):
If a vector has coordinates \(\mathbf{c} = (c_1, c_2)\) in the new basis, meaning it equals \(c_1(1, 1) + c_2(1, -2)\), then in standard coordinates it’s \(\mathbf{x} = P\mathbf{c}\) (that’s the column view of matrix–vector multiplication). Going the other way, \(\mathbf{c} = P^{-1}\mathbf{x}\). So:
-
\(P\) translates from new coordinates to standard coordinates.
-
\(P^{-1}\) translates from standard coordinates to new coordinates.
Using the \(2\times 2\) inverse formula, \(\det P = -2 - 1 = -3\) and
Example. The vector \(\mathbf{x} = (3, 0)\) has new coordinates \(P^{-1}(3, 0) = (2, 1)\). Check: \(2(1, 1) + 1(1, -2) = (3, 0)\) ✓.
28.4.3. Diagonalisation: three moves
Now compute \(A\mathbf{x}\) by a detour through eigen-coordinates:
-
Translate into eigen-coordinates: \(\mathbf{c} = P^{-1}\mathbf{x}\).
-
In those coordinates \(A\) just scales each coordinate by its eigenvalue: multiply by \(D = \begin{bmatrix} 5 & 0 \\ 0 & 2 \end{bmatrix}\).
-
Translate back: multiply by \(P\).
So \(A\mathbf{x} = PDP^{-1}\mathbf{x}\) for every \(\mathbf{x}\), which means
where the columns of \(P\) are eigenvectors and \(D\) is diagonal with the matching eigenvalues. Reading right to left: change to the matrix’s own coordinates, stretch along the axes, change back. A complicated-looking transformation is a diagonal one viewed in a tilted coordinate system.
Check with \(\mathbf{x} = (3, 0)\). Coordinates \((2, 1)\); scale to \((10, 2)\); back to standard: \(10(1, 1) + 2(1, -2) = (12, 6)\). Directly: \(A(3, 0) = (12, 6)\) ✓.
|
Diagonalisable, similar, change of basis
|
28.4.4. Why powers become easy
Here’s the payoff. Apply \(A\) twice:
The inner \(P^{-1}P\) cancels: there’s no point changing back to standard coordinates between applications. The same happens for any power:
Powers of a diagonal matrix are trivial: raise each diagonal entry to the power. So the cost of \(A^n\) no longer depends on \(n\) (beyond computing two numerical powers).
Input: a diagonalisable \(n\times n\) matrix \(A\) and a power \(k\).
Steps:
-
Find the eigenvalues \(\lambda_1, \ldots, \lambda_n\) and eigenvectors \(\mathbf{v}_1, \ldots, \mathbf{v}_n\).
-
Form \(P = [\mathbf{v}_1 \mid \cdots \mid \mathbf{v}_n]\) and \(D = \operatorname{diag}(\lambda_1, \ldots, \lambda_n)\). Compute \(P^{-1}\).
-
Form \(D^k = \operatorname{diag}(\lambda_1^k, \ldots, \lambda_n^k)\).
-
Multiply \(A^k = PD^kP^{-1}\).
Output: \(A^k\), often as a closed-form formula in \(k\). If you only need \(A^k\mathbf{x}\): write \(\mathbf{x} = \sum c_i\mathbf{v}_i\) (i.e. \(\mathbf{c} = P^{-1}\mathbf{x}\)), and then \(A^k\mathbf{x} = \sum c_i\lambda_i^k\mathbf{v}_i\).
28.4.5. Worked example: a formula for \(A^n\)
Check at \(n = 1\): \(\tfrac{1}{3}(12, 3; 6, 9) = (4, 1; 2, 3) = A\) ✓. At \(n = 3\): top-left \((250 + 8)/3 = 86\), top-right \((125 - 8)/3 = 39\), bottom-left \((250 - 16)/3 = 78\), bottom-right \((125 + 16)/3 = 47\). Multiplying out \(A \cdot A \cdot A\) directly gives \(\begin{bmatrix} 86 & 39 \\ 78 & 47 \end{bmatrix}\) ✓.
The formula also tells you the long-run behaviour at a glance. For large \(n\), \(5^n\) dwarfs \(2^n\), so \(A^n \approx \tfrac{5^n}{3}\begin{bmatrix} 2 & 1 \\ 2 & 1 \end{bmatrix}\): every vector gets pulled towards the \((1, 1)\) direction and grows like \(5^n\). The largest eigenvalue governs the long run. That single observation drives the next three sections.
28.4.6. Where you’d use this: a population model
Ecologists track animal populations by age class with a Leslie matrix. Suppose each adult produces on average 3 juveniles a year, half the juveniles survive to become adults, and half the adults survive each year. With \(\mathbf{x}_n = (j_n, a_n)\) (juveniles, adults) in year \(n\), Try it: Run the Leslie model; Leslie matrix eigenvalues.
(Trace 0.5, determinant \(0 - 1.5 = -1.5\).) For \(\lambda = 1.5\), \(L - 1.5I = \begin{bmatrix} -1.5 & 3 \\ 0.5 & -1 \end{bmatrix}\) gives \(j = 2a\), eigenvector \((2, 1)\). So in the long run the population grows by 50% a year with two juveniles for every adult, whatever it started as. The eigenvalue \(-1\) adds a boom-and-bust wobble that alternates sign each year, but relative to the \(1.5^n\) growth it shrinks by a factor \(1/1.5 = 2/3\) a year. Start with 10 adults and no juveniles: year by year the totals are 35, 32.5, 68.75, 83.1, 144.7, 197.0, and the juvenile-to-adult ratio goes \(6, 0.86, 3.2, 1.42, 2.48, 1.72, \ldots\), closing in on 2. Fisheries managers use exactly this calculation, with more age classes, to set catch limits: harvest so that the dominant eigenvalue stays at about 1.
Beyond ecology, the same "largest eigenvalue governs the long run" reasoning gives compound growth of interacting quantities in economics, the spread rate of an epidemic across age groups (the famous \(R_0\) is the dominant eigenvalue of a "next-generation matrix"), and the asymptotic running time of algorithms defined by recurrences, as the next section shows.
|
Diagonalisation fails for defective matrices
If there aren’t \(n\) independent eigenvectors (the shear, or anything with a complex eigenvalue if you insist on real numbers), \(P\) isn’t invertible and \(A = PDP^{-1}\) is impossible. The general fallback is the Jordan form (nearly diagonal, with 1s just above the diagonal), which is theoretically important but numerically fragile; in practice, people use the Schur decomposition or the SVD (Section 28.11) instead. And even when \(A\) is diagonalisable, if its eigenvectors are nearly parallel then \(P\) is nearly singular and computing with \(PDP^{-1}\) loses accuracy. Symmetric matrices never have either problem. |
28.5. Fibonacci via matrix powers
28.5.1. The problem
The Fibonacci sequence (Chapter 14) is \(F_0 = 0\), \(F_1 = 1\), \(F_{n+1} = F_n + F_{n-1}\): 0, 1, 1, 2, 3, 5, 8, 13, … Is there a formula for \(F_n\) that doesn’t require computing all the earlier terms? And how fast does the sequence grow?
28.5.2. Turning a recurrence into a matrix
The recurrence needs the two most recent terms, so track them together as a state vector \((F_{n+1}, F_n)\). One step forward:
Starting from \((F_1, F_0) = (1, 0)\), \(n\) steps give \((F_{n+1}, F_n) = M^n(1, 0)\). So the Fibonacci numbers are the entries of the powers of \(M\). (In fact \(M^n = \begin{bmatrix} F_{n+1} & F_n \\ F_n & F_{n-1} \end{bmatrix}\).)
28.5.3. Eigenvalues of \(M\)
Trace 1, determinant \(-1\), so the characteristic equation is
These are the golden ratio \(\varphi = \tfrac{1 + \sqrt{5}}{2} \approx 1.618\) and its partner \(\psi = \tfrac{1 - \sqrt{5}}{2} \approx -0.618\). Eigenvectors: for either root \(\lambda\), the vector \((\lambda, 1)\) works, because
28.5.4. Deriving Binet’s formula
Write the starting vector as a combination of eigenvectors. Since \(\varphi - \psi = \sqrt{5}\),
(Check: top \((\varphi - \psi)/\sqrt{5} = 1\), bottom \((1 - 1)/\sqrt{5} = 0\) ✓.) Now apply \(M^n\): each eigenvector just gets multiplied by its eigenvalue to the \(n\):
The bottom entry is
This is Binet’s formula. A sequence of whole numbers is given by a formula full of square roots of 5, which all cancel. Since \(|\psi| < 1\), the \(\psi^n\) term shrinks to nothing, so \(F_n\) is simply \(\varphi^n/\sqrt{5}\) rounded to the nearest integer, and the ratio of consecutive terms tends to \(\varphi\). The growth rate of the sequence is the dominant eigenvalue.
28.5.5. Implementation: exact Fibonacci in \(O(\log n)\) steps
Binet’s formula is elegant but floating-point arithmetic ruins it for large \(n\). Matrix powers computed by repeated squaring stay in exact integers and need only about \(\log_2 n\) matrix multiplications (the same trick as fast modular exponentiation in Chapter 23): Try it: Matrix powers versus Binet’s formula.
def mat_mult(X, Y):
return [[X[0][0]*Y[0][0] + X[0][1]*Y[1][0], X[0][0]*Y[0][1] + X[0][1]*Y[1][1]],
[X[1][0]*Y[0][0] + X[1][1]*Y[1][0], X[1][0]*Y[0][1] + X[1][1]*Y[1][1]]]
def mat_pow(M, n):
"""M**n by repeated squaring: O(log n) matrix multiplications."""
result = [[1, 0], [0, 1]] # identity
while n > 0:
if n % 2 == 1: # this binary digit of n is 1
result = mat_mult(result, M)
M = mat_mult(M, M) # square for the next digit
n //= 2
return result
def fib(n):
return mat_pow([[1, 1], [1, 0]], n)[0][1]
print([fib(n) for n in range(1, 13)])
print(fib(100))
phi = (1 + 5 ** 0.5) / 2
print(round(phi ** 100 / 5 ** 0.5)) # Binet, dominant term only
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144] 354224848179261915075 354224848179263111168
The exact answer and the floating-point Binet answer agree in their first 14 digits and then diverge: a 64-bit float carries only about 16 significant digits (Chapter 32). Choose the method by the job: Binet for growth rates and estimates, exact matrix powers for exact values.
28.6. Markov chains and steady states
28.6.1. The problem
A crude weather model: if today is sunny, tomorrow is sunny with probability 0.9 and rainy with probability 0.1. If today is rainy, tomorrow is sunny or rainy with probability 0.5 each. It’s raining today. What’s the chance of sun in a week? In the long run, what fraction of days are sunny?
28.6.2. Setting it up as a matrix
Let \(\mathbf{x}_n = (s_n, r_n)\) be the probabilities of sun and rain on day \(n\). Tomorrow’s chance of sun comes from two routes, sunny-then-sunny and rainy-then-sunny:
Column \(j\) of \(P\) lists where you go from state \(j\); each column sums to 1. After \(n\) days, \(\mathbf{x}_n = P^n\mathbf{x}_0\): a matrix power again.
|
Markov chain, transition matrix, steady state
A Markov chain is a random process that moves between states, where the probability of the next state depends only on the current one (it has no memory of earlier history). Its transition matrix \(P\) has entry \(p_{ij}\) = probability of moving to state \(i\) from state \(j\). With this convention (column-stochastic), columns sum to 1 and distributions update as \(\mathbf{x} \mapsto P\mathbf{x}\). Many statistics texts use the transpose (row-stochastic, rows sum to 1, \(\mathbf{x}^\mathsf{T} \mapsto \mathbf{x}^\mathsf{T}P\)); check which a source uses. A steady state (or stationary distribution) is a probability vector with \(P\mathbf{x} = \mathbf{x}\): an eigenvector with eigenvalue 1, scaled so its entries sum to 1. |
28.6.3. The steady state is an eigenvector
If the distribution settles down to some \(\mathbf{x}^*\), then applying \(P\) once more leaves it unchanged: \(P\mathbf{x}^* = \mathbf{x}^*\). That’s an eigenvector with eigenvalue 1. Solve \((P - I)\mathbf{x} = \mathbf{0}\):
In the long run, five days in six are sunny, regardless of today’s weather.
28.6.4. How fast it gets there: the second eigenvalue
Trace \(1.4\), determinant \(0.45 - 0.05 = 0.4\), so \(\lambda^2 - 1.4\lambda + 0.4 = (\lambda - 1)(\lambda - 0.4) = 0\). One eigenvalue is 1 (the steady state); the other is 0.4. Decompose the starting distribution into the steady state plus a component along the second eigenvector; each day that second component is multiplied by 0.4. So the distance from equilibrium shrinks by a factor of 0.4 per day. Starting from rain, the chance of sun on day \(n\) works out to Try it: Explore the weather chain.
which gives \(s_1 = 0.5\), \(s_2 = 0.7\), \(s_3 = 0.78\), and by day 7, \(0.8320\): within 0.002 of \(5/6\). The code confirms it:
import numpy as np
P = np.array([[0.9, 0.5], # column j = today's state j; rows = tomorrow
[0.1, 0.5]])
x = np.array([0.0, 1.0]) # start: certainly rainy
for day in range(1, 7):
x = P @ x
print(day, x.round(4))
vals, vecs = np.linalg.eig(P)
print(vals)
k = np.argmax(np.isclose(vals, 1.0))
steady = vecs[:, k] / vecs[:, k].sum() # rescale so entries sum to 1
print(steady)
1 [0.5 0.5] 2 [0.7 0.3] 3 [0.78 0.22] 4 [0.812 0.188] 5 [0.8248 0.1752] 6 [0.8299 0.1701] [1. 0.4] [0.83333333 0.16666667]
Why is 1 always an eigenvalue of a transition matrix, and why are the others no bigger than 1 in size? Because the columns sum to 1, multiplying by \(P\) preserves total probability, so no component can grow forever. The gap between 1 and the next-largest eigenvalue size (here \(1 - 0.4 = 0.6\)) is the spectral gap, and it controls the convergence speed: a big gap means the chain forgets its starting point quickly.
|
When there’s no single steady state
Two things can go wrong. A chain that cycles (\(P = \begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix}\): rain always follows sun and vice versa) has a second eigenvalue of \(-1\), so the distribution oscillates forever and never settles, even though a stationary distribution \((\tfrac{1}{2}, \tfrac{1}{2})\) exists. A chain that splits into separate pieces that can’t reach each other has eigenvalue 1 more than once and several steady states, depending on where you start. A chain with neither problem (irreducible and aperiodic) always converges to a unique steady state from any starting distribution; that’s the content of the Perron–Frobenius theorem. PageRank’s "damping" trick exists precisely to force these conditions. |
Try it: Watch a cycling chain.
28.6.5. Where you’d use this
-
Everyday: brand switching and board games. Each month 20% of a phone network’s customers leave for its rival, and 10% of the rival’s customers come the other way. The transition matrix \(\begin{bmatrix} 0.8 & 0.1 \\ 0.2 & 0.9 \end{bmatrix}\) has steady state from \(0.2a = 0.1b\), so \(b = 2a\): in the long run the first network keeps only \(\tfrac{1}{3}\) of the market, wherever it starts. (Halving its churn from 20% to 10% would make the shares equal.) The same eigenvector calculation, with 40 states, shows which Monopoly squares players land on most often. Try it: Brand switching chain.
-
Science: DNA sequence evolution models, chemical reaction networks, the queue at a call centre and credit-rating changes for bonds (rated AA this year, what’s the chance of A next year?) are all Markov chains whose long-run behaviour is an eigenvector.
-
Computing: your phone keyboard’s next-word suggestions began as Markov chains on words; Markov chain Monte Carlo (MCMC) deliberately builds a chain whose steady state is a probability distribution you want to sample, and is the workhorse of Bayesian statistics (Chapter 29); and the random surfer of the next section is a Markov chain on web pages.
28.7. PageRank: the eigenvector that built Google
28.7.1. The problem
In 1998 the web had tens of millions of pages, and keyword search returned thousands of matches for any query. Which matches should come first? Larry Page and Sergey Brin’s idea: a page is important if important pages link to it. That sounds circular, and it is. Eigenvectors are how you solve circular definitions.
28.7.2. The random surfer
Imagine a surfer who starts on a random page and, at every step, clicks a random link on the current page. Over a long time, what fraction of steps does the surfer spend on each page? Pages with many incoming links from frequently visited pages will be visited often. That long-run fraction is the page’s PageRank, and it’s exactly the steady state of a Markov chain whose states are web pages.
Here’s a tiny web:
The link matrix \(M\) has \(m_{ij} = 1/(\text{number of out-links of } j)\) if page \(j\) links to page \(i\), and 0 otherwise. Columns are pages being left; each column splits its page’s "vote" equally among its links (Chapter 25 calls this a weighted adjacency matrix):
28.7.3. Damping: the teleporting surfer
The pure random surfer gets stuck in pages with no out-links and in closed loops, and page D (no incoming links) would get rank zero, which breaks the uniqueness conditions above. The fix: with probability \(d\) (the damping factor, classically 0.85) the surfer follows a random link; with probability \(1 - d\) they get bored and jump to a page chosen uniformly at random. With \(n\) pages, the Google matrix is Try it: Move the damping slider.
where \(J\) is the matrix of all 1s. Every entry of \(G\) is positive, so the Perron–Frobenius theorem guarantees a unique steady state with all-positive entries, and the chain converges to it. The PageRank vector \(\mathbf{r}\) is the eigenvector of \(G\) with eigenvalue 1:
28.7.4. Power iteration
For the real web, \(G\) has billions of rows. You can’t run a textbook eigenvalue algorithm on it. But you don’t need to: you know the eigenvalue you want (1), you know it’s the largest, and the diagonalisation section showed that repeatedly applying a matrix swings any vector towards its dominant eigenvector. So just keep multiplying. Try it: Watch power iteration converge.
Why it works: write \(\mathbf{v}_0 = c_1\mathbf{v}_1 + c_2\mathbf{v}_2 + \cdots\) in eigenvectors. Then \(A^k\mathbf{v}_0 = c_1\lambda_1^k\mathbf{v}_1 + c_2\lambda_2^k\mathbf{v}_2 + \cdots = \lambda_1^k\big(c_1\mathbf{v}_1 + c_2(\lambda_2/\lambda_1)^k\mathbf{v}_2 + \cdots\big)\). If \(|\lambda_1|\) is strictly the largest, every ratio \((\lambda_i/\lambda_1)^k\) shrinks to zero, leaving only the \(\mathbf{v}_1\) direction. Renormalising each step stops the numbers overflowing.
Input: a square matrix \(A\) (for PageRank, the Google matrix \(G\), or just a way to compute \(G\mathbf{v}\)); a tolerance \(\varepsilon\); a maximum number of iterations.
Steps:
-
Start with any vector \(\mathbf{v}\) having a component along the dominant eigenvector (for PageRank, the uniform vector \((1/n, \ldots, 1/n)\)).
-
Compute \(\mathbf{w} = A\mathbf{v}\).
-
Renormalise: \(\mathbf{w} \leftarrow \mathbf{w}/\|\mathbf{w}\|\) (for PageRank, divide by the sum of entries, which for \(G\) stays 1 automatically).
-
If \(\|\mathbf{w} - \mathbf{v}\| < \varepsilon\), stop. Otherwise set \(\mathbf{v} \leftarrow \mathbf{w}\) and go to step 2.
-
Estimate the eigenvalue with the Rayleigh quotient \(\lambda \approx \mathbf{v}^\mathsf{T}A\mathbf{v}\) (for a unit vector \(\mathbf{v}\)); for PageRank it’s 1 by construction.
Output: the dominant eigenvector \(\mathbf{v}\) (the PageRank vector) and its eigenvalue. Convergence is geometric with ratio \(|\lambda_2/\lambda_1|\); for the Google matrix, \(|\lambda_2| \le d\), so the error shrinks at least by \(0.85\) per iteration.
28.7.5. Worked example: ranking the tiny web
Before running code, predict. D has no incoming links, so it only gets teleport traffic: \(r_D = (1 - d)/n = 0.15/4 = 0.0375\). C receives links from three pages (including all of B’s vote and all of D’s) and should rank highest. A receives all of C’s vote, so A should be close behind. B receives only half of A’s vote. Try it: Rank the tiny web; Rank the tiny web.
import numpy as np
pages = ["A", "B", "C", "D"]
links = {"A": ["B", "C"], "B": ["C"], "C": ["A"], "D": ["C"]}
n = len(pages)
idx = {p: i for i, p in enumerate(pages)}
# Link matrix: column j spreads page j's vote equally over its out-links.
M = np.zeros((n, n))
for src, dests in links.items():
for dst in dests:
M[idx[dst], idx[src]] = 1 / len(dests)
d = 0.85 # damping factor
G = d * M + (1 - d) / n * np.ones((n, n)) # the "Google matrix"
r = np.full(n, 1 / n) # start: equal rank everywhere
for it in range(1, 101):
r_new = G @ r
change = np.abs(r_new - r).sum()
r = r_new
if change < 1e-10:
break
print("iterations:", it)
for p in pages:
print(p, round(r[idx[p]], 4))
iterations: 47 A 0.3725 B 0.1958 C 0.3941 D 0.0375
Check the steady-state equations by hand. Each page’s rank is its teleport share plus \(d\) times the votes it receives:
and the four ranks sum to 1. The prediction holds: C first, A a close second (one very good inbound link can be worth as much as several mediocre ones), then B, then the unlinked D.
|
The $25,000,000,000 eigenvector
That was the title of a 2006 paper by Kurt Bryan and Tanya Leise explaining PageRank to undergraduates, the figure being roughly Google’s market value at the time. The idea wasn’t entirely new: in 1976 Gabriel Pinski and Francis Narin ranked scientific journals by an eigenvector of the citation matrix, and in 1953 the sociologist Leo Katz proposed a similar measure of social status. What Page and Brin added was the random-surfer interpretation, the damping trick that makes it well-posed on a messy real web, and the engineering to compute it over the entire web. "PageRank" is a pun: it ranks pages, and it was invented by Larry Page. |
|
Grow your own web
The learner draws pages as nodes and links as arrows. The app animates a crowd of random surfers hopping along links (with occasional teleports), sizes each node by the fraction of surfers on it, and shows the power-iteration vector converging alongside. Sliders: damping factor \(d\) from 0 to 1 (watch rankings flatten towards uniform as \(d \to 0\), and convergence slow as \(d \to 1\)). Challenges: add a "link farm" of pages that all point at one page and see how much rank it gains; create a dead-end page and a closed loop and observe what goes wrong with \(d = 1\). |
28.8. Exercises: powers, chains, and ranking
-
(Recognition) A Markov transition matrix has eigenvalues 1, 0.95 and 0.2. Another has 1, 0.5 and 0.1. Which converges to its steady state faster, and why?
-
(Application) Use diagonalisation to find a formula for \(\begin{bmatrix} 2 & 1 \\ 1 & 2 \end{bmatrix}^n\) (eigenvalues and eigenvectors from the earlier exercise).
-
(Application) A gym member who works out today works out tomorrow with probability 0.7; one who skips today works out tomorrow with probability 0.4. Find the long-run fraction of days spent working out. Try it: Check the gym chain.
-
(Problem solving) In the tiny web, add a link from B to D. Without computing, predict how the ranks of B, C and D change. Then explain why a page with no out-links (a "dangling node") breaks the column-sum property of \(M\), and suggest a fix. Try it: Add the link B to D; Add the link B → D.
Answers
-
The second: its second-largest eigenvalue size is 0.5, so errors shrink by half each step, versus only 5% per step (\(\times 0.95\)) for the first. Spectral gap 0.5 versus 0.05.
-
\(P = \begin{bmatrix} 1 & 1 \\ 1 & -1 \end{bmatrix}\), \(D = \operatorname{diag}(3, 1)\), \(P^{-1} = \tfrac{1}{2}\begin{bmatrix} 1 & 1 \\ 1 & -1 \end{bmatrix}\). \(PD^nP^{-1} = \tfrac{1}{2}\begin{bmatrix} 3^n + 1 & 3^n - 1 \\ 3^n - 1 & 3^n + 1 \end{bmatrix}\). Check \(n = 1\): \(\tfrac{1}{2}(4, 2; 2, 4) = (2, 1; 1, 2)\) ✓.
-
States (work out, skip): \(P = \begin{bmatrix} 0.7 & 0.4 \\ 0.3 & 0.6 \end{bmatrix}\). Steady state: \(-0.3w + 0.4s = 0\), so \(w = \tfrac{4}{3}s\); with \(w + s = 1\), \(s = 3/7\), \(w = 4/7 \approx 0.571\).
-
B’s vote is now split between C and D, so C loses rank and D gains (it finally has an inbound link). A depends entirely on C, so A falls too, and B, which depends on A, falls slightly. Running the code with the extra link confirms it: A 0.3426, B 0.1831, C 0.3590, D 0.1153. A dangling page’s column in \(M\) is all zeros, so the column sums to 0, not 1, and probability "leaks" out each step. Standard fix: treat a dangling page as linking to every page (replace its column with \(1/n\) everywhere), i.e. the surfer teleports from dead ends.
28.9. Symmetric matrices and orthogonal eigenvectors
28.9.1. The observation
In the exercise above, \(\begin{bmatrix} 2 & 1 \\ 1 & 2 \end{bmatrix}\) had eigenvectors \((1, 1)\) and \((1, -1)\), which are perpendicular. That’s no accident. The matrix is symmetric (\(A^\mathsf{T} = A\), Chapter 27), and symmetric matrices are the best-behaved matrices in linear algebra.
|
The spectral theorem
If \(A\) is a real symmetric \(n\times n\) matrix, then:
Geometrically: every symmetric matrix is a pure stretch along some set of perpendicular axes. Rotate to those axes (\(Q^\mathsf{T}\)), stretch (\(\Lambda\)), rotate back (\(Q\)). No shear, no rotation. |
28.9.2. Why the eigenvectors are perpendicular
The proof is short and uses only the transpose rule \(\mathbf{u}\cdot(A\mathbf{v}) = \mathbf{u}^\mathsf{T}A\mathbf{v} = (A^\mathsf{T}\mathbf{u})^\mathsf{T}\mathbf{v}\). Suppose \(A\mathbf{u} = \lambda\mathbf{u}\) and \(A\mathbf{v} = \mu\mathbf{v}\) with \(\lambda \neq \mu\). Compute \(\mathbf{u}^\mathsf{T}A\mathbf{v}\) two ways:
The two are equal, so \((\lambda - \mu)(\mathbf{u}\cdot\mathbf{v}) = 0\). Since \(\lambda \neq \mu\), we must have \(\mathbf{u}\cdot\mathbf{v} = 0\). ∎
Symmetric matrices turn up constantly: covariance matrices (next section), the \(A^\mathsf{T}A\) of least squares, graph Laplacians, the Hessian of second derivatives (Chapter 20), and the stiffness matrices of engineering. Whenever you have one, use np.linalg.eigh, which exploits the symmetry.
28.10. Principal component analysis
28.10.1. The problem
You’ve measured 500 people’s height and arm span. The two numbers are strongly correlated: tall people have long arms. Plotted, the data form an elongated, tilted cloud. Is there a single number that captures most of what distinguishes one person from another? More generally, you have data with 768 features per item (embeddings, again). Can you find a handful of directions that capture most of the variation, to visualise, compress, or denoise it?
28.10.2. Intuition: find the long axis of the cloud
The cloud is long in one direction and thin in the other. If you projected every point onto the long axis (Chapter 26), you’d keep most of the spread between people and lose only the small sideways scatter. That long axis is the direction of maximum variance, and finding it is principal component analysis.
28.10.3. Why it works: variance along a direction is a quadratic form
Centre the data (subtract the mean of each feature), so the cloud sits at the origin. Put the \(N\) centred data points as rows of a matrix \(X\) (\(N\times p\) for \(p\) features). The variance of the data projected onto a unit direction \(\mathbf{u}\) is the average of the squared projections \((\mathbf{x}_i\cdot\mathbf{u})^2\):
The matrix \(C\) is the covariance matrix (\(p\times p\), symmetric). Its diagonal entries are the variances of each feature; its off-diagonal entries are the covariances between pairs of features (Chapter 31). Now write \(C = Q\Lambda Q^\mathsf{T}\) (spectral theorem) and let \(\mathbf{y} = Q^\mathsf{T}\mathbf{u}\), the direction expressed in eigen-coordinates (still a unit vector, since \(Q^\mathsf{T}\) is a rotation):
With \(y_1^2 + \cdots + y_p^2 = 1\), this is a weighted average of the eigenvalues. It’s largest when all the weight goes on the largest eigenvalue: \(\mathbf{y} = (1, 0, \ldots, 0)\), i.e. \(\mathbf{u} = \) the top eigenvector. So:
-
The direction of maximum variance is the eigenvector of the covariance matrix with the largest eigenvalue, and that eigenvalue is the variance along it.
-
The next-best direction perpendicular to the first is the second eigenvector, and so on.
-
The eigenvalues add up to the total variance (the trace of \(C\)), so \(\lambda_k / \sum\lambda_i\) is the fraction of variance explained by component \(k\).
|
Covariance matrix, principal components
|
Input: a data matrix \(X\) with \(N\) rows (observations) and \(p\) columns (features); the number \(k\) of components to keep.
Steps:
-
Centre: subtract each column’s mean. (If features have different units, e.g. centimetres and kilograms, also divide each column by its standard deviation, i.e. standardise.)
-
Covariance: \(C = \frac{1}{N - 1}X^\mathsf{T}X\).
-
Eigen-decompose the symmetric \(C\) (
np.linalg.eigh), and sort eigenvalues from largest to smallest, reordering the eigenvector columns to match. -
Choose \(k\): look at the fraction of variance explained; keep enough components to reach, say, 90–95%, or look for an "elbow" where eigenvalues drop off.
-
Project: scores \(= X Q_k\), where \(Q_k\) holds the top \(k\) eigenvectors.
Output: the principal directions, their variances, and each observation’s \(k\)-dimensional scores.
28.10.4. Worked example: height and arm span
import numpy as np
rng = np.random.default_rng(1)
# 500 people: height (cm) and arm span (cm), strongly correlated.
height = rng.normal(170, 10, 500)
span = height + rng.normal(0, 4, 500)
X = np.column_stack([height, span])
Xc = X - X.mean(axis=0) # 1. centre each column
C = Xc.T @ Xc / (len(X) - 1) # 2. covariance matrix (2 x 2)
vals, vecs = np.linalg.eigh(C) # 3. eigen-decomposition (symmetric)
order = np.argsort(vals)[::-1] # sort largest first
vals, vecs = vals[order], vecs[:, order]
vecs = vecs * np.sign(vecs[0]) # (fix the arbitrary sign of each axis)
print("covariance:\n", C.round(1))
print("variances along principal axes:", vals.round(1))
print("fraction explained:", (vals / vals.sum()).round(3))
print("first principal direction:", vecs[:, 0].round(3))
scores = Xc @ vecs[:, :1] # 4. project onto the top axis
print(scores.shape)
Try it: PCA on height and arm span.
covariance: [[ 83.5 83.5] [ 83.5 101.3]] variances along principal axes: [176.4 8.4] fraction explained: [0.954 0.046] first principal direction: [0.669 0.744] (500, 1)
Interpret each line. The covariance matrix says height varies with variance 83.5 (standard deviation about 9.1 cm), arm span slightly more (101.3, because it adds measurement noise), and they covary strongly (83.5: arm span is height plus independent noise, so their covariance is approximately height’s own variance). The first principal component, \((0.669, 0.744)\), points roughly along the diagonal: "overall size", with both measurements increasing together. It carries 95.4% of the variance. The second, perpendicular to it, is "arms long or short for your height", with only 4.6%. So one number per person, their score on PC1, captures almost everything: two dimensions have been reduced to one with little loss. The trace checks out too: \(83.5 + 101.3 = 184.8 \approx 176.4 + 8.4\).
28.10.5. Choosing a method, and when PCA misleads
PCA finds linear structure. It’s the right first tool for correlated numeric features, for visualising high-dimensional data in 2D, and for compressing before another algorithm. Know its limits:
|
PCA failure modes
|
Try it: See mixed units hijack PC1.
28.10.6. Where you’d use this
-
Everyday: why clothes come in S, M and L. Our height-and-arm-span example is the reason a single "size" works at all: body measurements are so strongly correlated that one number, essentially the score on PC1 ("overall size"), captures most of the variation. Garment designers run PCA on thousands of body scans to choose size charts that fit the most people with the fewest sizes.
-
Science: in a 2008 study of about 1,400 Europeans genotyped at about 200,000 genetic markers, a plot of each person’s scores on the first two principal components reproduced a recognisable map of Europe; geography was the direction of greatest genetic variation. In finance, three principal components of interest-rate movements, interpreted as "level", "slope" and "curvature" of the yield curve, typically explain well over 90% of the day-to-day variation, so banks hedge risk against those three numbers instead of dozens of rates. Climate scientists call principal components of weather maps empirical orthogonal functions; the leading one in the tropical Pacific is the El Niño pattern.
-
Computing: "eigenfaces" (1991) recognised faces by projecting photos onto the top principal components of a face dataset; PCA is also the standard first step before clustering or plotting any high-dimensional data, as the next sidebar describes.
|
Rotate the cloud
A scatter plot of 2D data with a draggable line through the centre. As the learner rotates the line, the app projects every point onto it and shows the variance of the projections as a live number and a dial. The learner hunts for the maximum, then clicks "reveal" to see PC1 drawn from the eigenvector of the covariance matrix, landing exactly on their best line. Further sliders change the correlation and the noise, so the learner sees the eigenvalue ratio (variance explained) track how elongated the cloud is. Try it: Rotate the cloud |
28.11. The singular value decomposition
28.11.1. The problem
Eigenvectors have a frustrating limitation: they only exist for square matrices (a \(3\times 2\) matrix takes 2D vectors to 3D vectors, so "\(A\mathbf{v}\) is a multiple of \(\mathbf{v}\)" makes no sense), and even square matrices may lack enough of them. Is there a decomposition that works for every matrix, of any shape?
28.11.2. Intuition: rotate, scale, rotate
There is, and it’s the most useful factorisation in applied mathematics. Watch what a \(2\times 2\) matrix does to the unit circle: it always produces an ellipse. An ellipse has perpendicular axes. So some pair of perpendicular input directions (on the circle) must map to the perpendicular axes of the ellipse. That gives a three-step description of any matrix:
-
\(V\) (\(n\times n\), orthogonal): its columns \(\mathbf{v}_i\) are the right singular vectors, the perpendicular input directions.
-
\(\Sigma\) (capital sigma, \(m\times n\), diagonal): the singular values \(\sigma_1 \ge \sigma_2 \ge \cdots \ge 0\), the stretch factors.
-
\(U\) (\(m\times m\), orthogonal): its columns \(\mathbf{u}_i\) are the left singular vectors, the output directions (the ellipse’s axes).
And \(A\mathbf{v}_i = \sigma_i\mathbf{u}_i\): each input axis goes to an output axis, scaled. This is the eigenvector idea generalised: you’re allowed different directions on the input and output sides, and in exchange it works for every matrix.
|
Singular value decomposition
Every real \(m\times n\) matrix can be written \(A = U\Sigma V^\mathsf{T}\) with \(U\), \(V\) orthogonal and \(\Sigma\) diagonal with non-negative entries \(\sigma_1 \ge \sigma_2 \ge \cdots\) (the singular values). The singular values are the square roots of the eigenvalues of the symmetric matrix \(A^\mathsf{T}A\); the columns of \(V\) are its eigenvectors. The number of nonzero singular values is the rank of \(A\). For a symmetric matrix with non-negative eigenvalues, the SVD and the eigendecomposition coincide. |
28.11.3. Worked example: a 2×2 SVD by hand
Find the singular values of \(A = \begin{bmatrix} 3 & 0 \\ 4 & 5 \end{bmatrix}\). Try it: SVD as rotate–stretch–rotate; Circle to ellipse, step by step.
Eigenvectors of \(A^\mathsf{T}A\) (same pattern as the \(\begin{bmatrix} 2 & 1 \\ 1 & 2 \end{bmatrix}\) exercise): \(\mathbf{v}_1 = (1, 1)/\sqrt{2}\), \(\mathbf{v}_2 = (1, -1)/\sqrt{2}\). The output directions are \(\mathbf{u}_i = A\mathbf{v}_i/\sigma_i\):
Checks: \(\mathbf{u}_1\cdot\mathbf{u}_2 = (3 - 3)/10 = 0\), both have length 1, and \(\sigma_1\sigma_2 = \sqrt{225} = 15 = |\det A|\) (area scaling is the product of the stretches). The unit circle becomes an ellipse with semi-axes 6.71 along \((1, 3)\) and 2.24 along \((3, -1)\).
28.11.4. Low-rank approximation
Multiply out \(U\Sigma V^\mathsf{T}\) column by column and you get a sum of simple pieces:
Each term \(\mathbf{u}_i\mathbf{v}_i^\mathsf{T}\) (column times row, an outer product) is a rank-1 matrix, and the terms are ordered by importance \(\sigma_i\). Keeping only the first \(k\) terms gives a rank-\(k\) matrix \(A_k\). The Eckart–Young theorem says this truncation is the best possible rank-\(k\) approximation to \(A\): no other rank-\(k\) matrix is closer (in the sense of summed squared entry differences). And storing it takes only \(k(m + n + 1)\) numbers instead of \(mn\).
Input: an \(m\times n\) matrix \(A\) (an image, a ratings table, a term–document matrix) and a target rank \(k\).
Steps:
-
Compute the SVD:
U, s, Vt = np.linalg.svd(A, full_matrices=False). -
Inspect the singular values
s(plot them on a log scale) to choose \(k\): look for where they drop off. -
Keep the first \(k\) columns of \(U\), the first \(k\) singular values and the first \(k\) rows of \(V^\mathsf{T}\).
-
Reconstruct \(A_k = U_k\,\operatorname{diag}(s_k)\,V_k^\mathsf{T}\) if you need the matrix itself, or use the factors directly as compact representations.
Output: \(A_k\), the best rank-\(k\) approximation, with relative error \(\sqrt{\sum_{i>k}\sigma_i^2}\big/\sqrt{\sum_i\sigma_i^2}\).
28.11.5. Worked example: compressing an image
A greyscale image is just a matrix of brightness values. Here’s a synthetic 200 × 200 image (smooth waves plus a bright disc plus noise) and its low-rank approximations: Try it: Compress the image yourself.
import numpy as np
# A synthetic 200 x 200 greyscale "image": smooth shapes plus a little noise.
y, x = np.mgrid[0:200, 0:200] / 200.0
img = (np.sin(6 * x) * np.cos(4 * y)
+ ((x - 0.6) ** 2 + (y - 0.4) ** 2 < 0.05)
+ 0.05 * np.random.default_rng(0).standard_normal((200, 200)))
U, s, Vt = np.linalg.svd(img)
print("largest singular values:", s[:6].round(1))
for k in [1, 5, 20, 50]:
approx = U[:, :k] @ np.diag(s[:k]) @ Vt[:k, :] # rank-k approximation
err = np.linalg.norm(img - approx) / np.linalg.norm(img)
stored = k * (200 + 200 + 1)
print(f"rank {k:2d}: relative error {err:.3f}, numbers stored {stored:6d} of 40000")
largest singular values: [113.5 68.4 18.1 10.5 7.3 5.7] rank 1: relative error 0.544, numbers stored 401 of 40000 rank 5: relative error 0.114, numbers stored 2005 of 40000 rank 20: relative error 0.071, numbers stored 8020 of 40000 rank 50: relative error 0.050, numbers stored 20050 of 40000
The wave part of the image, \(\sin(6x)\cos(4y)\), is a product of a function of \(x\) and a function of \(y\): exactly rank 1. That’s why the first singular value is so dominant. The disc needs a few more terms (its edges are curved, not aligned with rows and columns). Beyond about rank 20 the remaining singular values are noise, and dropping them improves the image by denoising it. Real photographs aren’t this cooperative, which is why JPEG uses a different transform (the discrete cosine transform), but the idea of "keep the big coefficients, drop the small ones" is the same.
28.11.6. Latent semantic indexing: SVD in information retrieval
Recall the blind spot of bag-of-words search (Chapter 26): a query for "car" scores zero against a document that only says "automobile". Latent semantic indexing (LSI, also called latent semantic analysis), introduced by Deerwester, Dumais and colleagues in 1990, fixes this with the SVD. Try it: LSI concept space.
Build the term–document matrix \(A\): one row per term, one column per document. Its rank-\(k\) approximation forces the documents into a \(k\)-dimensional "concept" space. Terms that co-occur across documents ("car", "automobile" and "engine" all appear alongside each other) end up sharing a concept direction, so documents using different words for the same thing get similar concept vectors.
import numpy as np
# Term-document matrix: rows are terms, columns are documents.
terms = ["car", "automobile", "engine", "flower", "petal"]
# d1 d2 d3 d4 d5
A = np.array([[1, 0, 1, 0, 0], # car
[0, 1, 1, 0, 0], # automobile
[1, 1, 1, 0, 0], # engine
[0, 0, 0, 1, 1], # flower
[0, 0, 0, 1, 0]], # petal
dtype=float)
q = np.array([1, 0, 0, 0, 0], dtype=float) # query: "car"
U, s, Vt = np.linalg.svd(A)
k = 2 # keep two "concepts"
Uk, sk = U[:, :k], s[:k]
docs_k = Vt[:k, :].T * sk # documents in concept space
q_k = q @ Uk # query in concept space
def cos(a, b):
return a @ b / (np.linalg.norm(a) * np.linalg.norm(b))
for j in range(5):
print(f"d{j+1}: raw {cos(q, A[:, j]):.2f} latent {cos(q_k, docs_k[j]):.2f}")
d1: raw 0.71 latent 1.00 d2: raw 0.00 latent 1.00 d3: raw 0.58 latent 1.00 d4: raw 0.00 latent 0.00 d5: raw 0.00 latent 0.00
Document d2 never mentions "car" and scores 0.00 on raw cosine similarity. In the 2-concept space it scores as high as the documents that do, because "automobile" and "engine" share the vehicle concept with "car". The flower documents stay at zero. (With only two clear topics, rank 2 sorts everything perfectly; real collections use \(k\) of a few hundred.)
28.11.7. Where you’d use this
-
Everyday: the worked example above is the arithmetic of image compression: the rank-20 picture keeps 8020 of 40,000 numbers, 20% of the storage, with about 7% error. Removing hiss from an old recording or noise from a low-light photo uses the same move: keep the large singular values (the signal) and drop the flat tail (mostly noise).
-
Science and engineering: to line up two 3D scans of the same object, or compare two shapes of the same protein, you need the best rotation taking one set of points onto the other. The standard answer (the Kabsch algorithm) takes the SVD of a \(3\times 3\) matrix built from the points and reads the rotation off \(U\) and \(V\). Medical imaging, seismology and climate science all use the SVD to separate a few strong patterns from noisy measurements, and the ratio \(\sigma_1/\sigma_n\) (the condition number) tells an engineer whether a measurement system can be trusted to invert (Chapter 32).
-
Computing: recommendation, search, model compression and least squares, collected in the next sidebar.
|
Choosing between eigendecomposition, PCA and SVD
|
|
Compress it yourself
The learner uploads a photo (or picks one). The app converts it to greyscale, computes its SVD and shows a slider for the rank \(k\), redrawing the rank-\(k\) image live alongside a plot of the singular values with the cutoff marked, the storage cost, and the relative error. A second panel shows the individual rank-1 "layers" \(\sigma_i\mathbf{u}_i\mathbf{v}_i^\mathsf{T}\) for \(i = 1, 2, 3, \ldots\), so the learner sees the image being built from stripes. A third shows the unit circle turning into an ellipse for a user-chosen \(2\times 2\) matrix, step by step through \(V^\mathsf{T}\), \(\Sigma\) and \(U\). |
28.12. Exercises: symmetric matrices, PCA, and SVD
-
(Recognition) A covariance matrix for three features has eigenvalues 9, 0.9 and 0.1. What fraction of the variance does the first principal component explain? How many components would you keep?
-
(Application) Find the principal directions of data with covariance matrix \(C = \begin{bmatrix} 5 & 2 \\ 2 & 2 \end{bmatrix}\) and the variance along each.
-
(Application) Find the singular values of \(\begin{bmatrix} 2 & 0 \\ 0 & -3 \end{bmatrix}\). Why aren’t they just the eigenvalues?
-
(Problem solving) A 1000 × 800 matrix has singular values that fall from 500 to about 1 by index 30 and stay near 1 after that. Estimate the storage saving of a rank-30 approximation and the relative error, and say what the flat tail probably represents.
Answers
-
Total variance \(9 + 0.9 + 0.1 = 10\), so PC1 explains \(9/10 = 90\%\). PC1 and PC2 together explain 99%. Keep one component if 90% suffices, two for 99%; the third (1%) is probably noise.
-
Trace 7, determinant \(10 - 4 = 6\): \(\lambda^2 - 7\lambda + 6 = (\lambda - 6)(\lambda - 1) = 0\). For 6: \(C - 6I = \begin{bmatrix} -1 & 2 \\ 2 & -4 \end{bmatrix}\), \(x = 2y\), direction \((2, 1)/\sqrt{5}\). For 1: \(C - I = \begin{bmatrix} 4 & 2 \\ 2 & 1 \end{bmatrix}\), \(y = -2x\), direction \((1, -2)/\sqrt{5}\). Perpendicular ✓ (\(2 - 2 = 0\)). Variances 6 and 1; PC1 explains \(6/7 \approx 86\%\).
-
\(A^\mathsf{T}A = \operatorname{diag}(4, 9)\), so \(\sigma = 3, 2\) (sorted). Eigenvalues are 2 and \(-3\); singular values are always non-negative stretch factors, and the sign (a flip) is absorbed into \(U\) or \(V\). Singular values are the absolute values of the eigenvalues here because the matrix is symmetric.
-
Full storage: \(800\,000\) numbers. Rank 30: \(30(1000 + 800 + 1) = 54\,030\), about 6.8% of the original. Relative error: \(\sqrt{\sum_{i>30}\sigma_i^2}/\sqrt{\sum\sigma_i^2}\); the tail has about 770 values near 1, contributing roughly \(770\), while the top 30 contribute at least \(500^2 = 250\,000\), so the error is roughly \(\sqrt{770/250\,000} \approx 0.055\) or less, about 5%. The flat tail is most likely noise: structureless variation spread evenly across all directions.
28.13. Summary
-
An eigenvector of \(A\) is a direction \(A\) only stretches: \(A\mathbf{v} = \lambda\mathbf{v}\). The eigenvalue \(\lambda\) is the stretch factor. Find eigenvalues from \(\det(A - \lambda I) = 0\) (for \(2\times 2\): \(\lambda^2 - (\operatorname{tr}A)\lambda + \det A = 0\)), then eigenvectors from \((A - \lambda I)\mathbf{v} = \mathbf{0}\).
-
Eigenvectors can be complex (rotations) or too few (shears). In code, use
eig, oreighfor symmetric matrices, never polynomial roots. -
Change of basis: \(P\) converts eigen-coordinates to standard ones. Diagonalisation \(A = PDP^{-1}\) makes powers easy: \(A^n = PD^nP^{-1}\). The largest eigenvalue governs the long run.
-
Fibonacci: the matrix \(\begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}\) has eigenvalues \(\varphi, \psi\), giving Binet’s formula; repeated squaring gives exact values in \(O(\log n)\) steps.
-
A Markov chain's steady state is the eigenvector with eigenvalue 1; the second eigenvalue sets the convergence speed. PageRank is the steady state of a damped random surfer, computed by power iteration.
-
Symmetric matrices have real eigenvalues and orthonormal eigenvectors: \(A = Q\Lambda Q^\mathsf{T}\), a pure stretch along perpendicular axes.
-
PCA: the eigenvectors of the covariance matrix are the directions of maximum variance; eigenvalues are the variances.
-
The SVD \(A = U\Sigma V^\mathsf{T}\) (rotate, scale, rotate) works for every matrix. Truncating it gives the best low-rank approximation, used for image compression, latent semantic indexing, recommenders and model compression.
28.14. Interactive tools
Browser tools on the companion website that go with this chapter. Each example link in the chapter opens one of them with that example already set up.
28.15. Mastery check
-
Show that \((1, 2)\) is an eigenvector of \(\begin{bmatrix} 2 & 1 \\ 2 & 3 \end{bmatrix}\) and state its eigenvalue. Then find the other eigenvalue using the trace, without solving a quadratic, and confirm it with the determinant.
-
Find all eigenvalues and eigenvectors of \(\begin{bmatrix} 5 & -2 \\ 1 & 2 \end{bmatrix}\).
-
For the matrix in question 2, write \(A = PDP^{-1}\) and use it to express \(A^{10}(3, 2)\) in terms of \(4^{10}\) and \(3^{10}\). What is \(A^{10}(1, 1)\)?
-
Explain in two or three sentences why a 2D rotation by \(30^\circ\) has no real eigenvectors, and what its eigenvalues are.
-
A Markov chain on two states has transition matrix \(\begin{bmatrix} 0.8 & 0.3 \\ 0.2 & 0.7 \end{bmatrix}\) (column-stochastic). Find its steady state and its second eigenvalue, and say how quickly it converges.
-
In power iteration, why does the starting vector not matter (almost), and what makes convergence slow?
-
Data have covariance matrix \(\begin{bmatrix} 4 & 0 \\ 0 & 1 \end{bmatrix}\). What are the principal components and the fraction of variance explained by the first? Describe the shape of the data cloud.
-
State what each factor in \(A = U\Sigma V^\mathsf{T}\) does geometrically, and explain how truncating it compresses a matrix.
Decision rule: All correct without hesitation → you’ve completed Part VII; move on to Chapter 29 (or Chapter 31, where covariance and regression use this chapter directly). Stuck on 1–2 or 4 → reread Section 28.1 and Section 28.2. Stuck on 3 → reread Section 28.4. Stuck on 5–6 → reread Section 28.6 and Section 28.7. Stuck on 7–8 → reread Section 28.9, Section 28.10 and Section 28.11.
Mastery check answers
-
\(A(1, 2) = (2 + 2,\ 2 + 6) = (4, 8) = 4(1, 2)\), so \((1, 2)\) is an eigenvector with \(\lambda = 4\). The trace is \(2 + 3 = 5 = \lambda_1 + \lambda_2\), so the other eigenvalue is \(5 - 4 = 1\). Determinant check: \(2\cdot 3 - 1\cdot 2 = 4 = 4 \times 1\) ✓. (Its eigenvector: \(A - I = \begin{bmatrix} 1 & 1 \\ 2 & 2 \end{bmatrix}\) gives \((1, -1)\).)
-
Trace 7, determinant \(10 + 2 = 12\): \(\lambda^2 - 7\lambda + 12 = (\lambda - 4)(\lambda - 3) = 0\). For 4: \(A - 4I = \begin{bmatrix} 1 & -2 \\ 1 & -2 \end{bmatrix}\), \(x = 2y\), \(\mathbf{v}_1 = (2, 1)\). Check: \(A(2, 1) = (10 - 2, 2 + 2) = (8, 4)\) ✓. For 3: \(A - 3I = \begin{bmatrix} 2 & -2 \\ 1 & -1 \end{bmatrix}\), \(x = y\), \(\mathbf{v}_2 = (1, 1)\). Check: \(A(1, 1) = (3, 3)\) ✓.
-
\(P = \begin{bmatrix} 2 & 1 \\ 1 & 1 \end{bmatrix}\), \(D = \operatorname{diag}(4, 3)\), \(\det P = 1\), \(P^{-1} = \begin{bmatrix} 1 & -1 \\ -1 & 2 \end{bmatrix}\). Eigen-coordinates of \((3, 2)\): \(P^{-1}(3, 2) = (3 - 2,\ -3 + 4) = (1, 1)\), i.e. \((3, 2) = 1\cdot(2, 1) + 1\cdot(1, 1)\). So \(A^{10}(3, 2) = 4^{10}(2, 1) + 3^{10}(1, 1) = (2\,097\,152 + 59\,049,\ 1\,048\,576 + 59\,049) = (2\,156\,201,\ 1\,107\,625)\). And \((1, 1)\) is itself the eigenvector \(\mathbf{v}_2\), so \(A^{10}(1, 1) = 3^{10}(1, 1) = (59\,049,\ 59\,049)\).
-
A rotation by \(30^\circ\) turns every nonzero vector by \(30^\circ\), so no vector stays on its own line, and there is no real eigenvector. Algebraically, trace \(2\cos 30^\circ = \sqrt{3}\) and determinant 1 give \(\lambda^2 - \sqrt{3}\lambda + 1 = 0\), with discriminant \(3 - 4 < 0\). The eigenvalues are \(\cos 30^\circ \pm i\sin 30^\circ = e^{\pm i\pi/6}\): complex numbers of size 1 at angle \(\pm 30^\circ\), encoding "rotate by 30°, no scaling".
-
Steady state: \(-0.2x + 0.3y = 0\), so \(x = 1.5y\); with \(x + y = 1\), \(y = 0.4\), \(x = 0.6\). Steady state \((0.6, 0.4)\). Trace 1.5, so the second eigenvalue is \(1.5 - 1 = 0.5\) (check: determinant \(0.56 - 0.06 = 0.5 = 1 \times 0.5\) ✓). The distance from equilibrium halves every step.
-
Write the start as a combination of eigenvectors; after \(k\) steps each component is multiplied by \(\lambda_i^k\), and the dominant one outgrows the rest, so any start with a nonzero component along the dominant eigenvector converges to it (a random start almost surely has one). Convergence is slow when \(|\lambda_2/\lambda_1|\) is close to 1, i.e. when the two largest eigenvalues are nearly equal in size; it fails if they’re equal in size.
-
The covariance is already diagonal, so the principal components are the axes: PC1 = \(\mathbf{e}_1\) (variance 4, sd 2), PC2 = \(\mathbf{e}_2\) (variance 1, sd 1). PC1 explains \(4/5 = 80\%\). The cloud is an axis-aligned ellipse, twice as wide (horizontally) as it is tall, with the two features uncorrelated.
-
\(V^\mathsf{T}\) rotates (or reflects) the input so the special perpendicular input directions line up with the axes; \(\Sigma\) stretches each axis by a singular value (and may change dimension); \(U\) rotates the result into place. The matrix equals \(\sum\sigma_i\mathbf{u}_i\mathbf{v}_i^\mathsf{T}\) with terms in decreasing importance; keeping the top \(k\) gives the best rank-\(k\) approximation, stored in \(k(m + n + 1)\) numbers instead of \(mn\), with error determined by the discarded singular values.