3  Week 3: Building the Design Matrix Framework

NoteLearning Objectives

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

  1. Construct design matrices from raw data for different types of predictors
  2. Understand and apply different coding schemes (cell means model, effects model, reference cell coding)
  3. Write the general linear model in matrix form with correct notation
  4. State and interpret the assumptions underlying linear models (Gauss-Markov conditions)
  5. Identify when design matrices are full rank vs. rank deficient
  6. Build design matrices manually in R and compare with model.matrix()

3.1 Introduction: From Raw Data to Matrix Representation

In Weeks 1 and 2, we established the computational foundations and reviewed the linear algebra essentials needed for linear models. Now we address the critical question: How do we get from raw data to the matrix equations we solve?

ImportantThe Bridge from Reality to Mathematics

The design matrix (denoted \(\mathbf{X}\)) is the bridge between:

  • Raw data: Numbers in spreadsheets, databases, or field records
  • Mathematical model: \(\mathbf{y} = \mathbf{X}\boldsymbol{\beta} + \mathbf{e}\)

Understanding how to construct \(\mathbf{X}\) is fundamental to applying linear models in animal breeding and genetics.

3.1.1 Why This Matters in Animal Breeding

In animal breeding applications, we constantly work with different types of predictors:

  • Categorical factors: Breed, sex, herd, diet, pen, treatment
  • Continuous covariates: Birth weight, age, days in milk, temperature
  • Mixed models: Contemporary groups (herd-year-season) combined with genetic relationships

The way we code these predictors into the design matrix determines:

  1. What parameters we estimate (individual means vs. differences from baseline)
  2. Whether solutions are unique (full rank vs. rank deficient)
  3. How we interpret results (absolute effects vs. contrasts)
  4. Which hypotheses we can test (estimable functions)

3.2 The General Linear Model

3.2.1 Matrix Form

The general linear model expresses the relationship between observations and parameters as:

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

where:

  • \(\mathbf{y}\): \((n \times 1)\) vector of observations (response variable)
  • \(\mathbf{X}\): \((n \times p)\) design matrix (known constants)
  • \(\boldsymbol{\beta}\): \((p \times 1)\) vector of unknown parameters (to be estimated)
  • \(\mathbf{e}\): \((n \times 1)\) vector of random errors
NoteDimensions Matter

Always verify matrix dimensions for compatibility:

  • \(\mathbf{X}\) is \(n \times p\) (n observations, p parameters)
  • \(\boldsymbol{\beta}\) is \(p \times 1\) (p parameters to estimate)
  • \(\mathbf{X}\boldsymbol{\beta}\) is \((n \times p)(p \times 1) = (n \times 1)\)
  • \(\mathbf{y}\) is \(n \times 1\)
  • \(\mathbf{e}\) is \(n \times 1\)

The model equation is dimensionally consistent.

3.2.2 Expected Value Form

Taking expected values of both sides:

\[ E(\mathbf{y}) = E(\mathbf{X}\boldsymbol{\beta} + \mathbf{e}) = \mathbf{X}\boldsymbol{\beta} + E(\mathbf{e}) = \mathbf{X}\boldsymbol{\beta} \]

This assumes \(E(\mathbf{e}) = \mathbf{0}\) (errors have mean zero, discussed below).

3.2.3 Scalar Form for Individual Observations

For the \(i\)-th observation:

\[ y_i = \sum_{j=1}^{p} x_{ij}\beta_j + e_i = x_{i1}\beta_1 + x_{i2}\beta_2 + \cdots + x_{ip}\beta_p + e_i \]

where \(x_{ij}\) is the element in row \(i\), column \(j\) of \(\mathbf{X}\).

3.3 Building Design Matrices for Different Predictor Types

The structure of \(\mathbf{X}\) depends on the type of predictors in your model. Let’s examine the three main types.

3.3.1 Continuous Predictors (Regression)

When all predictors are continuous variables, the design matrix includes:

  1. A column of ones (for the intercept)
  2. Columns for each predictor variable

Example: Simple Linear Regression

Model: \(y_i = \beta_0 + \beta_1 x_i + e_i\)

For \(n=4\) observations:

# Data: broiler weight (kg) vs. age (days)
age <- c(21, 28, 35, 42)
weight <- c(0.5, 0.9, 1.4, 1.9)

# Design matrix: first column is intercept, second is predictor
X <- cbind(1, age)
print(X)
       age
[1,] 1  21
[2,] 1  28
[3,] 1  35
[4,] 1  42
# Check dimensions
cat("\nDimensions: n =", nrow(X), "observations, p =", ncol(X), "parameters\n")

Dimensions: n = 4 observations, p = 2 parameters

The design matrix is:

