Quant Memo
Core

QR Decomposition

Splitting a matrix into an orthogonal piece and a triangular piece — the numerically stable engine behind real-world least-squares regression.

Prerequisites: Gram-Schmidt Orthogonalization, Inner Products and Orthogonality

Fitting a regression by literally computing (XTX)1XTy(X^TX)^{-1}X^Ty works on paper but is numerically fragile: squaring XX into XTXX^TX squares any near-collinearity problems in the data before you even start inverting. QR decomposition sidesteps this entirely — it factors the design matrix directly, without ever forming XTXX^TX, and is the actual method most serious regression libraries use under the hood.

An analogy: a rotate-then-stretch recipe

Any matrix's effect on space can be described as first rotating (and possibly reflecting) everything rigidly, without any distortion, and then stretching or shearing along the new axes. QR decomposition writes exactly this recipe down explicitly: QQ is the pure rotation/reflection part — it preserves lengths and angles perfectly, like a rigid camera turn — and RR is the triangular "stretch" part that does all the actual distorting. Because QQ never distorts anything, it never amplifies numerical errors, which is precisely why algorithms built on QR tend to be far more numerically trustworthy than ones that square the matrix first.

The mathematics, one symbol at a time

QR decomposition factors a matrix AA (say, m×nm \times n with mnm \geq n, a tall design matrix) as

A=QR,A = QR,

where QQ is an m×nm \times n matrix with orthonormal columns (each column has length 1, and every pair of columns is orthogonal, so QTQ=IQ^TQ = I) and RR is an n×nn \times n upper-triangular matrix. In words: QQ's columns form a "clean," non-overlapping coordinate system for the same space AA's columns span, built via Gram-Schmidt (see Gram-Schmidt Orthogonalization), and RR records how the original columns of AA combine those clean directions to reconstruct themselves. For solving least squares, AxbAx \approx b becomes QRxbQRx \approx b; multiplying both sides by QTQ^T (which, crucially, exactly undoes QQ since QTQ=IQ^TQ=I, with no numerical error introduced) gives Rx=QTbRx = Q^Tb — a small, easy, upper-triangular system, solved directly by back-substitution, with none of the error-amplifying squaring that (XTX)1XTy(X^TX)^{-1}X^Ty requires.

Worked example 1: QR by hand via Gram-Schmidt

Take AA's columns v1=(3,1)v_1=(3,1), v2=(2,2)v_2=(2,2) — the same pair from the Gram-Schmidt example. Orthogonalizing gave u1=(3,1)u_1=(3,1), u2=(0.4,1.2)u_2=(-0.4,1.2). Normalize: u1=103.162\|u_1\|=\sqrt{10}\approx3.162, so q1=(0.949,0.316)q_1=(0.949,0.316); u2=0.16+1.44=1.61.265\|u_2\|=\sqrt{0.16+1.44}=\sqrt{1.6}\approx1.265, so q2=(0.316,0.949)q_2=(-0.316,0.949). These form QQ. Then R=QTAR = Q^TA: r11=q1,v1=u1=3.162r_{11}=\langle q_1,v_1\rangle = \|u_1\| = 3.162, r12=q1,v2=0.949(2)+0.316(2)2.530r_{12}=\langle q_1,v_2\rangle=0.949(2)+0.316(2)\approx2.530, r22=u2=1.265r_{22}=\|u_2\|=1.265 (and r21=0r_{21}=0 by construction). So R(3.1622.53001.265)R\approx\begin{pmatrix}3.162 & 2.530\\0&1.265\end{pmatrix} — upper-triangular, exactly as required.

Worked example 2: solving a small regression via QR

Reusing the earlier one-variable regression setup, A=(123)A=\begin{pmatrix}1\\2\\3\end{pmatrix}, b=(235)b=\begin{pmatrix}2\\3\\5\end{pmatrix}: A=1+4+9=143.742\|A\|=\sqrt{1+4+9}=\sqrt{14}\approx3.742, so q1=A/3.742(0.267,0.535,0.802)q_1 = A/3.742 \approx (0.267,0.535,0.802), and R=[A]=[3.742]R = [\,\|A\|\,] = [3.742] (a 1×11\times1 matrix here, since there's only one column). Then QTb=0.267(2)+0.535(3)+0.802(5)=0.535+1.604+4.0086.147Q^Tb = 0.267(2)+0.535(3)+0.802(5) = 0.535+1.604+4.008\approx6.147. Solving Rβ=QTbR\beta = Q^Tb: 3.742β=6.147β1.6433.742\beta = 6.147 \Rightarrow \beta\approx1.643 — matching the earlier pseudoinverse answer of 23/141.64323/14\approx1.643 exactly, but reached without ever squaring AA into ATAA^TA.

A Q (pure rotation, length-preserving) = R (triangular stretch)
QR splits any matrix into a rigid, error-free rotation Q and a triangular stretch R — because Q never distorts distances, no error gets amplified when it's applied or undone.
degree of collinearity in data normal equations: error grows fast QR: error grows much slower
As predictors become more collinear, error in the normal-equations approach (X-transpose-X inverse) grows far faster than error in QR-based least squares, because squaring the matrix squares its condition number.

What this means in practice

QR decomposition is the default method inside numpy.linalg.lstsq, R's lm(), and most production regression code precisely because it avoids ever forming XTXX^TX, keeping numerical error manageable even with moderately collinear factors. It's also the standard way to compute an orthonormal basis for a subspace, and shows up as a building block inside eigenvalue algorithms and the singular value decomposition (see Singular Value Decomposition).

QR decomposition writes A=QRA=QR with QQ orthonormal (a pure, error-free rotation) and RR upper-triangular; solving least squares via Rx=QTbRx=Q^Tb avoids ever forming XTXX^TX, making it the numerically preferred alternative to the normal-equations formula (XTX)1XTy(X^TX)^{-1}X^Ty.

Don't assume QR and the normal-equations formula always give meaningfully different numerical answers — on well-conditioned, nearly-orthogonal data they agree closely, and the difference only becomes practically important as predictors get more collinear (see The Matrix Condition Number). The mistake is picking the normal-equations formula out of habit for any regression pipeline that might later be fed correlated factors, rather than defaulting to QR (or SVD) as the numerically robust choice from the start.

Related concepts

Practice in interviews

Further reading

  • Trefethen & Bau, Numerical Linear Algebra, lecture 7
  • Golub & Van Loan, Matrix Computations, ch. 5
ShareTwitterLinkedIn