Here is an example of that very behavior.
library(ggplot2)
set.seed(2024)
N <- 100
R <- 10000
x <- sort(runif(N, -2, 2))
e <- c(17, rnorm(N - 2, 0, 1), -17)
Ey <- 2 - x
y <- Ey + e
L <- lm(y ~ x)
rss_in <- sum((y - predict(L))^2)
rss_out <- rep(NA, R)
for (i in 1:R){
e <- rnorm(N, 0, 1)
y <- Ey + e
preds <- predict(L)
resids <- y - preds
rss_out[i] <- sum((resids)^2)
}
plot(density(rss_out))
abline(v = rss_in)
d <- data.frame(
RSS_Out = rss_out,
C = "Out-of-sample Residual Sum of Squares"
)
ggplot(d, aes(x = RSS_Out, fill = C)) +
geom_density(alpha = 0.2) +
# geom_vline(xintercept = rss_in) +
theme(legend.position = "none")
range(rss_out)
In this example, the out-of-sample residual sum of squares ranges from $75$ to $209$, so the expected value is (probably) in that range. However, the in-sample residual sum of squares is much higher at $641$.
The example works on the idea that, sure, the expected value of the out-of-sample residual sum of squares uses the same standard normal error term as there is in-sample, except that, for this particular set of in-sample data, there was some rotten luck where that standard normal error threw two huge errors.
If you are formal about showing what the expected out-of-sample residual sum of squares is and that it is lower than $641$, then you have a complete proof. I am content to let these particular $x$ values be the only possible values. If you want to change $x$ to be binary to make that assumption more realistic, x <- c(rep(0, N/2), rep(1, N/2))
tells the same story with slightly different numbers.