\[ \mathbf{X} = \begin{bmatrix} 1 & 21 \\ 1 & 28 \\ 1 & 35 \\ 1 & 42 \end{bmatrix}_{4 \times 2} \]

The parameter vector is:

\[ \boldsymbol{\beta} = \begin{bmatrix} \beta_0 \\ \beta_1 \end{bmatrix}_{2 \times 1} \]

Multiple Regression

With multiple continuous predictors: \(y_i = \beta_0 + \beta_1 x_{1i} + \beta_2 x_{2i} + \cdots + \beta_k x_{ki} + e_i\)

# Example: Predict lamb weaning weight from birth weight and dam age
birth_wt <- c(4.5, 4.0, 4.8, 4.2, 4.6)
dam_age <- c(3, 5, 4, 6, 4)
wean_wt <- c(28, 24, 30, 26, 29)

# Design matrix: intercept, birth weight, dam age
X_mult <- cbind(1, birth_wt, dam_age)
colnames(X_mult) <- c("Intercept", "BirthWt", "DamAge")
print(X_mult)
     Intercept BirthWt DamAge
[1,]         1     4.5      3
[2,]         1     4.0      5
[3,]         1     4.8      4
[4,]         1     4.2      6
[5,]         1     4.6      4

\[ \mathbf{X} = \begin{bmatrix} 1 & 4.5 & 3 \\ 1 & 4.0 & 5 \\ 1 & 4.8 & 4 \\ 1 & 4.2 & 6 \\ 1 & 4.6 & 4 \end{bmatrix}_{5 \times 3}, \quad \boldsymbol{\beta} = \begin{bmatrix} \beta_0 \\ \beta_1 \\ \beta_2 \end{bmatrix}_{3 \times 1} \]

3.3.2 Categorical Predictors (ANOVA)

Categorical predictors (factors) require indicator variables (also called dummy variables). There are two primary coding schemes.

Cell Means Model

The cell means model estimates a separate mean for each group, with no explicit intercept.

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

where \(\mu_i\) is the mean for group \(i\) (\(i = 1, \ldots, g\) groups).

The design matrix has one column per group, with indicators for group membership:

\[ x_{ij} = \begin{cases} 1 & \text{if observation } j \text{ is in group } i \\ 0 & \text{otherwise} \end{cases} \]

Example: Pig Litter Size by Breed (Cell Means)

# Load the pig data
pig_data <- data.frame(
  breed = c("Yorkshire", "Yorkshire", "Landrace", "Landrace", "Duroc", "Duroc"),
  litter_size = c(11, 12, 10, 11, 9, 10)
)

print(pig_data)
      breed litter_size
1 Yorkshire          11
2 Yorkshire          12
3  Landrace          10
4  Landrace          11
5     Duroc           9
6     Duroc          10
# Cell means design matrix: one column per breed
# Yorkshire = column 1, Landrace = column 2, Duroc = column 3
X_cell <- model.matrix(~ breed - 1, data = pig_data)  # -1 removes intercept
print(X_cell)
  breedDuroc breedLandrace breedYorkshire
1          0             0              1
2          0             0              1
3          0             1              0
4          0             1              0
5          1             0              0
6          1             0              0
attr(,"assign")
[1] 1 1 1
attr(,"contrasts")
attr(,"contrasts")$breed
[1] "contr.treatment"
# Check rank
cat("\nRank of X:", qr(X_cell)$rank, "\n")

Rank of X: 3 
cat("Number of parameters:", ncol(X_cell), "\n")
Number of parameters: 3 

The cell means design matrix is:

\[ \mathbf{X}_{\text{cell}} = \begin{bmatrix} 1 & 0 & 0 \\ 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \\ 0 & 0 & 1 \end{bmatrix}_{6 \times 3} \]

Parameter vector:

\[ \boldsymbol{\beta}_{\text{cell}} = \begin{bmatrix} \mu_{\text{Duroc}} \\ \mu_{\text{Landrace}} \\ \mu_{\text{Yorkshire}} \end{bmatrix}_{3 \times 1} \]

TipCell Means Model Properties
  • Always full rank: \(r(\mathbf{X}) = p\) (number of groups)
  • Direct interpretation: \(\mu_i\) is the mean for group \(i\)
  • All parameters estimable: Each \(\mu_i\) can be uniquely estimated
  • Common in animal breeding: Natural for comparing breed means, treatment means, etc.

Effects Model (with Constraints)

The effects model decomposes each observation into an overall mean plus group-specific deviations.

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

where:

  • \(\mu\): overall mean (intercept)
  • \(\alpha_i\): effect of group \(i\) (deviation from overall mean)

Problem: This model is overparameterized without constraints.

With \(g\) groups, we have \(g+1\) parameters (\(\mu\) and \(\alpha_1, \ldots, \alpha_g\)), but only \(g\) distinct means. We need a constraint to make parameters identifiable.

