House price prediction
What exactly are we trying to predict?
Suppose you have historical information about California neighborhoods: income, housing age, room counts and location. Can these inputs help estimate a neighborhood’s median house value more accurately than giving every neighborhood the same estimate? That is the question we will test.
This is regression: predicting a numerical quantity rather than choosing a category. The project uses the California housing dataset and preserves our original linear-regression comparison. Its records come from the 1990 census and describe census block groups, small geographic areas. A row is not an individual house, and the target is not today’s asking price.
You will build a complete pipeline, compare it with a simple baseline and explain its mistakes. Review linear regression, training/test separation and feature scaling if needed.
Same 4,128 held-out neighborhoods, different average errors
Mean absolute error in dollars · shorter is better
Shared scale: $0 to $100,000 average error
What you’ll learn
- Explain what one housing record and its target represent, including the dollar conversion.
- Fit preparation and a linear model on training rows, then trace an untouched test input through them.
- Compare the model with a constant training-mean prediction and calculate an individual absolute error.
- Distinguish average error, larger errors and R-squared, and describe the limits of this historical random-split result.
Understand the eight inputs before using them
The loader supplies eight numeric inputs. MedInc describes median income; HouseAge describes median house age. AveRooms and AveBedrms are average rooms and bedrooms per household. Population is the block-group population, and AveOccup is average household occupancy. Latitude and Longitude locate the area. The dataset loader returns their names alongside the input array.
An average room count is not a floor plan. It may be fractional, and unusual housing patterns can produce unusually large averages. The target, median house value, is a different kind of summary: the middle value in an ordered collection, rather than the arithmetic mean.
The numerical target uses units of $100,000. A target of 2.5 represents a recorded median of $250,000. Multiplying an error by 100,000 expresses the same discrepancy in dollars; it does not improve the prediction. See the dataset description and historical source.
Load the dataset and inspect one actual record
Prepare and run
Use Python with NumPy and scikit-learn in the course environment. The first fetch_california_housing() call downloads the dataset; later calls reuse its local cache. No separate CSV is needed. Each program includes its full setup.
Python · complete runnable example
import numpy as np
from sklearn.datasets import fetch_california_housing
data = fetch_california_housing()
print("Input shape:", data.data.shape)
print("Target shape:", data.target.shape)
print("Missing input cells:", int(np.isnan(data.data).sum()))
print("First block group:")
for name, value in zip(data.feature_names, data.data[0]):
print(name + ":", round(float(value), 3))
print("Recorded median value in dollars:", round(data.target[0] * 100000))
print("Largest recorded target in dollars:", round(data.target.max() * 100000))
print("Rows at that maximum:", int((data.target == data.target.max()).sum()))Expected output
Input shape: (20640, 8)
Target shape: (20640,)
Missing input cells: 0
First block group:
MedInc: 8.325
HouseAge: 41.0
AveRooms: 6.984
AveBedrms: 1.024
Population: 322.0
AveOccup: 2.556
Latitude: 37.88
Longitude: -122.23
Recorded median value in dollars: 452600
Largest recorded target in dollars: 500001
Rows at that maximum: 965data.data is the input array: 20,640 rows and eight columns. data.target supplies one recorded median value per row. The first record’s target, 4.526, converts to $452,600. Its average room count is fractional because it summarizes households, not rooms in one home.
np.isnan checks for missing numerical cells; none appear in this loaded input matrix. That makes an imputer unnecessary for this example, although another housing source may need one. zip pairs each input name with its corresponding first-row value.
The checked data has 965 targets at the maximum recorded value of $500,001. The repeated upper endpoint is important when inspecting expensive areas: do not read that endpoint as a precise measurement of all higher-valued areas or assume the data describes the complete upper tail of values.
Split before learning any preparation or prediction rules
We reserve 20% of the rows with test_size=0.2: 16,512 training records and 4,128 test records. Passing inputs and targets together to train_test_split preserves their pairing. The fixed random_state=7 makes this shuffled split repeatable for the same dataset.
The scaler learns each input column’s mean and spread from training rows. The linear model learns its coefficients from prepared training rows and their targets. The baseline learns a constant from training targets. None of those learning steps use the test targets.
A random row split is suitable for this limited teaching comparison. Nearby neighborhoods can still land in both sets. It does not measure success in a wholly new geographic area or a future housing market. We will keep that difference visible when reporting the result.
Prepare eight inputs, then add their learned contributions
The pipeline first applies StandardScaler, which subtracts each input column’s training mean and divides by its training standard deviation. An input near its training mean becomes near zero; an above-average input usually becomes positive. These are feature units, not dollars.
LinearRegression then predicts a target by adding an intercept and eight contributions. A contribution is a scaled feature value multiplied by that feature’s learned coefficient. Fitting chooses coefficients to minimize squared training errors. Predicting reuses those coefficients and the same fitted scaler.
Scaling is kept inside the pipeline so evaluation has the right preparation boundary. In unregularized least squares with an intercept, rescaling nonconstant features does not generally improve predictions: coefficients can compensate for the change of units. It is not a fix for nonlinear relationships or missing information. Model diagnosis still depends on held-out evidence.
Give the model a simple alternative to beat
A baseline is a simple prediction procedure used as a point of comparison. Here DummyRegressor(strategy="mean") learns the mean training target, about $205,895, and predicts that same value for every test record. It ignores the eight housing inputs.
Both procedures train on the same partition and are evaluated on the same test rows. This prevents us from comparing one model on easier neighborhoods and another on harder ones. Computing the baseline from test targets would instead use answers unavailable when making those predictions.
The mean is the constant that minimizes squared training errors. A median constant is also worth considering when the primary measure is absolute error, because it minimizes absolute training errors. The retained mean baseline is a reference, not a claim that it is the strongest possible constant predictor. Plan additional comparisons using training cross-validation, rather than selecting alternatives on this final test result.
Fit the pipeline and compare its held-out error
Prepare and run
Use Python with NumPy and scikit-learn in the course environment. The first fetch_california_housing() call downloads the dataset; later calls reuse its local cache. No separate CSV is needed. Each program includes its full setup.
Python · complete runnable example
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.dummy import DummyRegressor
from sklearn.metrics import mean_absolute_error, r2_score
# First use may download the dataset; later calls use its local cache.
data = fetch_california_housing()
train_X, test_X, train_y, test_y = train_test_split(data.data, data.target, test_size=0.2, random_state=7)
model = make_pipeline(StandardScaler(), LinearRegression()).fit(train_X, train_y)
baseline = DummyRegressor(strategy="mean").fit(train_X, train_y)
prediction = model.predict(test_X)
print("Model MAE in dollars:", round(mean_absolute_error(test_y, prediction) * 100000))
print("Baseline MAE in dollars:", round(mean_absolute_error(test_y, baseline.predict(test_X)) * 100000))
print("R2:", round(r2_score(test_y, prediction), 3))Expected output
Model MAE in dollars: 53137
Baseline MAE in dollars: 91480
R2: 0.608make_pipeline(StandardScaler(), LinearRegression()) connects preparation to prediction. Calling fit on training rows fits both steps; predict(test_X) applies them in that order without fitting again. The baseline follows the same training/test boundary.
Mean absolute error (MAE) averages the magnitudes of prediction mistakes. This run reports $53,137 for the model and $91,480 for the baseline. The model therefore makes smaller average absolute errors than this baseline on this test set. Individual mistakes can still be much larger.
R-squared compares the model’s total squared test error with the squared deviations of the test targets from their own mean. A value of 0.608 indicates a reduction in that squared-error quantity relative to that reference; it is not 60.8% accuracy. The R-squared reference uses a test-mean denominator by definition, whereas our separately trained baseline predicts the training mean. See the metric definition.
Follow one prediction all the way into dollars
The first held-out record has a recorded median value of $360,000. The fitted pipeline predicts about $165,084. To understand where that prediction comes from, retrieve the fitted scaler and regression, transform that input, and multiply each standardized feature by its coefficient. The next program includes the entire training setup before doing this calculation.
The first test record: one estimate, a visible shortfall
The unrounded prediction is about 1.650842 target units. Its signed error is prediction − recorded, about −1.949158 units. Taking the absolute value and multiplying by 100,000 gives an error of about $194,916. Use unrounded values for the calculation and round only for presentation.
Reconstruct that prediction from eight contributions
Prepare and run
Use Python with NumPy and scikit-learn in the course environment. The first fetch_california_housing() call downloads the dataset; later calls reuse its local cache. No separate CSV is needed. Each program includes its full setup.
Python · complete runnable example
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.dummy import DummyRegressor
from sklearn.metrics import mean_absolute_error, r2_score
# First use may download the dataset; later calls use its local cache.
data = fetch_california_housing()
train_X, test_X, train_y, test_y = train_test_split(data.data, data.target, test_size=0.2, random_state=7)
model = make_pipeline(StandardScaler(), LinearRegression()).fit(train_X, train_y)
baseline = DummyRegressor(strategy="mean").fit(train_X, train_y)
prediction = model.predict(test_X)
import numpy as np
# Inspect the first held-out block group; do not refit anything here.
scaled = model.steps[0][1].transform(test_X[:1])[0]
regression = model.steps[1][1]
contributions = scaled * regression.coef_
print("Training rows:", len(train_X))
print("Test rows:", len(test_X))
print("Intercept in target units:", round(float(regression.intercept_), 3))
for name, value in zip(data.feature_names, contributions):
print(name + " contribution:", round(float(value), 4))
manual = float(regression.intercept_ + contributions.sum())
print("Manual prediction in dollars:", round(manual * 100000))
print("Matches pipeline:", bool(np.isclose(manual, prediction[0])))
print("Recorded value in dollars:", round(test_y[0] * 100000))
print("Signed error (predicted - recorded):", round((prediction[0] - test_y[0]) * 100000))
print("Absolute error in dollars:", round(abs(prediction[0] - test_y[0]) * 100000))Expected output
Training rows: 16512
Test rows: 4128
Intercept in target units: 2.059
MedInc contribution: -0.791
HouseAge contribution: 0.0216
AveRooms contribution: 0.2582
AveBedrms contribution: -0.0046
Population contribution: -0.0073
AveOccup contribution: -0.0005
Latitude contribution: 0.6618
Longitude contribution: -0.5464
Manual prediction in dollars: 165084
Matches pipeline: True
Recorded value in dollars: 360000
Signed error (predicted - recorded): -194916
Absolute error in dollars: 194916model.steps[0][1] is the fitted scaler; model.steps[1][1] is the fitted regression. Their output and coefficients follow the same eight-column order as data.feature_names. Multiplying corresponding values gives contributions in target units. Add those unrounded contributions to the unrounded intercept to recover the pipeline’s prediction.
The intercept is about 2.059 units, or $205,895. For this record, the median-income contribution is negative while the latitude contribution is positive. Those are contributions of the fitted formula at this particular input, not estimates of causal effects. Correlated features and the model’s assumptions make “change one feature” interpretations especially easy to overstate.
The printed contributions are rounded to four decimals. Adding only those displayed numbers can give a slightly different result. np.isclose checks that the calculation using full-precision values agrees with the pipeline.
Inspect individual mistakes and the larger-error tail
Prepare and run
Use Python with NumPy and scikit-learn in the course environment. The first fetch_california_housing() call downloads the dataset; later calls reuse its local cache. No separate CSV is needed. Each program includes its full setup.
Python · complete runnable example
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.dummy import DummyRegressor
from sklearn.metrics import mean_absolute_error, r2_score
# First use may download the dataset; later calls use its local cache.
data = fetch_california_housing()
train_X, test_X, train_y, test_y = train_test_split(data.data, data.target, test_size=0.2, random_state=7)
model = make_pipeline(StandardScaler(), LinearRegression()).fit(train_X, train_y)
baseline = DummyRegressor(strategy="mean").fit(train_X, train_y)
prediction = model.predict(test_X)
import numpy as np
from sklearn.metrics import root_mean_squared_error
errors = np.abs(test_y - prediction) * 100000
print("First three held-out records:")
for actual, estimate in zip(test_y[:3], prediction[:3]):
print("Recorded:", round(actual * 100000),
"Predicted:", round(estimate * 100000),
"Absolute error:", round(abs(actual - estimate) * 100000))
print("Mean absolute error:", round(errors.mean()))
print("Median absolute error:", round(float(np.median(errors))))
print("90th percentile absolute error:", round(float(np.quantile(errors, 0.9))))
print("Maximum absolute error:", round(errors.max()))
print("Root mean squared error:", round(root_mean_squared_error(test_y, prediction) * 100000))
print("Negative predictions:", int((prediction < 0).sum()))
print("Training-mean baseline in dollars:", round(float(baseline.constant_[0, 0]) * 100000))Expected output
First three held-out records:
Recorded: 360000 Predicted: 165084 Absolute error: 194916
Recorded: 336000 Predicted: 247798 Absolute error: 88202
Recorded: 269900 Predicted: 242551 Absolute error: 27349
Mean absolute error: 53137
Median absolute error: 41305
90th percentile absolute error: 109622
Maximum absolute error: 357319
Root mean squared error: 72653
Negative predictions: 15
Training-mean baseline in dollars: 205895The first three records show that errors vary: about $194,916, $88,202 and $27,349. MAE averages magnitudes, so opposite directions cannot cancel. A signed average could conceal large overestimates and underestimates that offset one another.
The median absolute error is $41,305, while the 90th percentile is $109,622. The latter describes a cutoff near which 90% of the observed absolute test errors lie at or below; it is not a confidence interval for a future neighborhood. The largest test error is $357,319, well above the average.
Root mean squared error (RMSE) is $72,653. Squaring before averaging gives larger mistakes more influence than MAE does, then taking the square root returns the original target units. The metric choice changes what “better” emphasizes. Review regression error measures.
The model also produces 15 negative test predictions. An unconstrained linear formula can do that even though a negative house value is not a useful estimate here. Silently replacing these with zero changes the prediction procedure; any such policy must be specified and evaluated rather than hiding the limitation.
Practice: convert two mistakes and judge a claim
Your task: An illustrative neighborhood has target 2.4 and prediction 1.9. A second has target 1.2 and prediction 1.7. Calculate each signed error and their two-record MAE in dollars. Then assess: “An MAE of $53,137 means every individual house price is within $53,137.”
Worked answer
Using predicted minus recorded, the first error is 1.9 − 2.4 = −0.5, or −$50,000; the second is 1.7 − 1.2 = +0.5, or +$50,000. Both absolute errors are $50,000, so their MAE is $50,000. Their mean signed error is zero, but neither prediction is correct.
The claim is wrong twice: MAE is an average, not a maximum, and this project’s target describes neighborhood medians, not individual sale prices. The actual first test error of about $194,916 already demonstrates that a test-record error can exceed the reported average.
Match the evaluation to the question you want answered
For an exploratory review, plot predicted versus recorded values and examine errors against income, location and target level. Look for regions with consistently high errors, underestimates at the upper recorded endpoint, or extreme average-occupancy inputs. Report group sizes as well as average errors. These observations can suggest weaknesses; they do not by themselves prove why errors occur.
Nearby areas share conditions. If the intended question concerns new regions, define geographic groups and keep selected groups outside training. If the question concerns future prices, obtain suitable dated records and test on later periods. A dataset from one census snapshot cannot validate future-market performance.
Possible next experiments include regularized regression, different feature representations or tree models. Compare them through training validation with complete pipelines. If inspecting the test errors changes the design, do not present an improved score on the same reused test set as untouched final evidence. See how to separate model selection from final evaluation.
Retain dataset provenance, versions, target units, geographic scope, split settings, preparation, baseline and metric definitions in your report. Historical aggregate data cannot establish the quality of a current individual-property valuation tool.
Check what the target represents
Check the fair constant comparison
Check what average error does not promise
Keep the target, comparison and error meaning together
You have completed a regression experiment using eight historical neighborhood inputs. Preparation and model coefficients are learned from 16,512 training records, then reused to predict 4,128 held-out records. The target remains a recorded block-group median in units of $100,000.
- Comparison: the constant training-mean baseline has $91,480 MAE; the fitted linear pipeline has $53,137 MAE on the same test rows.
- One prediction: add the fitted intercept and eight full-precision feature contributions, then convert target units to dollars.
- Errors: the first record is underestimated by about $194,916. Average error is neither a maximum nor a future prediction interval.
- Interpretation: R-squared 0.608 is a squared-error comparison, not percentage accuracy. Dataset age, geographic overlap and aggregate targets limit the claim.
A useful project report lets another learner reproduce both the prediction procedure and the evaluation. You should be able to explain what one record means, why the baseline is fair, and why an attractive average score does not settle the real-world question.
Reaching the recap and completing the quizzes are separate milestones.