v1 <- c(1, 0)
v2 <- c(0, 1)
# Linear combination: 2*v1 + 3*v2
result <- 2*v1 + 3*v2
print("2*v1 + 3*v2 =")[1] "2*v1 + 3*v2 ="
print(result)[1] 2 3
Last week, we worked with a simple model where \(\mathbf{X}'\mathbf{X}\) was just a scalar (the sample size \(n\)). Inverting was trivial: \((n)^{-1} = 1/n\).
But what happens when:
In these cases, \(\mathbf{X}'\mathbf{X}\) becomes a larger matrix that may not be full rank - meaning it doesn’t have a regular inverse. Understanding matrix rank and generalized inverses is essential for:
A vector \(\mathbf{v} = \begin{bmatrix} v_1 \\ v_2 \\ \vdots \\ v_n \end{bmatrix}\) can be thought of as a point in \(n\)-dimensional space.
For example, \(\mathbf{v} = \begin{bmatrix} 2 \\ 3 \end{bmatrix}\) is a point in 2-D space.
A linear combination of vectors \(\mathbf{v}_1, \mathbf{v}_2, \ldots, \mathbf{v}_k\) is:
\[ c_1\mathbf{v}_1 + c_2\mathbf{v}_2 + \cdots + c_k\mathbf{v}_k \]
where \(c_1, c_2, \ldots, c_k\) are scalars.
Example:
v1 <- c(1, 0)
v2 <- c(0, 1)
# Linear combination: 2*v1 + 3*v2
result <- 2*v1 + 3*v2
print("2*v1 + 3*v2 =")[1] "2*v1 + 3*v2 ="
print(result)[1] 2 3
Vectors \(\mathbf{v}_1, \mathbf{v}_2, \ldots, \mathbf{v}_k\) are linearly independent if the only solution to:
\[ c_1\mathbf{v}_1 + c_2\mathbf{v}_2 + \cdots + c_k\mathbf{v}_k = \mathbf{0} \]
is \(c_1 = c_2 = \cdots = c_k = 0\).
In other words, no vector can be written as a linear combination of the others.
Example of linear independence:
# These vectors are linearly independent
v1 <- c(1, 0)
v2 <- c(0, 1)
# No combination (except 0*v1 + 0*v2) gives the zero vector
# They point in different directionsExample of linear dependence:
# These vectors are linearly dependent
v1 <- c(1, 2)
v2 <- c(2, 4) # v2 = 2*v1
# v2 can be written as a multiple of v1
# Therefore: 2*v1 - 1*v2 = 0 (non-trivial combination)
print("2*v1 - v2 =")[1] "2*v1 - v2 ="
print(2*v1 - v2)[1] 0 0
In a design matrix \(\mathbf{X}\):
Rank deficiency means we can’t uniquely estimate all parameters!
The rank of a matrix \(\mathbf{A}\), denoted \(r(\mathbf{A})\), is the number of linearly independent rows (or columns).
Key properties:
A matrix \(\mathbf{A}\) (\(m \times n\)) is:
For a square matrix (\(n \times n\)):
library(MASS)
# Full rank matrix
A_full <- matrix(c(1, 2, 3, 4), nrow = 2, ncol = 2)
print("Full rank matrix:")[1] "Full rank matrix:"
print(A_full) [,1] [,2]
[1,] 1 3
[2,] 2 4
print(paste("Rank:", qr(A_full)$rank))[1] "Rank: 2"
# Rank deficient matrix
A_deficient <- matrix(c(1, 2, 2, 4), nrow = 2, ncol = 2)
print("Rank deficient matrix:")[1] "Rank deficient matrix:"
print(A_deficient) [,1] [,2]
[1,] 1 2
[2,] 2 4
print(paste("Rank:", qr(A_deficient)$rank))[1] "Rank: 1"
# Note: second column is 2 times first columnConsider a beef feedlot study with 4 steers in 2 pens:
# Data: 4 steers, 2 pens (2 steers each)
# Pen: 1, 1, 2, 2
# Design matrix (effects model: overall mean + pen effect)
X <- matrix(c(
1, 1, 0, # Steer 1: in pen 1
1, 1, 0, # Steer 2: in pen 1
1, 0, 1, # Steer 3: in pen 2
1, 0, 1 # Steer 4: in pen 2
), nrow = 4, ncol = 3, byrow = TRUE)
colnames(X) <- c("mu", "pen1", "pen2")
print("Design matrix X:")[1] "Design matrix X:"
print(X) mu pen1 pen2
[1,] 1 1 0
[2,] 1 1 0
[3,] 1 0 1
[4,] 1 0 1
print(paste("Number of columns (parameters):", ncol(X)))[1] "Number of columns (parameters): 3"
print(paste("Rank of X:", qr(X)$rank))[1] "Rank of X: 2"
# X'X
XtX <- t(X) %*% X
print("X'X:")[1] "X'X:"
print(XtX) mu pen1 pen2
mu 4 2 2
pen1 2 2 0
pen2 2 0 2
print(paste("Rank of X'X:", qr(XtX)$rank))[1] "Rank of X'X: 2"
Problem: We have 3 columns but rank = 2. Why?
The first column (overall mean) equals the sum of the second and third columns (pen effects):
\[ \text{Column 1} = \text{Column 2} + \text{Column 3} \]
This is called linear dependence or singularity.
Consequences: - \(\mathbf{X}'\mathbf{X}\) is not invertible (no regular inverse exists) - Individual parameters (\(\mu\), \(\alpha_1\), \(\alpha_2\)) cannot be uniquely estimated - We need constraints or generalized inverses to obtain solutions
Solution: Use generalized inverses (next section) or apply constraints to make the design matrix full rank.
For a square matrix \(\mathbf{A}\) (\(n \times n\)), the inverse \(\mathbf{A}^{-1}\) satisfies:
\[ \mathbf{A}\mathbf{A}^{-1} = \mathbf{A}^{-1}\mathbf{A} = \mathbf{I} \]
Requirements for \(\mathbf{A}^{-1}\) to exist:
# Full rank 2x2 matrix
A <- matrix(c(4, 3, 3, 2), nrow = 2, ncol = 2)
print("Matrix A:")[1] "Matrix A:"
print(A) [,1] [,2]
[1,] 4 3
[2,] 3 2
# Compute inverse
A_inv <- solve(A)
print("A inverse:")[1] "A inverse:"
print(A_inv) [,1] [,2]
[1,] -2 3
[2,] 3 -4
# Verify: A * A^-1 = I
I_check <- A %*% A_inv
print("A * A^-1 (should be identity):")[1] "A * A^-1 (should be identity):"
print(round(I_check, 10)) [,1] [,2]
[1,] 1 0
[2,] 0 1
# Rank deficient matrix
B <- matrix(c(1, 2, 2, 4), nrow = 2, ncol = 2)
print("Rank deficient matrix B:")[1] "Rank deficient matrix B:"
print(B) [,1] [,2]
[1,] 1 2
[2,] 2 4
print(paste("Rank:", qr(B)$rank))[1] "Rank: 1"
print(paste("Determinant:", det(B)))[1] "Determinant: 0"
# Try to invert - this will fail
try({
B_inv <- solve(B)
}, silent = FALSE)Error in solve.default(B) :
Lapack routine dgesv: system is exactly singular: U[2,2] = 0
The error occurs because B is singular (not full rank). We need a generalized inverse instead!
A generalized inverse (g-inverse) of matrix \(\mathbf{A}\) is any matrix \(\mathbf{A}^{-}\) that satisfies:
\[ \mathbf{A}\mathbf{A}^{-}\mathbf{A} = \mathbf{A} \tag{2.1}\]
Key facts:
We now extend our notation from Week 1 to include generalized inverses:
ginv() in RKey distinction from Week 1: - Regular inverse \(\mathbf{A}^{-1}\): requires square, full rank matrix - Generalized inverse \(\mathbf{A}^{-}\): works for any matrix, including rank deficient
This notation will be essential for handling overparameterized models and missing cells in ANOVA (Weeks 7-12).
The Moore-Penrose inverse \(\mathbf{A}^{+}\) is a special g-inverse that satisfies four conditions:
The Moore-Penrose inverse is unique and computed in R using ginv() from the MASS package (Penrose 1955; Moore 1920).
library(MASS)
# Rank deficient matrix
B <- matrix(c(1, 2, 2, 4), nrow = 2, ncol = 2)
print("Rank deficient matrix B:")[1] "Rank deficient matrix B:"
print(B) [,1] [,2]
[1,] 1 2
[2,] 2 4
print(paste("Rank:", qr(B)$rank))[1] "Rank: 1"
# Compute Moore-Penrose generalized inverse
B_ginv <- ginv(B)
print("Generalized inverse B^-:")[1] "Generalized inverse B^-:"
print(B_ginv) [,1] [,2]
[1,] 0.04 0.08
[2,] 0.08 0.16
# Verify the defining property: B * B^- * B = B
check <- B %*% B_ginv %*% B
print("B * B^- * B (should equal B):")[1] "B * B^- * B (should equal B):"
print(round(check, 10)) [,1] [,2]
[1,] 1 2
[2,] 2 4
# Note: B * B^- ≠ I (identity)
BB_ginv <- B %*% B_ginv
print("B * B^- (NOT identity):")[1] "B * B^- (NOT identity):"
print(round(BB_ginv, 10)) [,1] [,2]
[1,] 0.2 0.4
[2,] 0.4 0.8
Regular inverse: \(\mathbf{A}\mathbf{A}^{-1} = \mathbf{I}\)
Generalized inverse: \(\mathbf{A}\mathbf{A}^{-}\mathbf{A} = \mathbf{A}\) (but \(\mathbf{A}\mathbf{A}^{-} \neq \mathbf{I}\) in general)
For rank deficient \(\mathbf{X}'\mathbf{X}\):
\[ \mathbf{X}'\mathbf{X}\mathbf{b} = \mathbf{X}'\mathbf{y} \tag{2.2}\]
One solution is:
\[ \mathbf{b} = (\mathbf{X}'\mathbf{X})^{-}\mathbf{X}'\mathbf{y} \tag{2.3}\]
Important: This gives a solution, but not necessarily a unique solution. Different g-inverses give different \(\mathbf{b}\) vectors.
However, certain functions of \(\mathbf{b}\) (called estimable functions) are unique regardless of which g-inverse is used. We’ll explore this in Week 8.
A system of linear equations can be written as:
\[ \mathbf{A}\mathbf{x} = \mathbf{b} \tag{2.4}\]
Where:
Case 1: Unique Solution
A <- matrix(c(2, 1, 1, 3), nrow = 2, ncol = 2)
b <- c(8, 7)
print("System: Ax = b")[1] "System: Ax = b"
print("A:")[1] "A:"
print(A) [,1] [,2]
[1,] 2 1
[2,] 1 3
print("b:")[1] "b:"
print(b)[1] 8 7
# Unique solution
x <- solve(A) %*% b
print("Solution x:")[1] "Solution x:"
print(x) [,1]
[1,] 3.4
[2,] 1.2
# Verify
print("Ax (should equal b):")[1] "Ax (should equal b):"
print(A %*% x) [,1]
[1,] 8
[2,] 7
Case 2: Infinite Solutions
# System: x1 + 2*x2 = 5
# 2*x1 + 4*x2 = 10 (second equation is 2× first)
A <- matrix(c(1, 2, 2, 4), nrow = 2, ncol = 2, byrow = TRUE)
b <- c(5, 10)
print("System (second equation redundant):")[1] "System (second equation redundant):"
print(A) [,1] [,2]
[1,] 1 2
[2,] 2 4
print(paste("Rank:", qr(A)$rank))[1] "Rank: 1"
# One solution using generalized inverse
A_ginv <- ginv(A)
x_particular <- A_ginv %*% b
print("One particular solution:")[1] "One particular solution:"
print(x_particular) [,1]
[1,] 1
[2,] 2
# But infinitely many solutions exist!
# Any x = [5 - 2*t, t] for any t worksCase 3: No Solution (Inconsistent)
# System: x1 + 2*x2 = 5
# 2*x1 + 4*x2 = 12 (contradicts first equation!)
A <- matrix(c(1, 2, 2, 4), nrow = 2, ncol = 2, byrow = TRUE)
b_inconsistent <- c(5, 12)
print("Inconsistent system:")[1] "Inconsistent system:"
print(A) [,1] [,2]
[1,] 1 2
[2,] 2 4
print("b:")[1] "b:"
print(b_inconsistent)[1] 5 12
# ginv() gives "least squares" solution (minimizes ||Ax - b||)
x_ls <- ginv(A) %*% b_inconsistent
print("Least squares solution (doesn't satisfy exactly):")[1] "Least squares solution (doesn't satisfy exactly):"
print(x_ls) [,1]
[1,] 1.16
[2,] 2.32
print("Ax (does NOT equal b):")[1] "Ax (does NOT equal b):"
print(A %*% x_ls) [,1]
[1,] 5.8
[2,] 11.6
In genetic evaluation:
We almost always encounter Case 2 in animal breeding, which is why understanding generalized inverses and estimability is crucial.
Let’s work through a complete example with rank deficiency.
Three breeds of pigs with unequal replication:
Litter sizes: Yorkshire (11, 12, 10), Landrace (10, 11), Duroc (9)
# Litter size data
litter_size <- c(11, 12, 10, 10, 11, 9)
breed <- factor(c("Yorkshire", "Yorkshire", "Yorkshire",
"Landrace", "Landrace", "Duroc"))
data_pig <- data.frame(breed = breed, litter_size = litter_size)
print(data_pig) breed litter_size
1 Yorkshire 11
2 Yorkshire 12
3 Yorkshire 10
4 Landrace 10
5 Landrace 11
6 Duroc 9
Model: \(y_{ij} = \mu_i + e_{ij}\) where \(i\) = breed, \(j\) = observation within breed
# Design matrix: indicator for each breed
X_cell <- model.matrix(~ breed - 1, data = data_pig)
print("Cell means design matrix:")[1] "Cell means design matrix:"
print(X_cell) breedDuroc breedLandrace breedYorkshire
1 0 0 1
2 0 0 1
3 0 0 1
4 0 1 0
5 0 1 0
6 1 0 0
attr(,"assign")
[1] 1 1 1
attr(,"contrasts")
attr(,"contrasts")$breed
[1] "contr.treatment"
print(paste("Rank of X:", qr(X_cell)$rank))[1] "Rank of X: 3"
# This is full rank! (3 columns, rank = 3)
# Solve using normal equations
y <- litter_size
XtX <- t(X_cell) %*% X_cell
Xty <- t(X_cell) %*% y
print("X'X:")[1] "X'X:"
print(XtX) breedDuroc breedLandrace breedYorkshire
breedDuroc 1 0 0
breedLandrace 0 2 0
breedYorkshire 0 0 3
print("X'y:")[1] "X'y:"
print(Xty) [,1]
breedDuroc 9
breedLandrace 21
breedYorkshire 33
# Invert (regular inverse exists)
b_cell <- solve(XtX) %*% Xty
print("Breed means (cell means model):")[1] "Breed means (cell means model):"
print(b_cell) [,1]
breedDuroc 9.0
breedLandrace 10.5
breedYorkshire 11.0
# These are simply the breed averages!
print("Verify - breed averages:")[1] "Verify - breed averages:"
print(tapply(litter_size, breed, mean)) Duroc Landrace Yorkshire
9.0 10.5 11.0
Model: \(y_{ij} = \mu + \alpha_i + e_{ij}\) where \(\mu\) = overall mean, \(\alpha_i\) = breed effect
# Design matrix: intercept + breed effects
X_effects <- model.matrix(~ breed, data = data_pig)
print("Effects model design matrix:")[1] "Effects model design matrix:"
print(X_effects) (Intercept) breedLandrace breedYorkshire
1 1 0 1
2 1 0 1
3 1 0 1
4 1 1 0
5 1 1 0
6 1 0 0
attr(,"assign")
[1] 0 1 1
attr(,"contrasts")
attr(,"contrasts")$breed
[1] "contr.treatment"
print(paste("Number of columns:", ncol(X_effects)))[1] "Number of columns: 3"
print(paste("Rank of X:", qr(X_effects)$rank))[1] "Rank of X: 3"
# Rank deficient! (4 columns but rank = 3)
# X'X is singular
XtX_effects <- t(X_effects) %*% X_effects
print("X'X (effects model):")[1] "X'X (effects model):"
print(XtX_effects) (Intercept) breedLandrace breedYorkshire
(Intercept) 6 2 3
breedLandrace 2 2 0
breedYorkshire 3 0 3
print(paste("Rank of X'X:", qr(XtX_effects)$rank))[1] "Rank of X'X: 3"
print(paste("Determinant of X'X:", det(XtX_effects)))[1] "Determinant of X'X: 6"
# Cannot use regular inverse - use generalized inverse
XtX_ginv <- ginv(XtX_effects)
Xty_effects <- t(X_effects) %*% y
b_effects <- XtX_ginv %*% Xty_effects
print("Solution using generalized inverse:")[1] "Solution using generalized inverse:"
print(b_effects) [,1]
[1,] 9.0
[2,] 1.5
[3,] 2.0
Note: The solution depends on which generalized inverse we use. R’s ginv() sets one parameter to zero by default.
Let’s verify estimable functions are consistent:
# Contrast: Yorkshire - Duroc
# In cell means model: mu_Yorkshire - mu_Duroc
contrast_cell <- b_cell[1] - b_cell[3]
# In effects model: alpha_Yorkshire - alpha_Duroc
# (mu cancels out)
contrast_effects <- b_effects[2] - b_effects[3]
print("Yorkshire - Duroc difference:")[1] "Yorkshire - Duroc difference:"
print(paste("Cell means model:", round(contrast_cell, 4)))[1] "Cell means model: -2"
print(paste("Effects model:", round(contrast_effects, 4)))[1] "Effects model: -0.5"
print(paste("Match:", all.equal(contrast_cell, contrast_effects)))[1] "Match: Mean relative difference: 0.75"
The contrast (difference between breeds) is the same regardless of parameterization! This is an estimable function.
A sheep researcher measures fleece weight (kg) for 3 breeds across 2 farms, but not all breed × farm combinations are present (unbalanced design).
set.seed(456)
# Farm-Breed combinations (missing cells)
farm <- factor(c(rep("A", 5), rep("B", 4)))
breed <- factor(c(rep("Merino", 3), rep("Suffolk", 2), # Farm A
rep("Suffolk", 2), rep("Romney", 2))) # Farm B
# Note: No Merino on Farm B, No Romney on Farm A
fleece_weight <- c(
# Farm A: Merino
4.8, 5.1, 4.9,
# Farm A: Suffolk
5.5, 5.8,
# Farm B: Suffolk
5.2, 5.4,
# Farm B: Romney
5.9, 6.1
)
sheep_data <- data.frame(
farm = farm,
breed = breed,
fleece_weight = fleece_weight
)
print("Sheep fleece weight data:")[1] "Sheep fleece weight data:"
print(sheep_data) farm breed fleece_weight
1 A Merino 4.8
2 A Merino 5.1
3 A Merino 4.9
4 A Suffolk 5.5
5 A Suffolk 5.8
6 B Suffolk 5.2
7 B Suffolk 5.4
8 B Romney 5.9
9 B Romney 6.1
# Check structure
table(sheep_data$farm, sheep_data$breed)
Merino Romney Suffolk
A 3 0 2
B 0 2 2
# Full model: farm + breed + farm×breed
# But missing cells cause rank deficiency
X_full <- model.matrix(~ farm * breed, data = sheep_data)
print("Design matrix (first 5 rows):")[1] "Design matrix (first 5 rows):"
print(head(X_full, 5)) (Intercept) farmB breedRomney breedSuffolk farmB:breedRomney
1 1 0 0 0 0
2 1 0 0 0 0
3 1 0 0 0 0
4 1 0 0 1 0
5 1 0 0 1 0
farmB:breedSuffolk
1 0
2 0
3 0
4 0
5 0
print(paste("Number of columns:", ncol(X_full)))[1] "Number of columns: 6"
print(paste("Rank:", qr(X_full)$rank))[1] "Rank: 4"
# Rank deficient due to missing cells
# Solve using generalized inverse
y_sheep <- fleece_weight
XtX <- t(X_full) %*% X_full
Xty <- t(X_full) %*% y_sheep
print("X'X:")[1] "X'X:"
print(XtX) (Intercept) farmB breedRomney breedSuffolk farmB:breedRomney
(Intercept) 9 4 2 4 2
farmB 4 4 2 2 2
breedRomney 2 2 2 0 2
breedSuffolk 4 2 0 4 0
farmB:breedRomney 2 2 2 0 2
farmB:breedSuffolk 2 2 0 2 0
farmB:breedSuffolk
(Intercept) 2
farmB 2
breedRomney 0
breedSuffolk 2
farmB:breedRomney 0
farmB:breedSuffolk 2
print(paste("Rank of X'X:", qr(XtX)$rank))[1] "Rank of X'X: 4"
# Generalized inverse solution
XtX_ginv <- ginv(XtX)
b_sheep <- XtX_ginv %*% Xty
print("Parameter estimates:")[1] "Parameter estimates:"
print(b_sheep) [,1]
[1,] 4.93333333
[2,] 0.07333333
[3,] 0.49666667
[4,] 0.71666667
[5,] 0.49666667
[6,] -0.42333333
# Estimable contrasts within farm
# Suffolk at Farm A vs Suffolk at Farm B
# (these cells both exist, so difference is estimable)
# Verify with lm()
model_sheep <- lm(fleece_weight ~ farm * breed, data = sheep_data)
print("lm() coefficients:")[1] "lm() coefficients:"
print(coef(model_sheep)) (Intercept) farmB breedRomney breedSuffolk
4.9333333 -0.3500000 1.4166667 0.7166667
farmB:breedRomney farmB:breedSuffolk
NA NA
When dealing with missing cells or rank deficiency:
ginv())This week covered essential linear algebra for linear models:
ginv() in RWeek 3: Build design matrices for different model types
Week 4-5: Regression models (usually full rank)
Week 7-8: ANOVA models (often rank deficient) and estimable functions
Week 12: Deep dive into non-full rank models and constraints
qr(A)$rank - Compute rank of matrix Asolve(A) - Regular inverse of Aginv(A) - Moore-Penrose generalized inverse (MASS package)det(A) - Determinant of Amodel.matrix() - Create design matrix from formulaWork through Exercise Set 2 to solidify these concepts!
Previous: Week 1: Course Overview