Common constraint: \(\sum_{i=1}^{g} \alpha_i = 0\) (sum-to-zero constraint)

With this constraint, \(\alpha_i\) represents the deviation of group \(i\) from the overall mean.

Example: Pig Litter Size by Breed (Effects Model)

# Effects model design matrix: intercept + group indicators
# R uses reference cell coding by default (first level = 0)
X_effects <- model.matrix(~ breed, data = pig_data)
print(X_effects)
  (Intercept) breedLandrace breedYorkshire
1           1             0              1
2           1             0              1
3           1             1              0
4           1             1              0
5           1             0              0
6           1             0              0
attr(,"assign")
[1] 0 1 1
attr(,"contrasts")
attr(,"contrasts")$breed
[1] "contr.treatment"
# Check rank
cat("\nRank of X:", qr(X_effects)$rank, "\n")

Rank of X: 3 
cat("Number of parameters:", ncol(X_effects), "\n")
Number of parameters: 3 

The effects model design matrix (without constraint applied yet):

\[ \mathbf{X}_{\text{effects}} = \begin{bmatrix} 1 & 0 & 0 \\ 1 & 0 & 0 \\ 1 & 1 & 0 \\ 1 & 1 & 0 \\ 1 & 0 & 1 \\ 1 & 0 & 1 \end{bmatrix}_{6 \times 3} \]

WarningRank Deficiency Alert

The full effects model matrix (with all \(g+1\) parameters) would be:

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

This has rank \(r(\mathbf{X}) = 3 < 4\), making it rank deficient. Individual parameters (\(\mu\), \(\alpha_i\)) are not uniquely estimable, but contrasts like \(\alpha_i - \alpha_j\) are estimable.

We’ll address non-full rank models in detail in Week 12.

Reference Cell Coding (Set-to-Zero Constraint)

R’s default for categorical variables uses reference cell coding (also called treatment coding):

  • Set the first group’s effect to zero: \(\alpha_1 = 0\)
  • Other \(\alpha_i\) represent deviations from group 1

With this constraint:

  • \(\mu\) = mean of reference group (Duroc, alphabetically first)
  • \(\alpha_{\text{Landrace}}\) = Landrace effect = (Landrace mean) - (Duroc mean)
  • \(\alpha_{\text{Yorkshire}}\) = Yorkshire effect = (Yorkshire mean) - (Duroc mean)
# Compute group means
group_means <- tapply(pig_data$litter_size, pig_data$breed, mean)
print(group_means)
    Duroc  Landrace Yorkshire 
      9.5      10.5      11.5 
# Interpretation with Duroc as reference (first alphabetically)
cat("\nWith reference cell coding (Duroc as baseline):\n")

With reference cell coding (Duroc as baseline):
cat("μ (intercept) = Duroc mean =", group_means["Duroc"], "\n")
μ (intercept) = Duroc mean = 9.5 
cat("α_Landrace = Landrace - Duroc =", group_means["Landrace"] - group_means["Duroc"], "\n")
α_Landrace = Landrace - Duroc = 1 
cat("α_Yorkshire = Yorkshire - Duroc =", group_means["Yorkshire"] - group_means["Duroc"], "\n")
α_Yorkshire = Yorkshire - Duroc = 2 

3.3.3 Mixed Predictors (ANCOVA)

When models include both categorical and continuous predictors, we have Analysis of Covariance (ANCOVA).

Model: \(y_{ij} = \mu + \alpha_i + \beta(x_{ij} - \bar{x}) + e_{ij}\)

The design matrix combines indicator columns (for groups) and continuous columns (for covariates).

Example: Egg Production by Strain, Adjusted for Body Weight

# Data: egg production (eggs/month) by strain, with body weight covariate
egg_data <- data.frame(
  strain = rep(c("Strain1", "Strain2", "Strain3"), each = 3),
  body_weight = c(1.8, 2.0, 1.9, 1.7, 1.8, 1.9, 2.1, 2.3, 2.2),
  eggs = c(24, 26, 25, 22, 23, 24, 26, 28, 27)
)

# ANCOVA design matrix: strain indicators + centered body weight
egg_data$bw_centered <- egg_data$body_weight - mean(egg_data$body_weight)
X_ancova <- model.matrix(~ strain + bw_centered, data = egg_data)
print(X_ancova)
  (Intercept) strainStrain2 strainStrain3 bw_centered
1           1             0             0 -0.16666667
2           1             0             0  0.03333333
3           1             0             0 -0.06666667
4           1             1             0 -0.26666667
5           1             1             0 -0.16666667
6           1             1             0 -0.06666667
7           1             0             1  0.13333333
8           1             0             1  0.33333333
9           1             0             1  0.23333333
attr(,"assign")
[1] 0 1 1 2
attr(,"contrasts")
attr(,"contrasts")$strain
[1] "contr.treatment"
NoteCentering Covariates

