Back to Projects
ML Project

House Price Prediction

Download public House Prices train/test CSVs, clean mixed numeric/categorical features, train a log-price regression model, validate RMSLE, and create submission.csv.

Final deliverable: A complete Kaggle-style regression notebook that predicts SalePrice and writes submission.csv.

Dataset: House Prices public CSV mirror

The notebook downloads public train.csv and test.csv mirrors for the Kaggle House Prices problem. The target is SalePrice in train.csv.

  • SalePrice: target house sale price in train.csv
  • OverallQual, GrLivArea, GarageCars, TotalBsmtSF: strong numeric predictors
  • Neighborhood, HouseStyle, Exterior, SaleCondition: categorical predictors
  • Id: required identifier for submission.csv
Step 1

Download House Prices Data

Download public train.csv and test.csv files without Kaggle credentials.

Your task

Run the public download cell and confirm train.csv and test.csv exist.

Try writing this cell
# Run this cell in Colab, Kaggle, or a local notebook.
# It downloads public dataset files directly, so you do not need Kaggle credentials.

from pathlib import Path
from urllib.request import urlretrieve
import zipfile

DATA_DIR = Path("data/house-prices")
DATA_DIR.mkdir(parents=True, exist_ok=True)

def find_file(name):
    possible_roots = [DATA_DIR, Path("/kaggle/input")]
    for root in possible_roots:
        if root.exists():
            matches = list(root.rglob(name))
            if matches:
                return matches[0]
    raise FileNotFoundError(f"Could not find {name}. Run the dataset download cell first.")

files = {
    "train.csv": "https://raw.githubusercontent.com/ankita1112/House-Prices-Advanced-Regression/master/train.csv",
    "test.csv": "https://raw.githubusercontent.com/ankita1112/House-Prices-Advanced-Regression/master/test.csv"
}

for filename, url in files.items():
    target = DATA_DIR / filename
    if target.exists() and target.stat().st_size > 0:
        print(f"already exists: {target}")
        continue
    print(f"downloading {filename} ...")
    urlretrieve(url, target)
    print(f"saved: {target}")

for zip_path in DATA_DIR.glob("*.zip"):
    extract_dir = DATA_DIR / zip_path.stem
    extract_dir.mkdir(exist_ok=True)
    if any(extract_dir.iterdir()):
        print(f"already extracted: {extract_dir}")
        continue
    with zipfile.ZipFile(zip_path, "r") as zf:
        zf.extractall(extract_dir)
    print(f"extracted: {zip_path} -> {extract_dir}")

print("Files:")
all_files = [path for path in sorted(DATA_DIR.rglob("*")) if path.is_file()]
for path in all_files[:40]:
    print(path)
if len(all_files) > 40:
    print(f"... {len(all_files) - 40} more files not shown")
Expected output
A file list containing train.csv and test.csv.
Show reference solution
# Run this cell in Colab, Kaggle, or a local notebook.
# It downloads public dataset files directly, so you do not need Kaggle credentials.

from pathlib import Path
from urllib.request import urlretrieve
import zipfile

DATA_DIR = Path("data/house-prices")
DATA_DIR.mkdir(parents=True, exist_ok=True)

def find_file(name):
    possible_roots = [DATA_DIR, Path("/kaggle/input")]
    for root in possible_roots:
        if root.exists():
            matches = list(root.rglob(name))
            if matches:
                return matches[0]
    raise FileNotFoundError(f"Could not find {name}. Run the dataset download cell first.")

files = {
    "train.csv": "https://raw.githubusercontent.com/ankita1112/House-Prices-Advanced-Regression/master/train.csv",
    "test.csv": "https://raw.githubusercontent.com/ankita1112/House-Prices-Advanced-Regression/master/test.csv"
}

for filename, url in files.items():
    target = DATA_DIR / filename
    if target.exists() and target.stat().st_size > 0:
        print(f"already exists: {target}")
        continue
    print(f"downloading {filename} ...")
    urlretrieve(url, target)
    print(f"saved: {target}")

for zip_path in DATA_DIR.glob("*.zip"):
    extract_dir = DATA_DIR / zip_path.stem
    extract_dir.mkdir(exist_ok=True)
    if any(extract_dir.iterdir()):
        print(f"already extracted: {extract_dir}")
        continue
    with zipfile.ZipFile(zip_path, "r") as zf:
        zf.extractall(extract_dir)
    print(f"extracted: {zip_path} -> {extract_dir}")

