library(ggplot2)
set.seed(42)
f_true <- function(x) sin(pi * x)
n <- 80
x <- sort(runif(n, 0, 1))
y <- f_true(x) + rnorm(n, 0, 0.4)
# Manual 10-fold CV
k <- 10
folds <- sample(rep(1:k, length.out=n))
degrees <- 1:10
cv_mse <- sapply(degrees, function(d) {
fold_errs <- sapply(1:k, function(j) {
tr <- which(folds != j)
te <- which(folds == j)
fit <- lm(y[tr] ~ poly(x[tr], d))
xp <- poly(x[te], d, coefs=attr(poly(x[tr], d), "coefs"))
mean((y[te] - cbind(1, xp) %*% coef(fit))^2)
})
mean(fold_errs)
})
res <- data.frame(degree=degrees, cv_mse=cv_mse,
train_mse=sapply(degrees, function(d)
mean(residuals(lm(y ~ poly(x, d)))^2)))
ggplot(res, aes(x=degree)) +
geom_line(aes(y=train_mse), colour="#2980b9", linewidth=1.2) +
geom_line(aes(y=cv_mse), colour="#e74c3c", linewidth=1.2) +
geom_point(aes(y=cv_mse), colour="#e74c3c", size=2.5) +
geom_vline(xintercept=degrees[which.min(cv_mse)],
linetype="dashed", colour="#27ae60") +
scale_x_continuous(breaks=degrees) +
labs(title="10-fold CV vs training error",
subtitle=paste0("Green dashed = optimal degree by CV (", degrees[which.min(cv_mse)], ") | Red = CV MSE | Blue = Training MSE"),
x="Degree", y="MSE") +
theme_minimal()