We often center continuous covariates by subtracting the mean: \(x_{ij}^* = x_{ij} - \bar{x}\)

Benefits:

  1. The intercept represents the group mean at the average covariate value
  2. Reduces collinearity between interaction terms and main effects
  3. Makes parameter interpretation more intuitive

Note: Centering does NOT change \(R^2\), residuals, or fitted values—only the interpretation of \(\beta_0\).

3.4 Model Assumptions (Gauss-Markov Conditions)

For the general linear model \(\mathbf{y} = \mathbf{X}\boldsymbol{\beta} + \mathbf{e}\), we make specific assumptions about the error term \(\mathbf{e}\).

3.4.1 The Three Core Assumptions

ImportantGauss-Markov Assumptions

Under these assumptions, ordinary least squares (OLS) estimators are BLUE (Best Linear Unbiased Estimators):

  1. Linearity: The relationship between \(\mathbf{y}\) and \(\mathbf{X}\) is linear
    • Correctly specified: \(E(\mathbf{y}) = \mathbf{X}\boldsymbol{\beta}\)
  2. Zero Mean Errors: \(E(\mathbf{e}) = \mathbf{0}\)
    • Errors are unbiased (centered at zero)
    • No systematic over- or under-prediction
  3. Homoscedasticity and Independence: \(\text{Var}(\mathbf{e}) = \sigma^2\mathbf{I}\)
    • Homoscedasticity: Constant variance across all observations (\(\text{Var}(e_i) = \sigma^2\) for all \(i\))
    • Independence: Errors are uncorrelated (\(\text{Cov}(e_i, e_j) = 0\) for \(i \neq j\))

3.4.2 Variance-Covariance Structure

The assumption \(\text{Var}(\mathbf{e}) = \sigma^2\mathbf{I}\) can be written explicitly:

\[ \text{Var}(\mathbf{e}) = \begin{bmatrix} \sigma^2 & 0 & 0 & \cdots & 0 \\ 0 & \sigma^2 & 0 & \cdots & 0 \\ 0 & 0 & \sigma^2 & \cdots & 0 \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & 0 & \cdots & \sigma^2 \end{bmatrix} = \sigma^2 \mathbf{I} \]

  • Diagonal elements: \(\text{Var}(e_i) = \sigma^2\) (constant variance)
  • Off-diagonal elements: \(\text{Cov}(e_i, e_j) = 0\) (independence)

3.4.3 Additional Assumption for Inference: Normality

For hypothesis testing and confidence intervals, we often add:

  1. Normality: \(\mathbf{e} \sim N(\mathbf{0}, \sigma^2\mathbf{I})\)

This implies \(\mathbf{y} \sim N(\mathbf{X}\boldsymbol{\beta}, \sigma^2\mathbf{I})\)

NoteWhen is Normality Needed?
  • NOT needed for unbiasedness of \(\hat{\boldsymbol{\beta}}\) or minimum variance (Gauss-Markov)
  • IS needed for:
    • Exact \(t\)-tests and \(F\)-tests
    • Confidence intervals with stated coverage
    • Maximum likelihood interpretation

For large samples, the Central Limit Theorem provides approximate normality even if errors aren’t exactly normal.

3.4.4 Violations and Consequences

Violation Consequence Solution
Non-linearity Biased estimates Transform variables, add polynomial terms
\(E(\mathbf{e}) \neq \mathbf{0}\) Biased intercept Check model specification
Heteroscedasticity Inefficient estimates, incorrect SEs Weighted least squares, robust SEs
Correlation (e.g., time series) Inefficient estimates, incorrect SEs Generalized least squares, mixed models
Non-normality Invalid inference (test statistics) Transformations, bootstrapping, larger samples

We’ll explore diagnostics to check these assumptions in Week 11.

3.5 Small Example: Pig Litter Size by Breed

Let’s work through a complete small example, showing both cell means and effects model formulations.

3.5.1 Data

# Read the data
pig_data <- read.csv("data/pig_litter_breeds.csv")
print(pig_data)
  sow_id     breed litter_size
1      1 Yorkshire          11
2      2 Yorkshire          12
3      3  Landrace          10
4      4  Landrace          11
5      5     Duroc           9
6      6     Duroc          10
# Summary statistics by breed
cat("\nSummary by breed:\n")

Summary by breed:
by(pig_data$litter_size, pig_data$breed, function(x) {
  cat(sprintf("  n = %d, mean = %.2f, sd = %.2f\n", length(x), mean(x), sd(x)))
})
  n = 2, mean = 9.50, sd = 0.71
  n = 2, mean = 10.50, sd = 0.71
  n = 2, mean = 11.50, sd = 0.71