print("Files:")
all_files = [path for path in sorted(DATA_DIR.rglob("*")) if path.is_file()]
for path in all_files[:40]:
    print(path)
if len(all_files) > 40:
    print(f"... {len(all_files) - 40} more files not shown")
Why this step matters

The notebook owns its data setup, so the learner can run it in Colab, Kaggle, or locally without manually uploading files.

Common mistake
Downloading only train.csv and realizing at the end that the project cannot create a submission file.
Step 2

Load and Inspect House Prices

Read training and test files, inspect target distribution, and check missing values.

Your task

Load train.csv and test.csv, print shapes, inspect SalePrice, and list the most missing columns.

Try writing this cell
from pathlib import Path
import pandas as pd

possible_roots = [
    Path("data/house-prices"),
    Path("/kaggle/input"),
]

def find_file(name):
    for root in possible_roots:
        if root.exists():
            matches = list(root.rglob(name))
            if matches:
                return matches[0]
    raise FileNotFoundError(f"Could not find {name}. Run the dataset download/load cell first.")

train_path = find_file("train.csv")
df = pd.read_csv(train_path)

print(train_path)
print(df.shape)
df.head()

# Load test.csv

# Inspect SalePrice and missing values
Expected output
Train/test shapes, common missing columns, and a right-skewed SalePrice histogram.
Show reference solution
from pathlib import Path
import pandas as pd

possible_roots = [
    Path("data/house-prices"),
    Path("/kaggle/input"),
]

def find_file(name):
    for root in possible_roots:
        if root.exists():
            matches = list(root.rglob(name))
            if matches:
                return matches[0]
    raise FileNotFoundError(f"Could not find {name}. Run the dataset download/load cell first.")

train_path = find_file("train.csv")
df = pd.read_csv(train_path)

print(train_path)
print(df.shape)
df.head()

import numpy as np
import matplotlib.pyplot as plt

test_df = pd.read_csv(find_file("test.csv"))

print("train:", df.shape)
print("test:", test_df.shape)
display(df[["Id", "SalePrice", "OverallQual", "GrLivArea", "Neighborhood"]].head())
display(df.isna().sum().sort_values(ascending=False).head(12))

df["SalePrice"].hist(bins=40)
plt.title("SalePrice distribution")
plt.xlabel("SalePrice")
plt.ylabel("Count")
plt.show()
Why this step matters

House prices are skewed, so predicting log price usually gives a better RMSLE-style model than predicting raw dollars directly.

Common mistake
Treating missing categorical values as rows to drop. In this dataset, missing often means not present, such as no pool or alley.
Step 3

Build a Preprocessing Pipeline

Handle numeric and categorical columns consistently for train, validation, and test.

Your task

Separate SalePrice, identify numeric/categorical columns, and build imputers plus one-hot encoding.

Try writing this cell
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder

# Separate target and feature columns

# Build numeric and categorical preprocessing
Expected output
Counts of numeric and categorical features plus a reusable preprocessing object.
Show reference solution
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder

target = "SalePrice"
id_col = "Id"

X = df.drop(columns=[target])
y_log = np.log1p(df[target])

feature_cols = [col for col in X.columns if col != id_col]
numeric_features = X[feature_cols].select_dtypes(include=["number"]).columns.tolist()
categorical_features = X[feature_cols].select_dtypes(exclude=["number"]).columns.tolist()

numeric_pipeline = Pipeline([
    ("imputer", SimpleImputer(strategy="median")),
])

categorical_pipeline = Pipeline([
    ("imputer", SimpleImputer(strategy="most_frequent")),
    ("onehot", OneHotEncoder(handle_unknown="ignore", sparse_output=False)),
])

preprocess = ColumnTransformer([
    ("num", numeric_pipeline, numeric_features),
    ("cat", categorical_pipeline, categorical_features),
])

print(len(numeric_features), "numeric features")
print(len(categorical_features), "categorical features")
Why this step matters

The important part is consistency. The same preprocessing object is fitted on training data and reused on validation/test rows.

Common mistake
Running pd.get_dummies separately on train and test, which can create different columns.
Step 4

Train and Validate a Price Model

Fit a model on log SalePrice and validate with RMSLE-style error.

