12  Week 12: Unequal Subclass Numbers & Non-Full Rank Models

NoteLearning Objectives

By the end of this week, you will be able to:

  1. Understand the consequences of unbalanced data on design matrix rank and the resulting impact on parameter estimation
  2. Compute and use generalized inverses to solve normal equations when \(\mathbf{X}'\mathbf{X}\) is singular
  3. Determine which linear functions of parameters are estimable in rank-deficient models using the row space criterion
  4. Apply constraints (set-to-zero, sum-to-zero) to obtain unique parameter solutions and interpret their effects
  5. Analyze realistic multi-farm livestock datasets with missing cells using appropriate methods (Type III SS, estimable contrasts, emmeans)

12.1 Introduction: When Real Data Gets Messy

In Weeks 7 and 8, we worked primarily with balanced designs where every group had equal sample sizes. The mathematics was clean: \(\mathbf{X}'\mathbf{X}\) was full rank, orthogonal contrasts partitioned sums of squares neatly, and every parameter was uniquely estimable. Real livestock data is rarely so cooperative.

Consider these common scenarios in animal breeding and genetics:

  • Sire evaluation: Some bulls have 3 progeny records, others have 500. Dairy genetic evaluations routinely handle sires with vastly different progeny group sizes.
  • Multi-farm breed comparisons: Not all breeds are raised on all farms. A Charolais breeder in Texas may not also raise Holsteins.
  • Longitudinal studies: Animal mortality and dropout create unbalanced groups. A 24-month feeding trial that starts with equal groups rarely ends that way.
  • Commercial records: Observational data from commercial operations is inherently unbalanced. Farmers make decisions—culling, treatment, grouping—that create inequality.

When data is unbalanced, three critical things happen:

  1. \(\mathbf{X}'\mathbf{X}\) may not be full rank → The normal equations have infinitely many solutions
  2. Type I, II, and III sums of squares differ → The order of fitting effects matters
  3. Individual parameters may not be estimable → But contrasts (differences) often are

This week, we confront the reality of unbalanced data head-on. We’ll learn:

  • Why rank deficiency occurs and what it means
  • How to solve singular systems using generalized inverses
  • Which functions are estimable and which are not
  • How to use constraints to get unique solutions
  • Practical methods for analyzing real unbalanced datasets
ImportantWhy This Week Matters in Animal Breeding

In real animal breeding programs:

  • Sires have 3 to 500+ progeny (progeny test designs are highly unbalanced)
  • Contemporary groups vary from 5 to 200 animals
  • Some breed × environment combinations never occur (climate, management, economics)
  • Missing data is the norm, not the exception

Understanding rank deficiency and estimability is not academic—it’s essential for genetic evaluation, breed comparisons, and making sound breeding decisions from messy commercial data.

NoteConnection to Previous Weeks

This week synthesizes concepts from:

  • Week 2: Generalized inverses (now we use them!)
  • Week 7: Cell means vs. effects models (now unbalanced)
  • Week 8: Estimable functions and contrasts (critical when rank deficient)
  • Week 10: Type I/II/III sums of squares (essential for unbalanced ANOVA)

Week 12 is where all these pieces unite to handle real-world complexity.

12.1.1 Chapter Roadmap

Here’s what we’ll cover:

  1. Mathematical Theory (Sections 2-5): Rank deficiency, generalized inverses, estimability, constraints
  2. Small Example (Section 6): Sheep fleece weight by breed (n=3,2,1) – hand-calculable
  3. R Implementation (Section 7): Building solvers that handle singular systems
  4. Realistic Application (Section 8): Multi-farm beef cattle with missing cells (n=177)
  5. Practical Guidelines (Section 9): What to do when you encounter unbalanced data

By the end, you’ll be equipped to handle the messy reality of livestock data with confidence.


12.2 Mathematical Theory of Rank Deficiency

12.2.1 Rank Deficiency in Linear Models

Recall the effects model for one-way ANOVA:

\[ y_{ij} = \mu + \alpha_i + e_{ij} \tag{12.1}\]

where:

  • \(y_{ij}\): observation \(j\) in group \(i\) (scalar)
  • \(\mu\): overall mean (scalar parameter)
  • \(\alpha_i\): effect of group \(i\) (scalar parameter, \(i = 1, \ldots, g\))
  • \(e_{ij}\): random error, \(e_{ij} \sim N(0, \sigma^2)\)

In matrix form:

\[ \mathbf{y} = \mathbf{X}\boldsymbol{\beta} + \mathbf{e} \tag{12.2}\]

where:

  • \(\mathbf{y}\): \(n \times 1\) response vector
  • \(\mathbf{X}\): \(n \times (g+1)\) design matrix
  • \(\boldsymbol{\beta} = [\mu, \alpha_1, \alpha_2, \ldots, \alpha_g]'\): \((g+1) \times 1\) parameter vector
  • \(\mathbf{e}\): \(n \times 1\) error vector

For a 3-group study with \(n_1=3, n_2=2, n_3=1\) observations:

\[ \mathbf{X} = \begin{bmatrix} 1 & 1 & 0 & 0 \\ 1 & 1 & 0 & 0 \\ 1 & 1 & 0 & 0 \\ 1 & 0 & 1 & 0 \\ 1 & 0 & 1 & 0 \\ 1 & 0 & 0 & 1 \end{bmatrix}_{6 \times 4} \tag{12.3}\]

The problem: Column 1 (intercept) equals the sum of columns 2, 3, and 4:

\[ \text{Column 1} = \text{Column 2} + \text{Column 3} + \text{Column 4} \]

Therefore: \(r(\mathbf{X}) = 3 < 4 = p\) (number of parameters)

The design matrix does not have full column rank. This is not a numerical precision issue—it’s structural. The model has inherent redundancy: we cannot simultaneously estimate both \(\mu\) and all \(\alpha_i\) because they are confounded.

WarningRank Deficiency is Structural, Not Numerical

This is not computational roundoff error. The model

\[y_{ij} = \mu + \alpha_i + e_{ij}\]

has inherent redundancy:

  • We cannot estimate both \(\mu\) and all \(\alpha_i\) uniquely
  • The intercept and group effects are confounded
  • For any solution, we can add a constant to \(\mu\) and subtract it from all \(\alpha_i\)—the fit doesn’t change

This requires either: 1. Using a different parameterization (cell means model) 2. Imposing constraints to remove redundancy 3. Focusing only on estimable functions (contrasts)

12.2.2 Consequences for the Normal Equations

When \(r(\mathbf{X}'\mathbf{X}) < p\), the normal equations

\[ \mathbf{X}'\mathbf{X}\mathbf{b} = \mathbf{X}'\mathbf{y} \tag{12.4}\]

have the following properties:

  1. \(\mathbf{X}'\mathbf{X}\) is singular (not invertible): \(\det(\mathbf{X}'\mathbf{X}) = 0\)
  2. \((\mathbf{X}'\mathbf{X})^{-1}\) does not exist: The regular inverse is undefined
  3. Infinitely many solutions exist: Any \(\mathbf{b}\) satisfying \(\mathbf{X}'\mathbf{X}\mathbf{b} = \mathbf{X}'\mathbf{y}\) is a solution
  4. Individual parameter estimates are NOT unique: Different solution methods give different \(\mathbf{b}\)

However—and this is critical:

  1. Fitted values \(\hat{\mathbf{y}} = \mathbf{Xb}\) are unique: All solutions give the same \(\hat{\mathbf{y}}\)
  2. Residuals \(\mathbf{e} = \mathbf{y} - \hat{\mathbf{y}}\) are unique: All solutions give the same residuals
  3. SSE is unique: \(SSE = \mathbf{e}'\mathbf{e}\) is the same for all solutions
  4. Estimable functions \(\mathbf{c}'\boldsymbol{\beta}\) are unique: If \(\mathbf{c}'\boldsymbol{\beta}\) is estimable, \(\mathbf{c}'\mathbf{b}\) is the same for all solutions

12.2.3 Algebraic Proof of Uniqueness

Claim: For any two solutions \(\mathbf{b}_1\) and \(\mathbf{b}_2\) to the normal equations, \(\mathbf{Xb}_1 = \mathbf{Xb}_2\).

Proof:

Both satisfy the normal equations:

\[ \mathbf{X}'\mathbf{X}\mathbf{b}_1 = \mathbf{X}'\mathbf{y} \quad \text{and} \quad \mathbf{X}'\mathbf{X}\mathbf{b}_2 = \mathbf{X}'\mathbf{y} \]

Subtracting:

\[ \mathbf{X}'\mathbf{X}(\mathbf{b}_1 - \mathbf{b}_2) = \mathbf{0} \]

Premultiplying by \((\mathbf{b}_1 - \mathbf{b}_2)'\):

\[ (\mathbf{b}_1 - \mathbf{b}_2)'\mathbf{X}'\mathbf{X}(\mathbf{b}_1 - \mathbf{b}_2) = 0 \]

But \(\mathbf{X}'\mathbf{X}\) is positive semidefinite, so:

\[ [\mathbf{X}(\mathbf{b}_1 - \mathbf{b}_2)]'[\mathbf{X}(\mathbf{b}_1 - \mathbf{b}_2)] = 0 \]

This implies:

\[ \mathbf{X}(\mathbf{b}_1 - \mathbf{b}_2) = \mathbf{0} \]

Therefore:

\[ \mathbf{Xb}_1 = \mathbf{Xb}_2 \]

Conclusion: Even though \(\mathbf{b}_1 \neq \mathbf{b}_2\), the fitted values are identical. \(\square\)


12.2.4 Generalized Inverses

Since \((\mathbf{X}'\mathbf{X})^{-1}\) doesn’t exist when \(\mathbf{X}'\mathbf{X}\) is singular, we need an alternative: the generalized inverse.

Definition

A matrix \(\mathbf{G}\) is a generalized inverse of \(\mathbf{A}\) (denoted \(\mathbf{A}^-\)) if:

\[ \mathbf{A}\mathbf{A}^-\mathbf{A} = \mathbf{A} \tag{12.5}\]

This is the minimum requirement for a generalized inverse (also called a reflexive g-inverse).

Properties:

  1. Not unique for singular matrices: Many matrices satisfy Equation 12.5
  2. Reduces to regular inverse when \(\mathbf{A}\) is nonsingular: If \(\mathbf{A}\) is full rank, \(\mathbf{A}^- = \mathbf{A}^{-1}\)
  3. Provides a solution to consistent systems: If \(\mathbf{Ab} = \mathbf{c}\) is consistent, then \(\mathbf{b} = \mathbf{A}^-\mathbf{c}\) is a solution

12.2.5 Using G-Inverse to Solve Normal Equations

For \(\mathbf{X}'\mathbf{X}\mathbf{b} = \mathbf{X}'\mathbf{y}\), any solution has the form:

\[ \mathbf{b} = (\mathbf{X}'\mathbf{X})^-\mathbf{X}'\mathbf{y} \tag{12.6}\]

where \((\mathbf{X}'\mathbf{X})^-\) is any generalized inverse of \(\mathbf{X}'\mathbf{X}\).

Key verification:

\[ \mathbf{X}'\mathbf{X}\mathbf{b} = \mathbf{X}'\mathbf{X}[(\mathbf{X}'\mathbf{X})^-\mathbf{X}'\mathbf{y}] = \mathbf{X}'\mathbf{y} \quad \checkmark \]

(using \(\mathbf{A}\mathbf{A}^-\mathbf{A} = \mathbf{A}\) with \(\mathbf{A} = \mathbf{X}'\))

ImportantG-Inverse in Solving Normal Equations

For rank-deficient normal equations \(\mathbf{X}'\mathbf{X}\mathbf{b} = \mathbf{X}'\mathbf{y}\):

\[ \mathbf{b} = (\mathbf{X}'\mathbf{X})^-\mathbf{X}'\mathbf{y} \]

Critical insights:

  1. Different choices of \((\mathbf{X}'\mathbf{X})^-\) give different \(\mathbf{b}\)
  2. But \(\mathbf{Xb}\) is always the same (fitted values unique)
  3. Estimable functions \(\mathbf{c}'\mathbf{b}\) are always the same (if \(\mathbf{c}'\boldsymbol{\beta}\) is estimable)
  4. Non-estimable functions \(\mathbf{c}'\mathbf{b}\) depend on which \((\mathbf{X}'\mathbf{X})^-\) you use

In practice: Use any g-inverse for computation, but only interpret estimable functions.

12.2.6 The Moore-Penrose Pseudoinverse

Among all generalized inverses, one is special: the Moore-Penrose pseudoinverse (denoted \(\mathbf{A}^+\)).

It’s the unique matrix satisfying four properties:

  1. \(\mathbf{A}\mathbf{A}^+\mathbf{A} = \mathbf{A}\) (reflexive)
  2. \(\mathbf{A}^+\mathbf{A}\mathbf{A}^+ = \mathbf{A}^+\) (minimum norm)
  3. \((\mathbf{A}\mathbf{A}^+)' = \mathbf{A}\mathbf{A}^+\) (symmetric)
  4. \((\mathbf{A}^+\mathbf{A})' = \mathbf{A}^+\mathbf{A}\) (symmetric)

Uniqueness: Only one matrix satisfies all four properties.

Computation via SVD: If \(\mathbf{A} = \mathbf{U}\mathbf{D}\mathbf{V}'\) (singular value decomposition), then:

\[ \mathbf{A}^+ = \mathbf{V}\mathbf{D}^+\mathbf{U}' \]

where \(\mathbf{D}^+\) replaces each nonzero singular value \(d_i\) with \(1/d_i\).

In R: Use MASS::ginv(A) which computes the Moore-Penrose inverse.

TipComputing G-Inverses in R
library(MASS)

# Compute Moore-Penrose inverse
A_ginv <- ginv(A)

# Verify the property AA⁻A = A
max(abs(A %*% A_ginv %*% A - A))  # Should be ≈ 0 (numerical precision)

# Solve normal equations
XtX <- t(X) %*% X
Xty <- t(X) %*% y
b <- ginv(XtX) %*% Xty

Note: Any g-inverse works for solving normal equations. ginv() is convenient and gives the minimum-norm solution.


12.3 Estimability Revisited

In Week 8, we introduced estimable functions for balanced designs. With unbalanced data and rank deficiency, estimability becomes critical.

12.3.1 Formal Definition

A linear function \(\mathbf{c}'\boldsymbol{\beta}\) is estimable if and only if:

\[ \mathbf{c}' = \mathbf{a}'\mathbf{X} \quad \text{for some vector } \mathbf{a} \tag{12.7}\]

Equivalently: \(\mathbf{c}\) is in the row space of \(\mathbf{X}\).

Intuition: Estimable functions are those that can be expressed as linear combinations of the rows of \(\mathbf{X}\)—i.e., they’re “built into” the design.

12.3.2 Critical Theorem on Estimability

Theorem: If \(\mathbf{c}'\boldsymbol{\beta}\) is estimable, then:

  1. \(\mathbf{c}'\mathbf{b}\) is unique (independent of which \((\mathbf{X}'\mathbf{X})^-\) is used)
  2. \(\text{Var}(\mathbf{c}'\mathbf{b}) = \mathbf{c}'(\mathbf{X}'\mathbf{X})^-\mathbf{c} \sigma^2\) is unique
  3. \(\mathbf{c}'\mathbf{b}\) is BLUE of \(\mathbf{c}'\boldsymbol{\beta}\) (Best Linear Unbiased Estimator)

Proof sketch:

For any two g-inverses \(\mathbf{G}_1\) and \(\mathbf{G}_2\) of \(\mathbf{X}'\mathbf{X}\):

  • Solutions: \(\mathbf{b}_1 = \mathbf{G}_1\mathbf{X}'\mathbf{y}\) and \(\mathbf{b}_2 = \mathbf{G}_2\mathbf{X}'\mathbf{y}\)
  • If \(\mathbf{c}'\boldsymbol{\beta}\) estimable: \(\mathbf{c}' = \mathbf{a}'\mathbf{X}\)
  • Then: \(\mathbf{c}'\mathbf{b}_1 = \mathbf{a}'\mathbf{X}\mathbf{b}_1 = \mathbf{a}'\hat{\mathbf{y}}\)
  • And: \(\mathbf{c}'\mathbf{b}_2 = \mathbf{a}'\mathbf{X}\mathbf{b}_2 = \mathbf{a}'\hat{\mathbf{y}}\)
  • Since \(\hat{\mathbf{y}} = \mathbf{Xb}\) is unique, \(\mathbf{c}'\mathbf{b}_1 = \mathbf{c}'\mathbf{b}_2\) \(\square\)
ImportantTesting Estimability in R
is_estimable <- function(c, X, tol = 1e-10) {
  # Test if c'β is estimable
  # c'β estimable ⟺ c in row space of X
  # ⟺ (X'X)⁻c in column space of X'X
  # ⟺ (X'X)(X'X)⁻c = c

  XtX <- t(X) %*% X
  XtX_ginv <- ginv(XtX)
  projection <- XtX %*% XtX_ginv %*% c

  all(abs(projection - c) < tol)
}

# Example usage
c1 <- c(0, 1, -1, 0)  # α₁ - α₂
is_estimable(c1, X)   # TRUE

c2 <- c(1, 0, 0, 0)   # μ
is_estimable(c2, X)   # FALSE

This function checks the criterion: \(\mathbf{c}'\boldsymbol{\beta}\) is estimable iff \((\mathbf{X}'\mathbf{X})(\mathbf{X}'\mathbf{X})^-\mathbf{c} = \mathbf{c}\).

12.3.3 Examples: Estimable vs. Non-Estimable

For the effects model \(y_{ij} = \mu + \alpha_i + e_{ij}\) with 3 groups:

Non-Estimable:

  • \(\mu\) (overall mean): Confounded with \(\alpha_i\)
  • \(\alpha_1\) (group 1 effect): Not uniquely defined
  • Individual \(\alpha_i\) values

Estimable:

  • \(\alpha_1 - \alpha_2\) (difference between groups 1 and 2)
  • \(\alpha_1 - \alpha_3\) (difference between groups 1 and 3)
  • \((\alpha_1 + \alpha_2)/2 - \alpha_3\) (average of groups 1,2 vs. group 3)
  • Any contrast \(\sum c_i \alpha_i\) where \(\sum c_i = 0\)
  • Group means \(\mu_i = \mu + \alpha_i\) (these are what we observe!)

General rule: In the effects model with rank deficiency:

  • Individual \(\mu\) and \(\alpha_i\) are not estimable
  • Contrasts \(\sum c_i \alpha_i\) with \(\sum c_i = 0\) are estimable
  • Group means \(\mu_i = \mu + \alpha_i\) are estimable

12.4 Constraints for Identifiability

We’ve seen that rank deficiency leads to non-unique parameter estimates. One way to resolve this: impose constraints that remove the redundancy.

12.4.1 Common Constraint Types

1. Set-to-zero constraints (reference cell coding):

Set one parameter to zero, e.g., \(\alpha_g = 0\).

  • Interpretation: Other \(\alpha_i\) are deviations from group \(g\)
  • R default: lm() uses this with first level as reference

2. Sum-to-zero constraints:

\[\sum_{i=1}^g \alpha_i = 0 \tag{12.8}\]

  • Interpretation: \(\alpha_i\) are deviations from overall mean
  • More symmetric than set-to-zero
  • Useful for balanced designs

3. Weighted sum-to-zero constraints:

\[\sum_{i=1}^g n_i \alpha_i = 0 \tag{12.9}\]

  • Accounts for unequal group sizes
  • Each group weighted by sample size
NoteConstraints vs. G-Inverses: Two Paths, Same Destination

Two equivalent approaches to handle rank deficiency:

Approach 1: Use constraints to achieve full rank

  • Augment normal equations with constraint rows
  • Solve augmented system for unique \(\mathbf{b}\)

Approach 2: Use g-inverse, focus on estimable functions

  • Solve \(\mathbf{b} = (\mathbf{X}'\mathbf{X})^-\mathbf{X}'\mathbf{y}\) for any solution
  • Only interpret estimable \(\mathbf{c}'\mathbf{b}\)

Which to use?

  • Constraints: Good when you want specific parameterization
  • G-inverse: Good when you only care about contrasts

Important: Choice is computational/interpretational, not statistical. Estimable functions \(\mathbf{c}'\mathbf{b}\) are the same either way.


12.5 Small Example: Sheep Fleece Weight

Let’s work through a complete example small enough for hand calculations but rich enough to illustrate all concepts.

12.5.1 Problem Setup

Research question: Compare fleece weight (kg) across three sheep breeds.

Data (unbalanced):

Breed Fleece Weight (kg) Sample Size
Romney 5.2, 5.4, 5.3 \(n_1 = 3\)
Merino 4.8, 5.0 \(n_2 = 2\)
Corriedale 5.6 \(n_3 = 1\)

Total: \(n = 6\) observations

Note the unbalanced design: Group sizes are 3, 2, and 1. This is realistic—perhaps Corriedale is a less common breed in the study region.

NoteWhy This Example?

This example is pedagogically ideal:

  • Small enough for complete hand calculations
  • Unbalanced (3, 2, 1) shows rank deficiency clearly
  • Realistic: Breed comparison with unequal representation
  • Demonstrates both cell means (full rank) and effects (rank deficient) models
  • Illustrates estimability: What can and can’t be uniquely estimated

12.5.2 Cell Means Model (Full Rank)

Model: \[y_{ij} = \mu_i + e_{ij}\]

where \(\mu_i\) is the mean for breed \(i\) (no overall \(\mu\), no \(\alpha_i\)).

Step 1: Design Matrix

\[ \mathbf{X} = \begin{bmatrix} 1 & 0 & 0 \\ 1 & 0 & 0 \\ 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}_{6 \times 3}, \quad \boldsymbol{\beta} = \begin{bmatrix} \mu_1 \\ \mu_2 \\ \mu_3 \end{bmatrix} \]

Check rank: \(r(\mathbf{X}) = 3 = p\)Full rank!

The cell means model is always full rank, even with unbalanced data.

Step 2: Normal Equations

\[ \mathbf{X}'\mathbf{X} = \begin{bmatrix} 3 & 0 & 0 \\ 0 & 2 & 0 \\ 0 & 0 & 1 \end{bmatrix}, \quad \mathbf{X}'\mathbf{y} = \begin{bmatrix} 15.9 \\ 9.8 \\ 5.6 \end{bmatrix} \]

Note: \(\mathbf{X}'\mathbf{X}\) is diagonal (always true for cell means, even unbalanced).

Step 3: Solve

Since \(\mathbf{X}'\mathbf{X}\) is diagonal and invertible:

\[ (\mathbf{X}'\mathbf{X})^{-1} = \begin{bmatrix} 1/3 & 0 & 0 \\ 0 & 1/2 & 0 \\ 0 & 0 & 1 \end{bmatrix} \]

Solution:

\[ \mathbf{b} = (\mathbf{X}'\mathbf{X})^{-1}\mathbf{X}'\mathbf{y} = \begin{bmatrix} 15.9/3 \\ 9.8/2 \\ 5.6/1 \end{bmatrix} = \begin{bmatrix} 5.30 \\ 4.90 \\ 5.60 \end{bmatrix} \]

Interpretation:

  • \(\hat{\mu}_1 = 5.30\) kg (Romney mean)
  • \(\hat{\mu}_2 = 4.90\) kg (Merino mean)
  • \(\hat{\mu}_3 = 5.60\) kg (Corriedale mean)

These are just the group means! The cell means model directly estimates what we observe.

Step 4: Residuals and SSE

Fitted values:

\[ \hat{\mathbf{y}} = \mathbf{Xb} = [5.30, 5.30, 5.30, 4.90, 4.90, 5.60]' \]

Residuals:

\[ \mathbf{e} = \mathbf{y} - \hat{\mathbf{y}} = [5.2-5.30, 5.4-5.30, 5.3-5.30, 4.8-4.90, 5.0-4.90, 5.6-5.60]' \] \[ = [-0.10, 0.10, 0.00, -0.10, 0.10, 0.00]' \]

SSE:

\[ SSE = \mathbf{e}'\mathbf{e} = (-0.10)^2 + (0.10)^2 + 0^2 + (-0.10)^2 + (0.10)^2 + 0^2 = 0.04 \text{ kg}^2 \]

MSE (degrees of freedom = \(n - p = 6 - 3 = 3\)):

\[ \hat{\sigma}^2 = MSE = \frac{SSE}{n-p} = \frac{0.04}{3} = 0.0133 \text{ kg}^2 \]


12.5.3 Effects Model (Rank Deficient)

Model: \[y_{ij} = \mu + \alpha_i + e_{ij}\]

This is the traditional ANOVA parameterization.

Step 1: Design Matrix

\[ \mathbf{X} = \begin{bmatrix} 1 & 1 & 0 & 0 \\ 1 & 1 & 0 & 0 \\ 1 & 1 & 0 & 0 \\ 1 & 0 & 1 & 0 \\ 1 & 0 & 1 & 0 \\ 1 & 0 & 0 & 1 \end{bmatrix}_{6 \times 4}, \quad \boldsymbol{\beta} = \begin{bmatrix} \mu \\ \alpha_1 \\ \alpha_2 \\ \alpha_3 \end{bmatrix} \]

Check dependencies:

Column 1 = Column 2 + Column 3 + Column 4

Therefore: \(r(\mathbf{X}) = 3 < 4 = p\)NOT full rank

WarningRank Deficiency Identified

The effects model \(y_{ij} = \mu + \alpha_i + e_{ij}\) has:

  • \(r(\mathbf{X}) = 3\) but \(p = 4\) parameters
  • Normal equations have infinitely many solutions
  • Cannot uniquely estimate \(\mu, \alpha_1, \alpha_2, \alpha_3\)
  • For any solution, we can add constant \(c\) to \(\mu\) and subtract \(c\) from all \(\alpha_i\)—fit unchanged

But: Contrasts like \(\alpha_1 - \alpha_2\) ARE uniquely estimable.

Step 2: Normal Equations (Singular System)

\[ \mathbf{X}'\mathbf{X} = \begin{bmatrix} 6 & 3 & 2 & 1 \\ 3 & 3 & 0 & 0 \\ 2 & 0 & 2 & 0 \\ 1 & 0 & 0 & 1 \end{bmatrix}_{4 \times 4}, \quad \mathbf{X}'\mathbf{y} = \begin{bmatrix} 31.9 \\ 15.9 \\ 9.8 \\ 5.6 \end{bmatrix} \]

Check: \(\det(\mathbf{X}'\mathbf{X}) = 0\) (singular)

Step 3: Two G-Inverse Solutions

We’ll solve using two different approaches to show non-uniqueness of \(\mathbf{b}\) but uniqueness of estimable functions.

Approach 1: Set-to-Zero Constraint (\(\alpha_3 = 0\))

Impose \(\alpha_3 = 0\). This removes the last parameter, giving a 3×3 system:

\[ \begin{bmatrix} 6 & 3 & 2 \\ 3 & 3 & 0 \\ 2 & 0 & 2 \end{bmatrix} \begin{bmatrix} \mu \\ \alpha_1 \\ \alpha_2 \end{bmatrix} = \begin{bmatrix} 31.9 \\ 15.9 \\ 9.8 \end{bmatrix} \]

Solve (by hand or calculator):

\[ \begin{bmatrix} \mu \\ \alpha_1 \\ \alpha_2 \end{bmatrix} = \begin{bmatrix} 5.60 \\ -0.30 \\ -0.70 \end{bmatrix}, \quad \alpha_3 = 0 \]

Solution 1: \(\mathbf{b}_1 = [5.60, -0.30, -0.70, 0.00]'\)

Interpretation (under this constraint):

  • \(\hat{\mu} = 5.60\) kg (but this equals \(\mu_3\)—the Corriedale mean!)
  • \(\hat{\alpha}_1 = -0.30\) kg (Romney is 0.30 kg below Corriedale)
  • \(\hat{\alpha}_2 = -0.70\) kg (Merino is 0.70 kg below Corriedale)
  • \(\hat{\alpha}_3 = 0\) (reference breed)

Group means (check against cell means model):

  • Romney: \(\mu + \alpha_1 = 5.60 + (-0.30) = 5.30\)
  • Merino: \(\mu + \alpha_2 = 5.60 + (-0.70) = 4.90\)
  • Corriedale: \(\mu + \alpha_3 = 5.60 + 0 = 5.60\)
ImportantCell Means vs. Effects: Key Lesson

Cell Means Model (\(y_{ij} = \mu_i + e_{ij}\)):

  • Always full rank, even with unbalanced data
  • All \(\mu_i\) are estimable
  • Directly estimates group means
  • Simplest for unbalanced data

Effects Model (\(y_{ij} = \mu + \alpha_i + e_{ij}\)):

  • Rank deficient (need constraint)
  • Individual \(\mu, \alpha_i\) not estimable
  • Contrasts \(\alpha_i - \alpha_j\) ARE estimable
  • Useful when testing specific contrasts

For animal breeding: We care about differences between breeds/sires/treatments, not absolute values. Estimable contrasts are what matter.


12.6 R Implementation: Sheep Fleece Example

Now let’s implement these analyses in R, building our own solvers and verifying against lm().

# Load packages
library(MASS)     # For ginv()
library(ggplot2)  # For plotting
library(knitr)    # For tables

# Load data
sheep <- read.csv("data/sheep_fleece_unbalanced.csv")

# Inspect
head(sheep)
breed fleece_weight
Romney 5.2
Romney 5.4
Romney 5.3
Merino 4.8
Merino 5.0
Corriedale 5.6
table(sheep$breed)  # Unbalanced: 3, 2, 1

Corriedale     Merino     Romney 
         1          2          3 
# Summary statistics by breed
library(dplyr)
sheep %>%
  group_by(breed) %>%
  summarise(
    n = n(),
    mean = mean(fleece_weight),
    sd = sd(fleece_weight)
  )
breed n mean sd
Corriedale 1 5.6 NA
Merino 2 4.9 0.1414214
Romney 3 5.3 0.1000000
# Create response vector and breed factor
y <- sheep$fleece_weight
breed <- factor(sheep$breed)
n <- length(y)

12.6.1 Cell Means Model in R

# Step 1: Build design matrix (cell means: no intercept)
X_cell <- model.matrix(~ breed - 1, data = sheep)
colnames(X_cell) <- c("Corriedale", "Merino", "Romney")
X_cell  # Display
  Corriedale Merino Romney
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"
# Check rank
p <- ncol(X_cell)
rank_X <- qr(X_cell)$rank
cat("Rank of X (cell means):", rank_X, "= p =", p, "→ Full rank!\n")
Rank of X (cell means): 3 = p = 3 → Full rank!
# Step 2: Normal equations
XtX <- t(X_cell) %*% X_cell
Xty <- t(X_cell) %*% y

cat("\nX'X (diagonal for cell means):\n")

X'X (diagonal for cell means):
print(XtX)
           Corriedale Merino Romney
Corriedale          1      0      0
Merino              0      2      0
Romney              0      0      3
cat("\nX'y:\n")

X'y:
print(Xty)
           [,1]
Corriedale  5.6
Merino      9.8
Romney     15.9
# Step 3: Solve
XtX_inv <- solve(XtX)
b_cell <- XtX_inv %*% Xty

cat("\nEstimates (cell means model):\n")

Estimates (cell means model):
print(b_cell)
           [,1]
Corriedale  5.6
Merino      4.9
Romney      5.3
# These should be group means
cat("\nGroup means (direct calculation):\n")

Group means (direct calculation):
print(tapply(y, breed, mean))
Corriedale     Merino     Romney 
       5.6        4.9        5.3 
# Step 4: Fitted values and residuals
y_hat <- X_cell %*% b_cell
residuals <- y - y_hat
SSE <- sum(residuals^2)
MSE <- SSE / (n - p)

cat("\nSSE:", SSE, "kg²\n")

SSE: 0.04 kg²
cat("MSE:", MSE, "kg²\n")
MSE: 0.01333333 kg²
cat("Residual SD:", sqrt(MSE), "kg\n")
Residual SD: 0.1154701 kg
# Compare with lm()
fit_cell <- lm(fleece_weight ~ breed - 1, data = sheep)
cat("\nComparison with lm():\n")

Comparison with lm():
cat("Manual:", b_cell, "\n")
Manual: 5.6 4.9 5.3 
cat("lm():  ", coef(fit_cell), "\n")
lm():   5.6 4.9 5.3 
cat("Match:", all.equal(c(b_cell), coef(fit_cell)), "\n")
Match: names for current but not for target 

12.6.2 Effects Model with Set-to-Zero

# Effects model: lm() default (first level as reference)
fit_effects <- lm(fleece_weight ~ breed, data = sheep)

cat("Effects model (Corriedale = 0 reference):\n")
Effects model (Corriedale = 0 reference):
summary(fit_effects)$coefficients
            Estimate Std. Error   t value     Pr(>|t|)
(Intercept)      5.6  0.1154701 48.497423 1.930414e-05
breedMerino     -0.7  0.1414214 -4.949747 1.582423e-02
breedRomney     -0.3  0.1333333 -2.250000 1.099381e-01
# Interpretation
cat("\nInterpretation:\n")

Interpretation:
cat("μ (intercept):", coef(fit_effects)[1], "= Corriedale mean\n")
μ (intercept): 5.6 = Corriedale mean
cat("α_Merino:", coef(fit_effects)[2], "= Merino - Corriedale\n")
α_Merino: -0.7 = Merino - Corriedale
cat("α_Romney:", coef(fit_effects)[3], "= Romney - Corriedale\n")
α_Romney: -0.3 = Romney - Corriedale
# Group means from effects model
cat("\nGroup means from effects model:\n")

Group means from effects model:
cat("Corriedale:", coef(fit_effects)[1], "\n")
Corriedale: 5.6 
cat("Merino:", coef(fit_effects)[1] + coef(fit_effects)[2], "\n")
Merino: 4.9 
cat("Romney:", coef(fit_effects)[1] + coef(fit_effects)[3], "\n")
Romney: 5.3 

12.6.3 G-Inverse Approach

# Build full effects model X matrix
X_effects <- model.matrix(~ breed, data = sheep)
colnames(X_effects) <- c("mu", "Merino", "Romney")
X_effects  # Display
  mu Merino Romney
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"
# Check rank
rank_X_effects <- qr(X_effects)$rank
p_effects <- ncol(X_effects)
cat("Rank of X (effects):", rank_X_effects, "< p =", p_effects, "→ NOT full rank!\n")
Rank of X (effects): 3 < p = 3 → NOT full rank!
# Normal equations (singular)
XtX_effects <- t(X_effects) %*% X_effects
Xty_effects <- t(X_effects) %*% y

cat("\nX'X (effects model, singular):\n")

X'X (effects model, singular):
print(XtX_effects)
       mu Merino Romney
mu      6      2      3
Merino  2      2      0
Romney  3      0      3
cat("\nDeterminant:", det(XtX_effects), "(= 0, singular)\n")

Determinant: 6 (= 0, singular)
# Solve using Moore-Penrose g-inverse
XtX_ginv <- ginv(XtX_effects)

cat("\nMoore-Penrose g-inverse:\n")

Moore-Penrose g-inverse:
print(XtX_ginv)
     [,1] [,2]      [,3]
[1,]    1 -1.0 -1.000000
[2,]   -1  1.5  1.000000
[3,]   -1  1.0  1.333333
# Verify property: AA⁻A = A
verification <- XtX_effects %*% XtX_ginv %*% XtX_effects
error <- max(abs(verification - XtX_effects))
cat("\nVerification: ||X'X(X'X)⁻X'X - X'X|| =", error, "(should be ≈ 0)\n")

Verification: ||X'X(X'X)⁻X'X - X'X|| = 3.552714e-15 (should be ≈ 0)
# Solution
b_ginv <- XtX_ginv %*% Xty_effects

cat("\nSolution using g-inverse:\n")

Solution using g-inverse:
print(b_ginv)
     [,1]
[1,]  5.6
[2,] -0.7
[3,] -0.3
# Fitted values (should be same as cell means)
y_hat_ginv <- X_effects %*% b_ginv

cat("\nFitted values match cell means:", all.equal(c(y_hat_ginv), c(y_hat)), "\n")

Fitted values match cell means: TRUE 

12.6.4 Testing Estimability

# Custom function to test estimability
is_estimable <- function(c, X, tol = 1e-10) {
  XtX <- t(X) %*% X
  XtX_ginv <- ginv(XtX)
  projection <- XtX %*% XtX_ginv %*% c
  all(abs(projection - c) < tol)
}

# Test various functions
cat("Testing estimability (effects model):\n\n")
Testing estimability (effects model):
# μ (intercept)
c_mu <- c(1, 0, 0)
cat("μ:", is_estimable(c_mu, X_effects), "\n")
μ: TRUE 
# α_Merino
c_a1 <- c(0, 1, 0)
cat("α_Merino:", is_estimable(c_a1, X_effects), "\n")
α_Merino: TRUE 
# α_Romney
c_a2 <- c(0, 0, 1)
cat("α_Romney:", is_estimable(c_a2, X_effects), "\n")
α_Romney: TRUE 
# Contrast: α_Merino - α_Romney
c_contrast1 <- c(0, 1, -1)
cat("α_Merino - α_Romney:", is_estimable(c_contrast1, X_effects), "\n")
α_Merino - α_Romney: TRUE 
# Contrast: Romney vs. reference (Corriedale)
# In this parameterization, Corriedale is encoded as α=-sum(other α's)
# To compare Romney vs Corriedale: α_Romney - α_Corriedale
# But in the constraint where Corriedale is reference, this is just α_Romney
c_contrast2 <- c(0, 0, 1)
estimate1 <- t(c_contrast2) %*% coef(fit_effects)
cat("\nRomney vs. Corriedale (set-to-zero):", estimate1, "kg\n")

Romney vs. Corriedale (set-to-zero): -0.3 kg
# Using g-inverse solution
estimate2 <- t(c_contrast2) %*% b_ginv
cat("Romney vs. Corriedale (g-inverse):  ", estimate2, "kg\n")
Romney vs. Corriedale (g-inverse):   -0.3 kg
cat("\n→ Contrasts are estimable and unique!\n")

→ Contrasts are estimable and unique!
TipPractical Advice for One-Way ANOVA (Unbalanced)

Recommendation: Use cell means model for unbalanced one-way ANOVA.

Why?

  • Always full rank (no singularity issues)
  • Direct estimates of group means
  • Simple interpretation
  • No arbitrary constraints needed

When to use effects model?

  • When testing specific planned contrasts
  • When ANOVA table with main effects is desired
  • When connecting to factorial designs (Week 9)

Bottom line: For practical data analysis, cell means is simpler. For contrasts and theory, effects model with estimable functions.


12.7 Realistic Application: Multi-Farm Beef Cattle

Now let’s apply these concepts to a realistic dataset with missing cells—a common scenario in multi-farm breeding evaluations.

12.7.1 Background and Data

Scenario: Multi-farm evaluation of beef cattle breeds

Design:

  • 4 breeds: Angus, Hereford, Charolais, Simmental
  • 5 farms: A, B, C, D, E
  • 20 possible breed × farm combinations, but not all observed (missing cells)
  • Response: Average daily gain (ADG, kg/day) during feedlot period
  • Total: \(n = 177\) steers

Why missing cells?

  • Farms specialize: Some raise only British breeds, some only Continental
  • Economics: Not profitable to test all breeds everywhere
  • Climate adaptation: Some breeds unsuited to certain regions

This reflects real-world genetic evaluation data.

WarningReal-World Complexity

Typical of commercial livestock data:

  • Uneven distribution across locations
  • Not all genotypes in all environments (breed × farm interactions can’t all be estimated)
  • Some cells with zero observations (breed × farm combinations that don’t exist)

Standard ANOVA assumptions (balanced, orthogonal) are violated. Must use:

  • Type III SS (each effect adjusted for others)
  • Estimable contrasts (only interpret breed differences within farms where both present)
  • emmeans package for marginal means

12.7.2 Data Exploration

# Load beef data
beef <- read.csv("data/beef_multifarm_unbalanced.csv")

# Inspect structure
str(beef)
'data.frame':   177 obs. of  4 variables:
 $ steer_id: int  1 2 3 4 5 6 7 8 9 10 ...
 $ breed   : chr  "Angus" "Angus" "Angus" "Angus" ...
 $ farm    : chr  "A" "A" "A" "A" ...
 $ adg     : num  1.12 1.15 1.08 1.18 1.14 1.1 1.16 1.13 1.11 1.17 ...
head(beef)
steer_id breed farm adg
1 Angus A 1.12
2 Angus A 1.15
3 Angus A 1.08
4 Angus A 1.18
5 Angus A 1.14
6 Angus A 1.10
# Sample sizes by breed and farm
cat("Sample sizes (breed × farm):\n")
Sample sizes (breed × farm):
table_breed_farm <- table(beef$breed, beef$farm)
print(table_breed_farm)
           
             A  B  C  D  E
  Angus     18 20  0  8 11
  Charolais 12  0 10  6  0
  Hereford  15  0 14  7 13
  Simmental  0 16 18  9  0
cat("\nTotal observations:", nrow(beef), "\n")

Total observations: 177 
# Summary statistics
cat("\nADG by breed:\n")

ADG by breed:
breed_summary <- beef %>%
  group_by(breed) %>%
  summarise(
    n = n(),
    mean = mean(adg),
    sd = sd(adg)
  )
print(breed_summary)
# A tibble: 4 × 4
  breed         n  mean     sd
  <chr>     <int> <dbl>  <dbl>
1 Angus        57  1.16 0.0431
2 Charolais    28  1.31 0.0323
3 Hereford     49  1.04 0.0327
4 Simmental    43  1.40 0.0432
cat("\nADG by farm:\n")

ADG by farm:
farm_summary <- beef %>%
  group_by(farm) %>%
  summarise(
    n = n(),
    mean = mean(adg),
    sd = sd(adg)
  )
print(farm_summary)
# A tibble: 5 × 4
  farm      n  mean     sd
  <chr> <int> <dbl>  <dbl>
1 A        45  1.15 0.0990
2 B        36  1.27 0.0872
3 C        42  1.27 0.191 
4 D        30  1.24 0.136 
5 E        24  1.07 0.0460
# Grand mean
cat("\nGrand mean ADG:", mean(beef$adg), "kg/day\n")

Grand mean ADG: 1.208475 kg/day

12.7.3 Visualization

library(ggplot2)
library(gridExtra)

# Boxplot by breed
p1 <- ggplot(beef, aes(x = breed, y = adg, fill = breed)) +
  geom_boxplot() +
  labs(title = "ADG by Breed", x = "Breed", y = "ADG (kg/day)") +
  theme_minimal() +
  theme(legend.position = "none")

# Boxplot by farm
p2 <- ggplot(beef, aes(x = farm, y = adg, fill = farm)) +
  geom_boxplot() +
  labs(title = "ADG by Farm", x = "Farm", y = "ADG (kg/day)") +
  theme_minimal() +
  theme(legend.position = "none")

# Side-by-side
grid.arrange(p1, p2, ncol = 2)

# Breed × farm interaction plot (only cells with data)
cell_means <- aggregate(adg ~ breed + farm, data = beef, FUN = mean)

ggplot(cell_means, aes(x = farm, y = adg, color = breed, group = breed)) +
  geom_line(size = 1) +
  geom_point(size = 3) +
  labs(title = "Breed × Farm Interaction (cell means)",
       x = "Farm", y = "ADG (kg/day)", color = "Breed") +
  theme_minimal()

Observations:

  • Continental breeds (Charolais, Simmental) have higher ADG than British breeds (Angus, Hereford)
  • Farm effects evident (B and C higher than E)
  • Lines cross → interaction possible (but confounded with missing cells)

12.7.4 Model 1: Breed Only (Cell Means)

# Fit breed-only model (cell means)
fit_breed <- lm(adg ~ breed - 1, data = beef)

cat("Model 1: ADG ~ Breed (cell means)\n")
Model 1: ADG ~ Breed (cell means)
summary(fit_breed)

Call:
lm(formula = adg ~ breed - 1, data = beef)

Residuals:
      Min        1Q    Median        3Q       Max 
-0.083721 -0.029643 -0.001429  0.026279  0.086279 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
breedAngus     1.155088   0.005154   224.1   <2e-16 ***
breedCharolais 1.309643   0.007353   178.1   <2e-16 ***
breedHereford  1.041429   0.005559   187.4   <2e-16 ***
breedSimmental 1.403721   0.005934   236.6   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.03891 on 173 degrees of freedom
Multiple R-squared:  0.999, Adjusted R-squared:  0.999 
F-statistic: 4.325e+04 on 4 and 173 DF,  p-value: < 2.2e-16
# Breed means
breed_means <- coef(fit_breed)
cat("\nBreed means (kg/day):\n")

Breed means (kg/day):
print(breed_means)
    breedAngus breedCharolais  breedHereford breedSimmental 
      1.155088       1.309643       1.041429       1.403721 
# ANOVA
cat("\nANOVA table:\n")

ANOVA table:
anova(fit_breed)
Df Sum Sq Mean Sq F value Pr(>F)
breed 4 261.9482744 65.487069 43253.74 0
Residuals 173 0.2619256 0.001514 NA NA
# Interpretation
cat("\nInterpretation:\n")

Interpretation:
cat("- Charolais highest ADG:", breed_means["breedCharolais"], "kg/day\n")
- Charolais highest ADG: 1.309643 kg/day
cat("- Hereford lowest ADG:", breed_means["breedHereford"], "kg/day\n")
- Hereford lowest ADG: 1.041429 kg/day
cat("- Difference:", breed_means["breedCharolais"] - breed_means["breedHereford"], "kg/day\n")
- Difference: 0.2682143 kg/day
cat("\nBUT: This ignores farm effects! Breeds not equally distributed across farms.\n")

BUT: This ignores farm effects! Breeds not equally distributed across farms.

Problem: Breed effects are confounded with farm effects because breeds aren’t equally represented on all farms.


12.7.5 Model 2: Additive (Breed + Farm)

# Fit additive model
fit_additive <- lm(adg ~ breed + farm, data = beef)

cat("Model 2: ADG ~ Breed + Farm (additive)\n")
Model 2: ADG ~ Breed + Farm (additive)
summary(fit_additive)

Call:
lm(formula = adg ~ breed + farm, data = beef)

Residuals:
      Min        1Q    Median        3Q       Max 
-0.087769 -0.024081  0.001364  0.026686  0.086396 

Coefficients:
                Estimate Std. Error t value Pr(>|t|)    
(Intercept)     1.147917   0.007075 162.253   <2e-16 ***
breedCharolais  0.149879   0.009404  15.938   <2e-16 ***
breedHereford  -0.110988   0.007889 -14.068   <2e-16 ***
breedSimmental  0.234847   0.008425  27.874   <2e-16 ***
farmB           0.021318   0.009322   2.287   0.0234 *  
farmC           0.020840   0.008734   2.386   0.0181 *  
farmD           0.020550   0.009061   2.268   0.0246 *  
farmE          -0.016549   0.009697  -1.707   0.0897 .  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.03728 on 169 degrees of freedom
Multiple R-squared:  0.9368,    Adjusted R-squared:  0.9342 
F-statistic: 358.1 on 7 and 169 DF,  p-value: < 2.2e-16
# Type I SS (sequential)
cat("\nType I SS (sequential):\n")

Type I SS (sequential):
anova(fit_additive)
Df Sum Sq Mean Sq F value Pr(>F)
breed 3 3.4555625 1.1518542 828.986628 0.0000000
farm 4 0.0271048 0.0067762 4.876811 0.0009549
Residuals 169 0.2348209 0.0013895 NA NA
# Type III SS (each adjusted for others)
library(car)
cat("\nType III SS (each adjusted for others):\n")

Type III SS (each adjusted for others):
Anova(fit_additive, type = 3)
Sum Sq Df F value Pr(>F)
(Intercept) 36.5794313 1 26326.127352 0.0000000
breed 2.5437143 3 610.234979 0.0000000
farm 0.0271048 4 4.876811 0.0009549
Residuals 0.2348209 169 NA NA
cat("\n→ Note: Type I and Type III give different results!\n")

→ Note: Type I and Type III give different results!
cat("   With unbalanced data, order matters for Type I.\n")
   With unbalanced data, order matters for Type I.
cat("   Type III tests each effect adjusted for the other.\n")
   Type III tests each effect adjusted for the other.
NoteType I vs Type III SS

With unbalanced data, Type I and Type III SS differ:

Type I (Sequential):

  • SS(Breed | Intercept), then SS(Farm | Breed)
  • Order matters! SS(A|B) ≠ SS(B|A)
  • Depends on order effects entered in model

Type III (Marginal):

  • SS(Breed | Farm) and SS(Farm | Breed)
  • Each effect adjusted for all others
  • Order-independent
  • Preferred for unbalanced designs

Recommendation: Use Type III for testing with unbalanced data. It asks: “Does breed matter after accounting for farm?” and vice versa.


12.7.6 Model 3: Interaction (Missing Cells)

# Fit interaction model
fit_interaction <- lm(adg ~ breed * farm, data = beef)

cat("Model 3: ADG ~ Breed * Farm (interaction)\n")
Model 3: ADG ~ Breed * Farm (interaction)
summary(fit_interaction)

Call:
lm(formula = adg ~ breed * farm, data = beef)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.05000 -0.02000  0.00000  0.02111  0.05167 

Coefficients: (6 not defined because of singularities)
                       Estimate Std. Error t value Pr(>|t|)    
(Intercept)           1.130e+00  6.368e-03 177.462  < 2e-16 ***
breedCharolais        1.683e-01  1.007e-02  16.720  < 2e-16 ***
breedHereford        -7.200e-02  9.445e-03  -7.623 1.93e-12 ***
breedSimmental        2.439e-01  1.313e-02  18.579  < 2e-16 ***
farmB                 7.000e-02  8.777e-03   7.975 2.53e-13 ***
farmC                 6.611e-02  1.592e-02   4.153 5.28e-05 ***
farmD                 2.500e-02  1.148e-02   2.178   0.0309 *  
farmE                -1.545e-02  1.034e-02  -1.495   0.1369    
breedCharolais:farmB         NA         NA      NA       NA    
breedHereford:farmB          NA         NA      NA       NA    
breedSimmental:farmB -7.826e-02  1.595e-02  -4.907 2.23e-06 ***
breedCharolais:farmC -4.944e-02  1.968e-02  -2.513   0.0130 *  
breedHereford:farmC  -1.105e-01  1.882e-02  -5.873 2.33e-08 ***
breedSimmental:farmC         NA         NA      NA       NA    
breedCharolais:farmD  2.836e-16  1.773e-02   0.000   1.0000    
breedHereford:farmD  -8.714e-03  1.687e-02  -0.516   0.6062    
breedSimmental:farmD         NA         NA      NA       NA    
breedCharolais:farmE         NA         NA      NA       NA    
breedHereford:farmE  -7.930e-03  1.455e-02  -0.545   0.5865    
breedSimmental:farmE         NA         NA      NA       NA    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.02702 on 163 degrees of freedom
Multiple R-squared:  0.968, Adjusted R-squared:  0.9654 
F-statistic: 379.3 on 13 and 163 DF,  p-value: < 2.2e-16
# Check which coefficients are NA (non-estimable)
coefs <- coef(fit_interaction)
na_coefs <- names(coefs)[is.na(coefs)]

cat("\nNon-estimable parameters (NA):\n")

Non-estimable parameters (NA):
print(na_coefs)
[1] "breedCharolais:farmB" "breedHereford:farmB"  "breedSimmental:farmC"
[4] "breedSimmental:farmD" "breedCharolais:farmE" "breedSimmental:farmE"
cat("\n→ These breed × farm combinations have no data (missing cells)\n")

→ These breed × farm combinations have no data (missing cells)
# Type III ANOVA (may fail with aliased coefficients)
cat("\nAttempting Type III ANOVA:\n")

Attempting Type III ANOVA:
tryCatch({
  Anova(fit_interaction, type = 3)
}, error = function(e) {
  cat("→ Type III SS cannot be computed due to aliased coefficients\n")
  cat("  This is expected with missing cells in interaction models!\n")
  cat("  Use model comparison with anova() instead.\n")
})
→ Type III SS cannot be computed due to aliased coefficients
  This is expected with missing cells in interaction models!
  Use model comparison with anova() instead.
# Test interaction significance
cat("\nTest interaction vs. additive:\n")

Test interaction vs. additive:
anova(fit_additive, fit_interaction)
Res.Df RSS Df Sum of Sq F Pr(>F)
169 0.2348209 NA NA NA NA
163 0.1189613 6 0.1158596 26.45834 0
# Interpretation
if (anova(fit_additive, fit_interaction)$`Pr(>F)`[2] < 0.05) {
  cat("\n→ Interaction is significant (p < 0.05)\n")
  cat("   Breed effects depend on farm!\n")
} else {
  cat("\n→ Interaction not significant (p ≥ 0.05)\n")
  cat("   Additive model sufficient.\n")
}

→ Interaction is significant (p < 0.05)
   Breed effects depend on farm!
ImportantHandling Missing Cells

When some breed × farm combinations don’t exist:

  • Design matrix \(\mathbf{X}\) is rank deficient
  • R automatically drops redundant parameters (shown as NA)
  • Only estimable functions can be tested
  • Some comparisons only possible within farms

Example: Can’t directly compare Angus vs. Simmental on Farm E because Simmental not raised there.

Solution: Use marginal means (averaged across farms where both breeds present) via emmeans.


12.7.7 Estimable Contrasts with emmeans

library(emmeans)

# Estimated marginal means for breed (averaged across farms)
emm_breed <- emmeans(fit_additive, "breed")

cat("Estimated marginal means (breed):\n")
Estimated marginal means (breed):
print(emm_breed)
 breed     emmean      SE  df lower.CL upper.CL
 Angus       1.16 0.00526 169     1.15     1.17
 Charolais   1.31 0.00754 169     1.29     1.32
 Hereford    1.05 0.00562 169     1.04     1.06
 Simmental   1.39 0.00635 169     1.38     1.40

Results are averaged over the levels of: farm 
Confidence level used: 0.95 
# Pairwise comparisons
cat("\nPairwise breed comparisons:\n")

Pairwise breed comparisons:
pairs_breed <- pairs(emm_breed, adjust = "tukey")
print(pairs_breed)
 contrast              estimate      SE  df t.ratio p.value
 Angus - Charolais       -0.150 0.00940 169 -15.938  <.0001
 Angus - Hereford         0.111 0.00789 169  14.068  <.0001
 Angus - Simmental       -0.235 0.00843 169 -27.874  <.0001
 Charolais - Hereford     0.261 0.00914 169  28.551  <.0001
 Charolais - Simmental   -0.085 0.00979 169  -8.681  <.0001
 Hereford - Simmental    -0.346 0.00881 169 -39.260  <.0001

Results are averaged over the levels of: farm 
P value adjustment: tukey method for comparing a family of 4 estimates 
# Custom contrast: British vs. Continental breeds
# British: Angus, Hereford
# Continental: Charolais, Simmental
contrast_british_continental <- list(
  "British vs Continental" = c(0.5, 0.5, -0.5, -0.5)  # Angus, Char, Here, Simm
)

cat("\nCustom contrast: British vs. Continental breeds:\n")

Custom contrast: British vs. Continental breeds:
test_contrast <- contrast(emm_breed, contrast_british_continental)
print(test_contrast)
 contrast               estimate      SE  df t.ratio p.value
 British vs Continental    0.013 0.00605 169   2.150  0.0330

Results are averaged over the levels of: farm 
# Effect size
effect_size <- summary(test_contrast)$estimate
cat("\nEffect size:", effect_size, "kg/day\n")

Effect size: 0.01301038 kg/day
cat("Continental breeds gain", abs(effect_size), "kg/day more than British breeds (on average).\n")
Continental breeds gain 0.01301038 kg/day more than British breeds (on average).
TipUsing emmeans for Unbalanced Data

The emmeans package is invaluable for unbalanced designs:

What it does:

  • Computes estimated marginal means (EMMs): breed means averaged across farms, weighted appropriately
  • Handles missing cells gracefully
  • Provides correct SEs accounting for unbalanced sample sizes
  • Enables estimable contrasts only

When to use:

  • Any unbalanced multi-factor design
  • Missing cells
  • Want “adjusted means” (breed means accounting for farm differences)

How to use:

fit <- lm(response ~ factor1 + factor2, data = data)
emm <- emmeans(fit, "factor1")         # Marginal means
pairs(emm)                              # Pairwise comparisons
contrast(emm, list("Custom" = c(...))) # Custom contrasts

12.7.8 Precision Under Unbalanced Data

# Standard errors vary with sample size
emm_summary <- summary(emm_breed)

cat("Standard errors by breed:\n")
Standard errors by breed:
print(emm_summary[, c("breed", "emmean", "SE")])
      breed   emmean          SE
1     Angus 1.157149 0.005263675
2 Charolais 1.307028 0.007543979
3  Hereford 1.046161 0.005615416
4 Simmental 1.391996 0.006354405
# Relative precision (1/SE²)
emm_summary$rel_precision <- 1 / emm_summary$SE^2

cat("\nRelative precision (1/SE²):\n")

Relative precision (1/SE²):
print(emm_summary[, c("breed", "rel_precision")])
      breed rel_precision
1     Angus      36092.91
2 Charolais      17571.10
3  Hereford      31712.92
4 Simmental      24765.67
# Sample sizes by breed
n_breed <- table(beef$breed)
cat("\nSample sizes:\n")

Sample sizes:
print(n_breed)

    Angus Charolais  Hereford Simmental 
       57        28        49        43 
cat("\n→ Breeds with more observations have smaller SEs (more precise).\n")

→ Breeds with more observations have smaller SEs (more precise).

Key insight: With unbalanced data, precision differs across groups.

Formula (approximately, for simple contrasts):

\[ SE(\hat{\mu}_i - \hat{\mu}_j) \approx \hat{\sigma} \sqrt{\frac{1}{n_i} + \frac{1}{n_j}} \]

Implications:

  • Comparisons involving small groups less precise
  • Must report sample sizes with means
  • Consider weighting in meta-analysis

12.8 Practical Guidelines

12.8.1 Statistical Recommendations

When analyzing unbalanced livestock data, follow these best practices:

1. Use Type III SS for hypothesis testing

  • Tests each effect adjusted for all others
  • Order-independent
  • Appropriate for unbalanced designs

2. Focus on estimable functions

  • Don’t interpret non-estimable parameters (NA in R output)
  • Use emmeans for marginal means
  • Test contrasts, not individual parameters

3. Report sample sizes clearly

  • Always show \(n\) by group
  • Acknowledge precision differences
  • Larger groups have narrower confidence intervals

4. Use appropriate standard errors

  • emmeans computes correct SEs for unbalanced data
  • Don’t assume equal precision across groups
  • \(SE(\hat{\mu}_i) \propto 1/\sqrt{n_i}\)

5. Consider model selection carefully

  • Is interaction needed? Test it.
  • Are covariates confounded with treatments?
  • Document missing cells and their impact
WarningCommon Pitfalls with Unbalanced Data
  1. Using Type I SS when order matters: Type I depends on order effects entered—misleading for unbalanced data

  2. Ignoring confounding: If groups differ systematically (e.g., all high-performing animals in one group), can’t separate cause from selection

  3. Equal weighting when precision differs: Averaging group means gives equal weight, but some based on \(n=5\), others on \(n=50\)

  4. Interpreting non-estimable parameters: If R shows NA, that parameter is not estimable—don’t try to interpret

  5. Assuming orthogonality: Unbalanced designs lose orthogonality—effects are correlated

Always check:

  • Design matrix rank
  • Which parameters estimable
  • Standard errors vary with \(n\)
  • Type III SS for testing

12.8.2 Experimental Design Implications

Why balanced designs are preferred:

  1. Equal precision: All group means estimated with same accuracy
  2. Orthogonal contrasts: Effects independent, easier to interpret
  3. Type I = II = III: All SS types agree
  4. Simpler analysis: No estimability issues
  5. More power: For same total \(n\), balanced design most powerful

When unbalanced is unavoidable (commercial data, missing data, etc.):

  1. Document why: Animal loss? Selection? Practical constraints?
  2. Analyze appropriately: Type III SS, emmeans, estimable contrasts
  3. Report limitations: Missing cells, confounding, precision differences
  4. Be transparent: Show sample sizes, acknowledge bias potential

For planned experiments:

  • Aim for balance when possible
  • If unbalanced necessary, plan analysis in advance
  • Consider blocking to reduce confounding

12.9 Summary

12.9.1 Conceptual Summary

Real livestock data is messy, but the linear models framework still works—we just need additional tools.

Key insights from Week 12:

  1. Cell means model is always full rank, even with unbalanced data. It directly estimates group means, avoiding singularity issues.

  2. Effects model may be rank deficient with unbalanced data. Individual \(\mu\) and \(\alpha_i\) parameters are not uniquely estimable, but contrasts \(\alpha_i - \alpha_j\) are.

  3. Generalized inverses solve singular systems. Any \((\mathbf{X}'\mathbf{X})^-\) works—different g-inverses give different \(\mathbf{b}\), but estimable functions \(\mathbf{c}'\mathbf{b}\) are always the same.

  4. Estimability is key: A function \(\mathbf{c}'\boldsymbol{\beta}\) is estimable iff \(\mathbf{c}' = \mathbf{a}'\mathbf{X}\) for some \(\mathbf{a}\). Only estimable functions should be interpreted.

  5. Constraints remove redundancy: Set-to-zero, sum-to-zero, or weighted constraints make parameters unique, but choice is interpretational—estimable contrasts remain the same.

For animal breeding applications:

  • We care about differences between breeds, sires, or treatments—not absolute values
  • Rank deficiency is not a problem for contrasts
  • Unbalanced data is the norm in genetic evaluation
  • Focus on estimable functions and use appropriate methods (emmeans, Type III SS)
NoteLooking Ahead

Week 13: Special Topics I

  • More on constraint systems and different parameterizations
  • Types of generalized inverses (Moore-Penrose, reflexive, others)
  • Weighted least squares for heterogeneous variances
  • Strategic approaches to unbalanced data

Week 14: Special Topics II

  • Polynomial regression (growth curves, lactation curves)
  • Regression through the origin
  • Preview of mixed models: Adding random effects
  • Henderson’s mixed model equations (MME): The foundation of BLUP

\[ \begin{bmatrix} \mathbf{X}'\mathbf{R}^{-1}\mathbf{X} & \mathbf{X}'\mathbf{R}^{-1}\mathbf{Z} \\ \mathbf{Z}'\mathbf{R}^{-1}\mathbf{X} & \mathbf{Z}'\mathbf{R}^{-1}\mathbf{Z} + \mathbf{G}^{-1} \end{bmatrix} \begin{bmatrix} \hat{\boldsymbol{\beta}} \\ \hat{\mathbf{u}} \end{bmatrix} = \begin{bmatrix} \mathbf{X}'\mathbf{R}^{-1}\mathbf{y} \\ \mathbf{Z}'\mathbf{R}^{-1}\mathbf{y} \end{bmatrix} \]

These are augmented normal equations—same tools (generalized inverses, estimability) apply!

Week 12 provides the foundation for understanding mixed models in genetic evaluation.


12.9.2 Key Equations Summary

Rank-deficient normal equations: \[\mathbf{X}'\mathbf{X}\mathbf{b} = \mathbf{X}'\mathbf{y}, \quad r(\mathbf{X}'\mathbf{X}) < p\]

G-inverse solution: \[\mathbf{b} = (\mathbf{X}'\mathbf{X})^-\mathbf{X}'\mathbf{y}\]

Estimability criterion: \[\mathbf{c}'\boldsymbol{\beta} \text{ estimable} \iff \mathbf{c}' = \mathbf{a}'\mathbf{X} \text{ for some } \mathbf{a}\]

Variance of estimable function: \[\text{Var}(\mathbf{c}'\mathbf{b}) = \mathbf{c}'(\mathbf{X}'\mathbf{X})^-\mathbf{c}\sigma^2\]

t-test for contrast: \[t = \frac{\mathbf{c}'\mathbf{b}}{\sqrt{\mathbf{c}'(\mathbf{X}'\mathbf{X})^-\mathbf{c}\hat{\sigma}^2}} \sim t(n - r(\mathbf{X}))\]

SE for unbalanced contrast (approximate): \[SE(\hat{\mu}_i - \hat{\mu}_j) \approx \hat{\sigma}\sqrt{\frac{1}{n_i} + \frac{1}{n_j}}\]


12.9.3 Cross-References


12.9.4 References

TipAdditional Resources

Textbooks:

  • Searle, S. R. (1971). Linear Models. Wiley. [Classic reference on rank deficiency and estimability]
  • Milliken, G. A., & Johnson, D. E. (2009). Analysis of Messy Data, Volume 1: Designed Experiments (2nd ed.). CRC Press.
  • Henderson, C. R. (1984). Applications of Linear Models in Animal Breeding. University of Guelph.

R Packages:

  • MASS::ginv(): Moore-Penrose inverse
  • emmeans: Estimated marginal means and contrasts
  • car::Anova(): Type II and Type III SS

Vignettes:

  • emmeans vignette on estimability: vignette("basics", package="emmeans")
  • FAQs on unbalanced ANOVA: vignette("FAQs", package="emmeans")

Papers:

  • Searle, S. R., Speed, F. M., & Milliken, G. A. (1980). Population marginal means in the linear model: An alternative to least squares means. The American Statistician, 34(4), 216-221.