pig_data$breed: Duroc
NULL
------------------------------------------------------------ 
pig_data$breed: Landrace
NULL
------------------------------------------------------------ 
pig_data$breed: Yorkshire
NULL

3.5.2 Cell Means Model

Model: \(y_{ij} = \mu_i + e_{ij}\) where \(i \in \{\text{Duroc, Landrace, Yorkshire}\}\)

# Construct design matrix manually
n <- nrow(pig_data)
X_cell <- matrix(0, nrow = n, ncol = 3)
X_cell[pig_data$breed == "Duroc", 1] <- 1
X_cell[pig_data$breed == "Landrace", 2] <- 1
X_cell[pig_data$breed == "Yorkshire", 3] <- 1
colnames(X_cell) <- c("Duroc", "Landrace", "Yorkshire")

cat("Cell Means Design Matrix:\n")
Cell Means Design Matrix:
print(X_cell)
     Duroc Landrace Yorkshire
[1,]     0        0         1
[2,]     0        0         1
[3,]     0        1         0
[4,]     0        1         0
[5,]     1        0         0
[6,]     1        0         0
# Response vector
y <- pig_data$litter_size

# Normal equations: X'X b = X'y
XtX <- t(X_cell) %*% X_cell
Xty <- t(X_cell) %*% y

cat("\nX'X (3×3):\n")

X'X (3×3):
print(XtX)
          Duroc Landrace Yorkshire
Duroc         2        0         0
Landrace      0        2         0
Yorkshire     0        0         2
cat("\nX'y (3×1):\n")

X'y (3×1):
print(Xty)
          [,1]
Duroc       19
Landrace    21
Yorkshire   23
# Solve for estimates
b_cell <- solve(XtX) %*% Xty
cat("\nParameter estimates:\n")

Parameter estimates:
print(b_cell)
          [,1]
Duroc      9.5
Landrace  10.5
Yorkshire 11.5
# These are just the group means!
cat("\nVerify these equal group means:\n")

Verify these equal group means:
print(tapply(y, pig_data$breed, mean))
    Duroc  Landrace Yorkshire 
      9.5      10.5      11.5 

Interpretation:

  • \(\hat{\mu}_{\text{Duroc}} = 9.5\) piglets per litter
  • \(\hat{\mu}_{\text{Landrace}} = 10.5\) piglets per litter
  • \(\hat{\mu}_{\text{Yorkshire}} = 11.5\) piglets per litter
TipCell Means = Group Means

In the cell means model with balanced data, the estimates are simply the group means. The math gives us:

\[ \hat{\mu}_i = \frac{\sum_{j=1}^{n_i} y_{ij}}{n_i} = \bar{y}_{i\cdot} \]

This is intuitive: our best estimate of a group’s mean is the average of observations in that group!

3.5.3 Effects Model (Reference Cell Coding)

Model: \(y_{ij} = \mu + \alpha_i + e_{ij}\) with \(\alpha_{\text{Duroc}} = 0\) (Duroc as reference)

# Use R's default reference cell coding
X_effects <- model.matrix(~ breed, data = pig_data)

cat("Effects Model Design Matrix:\n")
Effects Model Design Matrix:
print(X_effects)
  (Intercept) breedLandrace breedYorkshire
1           1             0              1
2           1             0              1
3           1             1              0
4           1             1              0
5           1             0              0
6           1             0              0
attr(,"assign")
[1] 0 1 1
attr(,"contrasts")
attr(,"contrasts")$breed
[1] "contr.treatment"
# Note: Duroc is reference (alphabetically first), so no column for it
# breedLandrace and breedYorkshire are deviations from Duroc

# Solve using lm() for comparison
fit_effects <- lm(litter_size ~ breed, data = pig_data)
cat("\nParameter estimates (reference cell coding):\n")

Parameter estimates (reference cell coding):
print(coef(fit_effects))
   (Intercept)  breedLandrace breedYorkshire 
           9.5            1.0            2.0 
# Manual calculation
XtX_eff <- t(X_effects) %*% X_effects
Xty_eff <- t(X_effects) %*% y
b_effects <- solve(XtX_eff) %*% Xty_eff

cat("\nManual calculation:\n")

Manual calculation:
print(b_effects)
               [,1]
(Intercept)     9.5
breedLandrace   1.0
breedYorkshire  2.0

Interpretation (with Duroc as reference):

  • \(\hat{\mu} = 9.5\) = Duroc mean (reference group)
  • \(\hat{\alpha}_{\text{Landrace}} = 1.0\) = Landrace mean - Duroc mean = \(10.5 - 9.5\)
  • \(\hat{\alpha}_{\text{Yorkshire}} = 2.0\) = Yorkshire mean - Duroc mean = \(11.5 - 9.5\)

