Skip to content

QR Decomposition

QR decomposition factors a matrix into orthogonal and upper triangular components.

Basic Decomposition

1. linalg.qr

import numpy as np
from scipy import linalg

def main():
    A = np.array([[1, 2],
                  [3, 4],
                  [5, 6]])

    Q, R = linalg.qr(A)

    print("A =")
    print(A)
    print()
    print("Q (orthogonal):")
    print(Q)
    print()
    print("R (upper triangular):")
    print(R)
    print()
    print("Verify Q @ R:")
    print(Q @ R)

if __name__ == "__main__":
    main()

2. Mathematical Form

\[A = QR\]

where: - \(Q\) is orthogonal (\(Q^TQ = I\)) - \(R\) is upper triangular

3. Orthogonality Check

import numpy as np
from scipy import linalg

def main():
    A = np.array([[1, 2],
                  [3, 4],
                  [5, 6]])

    Q, R = linalg.qr(A)

    print("Q.T @ Q (should be identity):")
    print((Q.T @ Q).round(10))

if __name__ == "__main__":
    main()

Mode Options

1. Full vs Reduced

import numpy as np
from scipy import linalg

def main():
    A = np.array([[1, 2],
                  [3, 4],
                  [5, 6]])  # 3x2

    # Full QR (default)
    Q_full, R_full = linalg.qr(A, mode='full')
    print(f"Full: Q shape {Q_full.shape}, R shape {R_full.shape}")

    # Economic/reduced QR
    Q_econ, R_econ = linalg.qr(A, mode='economic')
    print(f"Economic: Q shape {Q_econ.shape}, R shape {R_econ.shape}")

if __name__ == "__main__":
    main()

2. R Only

import numpy as np
from scipy import linalg

def main():
    A = np.array([[1, 2],
                  [3, 4],
                  [5, 6]])

    # Only compute R (faster)
    R = linalg.qr(A, mode='r')

    print("R only:")
    print(R)

if __name__ == "__main__":
    main()

3. Pivoting

import numpy as np
from scipy import linalg

def main():
    A = np.array([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])

    # QR with column pivoting: A @ P = Q @ R
    Q, R, P = linalg.qr(A, pivoting=True)

    print("Permutation P:")
    print(P)
    print()
    print("R (reveals rank):")
    print(R.round(10))

if __name__ == "__main__":
    main()

Solving Least Squares

1. QR for Overdetermined Systems

import numpy as np
from scipy import linalg

def main():
    # Overdetermined: more equations than unknowns
    A = np.array([[1, 1],
                  [1, 2],
                  [1, 3]])
    b = np.array([1, 2, 2])

    Q, R = linalg.qr(A, mode='economic')

    # Solve R @ x = Q.T @ b
    x = linalg.solve_triangular(R, Q.T @ b)

    print(f"Least squares solution: x = {x}")
    print(f"Residual: {np.linalg.norm(A @ x - b):.4f}")

if __name__ == "__main__":
    main()

2. Comparison with lstsq

import numpy as np
from scipy import linalg

def main():
    A = np.array([[1, 1],
                  [1, 2],
                  [1, 3],
                  [1, 4]])
    b = np.array([1, 2, 2, 3])

    # Method 1: QR
    Q, R = linalg.qr(A, mode='economic')
    x_qr = linalg.solve_triangular(R, Q.T @ b)

    # Method 2: lstsq
    x_lstsq, *_ = np.linalg.lstsq(A, b, rcond=None)

    print(f"QR solution:    {x_qr}")
    print(f"lstsq solution: {x_lstsq}")

if __name__ == "__main__":
    main()

Gram-Schmidt Connection

1. QR as Orthonormalization

import numpy as np
from scipy import linalg