Your task

Split training rows, train a random forest pipeline, and evaluate RMSE on log price.

Try writing this cell
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split

# Split train/validation

# Train model on log price

# Evaluate validation RMSLE
Expected output
A validation RMSLE-style score on held-out rows.
Show reference solution
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split

X_train, X_val, y_train_log, y_val_log = train_test_split(
    X[feature_cols], y_log, test_size=0.2, random_state=42
)

model = Pipeline([
    ("preprocess", preprocess),
    ("regressor", RandomForestRegressor(
        n_estimators=350,
        min_samples_leaf=2,
        random_state=42,
        n_jobs=-1,
    )),
])

model.fit(X_train, y_train_log)
val_pred_log = model.predict(X_val)
rmsle_like = np.sqrt(mean_squared_error(y_val_log, val_pred_log))

print("validation RMSLE-style score:", round(rmsle_like, 4))
Why this step matters

Kaggle House Prices is scored on log error. Training on log1p(SalePrice) makes the validation metric match the competition better.

Common mistake
Optimizing raw-dollar RMSE and then being surprised that the leaderboard uses log error.
Step 5

Analyze Errors

Inspect residuals and understand where the model struggles.

Your task

Convert log predictions back to prices and inspect actual vs predicted errors.

Try writing this cell
# Build an error analysis table

# Plot actual vs predicted values
Expected output
A residual table and an actual-vs-predicted scatter plot.
Show reference solution
actual_price = np.expm1(y_val_log)
predicted_price = np.expm1(val_pred_log).clip(0)

results = X_val.copy()
results["actual"] = actual_price
results["predicted"] = predicted_price
results["error"] = results["predicted"] - results["actual"]

display(results[["actual", "predicted", "error", "OverallQual", "GrLivArea"]].head())

plt.scatter(results["actual"], results["predicted"], alpha=0.35)
limit = max(results["actual"].max(), results["predicted"].max())
plt.plot([0, limit], [0, limit], color="red")
plt.xlabel("Actual SalePrice")
plt.ylabel("Predicted SalePrice")
plt.title("Actual vs Predicted House Price")
plt.show()
Why this step matters

Error analysis turns a metric into understanding. It helps identify overprediction, underprediction, and target range problems.

Common mistake
Stopping at RMSE and never looking at individual prediction failures.
Step 6

Write the Project Readout

Summarize the model, metric, limitations, and next improvements.

Your task

Write a short final readout a recruiter or interviewer could understand.

Try writing this cell
# Write your final readout in markdown:
# - Dataset
# - Baseline
# - Best model
# - Metric
# - Biggest limitation
# - Next improvement
Expected output
A concise project summary with metric, limitation, and next step.
Show reference solution
# Example readout:
# I used the California Housing dataset to predict median house value from income, rooms, occupancy, and location features.
# A linear regression baseline gave a simple reference point, while a random forest captured non-linear relationships better.
# I evaluated with RMSE because the target is numeric and RMSE is interpretable in the target unit.
# The biggest limitation is that the dataset is aggregated by block group, not individual houses.
# Next I would add cross-validation, tune hyperparameters, and inspect location-driven errors more deeply.
Why this step matters

Projects become portfolio-ready when you can explain the workflow, not just show code cells.

Common mistake
Writing “I got good accuracy” for a regression problem instead of using a regression metric like RMSE.
Step 7

Create House Prices Submission

Train on all labeled rows, predict test rows, and save Id/SalePrice submission.csv.

Your task

Fit the final pipeline on all training data, predict test_df, convert from log price, and save submission.csv.

Try writing this cell
# Fit model on all train rows

# Predict test rows

# Save submission.csv
Expected output
submission.csv with Id and SalePrice columns.
Show reference solution
model.fit(X[feature_cols], y_log)
test_pred_log = model.predict(test_df[feature_cols])
test_prices = np.expm1(test_pred_log).clip(0)

submission = pd.DataFrame({
    "Id": test_df["Id"],
    "SalePrice": test_prices,
})
submission.to_csv("submission.csv", index=False)
display(submission.head())
print("saved submission.csv", submission.shape)
Why this step matters

This is the file you can upload to the House Prices competition. The validation score tells you whether the notebook is sane before submission.

Common mistake
Forgetting to convert log predictions back with expm1 before writing SalePrice.