3.5.4 Comparing the Two Models

# Cell means model: estimates are group means
cat("Cell Means Model Estimates:\n")
Cell Means Model Estimates:
cat("Duroc:", b_cell[1], "\n")
Duroc: 9.5 
cat("Landrace:", b_cell[2], "\n")
Landrace: 10.5 
cat("Yorkshire:", b_cell[3], "\n\n")
Yorkshire: 11.5 
# Effects model: reconstruct group means
cat("Effects Model - Reconstructed Group Means:\n")
Effects Model - Reconstructed Group Means:
cat("Duroc (reference):", b_effects[1], "\n")
Duroc (reference): 9.5 
cat("Landrace:", b_effects[1] + b_effects[2], "\n")
Landrace: 10.5 
cat("Yorkshire:", b_effects[1] + b_effects[3], "\n\n")
Yorkshire: 11.5 
# Both give identical fitted values
fitted_cell <- X_cell %*% b_cell
fitted_effects <- X_effects %*% b_effects

cat("Fitted values match:", all.equal(fitted_cell[,1], fitted_effects[,1]), "\n")
Fitted values match: names for current but not for target 
ImportantKey Insight: Coding Doesn’t Change Predictions

Different coding schemes (cell means vs. effects model) give different parameter estimates, but:

  1. Fitted values \(\hat{\mathbf{y}} = \mathbf{X}\hat{\boldsymbol{\beta}}\) are identical
  2. Residuals \(\mathbf{e} = \mathbf{y} - \hat{\mathbf{y}}\) are identical
  3. \(R^2\), SSE, and all other model fit statistics are identical
  4. Estimable functions (like breed differences) give the same results

The choice of coding affects parameter interpretation, not model fit.

3.6 Realistic Application: Broiler Body Weight by Sex

Now let’s analyze a larger dataset: body weight (kg) for 20 broiler chickens at 42 days of age, by sex.

3.6.1 Exploratory Analysis

# Load data
broiler_data <- read.csv("data/broiler_bodyweight_sex.csv")

cat("Data structure:\n")
Data structure:
str(broiler_data)
'data.frame':   20 obs. of  3 variables:
 $ bird_id       : int  1 2 3 4 5 6 7 8 9 10 ...
 $ sex           : chr  "Male" "Male" "Male" "Male" ...
 $ body_weight_kg: num  2.85 2.92 2.78 2.88 2.95 2.82 2.9 2.87 2.93 2.8 ...
cat("\nFirst few rows:\n")

First few rows:
head(broiler_data)
bird_id sex body_weight_kg
1 Male 2.85
2 Male 2.92
3 Male 2.78
4 Male 2.88
5 Male 2.95
6 Male 2.82
# Summary statistics by sex
cat("\nSummary statistics by sex:\n")

Summary statistics by sex:
by(broiler_data$body_weight_kg, broiler_data$sex, function(x) {
  cat(sprintf("  n = %d, mean = %.3f, sd = %.3f, min = %.3f, max = %.3f\n",
              length(x), mean(x), sd(x), min(x), max(x)))
})
  n = 10, mean = 2.496, sd = 0.032, min = 2.450, max = 2.550
  n = 10, mean = 2.870, sd = 0.057, min = 2.780, max = 2.950
broiler_data$sex: Female
NULL
------------------------------------------------------------ 
broiler_data$sex: Male
NULL

3.6.2 Visualization

# Box plot
boxplot(body_weight_kg ~ sex, data = broiler_data,
        main = "Broiler Body Weight by Sex",
        xlab = "Sex",
        ylab = "Body Weight (kg)",
        col = c("lightblue", "lightpink"))

# Add means
means <- tapply(broiler_data$body_weight_kg, broiler_data$sex, mean)
points(1:2, means, pch = 19, col = "red", cex = 1.5)
legend("topright", legend = "Group mean", pch = 19, col = "red")

Observations:

  • Males are clearly heavier than females (sexual dimorphism)
  • Both groups show similar variation (standard deviations)
  • Distributions appear roughly symmetric (no obvious outliers)

3.6.3 Cell Means Model Analysis

# Cell means model: estimate mean for each sex
X_cell_broiler <- model.matrix(~ sex - 1, data = broiler_data)
y_broiler <- broiler_data$body_weight_kg

# Solve normal equations
XtX_broiler <- t(X_cell_broiler) %*% X_cell_broiler
Xty_broiler <- t(X_cell_broiler) %*% y_broiler

cat("X'X:\n")
X'X:
print(XtX_broiler)
          sexFemale sexMale
sexFemale        10       0
sexMale           0      10
cat("\nX'y:\n")

X'y:
print(Xty_broiler)
           [,1]
