Contents
,
Deep Breakdown
What it tells me (Situation Awareness): This is the seed step of the Gram-Schmidt orthogonalization process. Before you can subtract projections (the iterative part), you need to start somewhere: take the first vector from your input set, keep it as-is for the unnormalized basis (), and normalize it to unit length (). In DA/DS, Gram-Schmidt appears whenever you need an orthonormal basis from a set of linearly independent vectors: QR decomposition, orthogonal feature construction, building orthogonal polynomial bases for regression, or constructing orthonormal frames for 3D coordinate systems in spatial analytics.
Flowchart
Real-world use
Where it is used
You see this initialization step whenever you perform QR decomposition by hand or implement a numerical solver that needs to build an orthonormal basis incrementally. It is also the conceptual starting point when you think about why PCA loads are orthogonal: the first principal component is just a normalized direction in feature space, analogous to .
Problem it solves
An arbitrary set of linearly independent vectors is not orthogonal. Gram-Schmidt converts them into an orthonormal set that spans the same space. The initialization sets up the first "clean" direction from which all subsequent vectors will be made orthogonal.
How it solves the problem
Setting preserves the first direction exactly. Normalizing gives with . All subsequent steps will subtract the component of each new vector along (and earlier vectors), ensuring the resulting basis is orthonormal. The initialization is trivial because there is nothing to project against yet.
Example in a business
A data engineer builds a custom feature matrix for a regression model. The raw features include revenue, cost, and margin (which are linearly dependent: margin = revenue - cost). After dropping one redundant feature, they apply Gram-Schmidt to the remaining linearly independent features to decorrelate them before feeding into a ridge regression. The initialization picks the first feature vector (e.g., revenue), normalizes it, and from that point the process removes revenue's component from all other features.
The formula, piece by piece
- : the first unnormalized basis vector, set equal to (the first input vector); notation uses for "unnormalized" and interchangeably in some textbooks
- / : the first input vector from the set to be orthogonalized; no projection is applied here
- : the first orthonormal basis vector; the hat () denotes unit-norm normalization
- : the Euclidean norm of , equal to ; dividing by this scales to unit length
- The formula is just vector normalization: point in the same direction as but with length 1 Full formula: ,
The formula in words
"The first orthonormal basis vector is just the first input vector rescaled to have length 1. No projection is needed yet because there are no previous basis vectors to be orthogonal to." Tiny numeric example: . . . . Verify: .
Critical Concepts
- Direction is preserved: normalization changes only the length, not the direction; points exactly where points.
- Zero vector is invalid: if , division is undefined and the input set is not linearly independent; always check .
- Notation variation: some texts write , , or for the first orthonormal vector; the process is identical regardless of notation.
- Connection to QR: in the QR decomposition , the first column of is exactly , and the first diagonal entry of is .
- Numerical stability: the classical Gram-Schmidt process can accumulate floating-point errors in later steps; the modified Gram-Schmidt process or Householder reflections are preferred in production numerical solvers.
- This step alone is just normalization: the orthogonalization only happens in the subsequent steps () when projections are subtracted.
Step-by-step calculation (tiny example)
Step 1: Identify the first input vector Given input set: , , . Take directly: .
Step 2: Compute the norm of the first vector .
Step 3: Normalize to get the first orthonormal basis vector . Verify: . The first orthonormal basis vector is ready; proceed to .
Code examples
Beginner example (Python)
import numpy as np # -- Gram-Schmidt initialization: normalize the first vector -----------
v1 = np.array([1.0, 1.0, 0.0])
norm_v1 = float(np.linalg.norm(v1))
q1_hat = v1 / norm_v1
print(f"v1 = {v1}")
print(f"||v1|| = {norm_v1:.4f}")
print(f"q_hat_1 = {q1_hat.round(4)}")
print(f"||q_hat_1|| = {np.linalg.norm(q1_hat):.6f} (should be 1.0)") # Also verify: q_hat_1 and v1 point in the same direction
cos_angle = float(q1_hat @ v1 / (np.linalg.norm(q1_hat) * np.linalg.norm(v1)))
print(f"cos(angle between q_hat_1 and v1) = {cos_angle:.6f} (should be 1.0: same direction)")Beginner example (R)
# -- Gram-Schmidt initialization: normalize the first vector -----------
v1 <- c(1.0, 1.0, 0.0)
norm_v1 <- sqrt(sum(v1^2))
q1_hat <- v1 / norm_v1
cat(sprintf("v1 = %s\n", paste(round(v1, 4), collapse = ", ")))
cat(sprintf("||v1|| = %.4f\n", norm_v1))
cat(sprintf("q_hat_1 = %s\n", paste(round(q1_hat, 4), collapse = ", ")))
cat(sprintf("||q_hat_1|| = %.6f (should be 1.0)\n", sqrt(sum(q1_hat^2))))
cos_angle <- sum(q1_hat * v1) / (sqrt(sum(q1_hat^2)) * sqrt(sum(v1^2)))
cat(sprintf("cos(angle between q_hat_1 and v1) = %.6f (should be 1.0)\n", cos_angle))Professional example (Python)
import numpy as np
import pandas as pd
np.random.seed(42) # -- Business scenario: build an orthonormal feature basis for a regression model
# 4 raw marketing features: spend, impressions, clicks, conversions.
# They are correlated. Gram-Schmidt initialization picks the first orthonormal direction.
N_WEEKS = 100
FEATURE_NAMES = ["spend_keur", "impressions_m", "clicks_k", "conversions"] # Simulate correlated weekly feature data
true_cov = np.array([ [4.0, 2.0, 1.5, 0.8], [2.0, 3.0, 1.8, 0.9], [1.5, 1.8, 2.0, 1.0], [0.8, 0.9, 1.0, 1.5],
])
L_sim = np.linalg.cholesky(true_cov)
feature_data = np.random.normal(size=(N_WEEKS, len(FEATURE_NAMES))) @ L_sim.T
df = pd.DataFrame(feature_data, columns=FEATURE_NAMES) # Extract column vectors as the input to Gram-Schmidt
feature_matrix = df.to_numpy()
v1 = feature_matrix[:, 0] # first feature: spend # Gram-Schmidt initialization
norm_v1 = float(np.linalg.norm(v1))
q1_hat = v1 / norm_v1 # Show properties of the first orthonormal basis vector
cov_q1_q1 = float(q1_hat @ q1_hat)
print(f"First feature column: '{FEATURE_NAMES[0]}'")
print(f"||v1|| = {norm_v1:.4f}")
print(f"||q_hat_1|| = {np.linalg.norm(q1_hat):.6f} (should be 1.0)")
print(f"<q_hat_1, q_hat_1> = {cov_q1_q1:.6f} (should be 1.0)")
print(f"q_hat_1 direction (first 5 obs): {q1_hat[:5].round(4)}") # Verify it spans the same subspace as v1
explained = float((q1_hat @ v1) ** 2) / float(v1 @ v1)
print(f"\nVariance of v1 explained by q_hat_1: {explained:.4f} (should be 1.0: same span)")
print("\nInterpretation: q_hat_1 is the unit-norm version of the first feature direction; subsequent Gram-Schmidt steps will build orthogonal directions from the remaining features.")Source: domain knowledge