The idea: subtract the shadows, then normalize

Vectors a1, a2, a3 can span a space and still be awkward to work with: they lean on each other and have all sorts of lengths. The Gram–Schmidt process replaces them with vectors q1, q2, q3 that span the same space but are orthonormal: each has length 1 and each is perpendicular to the others. The recipe is one sentence: take the next vector, subtract its shadows on the q's you already have, and normalize what is left.

Left: a1 = (3, 1) and a2 = (2, 2) from the origin, q1 along a1, the purple shadow (q1·a2) q1 = (2.4, 0.8) of a2 on q1, and the dashed drop from the tip of a2 to the shadow's foot with a right-angle mark. Right: the remainder u2 = (−0.4, 1.2) slid to the origin, then shrunk to q2 = (−1, 3)/√10, perpendicular to q1 = (3, 1)/√10
a2 minus its shadow on q1 is the dashed drop u2: it is perpendicular to q1, and divided by its length it becomes q2.

The steps, one vector at a time

  1. q1 = a1 / |a1|. Nothing to subtract yet; just make it length 1.
  2. u2 = a2 − (q1·a2) q1, then q2 = u2 / |u2|.
  3. u3 = a3 − (q1·a3) q1 − (q2·a3) q2, then q3 = u3 / |u3|.

The shadow of a on a unit vector q is (q·a) q: the foot of the perpendicular dropped from the tip of a onto q's line, exactly as on the Projection Matrix page. What is left, the remainder u, is the dashed drop itself, so it meets q at a right angle. You can check it in one line: q1·u2 = q1·a2 − (q1·a2)(q1·q1) = 0, because q1·q1 = 1. For a3 the two shadows add up to its shadow on the plane of q1 and q2, and the drop is perpendicular to that whole plane. (Demo 1: Two vectors in 2D, Demo 2: Three vectors in 3D)

The start example, worked out exactly:

vectorremainder u|u|q
a1 = (0, 1, 1)(0, 1, 1)√2(0, 1, 1)/√2
a2 = (1, 0, 1)a2 − ½(0, 1, 1) = (1, −½, ½)√6/2(2, −1, 1)/√6
a3 = (1, 1, 0)a3 − (0, ½, ½) − (1/3, −1/6, 1/6) = (2/3, 2/3, −2/3)2√3/3(1, 1, −1)/√3

Q and R: A = QR

Read the steps backwards: every aj is rebuilt from the q's. a1 = r11 q1, a2 = r12 q1 + r22 q2, a3 = r13 q1 + r23 q2 + r33 q3, where the numbers are the ones the process used:

  • rij = qi·aj above the diagonal (how far aj reaches along qi),
  • rjj = |uj| on the diagonal (the length of what was left).

Put the a's as the columns of A, the q's as the columns of Q and the numbers in R: that is A = QR. R is upper triangular because aj only uses q1 … qj; the later q's were not made yet when aj was handled. For the start example R = [√2, √2/2, √2/2; 0, √6/2, √6/6; 0, 0, 2√3/3]. The page fills Q and R entry by entry, then rebuilds each column of A to check. (Demo 3: Build Q and R, check A = QR)

A = QR for a1 = (0, 1, 1), a2 = (1, 0, 1), a3 = (1, 1, 0): A with columns a1, a2, a3 equals Q with columns (0, 1, 1)/√2, (2, −1, 1)/√6, (1, 1, −1)/√3 times R = [√2, √2/2, √2/2; 0, √6/2, √6/6; 0, 0, 2√3/3]. The zeros below R's diagonal are greyed, and arrows show that column 2 of R uses only q1 and q2
Column j of R lists how much of q1 … qj builds aj; nothing below the diagonal, so R is upper triangular.

QᵀQ = I

Entry (i, j) of QᵀQ is qi·qj: 1 when i = j (unit length) and 0 otherwise (perpendicular). So QᵀQ = I, and when Q is square Q−1 = Qᵀ: undoing Q costs only a transpose. Multiplying by Q keeps lengths and angles, which is why numerical methods like it. (Demo 6: Check: QᵀQ = I)

A dependent vector leaves nothing

Take a3 = a1 + a2 = (1, 1, 2). It lies in the plane of q1 and q2, so its shadow on that plane is the whole vector: a3 = (3√2/2) q1 + (√6/2) q2, and the remainder is u3 = 0. There is nothing to normalize, so there is no q3, r33 = 0, and the rank is 2, not 3. A zero remainder is how Gram–Schmidt finds out that a vector depends on the ones before it. (Demo 4: A dependent vector leaves nothing)

Nearly parallel vectors: cancellation

a1 = (4, 1) and a2 = (5, 1) are about 2.7° apart. Almost all of a2 is its shadow: (q1·a2) q1 = (21/17)(4, 1), and the remainder u2 = (1/17, −4/17) has length √17/17 ≈ 0.24, about 4.8% of |a2| ≈ 5.1. Exactly, that is fine. In floating point it is not: subtracting two nearly equal numbers keeps their rounding errors but loses the leading digits, so the computed q2 can be visibly not perpendicular to q1. Libraries use modified Gram–Schmidt (subtract one shadow at a time from the updated vector) or Householder reflections instead. (Demo 5: Nearly parallel: a tiny remainder)

Where QR is used

  • Least squares: to fit Ax ≈ b, write A = QR. The normal equations AᵀAx = Aᵀb become Rx = Qᵀb, solved from the bottom up because R is triangular, without forming AᵀA (see Least Squares).
  • The QR algorithm for eigenvalues: factor A = QR, multiply back the other way RQ, repeat. The matrices keep the same eigenvalues and approach a triangular one with the eigenvalues on its diagonal (see Eigenvectors).
  • An orthonormal basis for a column space, for projections (P = QQᵀ) and for changing coordinates.

See also Linear Transformations, Vectors and the Dot Product, Projection Matrix and its 3D version Projection onto a Plane, Column Space and Null Space, Gaussian Elimination (A = LU, the other triangular factorization), Matrix Multiplication, Inverse Matrix, Rotation Matrix and 3D Rotation Matrix (orthogonal matrices), 3D Reflection Matrix, SVD and PCA and Diagonalization.

What the page leaves out

Modified Gram–Schmidt, re-orthogonalization and the Householder and Givens ways to compute QR, which are what numerical libraries use. Floating-point error itself (the page keeps every number exact). Pivoting to handle dependent columns, the full versus reduced QR (a square Q with extra columns when A is tall), more than three vectors, complex vectors, other inner products (Gram–Schmidt on functions gives the Legendre polynomials) and the shifted QR algorithm.