sexFemale 24.96
sexMale   28.70
b_cell_broiler <- solve(XtX_broiler) %*% Xty_broiler
cat("\nEstimated means (kg):\n")

Estimated means (kg):
print(b_cell_broiler)
           [,1]
sexFemale 2.496
sexMale   2.870
# Compute residuals and fit statistics
fitted_broiler <- X_cell_broiler %*% b_cell_broiler
residuals_broiler <- y_broiler - fitted_broiler
SSE_broiler <- sum(residuals_broiler^2)
SST_broiler <- sum((y_broiler - mean(y_broiler))^2)
R2_broiler <- 1 - SSE_broiler / SST_broiler

cat("\nModel fit:\n")

Model fit:
cat("SSE =", round(SSE_broiler, 4), "\n")
SSE = 0.0386 
cat("SST =", round(SST_broiler, 4), "\n")
SST = 0.738 
cat("R² =", round(R2_broiler, 4), "\n")
R² = 0.9476 

3.6.4 Effects Model Analysis

# Fit using lm() with effects model
fit_broiler <- lm(body_weight_kg ~ sex, data = broiler_data)

cat("Effects model summary:\n")
Effects model summary:
summary(fit_broiler)

Call:
lm(formula = body_weight_kg ~ sex, data = broiler_data)

Residuals:
    Min      1Q  Median      3Q     Max 
-0.0900 -0.0285  0.0020  0.0310  0.0800 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  2.49600    0.01465  170.36  < 2e-16 ***
sexMale      0.37400    0.02072   18.05 5.62e-13 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.04633 on 18 degrees of freedom
Multiple R-squared:  0.9476,    Adjusted R-squared:  0.9447 
F-statistic: 325.8 on 1 and 18 DF,  p-value: 5.617e-13
# Compare with manual calculation
X_eff_broiler <- model.matrix(fit_broiler)
b_eff_broiler <- solve(t(X_eff_broiler) %*% X_eff_broiler) %*%
                 t(X_eff_broiler) %*% y_broiler

cat("\nManual calculation of effects model:\n")

Manual calculation of effects model:
print(b_eff_broiler)
             [,1]
(Intercept) 2.496
sexMale     0.374

3.6.5 Interpretation

With Female as the reference category (alphabetically first):

  • Intercept = \(2.496\) kg = estimated mean for females
  • sexMale = \(0.371\) kg = difference between males and females
  • Male mean = \(2.496 + 0.371 = 2.867\) kg
NoteStatistical Significance

The lm() output shows:

  • t-statistic for sexMale = 18.66 (very large!)
  • p-value < 0.001 (highly significant)

This provides strong evidence that male broilers weigh more than females at 42 days of age.

We’ll cover the details of hypothesis testing in Weeks 5-6.

3.6.6 Verify Against Simple Calculations

# The estimates should match simple group means
group_means_broiler <- tapply(y_broiler, broiler_data$sex, mean)
cat("Group means from data:\n")
Group means from data:
print(group_means_broiler)
Female   Male 
 2.496  2.870 
cat("\nDifference (Male - Female):\n")

Difference (Male - Female):
print(group_means_broiler["Male"] - group_means_broiler["Female"])
 Male 
0.374 
cat("\nThese match our effects model estimates:\n")

These match our effects model estimates:
cat("Intercept (Female mean):", coef(fit_broiler)[1], "\n")
Intercept (Female mean): 2.496 
cat("sexMale (difference):", coef(fit_broiler)[2], "\n")
sexMale (difference): 0.374 

3.7 Building Design Matrices in R

R provides several ways to construct design matrices. Understanding both manual and automatic approaches deepens your understanding.

3.7.1 Manual Construction

For full control and learning, build \(\mathbf{X}\) manually:

# Example: 2 groups, 4 observations
group <- c("A", "A", "B", "B")
y_vals <- c(5, 7, 3, 4)

# Cell means model: create indicator columns
n <- length(y_vals)
X_manual <- matrix(0, nrow = n, ncol = 2)
X_manual[group == "A", 1] <- 1
X_manual[group == "B", 2] <- 1
colnames(X_manual) <- c("GroupA", "GroupB")

cat("Manually constructed design matrix:\n")
Manually constructed design matrix:
print(X_manual)
     GroupA GroupB
[1,]      1      0
[2,]      1      0
[3,]      0      1
[4,]      0      1

3.7.2 Using model.matrix()

The model.matrix() function automatically creates design matrices from formulas:

# Create a data frame
example_data <- data.frame(group = group, y = y_vals)

# Cell means model (no intercept)
X_cell_auto <- model.matrix(~ group - 1, data = example_data)
cat("Cell means (~ group - 1):\n")
Cell means (~ group - 1):
print(X_cell_auto)
  groupA groupB
