QR Decomposition & Gram-Schmidt
Orthogonalizing vectors, and the numerically stable way to do least squares.
On this page
Beginner: orthogonalization means turning a set of vectors into a set that all point perpendicular to each other, without changing what they span (section 1.9). The classic recipe for doing this is the Gram-Schmidt process: take each new vector, subtract off whatever part of it points along the directions you already have, and keep only what's left over — which is automatically perpendicular to everything before it.
Intermediate: QR decomposition packages this idea into a matrix factorization: any matrix A can be written as A = QR, where Q has orthonormal columns (the Gram-Schmidt-ed, unit-length version of A's columns) and R is upper-triangular (recording exactly how much of each original column was "redundant" with the earlier ones).
Advanced: QR gives a numerically superior way to solve least-squares regression compared to the normal equation from section 1.6 (w = (XᵀX)⁻¹Xᵀy). Forming XᵀX squares the condition number of X, amplifying numerical error; solving via QR avoids that squaring entirely, which is why serious statistical software defaults to a QR-based solver rather than the textbook formula.
Gram-Schmidt, step by step, for vectors a₁, a₂: e₁ = a₁/‖a₁‖, then e₂ = (a₂ − (a₂·e₁)e₁), normalized. Each new vector only ever has the previous directions subtracted out.
Given a fixed direction e₁ (unit length) and a new vector a₂, define the projection coefficient c = a₂·e₁ and subtract it off:
Claim: u is exactly perpendicular to e₁. Proof: compute the dot product directly:
(using e₁·e₁ = ‖e₁‖² = 1, since e₁ is a unit vector). This holds for any choice of a₂ — c is defined precisely so this cancellation always happens, which is exactly the live check shown in the diagram's readout.
Where this is used: repeating this subtraction against every previously built direction, one at a time, is the entire Gram-Schmidt algorithm — each new vector is only ever guaranteed perpendicular to what came immediately before it, which is exactly why the process must go through the vectors in order.
Blue is the fixed reference direction. Drag orange — grey dashed is the projection being removed, green is what's left: always exactly perpendicular to blue.
np.linalg.lstsq is what you should reach for in practice — but knowing it's doing QR (not the textbook normal equation) explains why it's the more numerically trustworthy choice.
import numpy as np
X = np.array([[1., 1.], [1., 2.], [1., 3.], [1., 4.]]) # design matrix
y = np.array([2., 4., 5., 8.])
Q, R = np.linalg.qr(X)
print(np.allclose(Q.T @ Q, np.eye(2))) # True -> Q's columns are orthonormal
# Solve the least-squares fit via QR instead of the fragile normal equation:
w = np.linalg.solve(R, Q.T @ y)
print(w) # intercept, slope
# NumPy's own least-squares solver uses this same idea internally:
w2, *_ = np.linalg.lstsq(X, y, rcond=None)
print(np.allclose(w, w2)) # True- Robotics and computer graphics use QR (or the related Householder reflections) to keep a sequence of rotation matrices numerically "clean" — repeated multiplication of rotation matrices slowly accumulates floating-point drift away from true orthogonality, and re-orthogonalizing via QR fixes it.
- Every serious linear regression / least-squares solver (R's
lm(), scikit-learn'sLinearRegression, NumPy'slstsq) uses QR or SVD internally instead of the raw normal equation.
- Implementing "classical" Gram-Schmidt naively for many vectors — it's numerically unstable in practice; real libraries use modified Gram-Schmidt or Householder reflections, which are mathematically equivalent but far more stable in floating point.
- Solving least squares via
(XᵀX)⁻¹Xᵀydirectly in code that matters — prefernp.linalg.lstsqor an explicit QR/SVD-based solve.
Going deeper
QR decomposition isn't just for least squares — the QR algorithm (repeatedly factoring a matrix as QR, then multiplying the factors back together in reverse order, and iterating) is the actual method general-purpose numerical libraries use to compute eigenvalues (section 1.7) for matrices larger than 2×2 or 3×3. The "solve the characteristic polynomial" method taught in school is essentially never used in real software — it's numerically unreliable for anything but the smallest matrices.
At the master level: the columns of Q form an orthonormal basis for the same column space as A — meaning QR is simultaneously an orthogonalization procedure, a rank-revealing factorization, and (via the QR algorithm) an eigenvalue solver, which is a lot of mileage from one relatively simple idea.