def main():
    # Columns of A
    A = np.array([[1, 1],
                  [1, 0],
                  [0, 1]])

    Q, R = linalg.qr(A, mode='economic')

    print("Original columns (A):")
    print(A)
    print()
    print("Orthonormal columns (Q):")
    print(Q)
    print()
    print("Column norms:")
    for i in range(Q.shape[1]):
        print(f"  ||q{i+1}|| = {np.linalg.norm(Q[:, i]):.4f}")
    print()
    print("Dot product q1 ยท q2:", np.dot(Q[:, 0], Q[:, 1]).round(10))

if __name__ == "__main__":
    main()

2. R Contains Projections

import numpy as np
from scipy import linalg

def main():
    A = np.array([[3, 1],
                  [4, 0],
                  [0, 2]])

    Q, R = linalg.qr(A, mode='economic')

    print("R diagonal = column norms of original (after orthogonalization):")
    print(f"R[0,0] = {R[0,0]:.4f}")
    print(f"R[1,1] = {R[1,1]:.4f}")

if __name__ == "__main__":
    main()

Matrix Rank

1. Rank from QR

import numpy as np
from scipy import linalg

def main():
    # Rank-deficient matrix
    A = np.array([[1, 2, 3],
                  [4, 5, 6],
                  [7, 8, 9]])  # Rank 2

    Q, R, P = linalg.qr(A, pivoting=True)

    print("R diagonal:")
    print(np.diag(R))
    print()

    # Count non-negligible diagonal entries
    tol = 1e-10
    rank = np.sum(np.abs(np.diag(R)) > tol)
    print(f"Numerical rank: {rank}")

if __name__ == "__main__":
    main()

2. Rank Estimation

import numpy as np
from scipy import linalg

def main():
    # Create rank-3 matrix of size 5x4
    np.random.seed(42)
    U = np.random.randn(5, 3)
    V = np.random.randn(3, 4)
    A = U @ V  # Rank at most 3

    Q, R, P = linalg.qr(A, pivoting=True)

    print("R diagonal (magnitude indicates rank):")
    print(np.abs(np.diag(R)))

if __name__ == "__main__":
    main()

Applications

1. Linear Regression

import numpy as np
from scipy import linalg

def main():
    # Data
    x = np.array([0, 1, 2, 3, 4])
    y = np.array([1, 2.1, 2.9, 4.2, 4.8])

    # Design matrix
    A = np.column_stack([np.ones_like(x), x])

    # QR solve
    Q, R = linalg.qr(A, mode='economic')
    coeffs = linalg.solve_triangular(R, Q.T @ y)

    intercept, slope = coeffs
    print(f"y = {slope:.3f}x + {intercept:.3f}")

if __name__ == "__main__":
    main()

2. Orthonormal Basis

import numpy as np
from scipy import linalg

def main():
    # Find orthonormal basis for column space
    A = np.array([[1, 2, 3],
                  [1, 0, 1],
                  [0, 1, 1],
                  [1, 1, 2]])

    Q, R = linalg.qr(A, mode='economic')

    print("Orthonormal basis for column space of A:")
    print(Q)

if __name__ == "__main__":
    main()

3. QR Iteration (Eigenvalues)

import numpy as np
from scipy import linalg

def main():
    A = np.array([[4, 1, 0],
                  [1, 3, 1],
                  [0, 1, 2]])

    # Simple QR iteration
    Ak = A.copy()
    for _ in range(30):
        Q, R = linalg.qr(Ak)
        Ak = R @ Q

    print("Eigenvalues (QR iteration):")
    print(np.diag(Ak))
    print()
    print("Eigenvalues (direct):")
    print(np.sort(np.linalg.eigvalsh(A))[::-1])

if __name__ == "__main__":
    main()

Summary

1. Functions

Function Description
linalg.qr(A) Full QR decomposition
linalg.qr(A, mode='economic') Reduced QR
linalg.qr(A, mode='r') R only
linalg.qr(A, pivoting=True) With column pivoting

2. Key Properties

  • \(Q^TQ = I\) (orthogonal)
  • \(R\) is upper triangular
  • Numerically stable for least squares
  • Column pivoting reveals rank