1      1      0
2      1      0
3      0      1
4      0      1
attr(,"assign")
[1] 1 1
attr(,"contrasts")
attr(,"contrasts")$group
[1] "contr.treatment"
# Effects model (with intercept)
X_effects_auto <- model.matrix(~ group, data = example_data)
cat("\nEffects model (~ group):\n")

Effects model (~ group):
print(X_effects_auto)
  (Intercept) groupB
1           1      0
2           1      0
3           1      1
4           1      1
attr(,"assign")
[1] 0 1
attr(,"contrasts")
attr(,"contrasts")$group
[1] "contr.treatment"
TipFormula Syntax in R
  • ~ group: Effects model with intercept (reference cell coding)
  • ~ group - 1: Cell means model (no intercept, one column per level)
  • ~ 0 + group: Equivalent to ~ group - 1
  • ~ x1 + x2: Multiple predictors (intercept + x1 + x2)
  • ~ group + x: ANCOVA (categorical + continuous)
  • ~ group * x: Includes main effects and interaction

3.7.3 Checking Design Matrix Properties

Always verify your design matrix:

# Function to check design matrix properties
check_design <- function(X, name = "X") {
  cat("\n=== Design Matrix Check:", name, "===\n")
  cat("Dimensions:", nrow(X), "×", ncol(X), "\n")
  cat("Rank:", qr(X)$rank, "\n")
  cat("Full rank?", qr(X)$rank == ncol(X), "\n")

  # Check for linear dependencies
  if (qr(X)$rank < ncol(X)) {
    cat("WARNING: Matrix is rank deficient!\n")
    cat("Number of parameters:", ncol(X), "\n")
    cat("Effective rank:", qr(X)$rank, "\n")
  }
}

# Check our broiler design matrices
check_design(X_cell_broiler, "Cell Means (Broiler)")

=== Design Matrix Check: Cell Means (Broiler) ===
Dimensions: 20 × 2 
Rank: 2 
Full rank? TRUE 
check_design(model.matrix(fit_broiler), "Effects Model (Broiler)")

=== Design Matrix Check: Effects Model (Broiler) ===
Dimensions: 20 × 2 
Rank: 2 
Full rank? TRUE 

3.8 Summary

3.8.1 Key Takeaways

NoteWhat We Learned
  1. Design Matrix is the Bridge: \(\mathbf{X}\) connects raw data to the model \(\mathbf{y} = \mathbf{X}\boldsymbol{\beta} + \mathbf{e}\)

  2. Predictor Types:

    • Continuous predictors → columns of numeric values
    • Categorical predictors → indicator (dummy) variables
  3. Coding Schemes:

    • Cell means: One parameter per group, always full rank
    • Effects model: Intercept + group effects, requires constraints for full rank
    • Reference cell: Set one group to zero, others are deviations
  4. Gauss-Markov Assumptions:

    • Linearity: \(E(\mathbf{y}) = \mathbf{X}\boldsymbol{\beta}\)
    • Zero mean errors: \(E(\mathbf{e}) = \mathbf{0}\)
    • Constant variance and independence: \(\text{Var}(\mathbf{e}) = \sigma^2\mathbf{I}\)
  5. Different Coding, Same Fit: Coding schemes change parameter interpretation but not fitted values, residuals, or \(R^2\)

  6. Always Check: Verify dimensions, rank, and linear independence of \(\mathbf{X}\)

3.8.2 Looking Ahead

Next week (Week 4: Simple Linear Regression), we’ll:

  • Derive least squares estimates for the simplest case: one continuous predictor
  • Understand the geometry of least squares (projection)
  • Interpret slope and intercept in biological contexts
  • Compute fitted values, residuals, and measures of fit
  • Build our first complete solver for regression

The design matrix concepts from this week provide the foundation for all subsequent work with linear models.

3.9 Additional Resources

3.9.1 R Functions Reference

Function Purpose Example
model.matrix() Create design matrix from formula model.matrix(~ breed, data)
cbind() Combine vectors/matrices by columns cbind(1, x1, x2)
qr()$rank Compute matrix rank qr(X)$rank
solve() Matrix inverse solve(XtX)
t() Matrix transpose t(X)
%*% Matrix multiplication X %*% beta

3.9.2 Key Concepts

  • Design matrix: Known constants relating observations to parameters
  • Cell means model: Estimates group means directly (full rank)
  • Effects model: Estimates overall mean + group deviations (may be rank deficient)
  • Reference cell coding: One group set to zero, others are contrasts
  • Gauss-Markov conditions: Assumptions ensuring OLS is BLUE
  • Estimable function: Linear combination of parameters that can be uniquely estimated

Previous: Week 2: Linear Algebra Essentials Next: Week 4: Simple Linear Regression