case study
Predicting Home Prices
For an applied regression course, my team built a full predictive-modeling pipeline on real housing data — not just fitting a line, but engineering features, selecting a model honestly, and validating it on data it had never seen. The trained model is embedded below: move the sliders and the prediction updates live.
Loading model…
The curve shows how the predicted price changes with lot size while your other choices stay fixed. The shaded band is an approximate prediction range (±2× the model's residual standard error).
What's under the hood
The widget is not a lookup table — it is the actual fitted regression equation running in your browser. The model was built in R and the workflow mirrors how a model earns trust in practice:
- Feature engineering. Predictors were centered, and derived terms — a log of lot size, a squared lot-size term, and interactions like air-conditioning × lot size — were added so the model could capture curvature and context, not just straight lines.
- Honest model selection. A backward-selection ladder removed one predictor at a time, and each candidate was scored two ways: by AIC and by repeated 5-fold cross-validation (5 folds × 6 repeats). Cross-validation judges a model on data it didn't train on, which is the score that actually matters for prediction.
- Outlier control. Influential observations were flagged with Cook's distance and removed from the training fit, then the whole selection process was re-run to confirm the choice was stable.
- Uncertainty. Coefficient intervals were estimated by bootstrap resampling, and predictions are reported with intervals — because a single number without a range is a guess dressed up as a fact.
A look at the code
The whole pipeline was written to be readable. Two small pieces capture the philosophy — scoring predictions only on data the model never saw, and judging each candidate model by repeated cross-validation rather than by how well it fit its own training data:
# Accuracy measured on a held-out test set the model never trained on.
get_metrics <- function(actual, predicted) {
data.frame(
RMSE = sqrt(mean((actual - predicted)^2)),
MAE = mean(abs(actual - predicted)),
Test_R2 = 1 - sum((actual - predicted)^2) /
sum((actual - mean(actual))^2)
)
} # Score every candidate model on validation folds it didn't train on.
# k = 5 folds, repeated 6 times, so each model gets 30 RMSE estimates.
for (r in 1:repeats) {
folds <- sample(rep(1:k, length.out = nrow(data)))
for (fold in 1:k) {
train.fold <- data[folds != fold, ]
valid.fold <- data[folds == fold, ]
fit <- lm(form, data = train.fold)
pred <- predict(fit, newdata = valid.fold)
rmse <- sqrt(mean((valid.fold$price - pred)^2)) # averaged later
}
} Excerpts from the project's R code, lightly trimmed. Cross-validation is what kept the model honest — a predictor that merely memorizes its training data scores badly here.
The mathematics behind it
The widget is friendly, but the machinery underneath is the linear-models math from the course. These are the core results the workflow leans on:
Ordinary least squares. The closed-form coefficients that minimize the sum of squared errors. The widget is literally evaluating the right-hand equation with the fitted \(\hat{\beta}\) and your inputs.
Residual variance. An unbiased estimate of the noise around the line; its square root \(s\) is the residual standard error — the \(\pm 2s\) band you see on the chart.
Goodness of fit. Adjusted \(R^2\) charges a penalty for every predictor added — the formal reason an extra variable that doesn't earn its keep (like bedrooms here) gets dropped.
Model selection. The criterion that scored each rung of the backward-elimination ladder: it rewards lower error but taxes complexity through the \(2p\) term. Lower is better.
Cook's distance. How far the entire fitted model moves if observation \(i\) is removed. Training points above the \(4/n\) cutoff were pulled before the final fit.
Uncertainty. The coefficient covariance (whose diagonal gives standard errors) and the prediction interval for a new home. The chart's shaded band is the practical \(\pm 2s\) approximation of this interval.
How it held up
The numbers above are measured on a held-out 20% test set the model never saw during training or selection — the honest test of whether it generalizes. Notably, the selection process dropped bedrooms and a rec-room indicator: once lot size, bathrooms, stories, and location were in the model, those added nothing to out-of-sample accuracy. Knowing what to leave out is as much a part of the craft as knowing what to keep.
Provenance & honest notes
This was a six-person team project for STAT 4310 (Applied Regression). The broader project also modeled California school test scores and a finance application (CAPM betas for six NYSE stocks against the S&P 500); I've featured the housing model here because it makes the most intuitive interactive demo. The data is the classic Anglin–Gençay Windsor, Ontario housing dataset (546 homes, prices in 1979 Canadian dollars) via R's AER package — so the dollar figures are a period artifact, not today's market. The prediction equation, centering constants, and fit metrics shown here were regenerated directly from the project's R code and match the team's rendered results exactly.