WageWage <- na.omit(Wage)
dim(Wage)
## [1] 3000 11
wage on ageset.seed(1)
max.d <- 10
cv.err <- rep(NA, max.d)
for (d in 1:max.d) {
fit <- glm(wage ~ poly(age, d), data = Wage)
cv.err[d] <- cv.glm(Wage, fit, K = 10)$delta[1]
}
raw.min <- which.min(cv.err)
# the CV curve is essentially flat beyond degree 3-4: adopt the smallest degree whose
# CV error is within 0.2% of the minimum (a parsimonious "within-noise" choice)
poly.d <- min(which(cv.err <= 1.002 * min(cv.err)))
plot(1:max.d, cv.err, type = "b", xlab = "Degree", ylab = "10-fold CV error",
main = "Polynomial degree selection")
points(raw.min, cv.err[raw.min], col = "grey40", pch = 1)
points(poly.d, cv.err[poly.d], col = "red", pch = 19)
c(raw_argmin = raw.min, chosen_degree = poly.d, cv_error = round(cv.err[poly.d], 2))
## raw_argmin chosen_degree cv_error
## 9.00 4.00 1595.65
The CV error drops sharply from degree 1 to 2–3 and is then essentially flat: every degree from 3 to 10 lies within about \(0.3\%\) of the overall minimum, so the nominal argmin at degree 9 is not meaningfully better than a low-order fit (the difference is CV noise). Applying a “within-noise” rule — the smallest degree whose CV error is within \(0.2\%\) of the minimum — gives degree 4, a parsimonious choice consistent with the elbow of the curve.
fits <- lapply(1:5, function(d) lm(wage ~ poly(age, d), data = Wage))
do.call(anova, fits)
## Analysis of Variance Table
##
## Model 1: wage ~ poly(age, d)
## Model 2: wage ~ poly(age, d)
## Model 3: wage ~ poly(age, d)
## Model 4: wage ~ poly(age, d)
## Model 5: wage ~ poly(age, d)
## Res.Df RSS Df Sum of Sq F Pr(>F)
## 1 2998 5022216
## 2 2997 4793430 1 228786 143.5931 < 2.2e-16 ***
## 3 2996 4777674 1 15756 9.8888 0.001679 **
## 4 2995 4771604 1 6070 3.8098 0.051046 .
## 5 2994 4770322 1 1283 0.8050 0.369682
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
The ANOVA of nested models shows that going from degree 1 to 2 and from 2 to 3 is highly significant (\(p < 0.001\)), degree 3 to 4 is marginal (around the \(5\%\) level), and degree 4 to 5 is clearly not significant. ANOVA therefore points to a cubic (degree 3, or at most 4) fit — consistent with the cross-validation result: both approaches agree that a degree-3/4 polynomial captures the age–wage relationship and higher-order terms add nothing.
age.grid <- seq(min(Wage$age), max(Wage$age), length = 100)
fit <- lm(wage ~ poly(age, poly.d), data = Wage)
pred <- predict(fit, newdata = list(age = age.grid), se = TRUE)
se.bands <- cbind(pred$fit + 2 * pred$se.fit, pred$fit - 2 * pred$se.fit)
plot(Wage$age, Wage$wage, col = "darkgrey", cex = 0.5,
xlab = "Age", ylab = "Wage", main = paste0("Degree-", poly.d, " polynomial fit"))
lines(age.grid, pred$fit, col = "blue", lwd = 2)
matlines(age.grid, se.bands, col = "blue", lty = 2)
The fit rises steeply through the twenties and thirties, peaks around age 40–50, and declines afterward.
wage on ageset.seed(1)
max.cuts <- 10
cv.err <- rep(NA, max.cuts)
for (k in 2:max.cuts) {
Wage$age.cut <- cut(Wage$age, k)
fit <- glm(wage ~ age.cut, data = Wage)
cv.err[k] <- cv.glm(Wage, fit, K = 10)$delta[1]
}
Wage$age.cut <- NULL
best.k <- which.min(cv.err)
plot(2:max.cuts, cv.err[2:max.cuts], type = "b", xlab = "Number of intervals",
ylab = "10-fold CV error", main = "Step-function cut selection")
points(best.k, cv.err[best.k], col = "red", pch = 19)
c(best_cuts = best.k, cv_error = round(cv.err[best.k], 2))
## best_cuts cv_error
## 8.00 1601.32
Cross-validation selects 8 intervals (i.e. 7 internal cut points). The error falls quickly up to about 7–8 bins and then flattens.
fit <- lm(wage ~ cut(age, best.k), data = Wage)
pred <- predict(fit, newdata = list(age = age.grid), se = TRUE)
se.bands <- cbind(pred$fit + 2 * pred$se.fit, pred$fit - 2 * pred$se.fit)
plot(Wage$age, Wage$wage, col = "darkgrey", cex = 0.5,
xlab = "Age", ylab = "Wage", main = paste0("Step function with ", best.k, " bins"))
lines(age.grid, pred$fit, col = "red", lwd = 2)
matlines(age.grid, se.bands, col = "red", lty = 2)
The step function reproduces the same overall shape as the polynomial — low wages for the young, a plateau in middle age, and a decline near retirement — but as a piecewise-constant approximation.
CollegeCollege <- na.omit(College)
dim(College)
## [1] 777 18
set.seed(1)
n <- nrow(College)
train <- sample(n, n / 2)
test <- setdiff(seq_len(n), train)
fwd <- regsubsets(Outstate ~ ., data = College[train, ],
nvmax = 17, method = "forward")
fwd.sum <- summary(fwd)
par(mfrow = c(1, 3))
plot(fwd.sum$cp, type = "b", xlab = "# predictors", ylab = "Cp", main = "Cp")
plot(fwd.sum$bic, type = "b", xlab = "# predictors", ylab = "BIC", main = "BIC")
plot(fwd.sum$adjr2,type = "b", xlab = "# predictors", ylab = "Adj R2", main = "Adj R2")
par(mfrow = c(1, 1))
c(min_cp = which.min(fwd.sum$cp),
min_bic = which.min(fwd.sum$bic),
max_adjr2 = which.max(fwd.sum$adjr2))
## min_cp min_bic max_adjr2
## 14 6 14
Cp and adjusted \(R^2\)
keep improving out to large models, but all three criteria show a sharp
“elbow”: the fit barely improves beyond about six
predictors. For a parsimonious yet satisfactory model we take the best
6-variable model:
coef(fwd, 6)
## (Intercept) PrivateYes Room.Board Terminal perc.alumni
## -4726.8810613 2717.7019276 1.1032433 36.9990286 59.0863753
## Expend Grad.Rate
## 0.1930814 33.8303314
The six selected predictors are Private,
Room.Board, Terminal,
perc.alumni, Expend, and
Grad.Rate.
We fit each continuous predictor with a smoothing spline (4 df) and
keep the qualitative Private as a linear term.
gam.fit <- gam(Outstate ~ Private + s(Room.Board, 4) + s(Terminal, 4) +
s(perc.alumni, 4) + s(Expend, 4) + s(Grad.Rate, 4),
data = College[train, ])
par(mfrow = c(2, 3))
plot(gam.fit, se = TRUE, col = "blue")
par(mfrow = c(1, 1))
Findings. Out-of-state tuition rises with
Room.Board, Terminal,
perc.alumni, and Grad.Rate, and private
colleges charge substantially more than public ones. The most striking
pattern is Expend: tuition increases steeply with
instructional spending up to about $15{,}000–20{,}000 per student and
then levels off (even bends down) — a clearly
non-linear, saturating relationship.
pred <- predict(gam.fit, newdata = College[test, ])
test.mse <- mean((College$Outstate[test] - pred)^2)
tss <- mean((College$Outstate[test] - mean(College$Outstate[test]))^2)
test.r2 <- 1 - test.mse / tss
c(test_MSE = round(test.mse), test_RMSE = round(sqrt(test.mse)), test_R2 = round(test.r2, 3))
## test_MSE test_RMSE test_R2
## 3353802.000 1831.000 0.766
On the held-out test set the GAM explains about 77% of the variation in out-of-state tuition (test \(R^2 \approx 0.766\)), with a typical prediction error (RMSE) of roughly $1,831. This is a good fit and improves on a purely linear model of the same predictors, reflecting the benefit of allowing non-linear terms.
lm.fit <- lm(Outstate ~ Private + Room.Board + Terminal + perc.alumni + Expend + Grad.Rate,
data = College[train, ])
lm.mse <- mean((College$Outstate[test] - predict(lm.fit, College[test, ]))^2)
c(GAM_test_MSE = round(test.mse), linear_test_MSE = round(lm.mse))
## GAM_test_MSE linear_test_MSE
## 3353802 3844857
summary(gam.fit)
##
## Call: gam(formula = Outstate ~ Private + s(Room.Board, 4) + s(Terminal,
## 4) + s(perc.alumni, 4) + s(Expend, 4) + s(Grad.Rate, 4),
## data = College[train, ])
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -7128.62 -1133.86 -74.25 1231.50 7369.50
##
## (Dispersion Parameter for gaussian family taken to be 3724586)
##
## Null Deviance: 6989966760 on 387 degrees of freedom
## Residual Deviance: 1363197370 on 365.9997 degrees of freedom
## AIC: 6995.069
##
## Number of Local Scoring Iterations: NA
##
## Anova for Parametric Effects
## Df Sum Sq Mean Sq F value Pr(>F)
## Private 1 1764398916 1764398916 473.717 < 2.2e-16 ***
## s(Room.Board, 4) 1 1616561254 1616561254 434.024 < 2.2e-16 ***
## s(Terminal, 4) 1 287918343 287918343 77.302 < 2.2e-16 ***
## s(perc.alumni, 4) 1 354690429 354690429 95.230 < 2.2e-16 ***
## s(Expend, 4) 1 601731164 601731164 161.556 < 2.2e-16 ***
## s(Grad.Rate, 4) 1 90312393 90312393 24.248 1.284e-06 ***
## Residuals 366 1363197370 3724586
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Anova for Nonparametric Effects
## Npar Df Npar F Pr(F)
## (Intercept)
## Private
## s(Room.Board, 4) 3 1.9107 0.1274
## s(Terminal, 4) 3 1.4636 0.2241
## s(perc.alumni, 4) 3 0.3498 0.7893
## s(Expend, 4) 3 26.1184 2.442e-15 ***
## s(Grad.Rate, 4) 3 0.9075 0.4375
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
The “Anova for Nonparametric Effects” table tests,
for each smooth term, whether a non-linear component is needed beyond
the linear term. Only Expend is
significant (\(p \ll 0.001\)), giving
strong evidence of a non-linear relationship with
tuition — exactly the saturating curve seen in part (b). All the other
smooth terms have large \(p\)-values
(Room.Board \(\approx
0.13\), Terminal \(\approx
0.22\), perc.alumni \(\approx 0.79\), Grad.Rate
\(\approx 0.44\)), so there is
no evidence of non-linearity for them — their effects
on out-of-state tuition are adequately described as linear. In short,
Expend is the one clearly non-linear predictor, and the
rest are effectively linear.