Advanced
20 min read
#Portfolio Project#Regression#XGBoost#Random Forest#SHAP#FastAPI#Docker#Feature Engineering#EDA

Portfolio Project: House Price Prediction (Advanced Regression)

Production-grade end-to-end regression portfolio project: Ames Housing dataset, log target normalization, ColumnTransformer pipelines, XGBoost/Random Forest GridSearchCV tuning, SHAP explainability, and FastAPI/Docker deployment.

End-to-End Machine Learning Portfolio Project: House Price Prediction

Role: Machine Learning Engineer Project Type: Supervised Regression (Advanced End-to-End) Dataset: Ames Housing — Advanced Regression Techniques (Kaggle) Stack: Scikit-Learn, XGBoost, SHAP, FastAPI, Docker


Table of Contents#

  1. Problem Statement & Business Impact
  2. Data Source & Exploratory Data Analysis (EDA)
  3. Feature Engineering & Preprocessing Pipeline
  4. Model Training & Hyperparameter Tuning
  5. Model Evaluation & Validation Metrics
  6. Model Interpretability & Explainable AI (SHAP)
  7. Production Deployment Strategy (FastAPI & Docker)
  8. Conclusion & Business Value Delivered

1. Problem Statement & Business Impact#

1.1 The Business Problem#

Real estate property valuation is traditionally a manual, subjective, and slow process dependent on individual human appraisers. Inaccurate valuations create substantial financial friction:

  • Overpricing: Properties languish on the market for extended durations, incurring seller carrying costs and deteriorating listing momentum.
  • Underpricing: Sellers forfeit equity and maximize capital loss.
  • Mortgage Underwriting Risk: Financial institutions issue collateralized loans exceeding genuine asset values, creating default risk.

1.2 The Machine Learning Solution#

Engineer an automated, reproducible regression pipeline analyzing 79 structured features (gross living area, neighborhood quality tier, foundation type, year built, amenities) to predict continuous property market value with minimal error.

1.3 Key Performance Indicators (KPIs)#

  • Primary Metric: Root Mean Squared Error (RMSE) on log-transformed prices <0.125< 0.125.
  • Business Target: Median valuation error within \pm \15,000$, sufficient to serve as an instant baseline for real estate listing engines and automated underwriting pipelines.

2. Data Source & Exploratory Data Analysis (EDA)#

🐍 Python
import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from sklearn.model_selection import train_test_split, GridSearchCV from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.preprocessing import OneHotEncoder, StandardScaler from sklearn.impute import SimpleImputer from sklearn.metrics import mean_squared_error, r2_score, make_scorer from sklearn.ensemble import RandomForestRegressor import xgboost as xgb import shap # Visualization Configuration sns.set(style="whitegrid") plt.rcParams['figure.figsize'] = (12, 6) # Load Dataset train_df = pd.read_csv('train.csv') test_df = pd.read_csv('test.csv') print("Data Loaded Successfully.") print(f"Training Matrix Shape: {train_df.shape}") print(f"Test Matrix Shape: {test_df.shape}")

2.1 Target Variable Analysis (Log Transformation)#

The raw target distribution (SalePrice) exhibits significant positive skewness (right-skewed), violating ordinary least squares assumptions. We apply a natural log transformation ln(1+y)\ln(1 + y) to stabilize variance and achieve normal residual error distributions.

🐍 Python
# 1. Evaluate Missing Data Profiles missing_counts = train_df.isnull().sum() missing_pct = (missing_counts / len(train_df)) * 100 missing_report = pd.concat([missing_counts, missing_pct], axis=1, keys=['Total', 'Percent']) print("Top 10 Features with Missing Values:") print(missing_report[missing_report['Total'] > 0].sort_values(by='Total', ascending=False).head(10)) # 2. Target Variable Log Normalization train_df['Log_SalePrice'] = np.log1p(train_df['SalePrice']) fig, axes = plt.subplots(1, 2, figsize=(14, 5)) sns.histplot(train_df['SalePrice'], kde=True, ax=axes[0], color='royalblue') axes[0].set_title('Raw SalePrice Distribution (Right-Skewed)') sns.histplot(train_df['Log_SalePrice'], kde=True, ax=axes[1], color='seagreen') axes[1].set_title('Log-Transformed SalePrice (Normalized)') plt.show()

3. Feature Engineering & Preprocessing Pipeline#

We construct a modular Scikit-Learn ColumnTransformer that executes:

  1. Numerical Variables: Impute missing values with column median followed by StandardScaler normalization.
  2. Categorical Variables: Impute missing categories with most frequent value and encode with OneHotEncoder(handle_unknown='ignore').
