Orthogonal Complement
Fix a subspace inside a -dimensional space. Every vector in the space now splits into two parts — the part that lies in , and the part that does not — and this page says three things about that split which are stronger than you might expect:
it is unique, the leftover part lives in a subspace of its own, and the two parts satisfy Pythagoras exactly.
That is the entire content of §3.6, and it is what makes “the part PCA keeps” and “the part PCA discards” well-defined objects rather than loose talk.
What you’ll learn
Section titled “What you’ll learn”- The definition of the orthogonal complement , and why it is a subspace rather than just a set.
- The dimension count , and the fact that .
- Equation 3.36: the unique decomposition of any vector into a -part and a -part.
- Pythagoras for that split, measured over four thousand vectors in .
- Why a plane in is described by a single normal vector, and how that generalises to hyperplanes.
- A real dataset whose orthogonal complement turns out to be spanned by exactly three coordinate axes — identified, not guessed.
Intuition: the shadow and the height
Section titled “Intuition: the shadow and the height”Stand a pencil on a table. Its shadow on the tabletop is the part of it that lies in the plane of the table; the height of its tip above the table is the part that does not. Two numbers-worth of shadow, one number of height, three numbers of pencil.
That accounting is exactly the dimension count. And there is only one way to do it: the shadow is determined by the pencil, and so is the height. You cannot trade a bit of shadow for a bit of height — that is the uniqueness claim.
The Pythagorean part is the one worth pausing on. The squared length of the pencil equals the squared shadow plus the squared height. Not the lengths, the squares. Which means a component that looks small contributes even less than it looks: a direction carrying of the length carries of the squared length, and squared length is what variance is.
flowchart TD V["a D-dimensional space V"] V --> U["a subspace U, dim M"] U --> PERP["U-perp: every vector orthogonal to ALL of U
dim D − M"] PERP --> SUB["U-perp is itself a SUBSPACE
closed under sums and scaling"] PERP --> TRIV["U ∩ U-perp = {0}
only the zero vector is in both"] TRIV --> UNIQ["so x = p + q is UNIQUE
Eq 3.36"] UNIQ --> PYTH["and ‖x‖² = ‖p‖² + ‖q‖²
because ⟨p, q⟩ = 0"] PYTH --> ML["variance kept + variance lost = total
PCA, Ch 10"] PERP --> NORM["dim U-perp = 1 ⟹ ONE normal vector
describes the whole of U
hyperplanes, Ch 12"]
The math
Section titled “The math”Three points, in order of how easy they are to overlook.
is a subspace, not merely a set. If and are both orthogonal to everything in , then so is , by bilinearity: . So has a basis, a dimension, and everything else Chapter 2 gives a subspace.
The intersection is trivial. If is in both, it is orthogonal to itself, so , so by positive definiteness. Note which axiom did the work — this is one of the places where the positive-definiteness requirement on the inner product earns its keep.
“Orthogonal to every vector in ” only needs checking on a basis. By bilinearity, being orthogonal to is enough to be orthogonal to every combination of them. That turns an infinite condition into equations, which is what makes computable at all: it is the null space of the matrix whose rows are a basis of .
The unique decomposition
Section titled “The unique decomposition”Write for the first sum and for the second. Then , , and — which gives Pythagoras with no further work:
The cross term vanishing is the whole proof. Everything Chapter 10 says about “variance explained” rests on this line and on nothing else.
Normal vectors and hyperplanes
Section titled “Normal vectors and hyperplanes”The special case is the one you meet most often. A plane through the origin in has , so its complement is one-dimensional: a single direction , the normal vector, describes the entire plane, because
Two numbers’ worth of plane, described by one vector — that is the trade a codimension-one subspace offers, and it scales: a hyperplane in has dimension and is still described by one normal vector. Chapter 12 builds the support vector machine on precisely this, and the distance from a point to the plane has the closed form
which the figures below check against a brute-force search.
Worked example by hand
Section titled “Worked example by hand”Take the plane spanned by
and the vector .
Step 1 — find . It is one-dimensional, and the cross product gives a vector orthogonal to both:
Its length is , so the unit normal is
Step 2 — the part of . It is the projection onto the normal direction:
Step 3 — the part is whatever is left.
Step 4 — check. The right thing to test is the displacement , which is , against a basis of . Testing against instead would prove nothing, because lies in and has every reason to have a nonzero inner product with a basis vector of it.
| check | expected | measured |
|---|---|---|
| from the residual | — | |
| from | the same |
The last two rows agree exactly, and they have to: for a unit normal, , so dividing by is doing nothing. The division matters only when you skip the normalisation step and work with an unnormalised normal vector, which is the usual case in practice.
import numpy as np
b1 = np.array([1.0, 0.6, -0.3])
b2 = np.array([0.2, 1.0, 0.7])
x = np.array([0.9, -0.4, 1.6])
# U-perp is one-dimensional; the cross product spans it.
w = np.cross(b1, b2)
print("cross product:", w, " length:", np.linalg.norm(w))
w = w / np.linalg.norm(w)
print("unit normal: ", np.round(w, 6))
q = float(w @ x) * w # the U-perp part
p = x - q # the U part
print("<w, x> =", round(float(w @ x), 6))
print("q (in U-perp):", np.round(q, 6))
print("p (in U): ", np.round(p, 6))
print("<b1, q> =", f"{b1 @ q:.3e}", " <b2, q> =", f"{b2 @ q:.3e}")
print("Pythagoras: |p|^2 + |q|^2 =", round(float(p @ p + q @ q), 6),
" |x|^2 =", round(float(x @ x), 6))
print("distance from the residual:", round(float(np.linalg.norm(q)), 6))
print("distance from |<w,x>|: ", round(abs(float(w @ x)), 6))cross product: [ 0.72 -0.76 0.88] length: 1.3676256797823008
unit normal: [ 0.52646 -0.555708 0.643451]
<w, x> = 1.725618
q (in U-perp): [ 0.908469 -0.958939 1.110351]
p (in U): [-0.008469 0.558939 0.489649]
<b1, q> = -4.318e-17 <b2, q> = -4.931e-17
Pythagoras: |p|^2 + |q|^2 = 3.53 |x|^2 = 3.53
distance from the residual: 1.725618
distance from |<w,x>|: 1.725618Both orthogonality checks land below and Pythagoras is exact to the printed precision. The two routes to the distance agree exactly, which they must: for a unit normal is , so the division by does nothing.
See it move
Section titled “See it move”The first sketch is the split in the plane, where is a line and is the perpendicular line. Drag either the vector or the subspace.
The second sketch is the codimension-one case, where a single normal vector describes the whole subspace and the distance formula falls out.
From scratch
Section titled “From scratch”import numpy as np
def complement_basis(U_cols):
"""An orthonormal basis of the orthogonal complement of the column span.
The complement is the null space of the matrix whose ROWS are a basis of U,
and an SVD reads that off directly: the right singular vectors beyond the
rank span it.
"""
A = np.asarray(U_cols, dtype=float)
_, sv, Vt = np.linalg.svd(A.T, full_matrices=True)
rank = int(np.sum(sv > sv.max() * len(sv) * np.finfo(float).eps))
return Vt[rank:]
def split(x, U_cols):
"""Decompose x into its U part and its U-perp part."""
Q, _ = np.linalg.qr(np.asarray(U_cols, dtype=float))
p = Q @ (Q.T @ x)
return p, x - p
# Pythagoras, measured over 4000 random vectors against a fixed subspace.
rng = np.random.default_rng(23)
n, m, samples = 5, 2, 4000
Q, _ = np.linalg.qr(rng.normal(size=(n, m)))
P = Q @ Q.T
Pc = np.eye(n) - P
X = rng.normal(size=(samples, n)) * rng.uniform(0.3, 3.0, size=(samples, 1))
p = X @ P.T
q = X @ Pc.T
whole = np.sum(X ** 2, axis=1)
parts = np.sum(p ** 2, axis=1) + np.sum(q ** 2, axis=1)
print("vectors split: ", samples)
print("largest absolute gap: ", f"{np.max(np.abs(parts - whole)):.3e}")
print("largest relative gap: ", f"{np.max(np.abs(parts - whole) / whole):.3e}")
print("largest |<p, q>|: ", f"{np.max(np.abs(np.sum(p * q, axis=1))):.3e}")
print("dim U + dim U-perp: ", f"{m} + {n - m} = {n}")
print("complement basis shape: ", complement_basis(Q).shape)
print("complement really orthogonal to U:",
f"{np.abs(complement_basis(Q) @ Q).max():.3e}")vectors split: 4000
largest absolute gap: 2.842e-14
largest relative gap: 6.054e-16
largest |<p, q>|: 1.066e-14
dim U + dim U-perp: 2 + 3 = 5
complement basis shape: (3, 5)
complement really orthogonal to U: 1.243e-16Every claim on the page is in that output. The relative gap in Pythagoras is — under three units in the last place, over four thousand vectors — and the complement basis really is orthogonal to , to .
On real data
Section titled “On real data”Reading the plot
Section titled “Reading the plot”From the Pythagoras scatter. The colour is doing real work. It runs from dark (almost none of the vector’s length is inside ) to bright (almost all of it is), so the sample is not concentrated on some convenient middle case. Every one of the four thousand points sits on the diagonal regardless, and the largest relative deviation is .
The other measured column is the one to remember: peaks at . The cross term is what has to vanish for Pythagoras, and it does — which is the only reason the two squared parts add up. Split a vector along two non-perpendicular directions and the squares do not add.
From the three-dimensional plane. The measured distance from the residual is and the brute-force scan over a grid gives . The disagreement is in the sixth decimal and it is the grid’s fault, not the formula’s: with a spacing of over the plane, the nearest sampled point cannot be closer than the true foot. The closed form is exact; the search is the approximation. That ordering is easy to get backwards.
From the pixel grid. This is the figure to keep. The centred digit matrix has rank in dimensions, so its orthogonal complement is exactly -dimensional — and that complement is not an abstract three-dimensional thing, it is the span of the coordinate axes of pixels 0, 32 and 39. The comparison is measured: .
Those three pixels are zero in all images. Any direction that only moves them takes you nowhere the data can go, which is precisely what “in the orthogonal complement of the data” means. The abstract statement here reads , and every term is countable.
It also explains the arithmetic on the Basis and Rank page from the other direction: the rank is because three pixels never vary, and .
Pitfalls
Section titled “Pitfalls”Compare
Section titled “Compare”| object | dimension | how it is specified | what it is used for |
|---|---|---|---|
| subspace | spanning vectors | the part you keep | |
| complement | spanning vectors, or the null space of ‘s basis | the part you discard: residuals, reconstruction error | |
| hyperplane | one normal vector | decision boundaries (Ch 12) | |
| line | one direction vector | one principal component | |
| projector onto | rank | for orthonormal | §3.8 |
| projector onto | rank | residual maker |
The last row is worth memorising: the residual operator is one minus the projector, and its rank is the complement’s dimension. It appears in every least-squares derivation in Chapter 9.
-
Why is the decomposition x = p + q into a U-part and a U-perp-part unique?
And the trivial intersection itself comes from positive definiteness: a vector in both is orthogonal to itself, so its inner product with itself is zero, so it is the zero vector.
pch.quizShowAnswer
B — Because U and U-perp share only the zero vector — if there were two decompositions, their difference would be a nonzero vector lying in both subspaces at once — And the trivial intersection itself comes from positive definiteness: a vector in both is orthogonal to itself, so its inner product with itself is zero, so it is the zero vector.
-
The centred digit matrix has rank 61 in 64 pixel dimensions. What is its orthogonal complement?
The dimension count reads 61 + 3 = 64 with every term countable. It is also the reason the rank is 61 rather than 64 in the first place.
pch.quizShowAnswer
B — The span of the coordinate axes of pixels 0, 32 and 39 — the three that are zero in all 1797 images, verified by comparing the two projectors, which differ by 1.4e-14 — The dimension count reads 61 + 3 = 64 with every term countable. It is also the reason the rank is 61 rather than 64 in the first place.
-
The brute-force grid search finds a distance of 1.725620 where the closed form gives 1.725618. Which is more accurate?
The grid spacing is 0.005 over the plane. Getting the direction of this bias backwards is a common way to distrust a correct formula.
pch.quizShowAnswer
B — The closed form. A grid over the plane cannot land exactly on the foot of the perpendicular, so a search can only ever overestimate — the sixth-decimal difference is the grid spacing, not formula error — The grid spacing is 0.005 over the plane. Getting the direction of this bias backwards is a common way to distrust a correct formula.
-
Which statement about the orthogonal complement is wrong?
Orthogonal complement is not set complement. A line's complement in three dimensions is a plane, whereas the set of vectors not on the line is nearly all of the space. The correct relationship is a direct sum: U plus U-perp equals V.
pch.quizShowAnswer
C — It is the set of all vectors not in U — Orthogonal complement is not set complement. A line's complement in three dimensions is a plane, whereas the set of vectors not on the line is nearly all of the space. The correct relationship is a direct sum: U plus U-perp equals V.
🧪 Try It Yourself
Section titled “🧪 Try It Yourself”Exercise 1 – Find a complement basis
Section titled “Exercise 1 – Find a complement basis”Exercise 2 – Verify Pythagoras 4000 times
Section titled “Exercise 2 – Verify Pythagoras 4000 times”Exercise 3 – Break Pythagoras with an oblique split
Section titled “Exercise 3 – Break Pythagoras with an oblique split”Exercise 4 – Identify the digits complement
Section titled “Exercise 4 – Identify the digits complement”Exercise 5 – The residual operator
Section titled “Exercise 5 – The residual operator”Recall card
Section titled “Recall card”- The orthogonal complement is every vector orthogonal to all of U, and it is a subspace rather than a set, because orthogonality to a fixed vector is a linear condition.
- Its dimension is D minus the dimension of U, and the two subspaces share only the zero vector — which follows from positive definiteness, since a vector in both is orthogonal to itself.
- Checking orthogonality against a basis of U suffices, so the complement is the null space of the matrix whose rows are that basis.
- Every vector splits uniquely into a U part and a U-perp part, Equation 3.36.
- Pythagoras holds for that split because the cross term is zero. Measured over four thousand vectors in five dimensions, the largest relative gap was six times ten to the minus sixteen.
- A codimension-one subspace is described by a single normal vector, and the distance from a point to it is the absolute inner product divided by the normal’s length — which is the basis of the support vector machine in Chapter 12.
- The residual operator is one minus the projector; it is itself a projector, its rank is the complement’s dimension, and its trace equals its rank.
- The handwritten-digit data has rank 61 of 64, and its orthogonal complement is exactly the span of the three pixel axes that are zero in every image.
Next: Inner Product of Functions — the same definitions, with the sum replaced by an integral.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading