Chapter 6 Exercises: Linear Model Selection and Regularization

1 Exercise 2

1.1 (a) The lasso, relative to least squares, is:

Answer: iii. Less flexible and hence will give improved prediction accuracy when its increase in bias is less than its decrease in variance.

Justification: The lasso shrinks coefficients toward zero (and can zero some out), reducing flexibility relative to least squares. This raises bias slightly but can substantially cut variance, especially when p is large relative to n. It wins when that variance drop exceeds the bias increase.

1.2 (b) Ridge regression, relative to least squares, is:

Answer: iii. Less flexible and hence will give improved prediction accuracy when its increase in bias is less than its decrease in variance.

Justification: Same logic as lasso — the L2 penalty shrinks coefficients, trading a bit of bias for a lot of variance reduction, especially with correlated predictors.

1.3 (c) Non-linear methods, relative to least squares, are:

Answer: i. More flexible and hence will give improved prediction accuracy when its increase in variance is less than its decrease in bias.

Justification: Non-linear methods capture relationships least squares can’t, lowering bias but raising variance. They win when the bias reduction outweighs the added variance.


2 9:

data(College)
set.seed(1)

n <- nrow(College)
train <- sample(1:n, n * 0.7)
test <- (-train)

train.data <- College[train, ]
test.data  <- College[test, ]

2.1 (b) Least squares

lm.fit <- lm(Apps ~ ., data = train.data)
lm.pred <- predict(lm.fit, test.data)
lm.err <- mean((lm.pred - test.data$Apps)^2)
lm.err
[1] 1261630
x.train <- model.matrix(Apps ~ ., train.data)[, -1]
x.test  <- model.matrix(Apps ~ ., test.data)[, -1]
y.train <- train.data$Apps
y.test  <- test.data$Apps

2.2 (c) Ridge regression

set.seed(1)
cv.ridge <- cv.glmnet(x.train, y.train, alpha = 0)
best.lambda.ridge <- cv.ridge$lambda.min
ridge.pred <- predict(cv.ridge, s = best.lambda.ridge, newx = x.test)
ridge.err <- mean((ridge.pred - y.test)^2)

best.lambda.ridge
[1] 367.5286
ridge.err
[1] 1121034

2.3 (d) Lasso

set.seed(1)
cv.lasso <- cv.glmnet(x.train, y.train, alpha = 1)
best.lambda.lasso <- cv.lasso$lambda.min
lasso.pred <- predict(cv.lasso, s = best.lambda.lasso, newx = x.test)
lasso.err <- mean((lasso.pred - y.test)^2)

best.lambda.lasso
[1] 8.690175
lasso.err
[1] 1233246
lasso.coef <- predict(cv.lasso, s = best.lambda.lasso, type = "coefficients")
lasso.coef
18 x 1 sparse Matrix of class "dgCMatrix"
               s=8.690175
(Intercept) -587.01736837
PrivateYes  -467.41899274
Accept         1.66632106
Enroll        -0.73933203
Top10perc     47.57728851
Top25perc    -11.81001203
F.Undergrad    .         
P.Undergrad    0.06051514
Outstate      -0.07687500
Room.Board     0.15109731
Books          0.21880229
Personal       .         
PhD           -8.55085494
Terminal      -0.11958847
S.F.Ratio     11.05918988
perc.alumni    .         
Expend         0.05724753
Grad.Rate      6.16903184
sum(lasso.coef != 0) - 1
[1] 14

2.4 (e) PCR

set.seed(1)
pcr.fit <- pcr(Apps ~ ., data = train.data, scale = TRUE, validation = "CV")
validationplot(pcr.fit, val.type = "MSEP")

pcr.M <- which.min(MSEP(pcr.fit)$val[1, 1, ]) - 1
pcr.M
17 comps 
      17 
pcr.pred <- predict(pcr.fit, test.data, ncomp = pcr.M)
pcr.err <- mean((pcr.pred - y.test)^2)
pcr.err
[1] 1261630

2.5 (f) PLS

set.seed(1)
pls.fit <- plsr(Apps ~ ., data = train.data, scale = TRUE, validation = "CV")
validationplot(pls.fit, val.type = "MSEP")

pls.M <- which.min(MSEP(pls.fit)$val[1, 1, ]) - 1
pls.M
17 comps 
      17 
pls.pred <- predict(pls.fit, test.data, ncomp = pls.M)
pls.err <- mean((pls.pred - y.test)^2)
pls.err
[1] 1261630

2.6 (g) Compare results

results <- data.frame(
  Method = c("Least Squares", "Ridge", "Lasso", "PCR", "PLS"),
  Test_MSE = c(lm.err, ridge.err, lasso.err, pcr.err, pls.err)
)
results
         Method Test_MSE
1 Least Squares  1261630
2         Ridge  1121034
3         Lasso  1233246
4           PCR  1261630
5           PLS  1261630
test.avg <- mean(y.test)
results$Test_R2 <- 1 - results$Test_MSE / mean((y.test - test.avg)^2)
results
         Method Test_MSE   Test_R2
1 Least Squares  1261630 0.9134458
2         Ridge  1121034 0.9230914
3         Lasso  1233246 0.9153931
4           PCR  1261630 0.9134458
5           PLS  1261630 0.9134458

All five approaches predict Apps quite well, explaining roughly 91–92% of the variance in the test set (R² between 0.913 and 0.923). The differences among the five test errors are fairly small in relative terms, though ridge regression gives the lowest test MSE (1,121,034) and the highest R² (0.923), making it the best-performing method here.


3 11:

data(Boston)
set.seed(1)

n <- nrow(Boston)
train <- sample(1:n, n * 0.7)
test <- (-train)

train.data <- Boston[train, ]
test.data  <- Boston[test, ]

x.train <- model.matrix(crim ~ ., train.data)[, -1]
x.test  <- model.matrix(crim ~ ., test.data)[, -1]
y.train <- train.data$crim
y.test  <- test.data$crim

3.1 (a) regression methods

3.1.1

predict.regsubsets <- function(object, newdata, id) {
  form <- as.formula(object$call[[2]])
  mat <- model.matrix(form, newdata)
  coefi <- coef(object, id = id)
  mat[, names(coefi)] %*% coefi
}

regfit.best <- regsubsets(crim ~ ., data = train.data, nvmax = 13)

# Dynamically determine how many model sizes were actually fit
n.models <- length(summary(regfit.best)$rss)

val.errors <- rep(NA, n.models)
for (i in 1:n.models) {
  pred <- predict.regsubsets(regfit.best, test.data, id = i)
  val.errors[i] <- mean((test.data$crim - pred)^2)
}
val.errors
 [1] 61.10434 60.77902 60.84735 60.23869 58.97871 58.40561 58.05977 57.92830
 [9] 57.73286 57.71622 57.61458 57.61252
best.size <- which.min(val.errors)
best.size
[1] 12
coef(regfit.best, best.size)
 (Intercept)           zn        indus         chas          nox           rm 
10.045227938  0.036314364 -0.071184287 -0.708748953 -6.665524436  0.496807101 
         age          dis          rad          tax      ptratio        lstat 
-0.002561271 -0.724580690  0.562380947 -0.002121104 -0.343824763  0.227240402 
        medv 
-0.156620049 
subset.err <- val.errors[best.size]
subset.err
[1] 57.61252

3.1.2 Ridge regression

set.seed(1)
cv.ridge <- cv.glmnet(x.train, y.train, alpha = 0)
ridge.pred <- predict(cv.ridge, s = cv.ridge$lambda.min, newx = x.test)
ridge.err <- mean((ridge.pred - y.test)^2)
ridge.err
[1] 58.75168

3.1.3 Lasso

set.seed(1)
cv.lasso <- cv.glmnet(x.train, y.train, alpha = 1)
lasso.pred <- predict(cv.lasso, s = cv.lasso$lambda.min, newx = x.test)
lasso.err <- mean((lasso.pred - y.test)^2)
lasso.err
[1] 58.03118
lasso.coef <- predict(cv.lasso, s = cv.lasso$lambda.min, type = "coefficients")
lasso.coef
13 x 1 sparse Matrix of class "dgCMatrix"
            s=0.02835162
(Intercept)   7.67840242
zn            0.03213378
indus        -0.07624377
chas         -0.65273867
nox          -5.10788505
rm            0.33476995
age           .         
dis          -0.59663202
rad           0.52262120
tax           .         
ptratio      -0.29619321
lstat         0.22613918
medv         -0.12987386

3.1.4 PCR

set.seed(1)
pcr.fit <- pcr(crim ~ ., data = train.data, scale = TRUE, validation = "CV")
validationplot(pcr.fit, val.type = "MSEP")

pcr.M <- which.min(MSEP(pcr.fit)$val[1, 1, ]) - 1
pcr.M
12 comps 
      12 
pcr.pred <- predict(pcr.fit, test.data, ncomp = pcr.M)
pcr.err <- mean((pcr.pred - y.test)^2)
pcr.err
[1] 57.61252

3.2 (b)

results <- data.frame(
  Method = c("Best Subset", "Ridge", "Lasso", "PCR"),
  Test_MSE = c(subset.err, ridge.err, lasso.err, pcr.err)
)
results
       Method Test_MSE
1 Best Subset 57.61252
2       Ridge 58.75168
3       Lasso 58.03118
4         PCR 57.61252

3.3 (c) Does it use all features?

No. Looking at coef(regfit.best, best.size), the selected model contains 12 predictors zn, indus, chas, nox, rm, dis, rad, tax, ptratio, black, lstat, and medv but excludes age. Since Boston has 13 predictors total (excluding crim itself), this model does not use every available feature.