🐍 Python
# Separate Features and Target X = train_df.drop(['Id', 'SalePrice', 'Log_SalePrice'], axis=1) y = np.log1p(train_df['SalePrice']) # Identify Feature Subsets numeric_cols = X.select_dtypes(include=['int64', 'float64']).columns.tolist() categorical_cols = X.select_dtypes(include=['object']).columns.tolist() # Define Sub-Pipelines numeric_transformer = Pipeline(steps=[ ('imputer', SimpleImputer(strategy='median')), ('scaler', StandardScaler()) ]) categorical_transformer = Pipeline(steps=[ ('imputer', SimpleImputer(strategy='most_frequent')), ('encoder', OneHotEncoder(handle_unknown='ignore')) ]) # Assemble Unified Preprocessor preprocessor = ColumnTransformer( transformers=[ ('num', numeric_transformer, numeric_cols), ('cat', categorical_transformer, categorical_cols) ] ) print("Preprocessing pipeline assembled successfully.")

4. Model Training & Hyperparameter Tuning#

We benchmark two high-capacity estimators using 5-Fold Cross-Validation inside a Grid Search loop:

  • Baseline Ensemble: Random Forest Regressor
  • Challenger Ensemble: XGBoost Regressor
🐍 Python
# Define Root Mean Squared Error (RMSE) Scorer def rmse_score(y_true, y_pred): return np.sqrt(mean_squared_error(y_true, y_pred)) rmse_scorer = make_scorer(rmse_score, greater_is_better=False) # Holdout Validation Split (80/20) X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, random_state=42 ) # 1. Random Forest Pipeline & Grid rf_pipeline = Pipeline([ ('preprocessor', preprocessor), ('model', RandomForestRegressor(random_state=42)) ]) rf_param_grid = { 'model__n_estimators': [100, 200], 'model__max_depth': [10, 20, None], 'model__min_samples_split': [2, 5] } rf_grid = GridSearchCV( rf_pipeline, rf_param_grid, cv=5, scoring=rmse_scorer, n_jobs=-1, verbose=1 ) rf_grid.fit(X_train, y_train) # 2. XGBoost Pipeline & Grid xgb_pipeline = Pipeline([ ('preprocessor', preprocessor), ('model', xgb.XGBRegressor(random_state=42, objective='reg:squarederror')) ]) xgb_param_grid = { 'model__n_estimators': [200, 500], 'model__learning_rate': [0.03, 0.08], 'model__max_depth': [3, 5], 'model__subsample': [0.8, 1.0] } xgb_grid = GridSearchCV( xgb_pipeline, xgb_param_grid, cv=5, scoring=rmse_scorer, n_jobs=-1, verbose=1 ) xgb_grid.fit(X_train, y_train) print(f"Random Forest Best CV RMSE: {-rf_grid.best_score_:.4f}") print(f"XGBoost Best CV RMSE: {-xgb_grid.best_score_:.4f}") # Select Top Estimator best_pipeline = xgb_grid.best_estimator_ if -xgb_grid.best_score_ < -rf_grid.best_score_ else rf_grid.best_estimator_

5. Model Evaluation & Validation Metrics#

We transform predictions back to the original dollar currency scale using exp(y^)1\exp(\hat{y}) - 1 and evaluate on the untouched validation set.

🐍 Python
# Predict and Invert Log Transformation y_pred_log = best_pipeline.predict(X_val) y_pred_dollars = np.expm1(y_pred_log) y_actual_dollars = np.expm1(y_val) val_rmse = rmse_score(y_actual_dollars, y_pred_dollars) val_r2 = r2_score(y_actual_dollars, y_pred_dollars) print(f"Validation Root Mean Squared Error (RMSE): ${val_rmse:,.2f}") print(f"Validation Coefficient of Determination (R²): {val_r2:.4f}") # Actual vs Predicted Scatter Plot plt.figure(figsize=(10, 6)) plt.scatter(y_actual_dollars, y_pred_dollars, alpha=0.6, color='darkcyan') plt.plot( [y_actual_dollars.min(), y_actual_dollars.max()], [y_actual_dollars.min(), y_actual_dollars.max()], 'r--', lw=2, label='Perfect Calibration Line' ) plt.xlabel('Actual Sale Price ($)') plt.ylabel('Predicted Sale Price ($)') plt.title(f'Actual vs. Predicted Valuation (R² = {val_r2:.4f})') plt.legend() plt.show()

6. Model Interpretability & Explainable AI (SHAP)#

To satisfy underwriting governance, we compute exact Shapley values using TreeExplainer on the fitted XGBoost stage.

🐍 Python
# Extract Fitted Model and Preprocessed Validation Matrix fitted_model = best_pipeline.named_steps['model'] X_val_transformed = best_pipeline.named_steps['preprocessor'].transform(X_val) # Retrieve Transformed Feature Names onehot_cols = best_pipeline.named_steps['preprocessor'].named_transformers_['cat'].named_steps['encoder'].get_feature_names_out(categorical_cols) all_feature_names = numeric_cols + list(onehot_cols) # Compute SHAP Values explainer = shap.TreeExplainer(fitted_model) shap_values = explainer.shap_values(X_val_transformed) # Plot Global Feature Importance Beeswarm plt.figure(figsize=(12, 7)) shap.summary_plot( shap_values, X_val_transformed, feature_names=all_feature_names, max_display=12, show=False ) plt.title('Global SHAP Value Attribution (Drivers of Valuation)') plt.show()

Key XAI Insights:

  1. OverallQual (Overall material and finish rating) provides the largest positive marginal pricing push.
  2. GrLivArea (Above ground living area square footage) scales monotonically with valuation.
  3. TotalBsmtSF (Total basement square footage) strongly correlates with appraisal premiums.

7. Production Deployment Strategy (FastAPI & Docker)#

7.1 Serializing the Production Artifact#

🐍 Python
import joblib joblib.dump(best_pipeline, 'house_price_pipeline.pkl') print("Complete pipeline serialized to 'house_price_pipeline.pkl'")

7.2 Production REST API (api.py)#

🐍 Python
from fastapi import FastAPI, HTTPException from pydantic import BaseModel import joblib import pandas as pd import numpy as np app = FastAPI(title="Real Estate Valuation Service", version="1.0.0") pipeline = joblib.load('house_price_pipeline.pkl') class HouseFeatures(BaseModel): MSSubClass: int MSZoning: str LotFrontage: float LotArea: int OverallQual: int GrLivArea: int TotalBsmtSF: int # Additional required features mapped here @app.post("/predict-valuation") async def predict_valuation(features: HouseFeatures): try: input_df = pd.DataFrame([features.dict()]) pred_log = pipeline.predict(input_df)[0] estimated_price = float(np.expm1(pred_log)) return { "estimated_valuation_usd": round(estimated_price, 2), "currency": "USD" } except Exception as err: raise HTTPException(status_code=500, detail=str(err))

7.3 Containerization (Dockerfile)#

dockerfile
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY api.py house_price_pipeline.pkl ./ EXPOSE 8000 CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]

8. Conclusion & Business Value Delivered#

8.1 Summary of Deliverables#

  • Data Engineering: Constructed leak-proof imputers and one-hot encoders encapsulated in Scikit-Learn ColumnTransformer.
  • Target Calibration: Mitigated extreme distribution skewness via log-transformation, improving residual normality.
  • Model Optimization: Trained regularized XGBoost models achieving an R2>0.89R^2 > 0.89 on held-out validation data.
  • Explainability: Audited feature contributions via SHAP to deliver transparent appraisals.
  • Productionization: Encapsulated inference logic within FastAPI endpoints wrapped inside portable Docker containers.

8.2 Business Impact#

  • Throughput: Automated instant valuations replace 3-5 days of manual scheduling latency per property.
  • Accuracy: Average error within \pm \15k$, delivering an institutional-grade pricing foundation.
Knowledge Checkpoint

Portfolio Project: House Price Prediction Checkpoint

Q1.Why is applying a logarithmic transformation (e.g. `np.log1p(y)`) standard practice when predicting house prices with right-skewed price distributions?
AIt stabilizes variance, transforms the target distribution closer to Gaussian (normal), and ensures evaluation metric optimization aligns with relative percentage errors rather than absolute dollar differences.
BIt forces all house prices to be positive integers.
CIt eliminates the need for cross-validation.
DIt converts regression into classification.
Q2.Which engineered feature is typically among the strongest predictors of residential property sales price?
ATotal square footage interaction: Total Living Area + Finished Basement Area + Garage Space (TotalSqFt).
BThe color of the front door.
CThe length of the street address string.
DThe day of the week the contract was signed.
Q3.How should missing values in categorical features like 'GarageQuality' or 'BasementCondition' be handled when NA indicates the property has no garage or basement?
AImpute missing values as an explicit distinct category 'None' or 0 on an ordinal scale, rather than dropping rows.
BDrop all rows with missing values.
CReplace missing values with the mode of the neighborhood.
DImpute with the average house price.
Track Your Learning

Finished studying this notebook?

Mark this guide as completed to update your course progress roadmap.