House Prices Prediction Competition - Comprehensive AnalysisΒΆ
This notebook tackles the House Prices: Advanced Regression Techniques competition from Kaggle. The dataset contains 79 explanatory variables describing residential homes in Ames, Iowa, and our goal is to predict the final sale price of each home.
Key Challenge: Unlike typical house price predictions that focus on obvious features like bedrooms or square footage, this dataset includes detailed architectural and quality features that significantly impact pricing.
Dataset Overview:
- Training Set: 1460 homes with known sale prices
- Test Set: 1459 homes requiring price predictions
- Features: 79 variables including lot details, house quality ratings, construction materials, and amenities
- Target Variable: SalePrice (continuous variable in dollars)
Competition Context: Ask a home buyer to describe their dream house, and they probably won't begin with the height of the basement ceiling or the proximity to an east-west railroad. But this playground competition's dataset proves that much more influences price negotiations than the number of bedrooms or a white-picket fence.
With 79 explanatory variables describing (almost) every aspect of residential homes in Ames, Iowa, this competition challenges you to predict the final price of each home.
β 1. Introduction & OverviewΒΆ
Ask a home buyer to describe their dream house, and they probably won't begin with the height of the basement ceiling or the proximity to an east-west railroad. But this playground competition's dataset proves that much more influences price negotiations than the number of bedrooms or a white-picket fence.
With 79 explanatory variables describing (almost) every aspect of residential homes in Ames, Iowa, this competition challenges you to predict the final price of each home.
πΉ 2. Import Libraries & Set UpΒΆ
# =====================================================
# LIBRARY IMPORTS AND ENVIRONMENT SETUP
# =====================================================
# Core Data Manipulation and Analysis
import numpy as np # Numerical computing and array operations
import pandas as pd # Data manipulation and analysis
import matplotlib.pyplot as plt # Basic plotting and visualization
import seaborn as sns # Statistical data visualization
# Machine Learning - Core Algorithms
import xgboost as xg # Gradient boosting framework (main model)
# Scikit-learn - Model Selection and Evaluation
from sklearn.model_selection import train_test_split, GridSearchCV, KFold
from sklearn.metrics import accuracy_score, f1_score, recall_score, mean_absolute_error, mean_squared_error, r2_score, root_mean_squared_error, roc_auc_score
# Scikit-learn - Regression Models
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
# Scikit-learn - Data Preprocessing
from sklearn.preprocessing import StandardScaler, LabelEncoder
# Deep Learning with TensorFlow/Keras
import tensorflow as tf
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.optimizers import Adam
# Handling Imbalanced Data (if needed for classification tasks)
from imblearn.over_sampling import SMOTE
# Model Interpretability and Explainability
import shap # SHapley Additive exPlanations for model interpretation
# Environment Configuration
import warnings
warnings.filterwarnings("ignore") # Suppress warning messages for cleaner output
# Reproducibility Settings
SEED = 42 # Fixed random seed for reproducible results
np.random.seed(SEED) # Set NumPy random seed
print("β
Libraries loaded successfully! Environment ready for analysis.")
β Libraries loaded successfully! Environment ready for analysis.
c:\Users\user\anaconda3\envs\ml-env-stable\Lib\site-packages\tqdm\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html from .autonotebook import tqdm as notebook_tqdm
# =====================================================
# DATA LOADING
# =====================================================
# Load the competition datasets
# train.csv: Contains house features + sale prices for model training
# test.csv: Contains house features only, we need to predict prices for these
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
print(f"β
Data loaded successfully!")
print(f"π Training set shape: {train.shape}")
print(f"π Test set shape: {test.shape}")
β Data loaded successfully! π Training set shape: (1460, 81) π Test set shape: (1459, 80)
# =====================================================
# MISSING VALUES ANALYSIS
# =====================================================
# Identify missing values in the training dataset
# This is crucial for understanding data quality and planning preprocessing steps
# High missing value counts may indicate features that need special handling
missing_values = train.isnull().sum()
print("Missing values in training set:")
print(missing_values[missing_values > 0].sort_values(ascending=False))
Missing values in training set: PoolQC 1453 MiscFeature 1406 Alley 1369 Fence 1179 MasVnrType 872 FireplaceQu 690 LotFrontage 259 GarageType 81 GarageYrBlt 81 GarageFinish 81 GarageQual 81 GarageCond 81 BsmtExposure 38 BsmtFinType2 38 BsmtQual 37 BsmtCond 37 BsmtFinType1 37 MasVnrArea 8 Electrical 1 dtype: int64
Id 0
MSSubClass 0
MSZoning 0
LotFrontage 259
LotArea 0
...
MoSold 0
YrSold 0
SaleType 0
SaleCondition 0
SalePrice 0
Length: 81, dtype: int64
train.shape
(1460, 81)
# Quick summary of dataset
train.describe()
train.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 1460 entries, 0 to 1459 Data columns (total 81 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Id 1460 non-null int64 1 MSSubClass 1460 non-null int64 2 MSZoning 1460 non-null object 3 LotFrontage 1201 non-null float64 4 LotArea 1460 non-null int64 5 Street 1460 non-null object 6 Alley 91 non-null object 7 LotShape 1460 non-null object 8 LandContour 1460 non-null object 9 Utilities 1460 non-null object 10 LotConfig 1460 non-null object 11 LandSlope 1460 non-null object 12 Neighborhood 1460 non-null object 13 Condition1 1460 non-null object 14 Condition2 1460 non-null object 15 BldgType 1460 non-null object 16 HouseStyle 1460 non-null object 17 OverallQual 1460 non-null int64 18 OverallCond 1460 non-null int64 19 YearBuilt 1460 non-null int64 20 YearRemodAdd 1460 non-null int64 21 RoofStyle 1460 non-null object 22 RoofMatl 1460 non-null object 23 Exterior1st 1460 non-null object 24 Exterior2nd 1460 non-null object 25 MasVnrType 588 non-null object 26 MasVnrArea 1452 non-null float64 27 ExterQual 1460 non-null object 28 ExterCond 1460 non-null object 29 Foundation 1460 non-null object 30 BsmtQual 1423 non-null object 31 BsmtCond 1423 non-null object 32 BsmtExposure 1422 non-null object 33 BsmtFinType1 1423 non-null object 34 BsmtFinSF1 1460 non-null int64 35 BsmtFinType2 1422 non-null object 36 BsmtFinSF2 1460 non-null int64 37 BsmtUnfSF 1460 non-null int64 38 TotalBsmtSF 1460 non-null int64 39 Heating 1460 non-null object 40 HeatingQC 1460 non-null object 41 CentralAir 1460 non-null object 42 Electrical 1459 non-null object 43 1stFlrSF 1460 non-null int64 44 2ndFlrSF 1460 non-null int64 45 LowQualFinSF 1460 non-null int64 46 GrLivArea 1460 non-null int64 47 BsmtFullBath 1460 non-null int64 48 BsmtHalfBath 1460 non-null int64 49 FullBath 1460 non-null int64 50 HalfBath 1460 non-null int64 51 BedroomAbvGr 1460 non-null int64 52 KitchenAbvGr 1460 non-null int64 53 KitchenQual 1460 non-null object 54 TotRmsAbvGrd 1460 non-null int64 55 Functional 1460 non-null object 56 Fireplaces 1460 non-null int64 57 FireplaceQu 770 non-null object 58 GarageType 1379 non-null object 59 GarageYrBlt 1379 non-null float64 60 GarageFinish 1379 non-null object 61 GarageCars 1460 non-null int64 62 GarageArea 1460 non-null int64 63 GarageQual 1379 non-null object 64 GarageCond 1379 non-null object 65 PavedDrive 1460 non-null object 66 WoodDeckSF 1460 non-null int64 67 OpenPorchSF 1460 non-null int64 68 EnclosedPorch 1460 non-null int64 69 3SsnPorch 1460 non-null int64 70 ScreenPorch 1460 non-null int64 71 PoolArea 1460 non-null int64 72 PoolQC 7 non-null object 73 Fence 281 non-null object 74 MiscFeature 54 non-null object 75 MiscVal 1460 non-null int64 76 MoSold 1460 non-null int64 77 YrSold 1460 non-null int64 78 SaleType 1460 non-null object 79 SaleCondition 1460 non-null object 80 SalePrice 1460 non-null int64 dtypes: float64(3), int64(35), object(43) memory usage: 924.0+ KB
πΉ 4. Data Visualization & EDAΒΆ
float_cols = [col for col in train.columns if train[col].dtype == "float64"]
cols_per_row = 3
num_plots = len(float_cols)
rows = (num_plots // cols_per_row) + (num_plots % cols_per_row > 0)
fig, axes = plt.subplots(rows, cols_per_row, figsize=(15, 5 * rows))
axes = axes.flatten()
for idx, col in enumerate(float_cols):
sns.histplot(train[col], bins=50, kde=True, ax=axes[idx])
axes[idx].set_title(f"Distribution of {col}")
for i in range(idx + 1, len(axes)):
fig.delaxes(axes[i])
plt.tight_layout()
plt.show()
# =====================================================
# CATEGORICAL FEATURES ANALYSIS
# =====================================================
# Analyze the distribution of categorical (object type) features
# This helps understand feature cardinality and class imbalances
# High cardinality features may need special encoding strategies
# Get all categorical features
categorical_features = train.select_dtypes(include=['object']).columns
print(f"π Analyzing {len(categorical_features)} categorical features")
print(f"Features: {list(categorical_features)}")
# Set up visualization grid
num_features = len(categorical_features)
cols = 3
rows = (num_features // cols) + (num_features % cols > 0)
# Create pie chart subplots for each categorical feature
fig, axes = plt.subplots(rows, cols, figsize=(15, rows * 5))
axes = axes.flatten() # Flatten for easier indexing
# Generate pie charts showing class distributions
for i, feature in enumerate(categorical_features):
# Get value counts and create pie chart
value_counts = train[feature].value_counts()
# Only show percentages for categories with >1% representation to avoid clutter
def autopct_format(pct):
return f'{pct:.1f}%' if pct > 1 else ''
value_counts.plot.pie(
autopct=autopct_format,
ax=axes[i],
startangle=90,
cmap="viridis",
textprops={'fontsize': 8}
)
axes[i].set_title(f"{feature}\n({len(value_counts)} categories)", fontsize=10)
axes[i].set_ylabel("") # Remove ylabel for cleaner appearance
# Hide any unused subplots
for j in range(i + 1, len(axes)):
fig.delaxes(axes[j])
plt.tight_layout()
plt.show()
print("π‘ Analysis insights:")
print("- Features with many categories may need target encoding")
print("- Heavily imbalanced categories might be grouped")
print("- Some 'None' values may represent legitimate missing features (e.g., no garage)")
π Analyzing 43 categorical features Features: ['MSZoning', 'Street', 'Alley', 'LotShape', 'LandContour', 'Utilities', 'LotConfig', 'LandSlope', 'Neighborhood', 'Condition1', 'Condition2', 'BldgType', 'HouseStyle', 'RoofStyle', 'RoofMatl', 'Exterior1st', 'Exterior2nd', 'MasVnrType', 'ExterQual', 'ExterCond', 'Foundation', 'BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2', 'Heating', 'HeatingQC', 'CentralAir', 'Electrical', 'KitchenQual', 'Functional', 'FireplaceQu', 'GarageType', 'GarageFinish', 'GarageQual', 'GarageCond', 'PavedDrive', 'PoolQC', 'Fence', 'MiscFeature', 'SaleType', 'SaleCondition']
π‘ Analysis insights: - Features with many categories may need target encoding - Heavily imbalanced categories might be grouped - Some 'None' values may represent legitimate missing features (e.g., no garage)
# =====================================================
# COMPREHENSIVE CORRELATION HEATMAP
# =====================================================
# Create a correlation matrix for all numerical features
# This reveals linear relationships between features and with the target variable
# High correlations indicate potential multicollinearity issues
# Select only numerical features for correlation analysis
heatmap_train = pd.DataFrame()
for col in train.columns:
if train[col].dtype == "float64" or train[col].dtype == "int64":
heatmap_train[col] = train[col]
print(f"π Analyzing correlations for {heatmap_train.shape[1]} numerical features")
# Create comprehensive heatmap
plt.figure(figsize=(30, 12))
correlation_matrix = heatmap_train.corr()
# Generate heatmap with annotations
sns.heatmap(
correlation_matrix,
annot=True, # Show correlation values
cmap="coolwarm", # Diverging colormap (blue-white-red)
center=0, # Center colormap at 0
square=True, # Square aspect ratio
fmt='.2f', # Format correlation values to 2 decimal places
cbar_kws={"shrink": 0.8} # Adjust colorbar size
)
plt.title("Feature Correlation Matrix\n(Darker colors = stronger correlations)", fontsize=16, pad=20)
plt.xlabel("Features", fontsize=12)
plt.ylabel("Features", fontsize=12)
plt.xticks(rotation=45, ha='right')
plt.yticks(rotation=0)
plt.tight_layout()
plt.show()
print("π‘ Key insights from correlation analysis:")
print("- Strong positive correlations (>0.7) may indicate redundant features")
print("- Features highly correlated with SalePrice are prime candidates for modeling")
print("- Negative correlations can be as valuable as positive ones")
π Analyzing correlations for 38 numerical features
π‘ Key insights from correlation analysis: - Strong positive correlations (>0.7) may indicate redundant features - Features highly correlated with SalePrice are prime candidates for modeling - Negative correlations can be as valuable as positive ones
# =====================================================
# EXTRACT KEY FEATURES FOR FEATURE ENGINEERING
# =====================================================
# Extract unique features involved in high correlations for targeted feature engineering
# These features show strong relationships and are likely important for prediction
# Combine both feature columns and remove duplicates
l1 = high_corr_pairs['Feature 1'].tolist()
l2 = high_corr_pairs['Feature 2'].tolist()
interesting_features = list(set(l1 + l2))
# Remove target variable from feature list (we don't want to engineer features from target)
if 'SalePrice' in interesting_features:
interesting_features.remove('SalePrice')
print("π― Key Features Identified for Advanced Feature Engineering:")
print("=" * 60)
for i, feature in enumerate(interesting_features, 1):
print(f"{i:2d}. {feature}")
print(f"\nπ Total features selected: {len(interesting_features)}")
print("\nπ‘ Strategy: These features will be used to create interaction/combination features")
print(" that may capture complex relationships not visible in individual features.")
π― Key Features Identified for Advanced Feature Engineering: ============================================================ 1. TotRmsAbvGrd 2. GarageCars 3. 1stFlrSF 4. TotalBsmtSF 5. GrLivArea 6. YearBuilt 7. GarageYrBlt 8. GarageArea 9. OverallQual π Total features selected: 9 π‘ Strategy: These features will be used to create interaction/combination features that may capture complex relationships not visible in individual features.
πΉ 5. Feature EngineeringΒΆ
# =====================================================
# DATA PREPROCESSING - COLUMN CLEANUP
# =====================================================
# Clean column names by removing any leading/trailing whitespace
# This prevents potential issues with column access and ensures consistency
print("π§Ή Cleaning column names...")
train.columns = train.columns.str.strip()
test.columns = test.columns.str.strip()
print(f"β
Column names cleaned for both datasets")
print(f"π Train columns: {len(train.columns)}, Test columns: {len(test.columns)}")
π§Ή Cleaning column names... β Column names cleaned for both datasets π Train columns: 81, Test columns: 80
# =====================================================
# MISSING VALUES ASSESSMENT
# =====================================================
# Compare missing value patterns between training and test sets
# This helps identify features that need consistent imputation strategies
print("π MISSING VALUES ANALYSIS")
print("=" * 50)
print("π Training Set - Missing Values:")
train_missing = train.isnull().sum()
train_missing_features = train_missing[train_missing > 0].sort_values(ascending=False)
for feature, count in train_missing_features.items():
percentage = (count / len(train)) * 100
print(f" {feature}: {count} ({percentage:.1f}%)")
print(f"\nTotal features with missing values: {len(train_missing_features)}")
print(f"Train set, null count summary:\n{train.isnull().sum().describe()}")
print("\n" + "=" * 50)
print("π Test Set - Missing Values:")
test_missing = test.isnull().sum()
test_missing_features = test_missing[test_missing > 0].sort_values(ascending=False)
for feature, count in test_missing_features.items():
percentage = (count / len(test)) * 100
print(f" {feature}: {count} ({percentage:.1f}%)")
print(f"\nTotal features with missing values: {len(test_missing_features)}")
print(f"Test set, null count summary:\n{test.isnull().sum().describe()}")
print(f"\nπ‘ Key Observations:")
print(f"- Train set total missing values: {train.isnull().sum().sum()}")
print(f"- Test set total missing values: {test.isnull().sum().sum()}")
print(f"- Features missing in both sets need consistent handling strategy")
π MISSING VALUES ANALYSIS ================================================== π Training Set - Missing Values: PoolQC: 1453 (99.5%) MiscFeature: 1406 (96.3%) Alley: 1369 (93.8%) Fence: 1179 (80.8%) MasVnrType: 872 (59.7%) FireplaceQu: 690 (47.3%) LotFrontage: 259 (17.7%) GarageType: 81 (5.5%) GarageYrBlt: 81 (5.5%) GarageFinish: 81 (5.5%) GarageQual: 81 (5.5%) GarageCond: 81 (5.5%) BsmtExposure: 38 (2.6%) BsmtFinType2: 38 (2.6%) BsmtQual: 37 (2.5%) BsmtCond: 37 (2.5%) BsmtFinType1: 37 (2.5%) MasVnrArea: 8 (0.5%) Electrical: 1 (0.1%) Total features with missing values: 19 Train set, null count summary: count 81.000000 mean 96.654321 std 315.019252 min 0.000000 25% 0.000000 50% 0.000000 75% 0.000000 max 1453.000000 dtype: float64 ================================================== π Test Set - Missing Values: PoolQC: 1456 (99.8%) MiscFeature: 1408 (96.5%) Alley: 1352 (92.7%) Fence: 1169 (80.1%) MasVnrType: 894 (61.3%) FireplaceQu: 730 (50.0%) LotFrontage: 227 (15.6%) GarageQual: 78 (5.3%) GarageCond: 78 (5.3%) GarageYrBlt: 78 (5.3%) GarageFinish: 78 (5.3%) GarageType: 76 (5.2%) BsmtCond: 45 (3.1%) BsmtQual: 44 (3.0%) BsmtExposure: 44 (3.0%) BsmtFinType1: 42 (2.9%) BsmtFinType2: 42 (2.9%) MasVnrArea: 15 (1.0%) MSZoning: 4 (0.3%) Functional: 2 (0.1%) BsmtFullBath: 2 (0.1%) Utilities: 2 (0.1%) BsmtHalfBath: 2 (0.1%) Exterior1st: 1 (0.1%) Exterior2nd: 1 (0.1%) TotalBsmtSF: 1 (0.1%) BsmtUnfSF: 1 (0.1%) BsmtFinSF2: 1 (0.1%) BsmtFinSF1: 1 (0.1%) KitchenQual: 1 (0.1%) GarageArea: 1 (0.1%) GarageCars: 1 (0.1%) SaleType: 1 (0.1%) Total features with missing values: 33 Test set, null count summary: count 80.000000 mean 98.475000 std 317.118114 min 0.000000 25% 0.000000 50% 0.000000 75% 2.000000 max 1456.000000 dtype: float64 π‘ Key Observations: - Train set total missing values: 7829 - Test set total missing values: 7878 - Features missing in both sets need consistent handling strategy
# =====================================================
# OUTLIER DETECTION AND REMOVAL
# =====================================================
# Identify and remove extreme outliers that could negatively impact model performance
# Focus on combinations of quality ratings and prices that seem unrealistic
print("π OUTLIER DETECTION STRATEGY")
print("=" * 40)
print("Identifying houses with unusual quality-price combinations:")
# Define outlier conditions based on domain knowledge
outlier_conditions = [
("OverallQual == 4 & SalePrice > $200k", (train['OverallQual'] == 4) & (train['SalePrice'] > 2e5)),
("OverallQual == 8 & SalePrice > $500k", (train['OverallQual'] == 8) & (train['SalePrice'] > 5e5)),
("OverallQual == 10 & SalePrice > $700k", (train['OverallQual'] == 10) & (train['SalePrice'] > 7e5)),
("GrLivArea > 4000 sq ft", (train['GrLivArea'] > 4000)),
("OverallCond == 2 & SalePrice > $300k", (train['OverallCond'] == 2) & (train['SalePrice'] > 3e5)),
("OverallCond == 5 & SalePrice > $700k", (train['OverallCond'] == 5) & (train['SalePrice'] > 7e5)),
("OverallCond == 6 & SalePrice > $700k", (train['OverallCond'] == 6) & (train['SalePrice'] > 7e5))
]
# Collect all outliers
all_outliers = []
for description, condition in outlier_conditions:
outlier_count = condition.sum()
if outlier_count > 0:
print(f" {description}: {outlier_count} houses")
all_outliers.append(train[condition])
# Combine all outlier conditions
outliers = pd.concat(all_outliers).sort_index().drop_duplicates()
print(f"\nπ Summary:")
print(f" Total unique outliers identified: {len(outliers)}")
print(f" Outlier IDs: {list(outliers.index)}")
print(f" Percentage of dataset: {(len(outliers) / len(train)) * 100:.2f}%")
print("\nπ‘ Outlier Removal Rationale:")
print(" - Low quality houses with very high prices (potential data errors)")
print(" - Extremely large houses that don't represent typical market")
print(" - Houses in poor condition with luxury pricing")
π OUTLIER DETECTION STRATEGY ======================================== Identifying houses with unusual quality-price combinations: OverallQual == 4 & SalePrice > $200k: 1 houses OverallQual == 8 & SalePrice > $500k: 1 houses OverallQual == 10 & SalePrice > $700k: 2 houses GrLivArea > 4000 sq ft: 4 houses OverallCond == 2 & SalePrice > $300k: 1 houses OverallCond == 5 & SalePrice > $700k: 1 houses OverallCond == 6 & SalePrice > $700k: 1 houses π Summary: Total unique outliers identified: 7 Outlier IDs: [378, 457, 523, 691, 769, 1182, 1298] Percentage of dataset: 0.48% π‘ Outlier Removal Rationale: - Low quality houses with very high prices (potential data errors) - Extremely large houses that don't represent typical market - Houses in poor condition with luxury pricing
# Remove identified outliers from training set
# This should improve model generalization by removing extreme data points
original_size = len(train)
train = train.drop(outliers.index)
removed_count = original_size - len(train)
print(f"ποΈ OUTLIER REMOVAL COMPLETED")
print(f" Original training set size: {original_size}")
print(f" Outliers removed: {removed_count}")
print(f" New training set size: {len(train)}")
print(f" Percentage removed: {(removed_count / original_size) * 100:.2f}%")
print(f"\nβ
Training set is now cleaned and ready for feature engineering")
ποΈ OUTLIER REMOVAL COMPLETED Original training set size: 1460 Outliers removed: 7 New training set size: 1453 Percentage removed: 0.48% β Training set is now cleaned and ready for feature engineering
# =====================================================
# STRATEGIC MISSING VALUE IMPUTATION
# =====================================================
print("π§ MISSING VALUE IMPUTATION STRATEGY")
print("=" * 45)
# 1. LotFrontage: Impute based on neighborhood median
# Rationale: Lot frontage is often similar within the same neighborhood
print("1. LotFrontage: Neighborhood-based median imputation")
train_before = train["LotFrontage"].isnull().sum()
test_before = test["LotFrontage"].isnull().sum()
train["LotFrontage"] = train.groupby("Neighborhood")["LotFrontage"].transform(
lambda x: x.fillna(x.median()))
test["LotFrontage"] = test.groupby("Neighborhood")["LotFrontage"].transform(
lambda x: x.fillna(x.median()))
print(f" Train: {train_before} β {train['LotFrontage'].isnull().sum()} missing values")
print(f" Test: {test_before} β {test['LotFrontage'].isnull().sum()} missing values")
# 2. Garage Features: Fill categorical with 'None', numerical with 0
# Rationale: Missing garage features likely mean "no garage"
print("\n2. Garage Features: Categorical='None', Numerical=0")
garage_categorical = ['GarageType', 'GarageFinish', 'GarageQual', 'GarageCond']
garage_numerical = ['GarageYrBlt', 'GarageArea', 'GarageCars']
for col in garage_categorical:
train_before = train[col].isnull().sum()
test_before = test[col].isnull().sum()
train[col] = train[col].fillna('None')
test[col] = test[col].fillna('None')
print(f" {col}: Train {train_before}β{train[col].isnull().sum()}, Test {test_before}β{test[col].isnull().sum()}")
for col in garage_numerical:
train_before = train[col].isnull().sum()
test_before = test[col].isnull().sum()
train[col] = train[col].fillna(0)
test[col] = test[col].fillna(0)
print(f" {col}: Train {train_before}β{train[col].isnull().sum()}, Test {test_before}β{test[col].isnull().sum()}")
# 3. Feature Engineering: Create TotalSF (Total Square Footage)
# Rationale: Total living space is often more predictive than individual floor areas
print("\n3. Feature Engineering: Creating TotalSF")
train['TotalSF'] = train['TotalBsmtSF'] + train['1stFlrSF'] + train['2ndFlrSF']
test['TotalSF'] = test['TotalBsmtSF'] + test['1stFlrSF'] + test['2ndFlrSF']
print(f" β
TotalSF created: combines basement + 1st floor + 2nd floor square footage")
print("\nπ― Next: Apply general imputation strategy to remaining missing values")
π§ MISSING VALUE IMPUTATION STRATEGY ============================================= 1. LotFrontage: Neighborhood-based median imputation Train: 258 β 0 missing values Test: 227 β 0 missing values 2. Garage Features: Categorical='None', Numerical=0 GarageType: Train 81β0, Test 76β0 GarageFinish: Train 81β0, Test 78β0 GarageQual: Train 81β0, Test 78β0 GarageCond: Train 81β0, Test 78β0 GarageYrBlt: Train 81β0, Test 78β0 GarageArea: Train 0β0, Test 1β0 GarageCars: Train 0β0, Test 1β0 3. Feature Engineering: Creating TotalSF β TotalSF created: combines basement + 1st floor + 2nd floor square footage π― Next: Apply general imputation strategy to remaining missing values
# =====================================================
# COMPREHENSIVE MISSING VALUE IMPUTATION
# =====================================================
# Apply consistent imputation strategy to all remaining missing values
# This ensures both train and test sets have identical preprocessing
print("π§ COMPREHENSIVE IMPUTATION")
print("=" * 35)
# Count missing values before imputation
train_missing_before = train.isnull().sum().sum()
test_missing_before = test.isnull().sum().sum()
print(f"π Before imputation:")
print(f" Train set missing values: {train_missing_before}")
print(f" Test set missing values: {test_missing_before}")
print(f"\nπ Applying imputation rules:")
# Imputation strategy:
# - Categorical features (object type): fill with "None"
# - Numerical features (float64/int64): fill with column mean
for col in train.columns:
if train[col].isnull().sum() > 0: # Only process columns with missing values
if train[col].dtype == "object":
# Categorical: fill with "None"
train[col] = train[col].fillna("None")
print(f" {col} (categorical): filled with 'None'")
elif train[col].dtype in ["float64", "int64"]:
# Numerical: fill with mean
train_mean = train[col].mean()
train[col] = train[col].fillna(train_mean)
print(f" {col} (numerical): filled with mean ({train_mean:.2f})")
# Apply same imputation to test set
for col in test.columns:
if test[col].isnull().sum() > 0: # Only process columns with missing values
if test[col].dtype == "object":
# Categorical: fill with "None"
test[col] = test[col].fillna("None")
elif test[col].dtype in ["float64", "int64"]:
# Numerical: fill with test set mean
test_mean = test[col].mean()
test[col] = test[col].fillna(test_mean)
# Verify imputation success
train_missing_after = train.isnull().sum().sum()
test_missing_after = test.isnull().sum().sum()
print(f"\nβ
After imputation:")
print(f" Train set missing values: {train_missing_after}")
print(f" Test set missing values: {test_missing_after}")
if train_missing_after == 0 and test_missing_after == 0:
print(f"π Perfect! All missing values have been handled successfully.")
else:
print(f"β οΈ Warning: Some missing values remain - review needed.")
π§ COMPREHENSIVE IMPUTATION =================================== π Before imputation: Train set missing values: 7138 Test set missing values: 7262 π Applying imputation rules: Alley (categorical): filled with 'None' MasVnrType (categorical): filled with 'None' MasVnrArea (numerical): filled with mean (101.64) BsmtQual (categorical): filled with 'None' BsmtCond (categorical): filled with 'None' BsmtExposure (categorical): filled with 'None' BsmtFinType1 (categorical): filled with 'None' BsmtFinType2 (categorical): filled with 'None' Electrical (categorical): filled with 'None' FireplaceQu (categorical): filled with 'None' PoolQC (categorical): filled with 'None' Fence (categorical): filled with 'None' MiscFeature (categorical): filled with 'None' β After imputation: Train set missing values: 0 Test set missing values: 0 π Perfect! All missing values have been handled successfully.
# =====================================================
# MISSING VALUES VERIFICATION
# =====================================================
# Double-check that no missing values remain in either dataset
# This verification step is critical before proceeding to advanced feature engineering
print("π FINAL MISSING VALUES CHECK")
print("=" * 32)
# Check training set
train_nulls = train.columns[train.isnull().any()].tolist()
if train_nulls:
print("β Training set still has missing values in:")
for col in train_nulls:
print(f" - {col}: {train[col].isnull().sum()} missing")
else:
print("β
Training set: No missing values detected")
# Check test set
test_nulls = test.columns[test.isnull().any()].tolist()
if test_nulls:
print("β Test set still has missing values in:")
for col in test_nulls:
print(f" - {col}: {test[col].isnull().sum()} missing")
else:
print("β
Test set: No missing values detected")
# Summary
total_train_missing = train.isnull().sum().sum()
total_test_missing = test.isnull().sum().sum()
print(f"\nπ SUMMARY:")
print(f" Total missing values in train: {total_train_missing}")
print(f" Total missing values in test: {total_test_missing}")
if total_train_missing == 0 and total_test_missing == 0:
print(f"\nπ SUCCESS: Datasets are clean and ready for advanced feature engineering!")
else:
print(f"\nβ οΈ ATTENTION: Missing values detected - manual review required")
π FINAL MISSING VALUES CHECK ================================ β Training set: No missing values detected β Test set: No missing values detected π SUMMARY: Total missing values in train: 0 Total missing values in test: 0 π SUCCESS: Datasets are clean and ready for advanced feature engineering!
π IMPUTATION SUCCESS!ΒΆ
All missing values have been successfully handled through strategic imputation. The datasets are now complete and ready for advanced feature engineering and model training.
# =====================================================
# ADVANCED FEATURE ENGINEERING - INTERACTION FEATURES
# =====================================================
# Create combination features from highly correlated variables
# These interaction features can capture complex relationships not visible in individual features
import itertools
def create_combination_features(df, features):
"""
Create interaction features by computing mean of all 2-feature combinations
Args:
df (DataFrame): Input dataframe to modify
features (list): List of feature names to create combinations from
Returns:
DataFrame: Modified dataframe with new combination features
"""
print(f"π§ Creating combination features from {len(features)} high-correlation features")
print(f" Base features: {features}")
# Generate all possible 2-feature combinations
combinations = list(itertools.combinations(features, 2))
print(f" Total combinations to create: {len(combinations)}")
# Create new features for each combination
for i, comb in enumerate(combinations, 1):
# Create feature name by joining the two feature names
feature_name = "_".join(comb)
# Compute mean of the two features (could also use sum, product, etc.)
df[feature_name] = df[list(comb)].mean(axis=1)
print(f" {i:2d}. Created '{feature_name}' from {comb[0]} + {comb[1]}")
print(f"β
Feature engineering complete: {len(combinations)} new features created")
return df
# Apply feature engineering to both datasets
print("π― ADVANCED FEATURE ENGINEERING")
print("=" * 35)
print(f"Original feature count - Train: {train.shape[1]}, Test: {test.shape[1]}")
train = create_combination_features(train, interesting_features)
test = create_combination_features(test, interesting_features)
print(f"\nπ FEATURE ENGINEERING RESULTS:")
print(f" Final feature count - Train: {train.shape[1]}, Test: {test.shape[1]}")
print(f" New features added: {len(list(itertools.combinations(interesting_features, 2)))}")
print(f"\nπ‘ Rationale:")
print(f" - Interaction features capture relationships between correlated variables")
print(f" - Mean combinations can reveal hidden patterns in the data")
print(f" - These engineered features often improve model performance")
π― ADVANCED FEATURE ENGINEERING
===================================
Original feature count - Train: 82, Test: 81
π§ Creating combination features from 9 high-correlation features
Base features: ['TotRmsAbvGrd', 'GarageCars', '1stFlrSF', 'TotalBsmtSF', 'GrLivArea', 'YearBuilt', 'GarageYrBlt', 'GarageArea', 'OverallQual']
Total combinations to create: 36
1. Created 'TotRmsAbvGrd_GarageCars' from TotRmsAbvGrd + GarageCars
2. Created 'TotRmsAbvGrd_1stFlrSF' from TotRmsAbvGrd + 1stFlrSF
3. Created 'TotRmsAbvGrd_TotalBsmtSF' from TotRmsAbvGrd + TotalBsmtSF
4. Created 'TotRmsAbvGrd_GrLivArea' from TotRmsAbvGrd + GrLivArea
5. Created 'TotRmsAbvGrd_YearBuilt' from TotRmsAbvGrd + YearBuilt
6. Created 'TotRmsAbvGrd_GarageYrBlt' from TotRmsAbvGrd + GarageYrBlt
7. Created 'TotRmsAbvGrd_GarageArea' from TotRmsAbvGrd + GarageArea
8. Created 'TotRmsAbvGrd_OverallQual' from TotRmsAbvGrd + OverallQual
9. Created 'GarageCars_1stFlrSF' from GarageCars + 1stFlrSF
10. Created 'GarageCars_TotalBsmtSF' from GarageCars + TotalBsmtSF
11. Created 'GarageCars_GrLivArea' from GarageCars + GrLivArea
12. Created 'GarageCars_YearBuilt' from GarageCars + YearBuilt
13. Created 'GarageCars_GarageYrBlt' from GarageCars + GarageYrBlt
14. Created 'GarageCars_GarageArea' from GarageCars + GarageArea
15. Created 'GarageCars_OverallQual' from GarageCars + OverallQual
16. Created '1stFlrSF_TotalBsmtSF' from 1stFlrSF + TotalBsmtSF
17. Created '1stFlrSF_GrLivArea' from 1stFlrSF + GrLivArea
18. Created '1stFlrSF_YearBuilt' from 1stFlrSF + YearBuilt
19. Created '1stFlrSF_GarageYrBlt' from 1stFlrSF + GarageYrBlt
20. Created '1stFlrSF_GarageArea' from 1stFlrSF + GarageArea
21. Created '1stFlrSF_OverallQual' from 1stFlrSF + OverallQual
22. Created 'TotalBsmtSF_GrLivArea' from TotalBsmtSF + GrLivArea
23. Created 'TotalBsmtSF_YearBuilt' from TotalBsmtSF + YearBuilt
24. Created 'TotalBsmtSF_GarageYrBlt' from TotalBsmtSF + GarageYrBlt
25. Created 'TotalBsmtSF_GarageArea' from TotalBsmtSF + GarageArea
26. Created 'TotalBsmtSF_OverallQual' from TotalBsmtSF + OverallQual
27. Created 'GrLivArea_YearBuilt' from GrLivArea + YearBuilt
28. Created 'GrLivArea_GarageYrBlt' from GrLivArea + GarageYrBlt
29. Created 'GrLivArea_GarageArea' from GrLivArea + GarageArea
30. Created 'GrLivArea_OverallQual' from GrLivArea + OverallQual
31. Created 'YearBuilt_GarageYrBlt' from YearBuilt + GarageYrBlt
32. Created 'YearBuilt_GarageArea' from YearBuilt + GarageArea
33. Created 'YearBuilt_OverallQual' from YearBuilt + OverallQual
34. Created 'GarageYrBlt_GarageArea' from GarageYrBlt + GarageArea
35. Created 'GarageYrBlt_OverallQual' from GarageYrBlt + OverallQual
36. Created 'GarageArea_OverallQual' from GarageArea + OverallQual
β
Feature engineering complete: 36 new features created
π§ Creating combination features from 9 high-correlation features
Base features: ['TotRmsAbvGrd', 'GarageCars', '1stFlrSF', 'TotalBsmtSF', 'GrLivArea', 'YearBuilt', 'GarageYrBlt', 'GarageArea', 'OverallQual']
Total combinations to create: 36
1. Created 'TotRmsAbvGrd_GarageCars' from TotRmsAbvGrd + GarageCars
2. Created 'TotRmsAbvGrd_1stFlrSF' from TotRmsAbvGrd + 1stFlrSF
3. Created 'TotRmsAbvGrd_TotalBsmtSF' from TotRmsAbvGrd + TotalBsmtSF
4. Created 'TotRmsAbvGrd_GrLivArea' from TotRmsAbvGrd + GrLivArea
5. Created 'TotRmsAbvGrd_YearBuilt' from TotRmsAbvGrd + YearBuilt
6. Created 'TotRmsAbvGrd_GarageYrBlt' from TotRmsAbvGrd + GarageYrBlt
7. Created 'TotRmsAbvGrd_GarageArea' from TotRmsAbvGrd + GarageArea
8. Created 'TotRmsAbvGrd_OverallQual' from TotRmsAbvGrd + OverallQual
9. Created 'GarageCars_1stFlrSF' from GarageCars + 1stFlrSF
10. Created 'GarageCars_TotalBsmtSF' from GarageCars + TotalBsmtSF
11. Created 'GarageCars_GrLivArea' from GarageCars + GrLivArea
12. Created 'GarageCars_YearBuilt' from GarageCars + YearBuilt
13. Created 'GarageCars_GarageYrBlt' from GarageCars + GarageYrBlt
14. Created 'GarageCars_GarageArea' from GarageCars + GarageArea
15. Created 'GarageCars_OverallQual' from GarageCars + OverallQual
16. Created '1stFlrSF_TotalBsmtSF' from 1stFlrSF + TotalBsmtSF
17. Created '1stFlrSF_GrLivArea' from 1stFlrSF + GrLivArea
18. Created '1stFlrSF_YearBuilt' from 1stFlrSF + YearBuilt
19. Created '1stFlrSF_GarageYrBlt' from 1stFlrSF + GarageYrBlt
20. Created '1stFlrSF_GarageArea' from 1stFlrSF + GarageArea
21. Created '1stFlrSF_OverallQual' from 1stFlrSF + OverallQual
22. Created 'TotalBsmtSF_GrLivArea' from TotalBsmtSF + GrLivArea
23. Created 'TotalBsmtSF_YearBuilt' from TotalBsmtSF + YearBuilt
24. Created 'TotalBsmtSF_GarageYrBlt' from TotalBsmtSF + GarageYrBlt
25. Created 'TotalBsmtSF_GarageArea' from TotalBsmtSF + GarageArea
26. Created 'TotalBsmtSF_OverallQual' from TotalBsmtSF + OverallQual
27. Created 'GrLivArea_YearBuilt' from GrLivArea + YearBuilt
28. Created 'GrLivArea_GarageYrBlt' from GrLivArea + GarageYrBlt
29. Created 'GrLivArea_GarageArea' from GrLivArea + GarageArea
30. Created 'GrLivArea_OverallQual' from GrLivArea + OverallQual
31. Created 'YearBuilt_GarageYrBlt' from YearBuilt + GarageYrBlt
32. Created 'YearBuilt_GarageArea' from YearBuilt + GarageArea
33. Created 'YearBuilt_OverallQual' from YearBuilt + OverallQual
34. Created 'GarageYrBlt_GarageArea' from GarageYrBlt + GarageArea
35. Created 'GarageYrBlt_OverallQual' from GarageYrBlt + OverallQual
36. Created 'GarageArea_OverallQual' from GarageArea + OverallQual
β
Feature engineering complete: 36 new features created
π FEATURE ENGINEERING RESULTS:
Final feature count - Train: 118, Test: 117
New features added: 36
π‘ Rationale:
- Interaction features capture relationships between correlated variables
- Mean combinations can reveal hidden patterns in the data
- These engineered features often improve model performance
# =====================================================
# FEATURE ENGINEERING VERIFICATION
# =====================================================
# Verify that feature engineering was successful by examining the enhanced dataset
print("π FEATURE ENGINEERING VERIFICATION")
print("=" * 40)
# Display first few rows to see new features
print("π Sample of enhanced dataset with new combination features:")
sample_display = train.head()
# Show just the new combination features (last columns)
combination_features = [col for col in train.columns if '_' in col and col != 'SalePrice']
print(f"\nπ New combination features created ({len(combination_features)} total):")
for i, feature in enumerate(combination_features[:10], 1): # Show first 10
print(f" {i:2d}. {feature}")
if len(combination_features) > 10:
print(f" ... and {len(combination_features) - 10} more")
print(f"\nπ Dataset size summary:")
print(f" Training set: {train.shape[0]} rows Γ {train.shape[1]} features")
print(f" Test set: {test.shape[0]} rows Γ {test.shape[1]} features")
# Show the first few rows with the combination features visible
train.head()
π FEATURE ENGINEERING VERIFICATION
========================================
π Sample of enhanced dataset with new combination features:
π New combination features created (36 total):
1. TotRmsAbvGrd_GarageCars
2. TotRmsAbvGrd_1stFlrSF
3. TotRmsAbvGrd_TotalBsmtSF
4. TotRmsAbvGrd_GrLivArea
5. TotRmsAbvGrd_YearBuilt
6. TotRmsAbvGrd_GarageYrBlt
7. TotRmsAbvGrd_GarageArea
8. TotRmsAbvGrd_OverallQual
9. GarageCars_1stFlrSF
10. GarageCars_TotalBsmtSF
... and 26 more
π Dataset size summary:
Training set: 1453 rows Γ 118 features
Test set: 1459 rows Γ 117 features
| Id | MSSubClass | MSZoning | LotFrontage | LotArea | Street | Alley | LotShape | LandContour | Utilities | ... | GrLivArea_YearBuilt | GrLivArea_GarageYrBlt | GrLivArea_GarageArea | GrLivArea_OverallQual | YearBuilt_GarageYrBlt | YearBuilt_GarageArea | YearBuilt_OverallQual | GarageYrBlt_GarageArea | GarageYrBlt_OverallQual | GarageArea_OverallQual | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 60 | RL | 65.0 | 8450 | Pave | None | Reg | Lvl | AllPub | ... | 1856.5 | 1856.5 | 1129.0 | 858.5 | 2003.0 | 1275.5 | 1005.0 | 1275.5 | 1005.0 | 277.5 |
| 1 | 2 | 20 | RL | 80.0 | 9600 | Pave | None | Reg | Lvl | AllPub | ... | 1619.0 | 1619.0 | 861.0 | 634.0 | 1976.0 | 1218.0 | 991.0 | 1218.0 | 991.0 | 233.0 |
| 2 | 3 | 60 | RL | 68.0 | 11250 | Pave | None | IR1 | Lvl | AllPub | ... | 1893.5 | 1893.5 | 1197.0 | 896.5 | 2001.0 | 1304.5 | 1004.0 | 1304.5 | 1004.0 | 307.5 |
| 3 | 4 | 70 | RL | 60.0 | 9550 | Pave | None | IR1 | Lvl | AllPub | ... | 1816.0 | 1857.5 | 1179.5 | 862.0 | 1956.5 | 1278.5 | 961.0 | 1320.0 | 1002.5 | 324.5 |
| 4 | 5 | 60 | RL | 84.0 | 14260 | Pave | None | IR1 | Lvl | AllPub | ... | 2099.0 | 2099.0 | 1517.0 | 1103.0 | 2000.0 | 1418.0 | 1004.0 | 1418.0 | 1004.0 | 422.0 |
5 rows Γ 118 columns
# =====================================================
# CATEGORICAL ENCODING - LABEL ENCODING
# =====================================================
# Convert all categorical (object) features to numerical format using Label Encoding
# This is required for machine learning algorithms that only accept numerical inputs
print("π§ CATEGORICAL FEATURE ENCODING")
print("=" * 35)
# Initialize Label Encoder
le = LabelEncoder()
# Get list of categorical features
categorical_features = [col for col in train.columns if train[col].dtype == "object"]
print(f"π Found {len(categorical_features)} categorical features to encode:")
# Encode categorical features in training set
print(f"\nπ Encoding training set features...")
for i, col in enumerate(categorical_features, 1):
unique_values = train[col].nunique()
train[col] = le.fit_transform(train[col])
print(f" {i:2d}. {col}: {unique_values} categories β numerical labels")
# Encode categorical features in test set
print(f"\nπ Encoding test set features...")
test_categorical_features = [col for col in test.columns if test[col].dtype == "object"]
for i, col in enumerate(test_categorical_features, 1):
unique_values = test[col].nunique()
test[col] = le.fit_transform(test[col])
print(f" {i:2d}. {col}: {unique_values} categories β numerical labels")
print(f"\nβ
ENCODING COMPLETED")
print(f" All features are now numerical and ready for machine learning")
# Verify encoding success
train_object_count = (train.dtypes == 'object').sum()
test_object_count = (test.dtypes == 'object').sum()
print(f"\nπ Verification:")
print(f" Train set object features remaining: {train_object_count}")
print(f" Test set object features remaining: {test_object_count}")
if train_object_count == 0 and test_object_count == 0:
print(f"π Perfect! All features successfully converted to numerical format")
else:
print(f"β οΈ Warning: Some categorical features may need attention")
π§ CATEGORICAL FEATURE ENCODING
===================================
π Found 43 categorical features to encode:
π Encoding training set features...
1. MSZoning: 5 categories β numerical labels
2. Street: 2 categories β numerical labels
3. Alley: 3 categories β numerical labels
4. LotShape: 4 categories β numerical labels
5. LandContour: 4 categories β numerical labels
6. Utilities: 2 categories β numerical labels
7. LotConfig: 5 categories β numerical labels
8. LandSlope: 3 categories β numerical labels
9. Neighborhood: 25 categories β numerical labels
10. Condition1: 9 categories β numerical labels
11. Condition2: 8 categories β numerical labels
12. BldgType: 5 categories β numerical labels
13. HouseStyle: 8 categories β numerical labels
14. RoofStyle: 6 categories β numerical labels
15. RoofMatl: 7 categories β numerical labels
16. Exterior1st: 15 categories β numerical labels
17. Exterior2nd: 16 categories β numerical labels
18. MasVnrType: 4 categories β numerical labels
19. ExterQual: 4 categories β numerical labels
20. ExterCond: 5 categories β numerical labels
21. Foundation: 6 categories β numerical labels
22. BsmtQual: 5 categories β numerical labels
23. BsmtCond: 5 categories β numerical labels
24. BsmtExposure: 5 categories β numerical labels
25. BsmtFinType1: 7 categories β numerical labels
26. BsmtFinType2: 7 categories β numerical labels
27. Heating: 6 categories β numerical labels
28. HeatingQC: 5 categories β numerical labels
29. CentralAir: 2 categories β numerical labels
30. Electrical: 6 categories β numerical labels
31. KitchenQual: 4 categories β numerical labels
32. Functional: 7 categories β numerical labels
33. FireplaceQu: 6 categories β numerical labels
34. GarageType: 7 categories β numerical labels
35. GarageFinish: 4 categories β numerical labels
36. GarageQual: 6 categories β numerical labels
37. GarageCond: 6 categories β numerical labels
38. PavedDrive: 3 categories β numerical labels
39. PoolQC: 4 categories β numerical labels
40. Fence: 5 categories β numerical labels
41. MiscFeature: 5 categories β numerical labels
42. SaleType: 9 categories β numerical labels
43. SaleCondition: 6 categories β numerical labels
π Encoding test set features...
1. MSZoning: 6 categories β numerical labels
2. Street: 2 categories β numerical labels
3. Alley: 3 categories β numerical labels
4. LotShape: 4 categories β numerical labels
5. LandContour: 4 categories β numerical labels
6. Utilities: 2 categories β numerical labels
7. LotConfig: 5 categories β numerical labels
8. LandSlope: 3 categories β numerical labels
9. Neighborhood: 25 categories β numerical labels
10. Condition1: 9 categories β numerical labels
11. Condition2: 5 categories β numerical labels
12. BldgType: 5 categories β numerical labels
13. HouseStyle: 7 categories β numerical labels
14. RoofStyle: 6 categories β numerical labels
15. RoofMatl: 4 categories β numerical labels
16. Exterior1st: 14 categories β numerical labels
17. Exterior2nd: 16 categories β numerical labels
18. MasVnrType: 4 categories β numerical labels
19. ExterQual: 4 categories β numerical labels
20. ExterCond: 5 categories β numerical labels
21. Foundation: 6 categories β numerical labels
22. BsmtQual: 5 categories β numerical labels
23. BsmtCond: 5 categories β numerical labels
24. BsmtExposure: 5 categories β numerical labels
25. BsmtFinType1: 7 categories β numerical labels
26. BsmtFinType2: 7 categories β numerical labels
27. Heating: 4 categories β numerical labels
28. HeatingQC: 5 categories β numerical labels
29. CentralAir: 2 categories β numerical labels
30. Electrical: 4 categories β numerical labels
31. KitchenQual: 5 categories β numerical labels
32. Functional: 8 categories β numerical labels
33. FireplaceQu: 6 categories β numerical labels
34. GarageType: 7 categories β numerical labels
35. GarageFinish: 4 categories β numerical labels
36. GarageQual: 5 categories β numerical labels
37. GarageCond: 6 categories β numerical labels
38. PavedDrive: 3 categories β numerical labels
39. PoolQC: 3 categories β numerical labels
40. Fence: 5 categories β numerical labels
41. MiscFeature: 4 categories β numerical labels
42. SaleType: 10 categories β numerical labels
43. SaleCondition: 6 categories β numerical labels
β
ENCODING COMPLETED
All features are now numerical and ready for machine learning
π Verification:
Train set object features remaining: 0
Test set object features remaining: 0
π Perfect! All features successfully converted to numerical format
πΉ 6. Model Training and SubmissionΒΆ
# =====================================================
# DATA PREPARATION FOR MACHINE LEARNING
# =====================================================
# Split features and target variable, then create training/validation sets
# This separation is critical for unbiased model evaluation
print("π― PREPARING DATA FOR MACHINE LEARNING")
print("=" * 40)
# Separate features (X) and target variable (y)
print("π Separating features and target variable:")
# Training set: remove ID and target columns to create feature matrix
X = train.drop(columns=["Id", "SalePrice"])
print(f" Training features (X): {X.shape[0]} samples Γ {X.shape[1]} features")
# Test set: remove only ID column (no SalePrice in test set)
X_test = test.drop(columns=['Id'])
print(f" Test features (X_test): {X_test.shape[0]} samples Γ {X_test.shape[1]} features")
# Target variable: house sale prices
y = train['SalePrice']
print(f" Target variable (y): {len(y)} price values")
print(f" Price range: ${y.min():,.0f} - ${y.max():,.0f}")
print(f" Median price: ${y.median():,.0f}")
# Create train/validation split for model evaluation
print(f"\nπ Creating train/validation split:")
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.3, random_state=SEED)
print(f" Training set: {X_train.shape[0]} samples ({70}%)")
print(f" Validation set: {X_val.shape[0]} samples ({30}%)")
print(f"\nβ
Data preparation complete!")
print(f"π‘ Ready for hyperparameter tuning and model training")
π― PREPARING DATA FOR MACHINE LEARNING ======================================== π Separating features and target variable: Training features (X): 1453 samples Γ 116 features Test features (X_test): 1459 samples Γ 116 features Target variable (y): 1453 price values Price range: $34,900 - $625,000 Median price: $162,900 π Creating train/validation split: Training set: 1017 samples (70%) Validation set: 436 samples (30%) β Data preparation complete! π‘ Ready for hyperparameter tuning and model training
# =====================================================
# XGBOOST HYPERPARAMETER TUNING WITH GRID SEARCH
# =====================================================
# Perform comprehensive hyperparameter optimization using GridSearchCV
# This automated search finds the best combination of parameters for optimal performance
print("π XGBOOST HYPERPARAMETER OPTIMIZATION")
print("=" * 45)
# Define comprehensive parameter grid for XGBoost
# Each parameter impacts different aspects of model performance
param_grid = {
'n_estimators': [100, 200, 500], # Number of trees (more = better fit, slower training)
'learning_rate': [0.01, 0.05, 0.1], # Step size (lower = more conservative updates)
'max_depth': [3, 5, 7, 9], # Tree depth (higher = more complex interactions)
'subsample': [0.8, 0.9, 1.0], # Fraction of samples per tree (prevents overfitting)
'colsample_bytree': [0.8, 0.9, 1.0], # Fraction of features per tree (prevents overfitting)
'alpha': [0, 0.01, 0.1, 1], # L1 regularization (feature selection)
'lambda': [0, 0.1, 0.5, 1], # L2 regularization (weight penalty)
'gamma': [0, 0.1, 0.2, 1], # Minimum loss reduction for splits
'early_stopping_rounds': [5, 10, 20, 30] # Stop training if no improvement
}
print(f"π Parameter grid contains {len(param_grid)} parameters")
total_combinations = 1
for param, values in param_grid.items():
total_combinations *= len(values)
print(f" {param}: {len(values)} options")
print(f"\nπ Starting grid search with 5-fold cross-validation...")
print(f" Total parameter combinations to test: {total_combinations:,}")
print(f" Estimated search time: This may take several minutes")
# Initialize GridSearchCV with XGBoost
# cv=5 means 5-fold cross-validation for robust performance estimation
grid_search = GridSearchCV(
xg.XGBRegressor(tree_method="gpu_hist", random_state=SEED), # Use GPU if available
param_grid,
cv=5, # 5-fold cross-validation
n_jobs=-1, # Use all CPU cores
scoring='neg_mean_squared_error', # Minimize RMSE
verbose=1 # Show progress
)
# Fit grid search and find optimal parameters
grid_search.fit(X_train, y_train,
eval_set=[(X_train, y_train), (X_val, y_val)])
# Extract and display best parameters
best_params = grid_search.best_params_
best_score = -grid_search.best_score_ # Convert back from negative
print(f"\nπ HYPERPARAMETER OPTIMIZATION COMPLETE!")
print(f" Best cross-validation RMSE: ${best_score:,.2f}")
print(f"\nπ Optimal Parameters Found:")
print("=" * 30)
for param, value in best_params.items():
print(f" {param}: {value}")
print(f"\nπ‘ These parameters will be used for final model training")
π XGBOOST HYPERPARAMETER OPTIMIZATION ============================================= π Parameter grid contains 9 parameters n_estimators: 3 options learning_rate: 3 options max_depth: 4 options subsample: 3 options colsample_bytree: 3 options alpha: 4 options lambda: 4 options gamma: 4 options early_stopping_rounds: 4 options π Starting grid search with 5-fold cross-validation... Total parameter combinations to test: 82,944 Estimated search time: This may take several minutes Fitting 5 folds for each of 82944 candidates, totalling 414720 fits
--------------------------------------------------------------------------- KeyboardInterrupt Traceback (most recent call last) Cell In[24], line 47 37 grid_search = GridSearchCV( 38 xg.XGBRegressor(tree_method="gpu_hist", random_state=SEED), # Use GPU if available 39 param_grid, (...) 43 verbose=1 # Show progress 44 ) 46 # Fit grid search and find optimal parameters ---> 47 grid_search.fit(X_train, y_train, 48 eval_set=[(X_train, y_train), (X_val, y_val)]) 50 # Extract and display best parameters 51 best_params = grid_search.best_params_ File c:\Users\user\anaconda3\envs\ml-env-stable\Lib\site-packages\sklearn\base.py:1389, in _fit_context.<locals>.decorator.<locals>.wrapper(estimator, *args, **kwargs) 1382 estimator._validate_params() 1384 with config_context( 1385 skip_parameter_validation=( 1386 prefer_skip_nested_validation or global_skip_validation 1387 ) 1388 ): -> 1389 return fit_method(estimator, *args, **kwargs) File c:\Users\user\anaconda3\envs\ml-env-stable\Lib\site-packages\sklearn\model_selection\_search.py:1024, in BaseSearchCV.fit(self, X, y, **params) 1018 results = self._format_results( 1019 all_candidate_params, n_splits, all_out, all_more_results 1020 ) 1022 return results -> 1024 self._run_search(evaluate_candidates) 1026 # multimetric is determined here because in the case of a callable 1027 # self.scoring the return type is only known after calling 1028 first_test_score = all_out[0]["test_scores"] File c:\Users\user\anaconda3\envs\ml-env-stable\Lib\site-packages\sklearn\model_selection\_search.py:1571, in GridSearchCV._run_search(self, evaluate_candidates) 1569 def _run_search(self, evaluate_candidates): 1570 """Search all candidates in param_grid""" -> 1571 evaluate_candidates(ParameterGrid(self.param_grid)) File c:\Users\user\anaconda3\envs\ml-env-stable\Lib\site-packages\sklearn\model_selection\_search.py:970, in BaseSearchCV.fit.<locals>.evaluate_candidates(candidate_params, cv, more_results) 962 if self.verbose > 0: 963 print( 964 "Fitting {0} folds for each of {1} candidates," 965 " totalling {2} fits".format( 966 n_splits, n_candidates, n_candidates * n_splits 967 ) 968 ) --> 970 out = parallel( 971 delayed(_fit_and_score)( 972 clone(base_estimator), 973 X, 974 y, 975 train=train, 976 test=test, 977 parameters=parameters, 978 split_progress=(split_idx, n_splits), 979 candidate_progress=(cand_idx, n_candidates), 980 **fit_and_score_kwargs, 981 ) 982 for (cand_idx, parameters), (split_idx, (train, test)) in product( 983 enumerate(candidate_params), 984 enumerate(cv.split(X, y, **routed_params.splitter.split)), 985 ) 986 ) 988 if len(out) < 1: 989 raise ValueError( 990 "No fits were performed. " 991 "Was the CV iterator empty? " 992 "Were there no candidates?" 993 ) File c:\Users\user\anaconda3\envs\ml-env-stable\Lib\site-packages\sklearn\utils\parallel.py:77, in Parallel.__call__(self, iterable) 72 config = get_config() 73 iterable_with_config = ( 74 (_with_config(delayed_func, config), args, kwargs) 75 for delayed_func, args, kwargs in iterable 76 ) ---> 77 return super().__call__(iterable_with_config) File c:\Users\user\anaconda3\envs\ml-env-stable\Lib\site-packages\joblib\parallel.py:2072, in Parallel.__call__(self, iterable) 2066 # The first item from the output is blank, but it makes the interpreter 2067 # progress until it enters the Try/Except block of the generator and 2068 # reaches the first `yield` statement. This starts the asynchronous 2069 # dispatch of the tasks to the workers. 2070 next(output) -> 2072 return output if self.return_generator else list(output) File c:\Users\user\anaconda3\envs\ml-env-stable\Lib\site-packages\joblib\parallel.py:1682, in Parallel._get_outputs(self, iterator, pre_dispatch) 1679 yield 1681 with self._backend.retrieval_context(): -> 1682 yield from self._retrieve() 1684 except GeneratorExit: 1685 # The generator has been garbage collected before being fully 1686 # consumed. This aborts the remaining tasks if possible and warn 1687 # the user if necessary. 1688 self._exception = True File c:\Users\user\anaconda3\envs\ml-env-stable\Lib\site-packages\joblib\parallel.py:1800, in Parallel._retrieve(self) 1789 if self.return_ordered: 1790 # Case ordered: wait for completion (or error) of the next job 1791 # that have been dispatched and not retrieved yet. If no job (...) 1795 # control only have to be done on the amount of time the next 1796 # dispatched job is pending. 1797 if (nb_jobs == 0) or ( 1798 self._jobs[0].get_status(timeout=self.timeout) == TASK_PENDING 1799 ): -> 1800 time.sleep(0.01) 1801 continue 1803 elif nb_jobs == 0: 1804 # Case unordered: jobs are added to the list of jobs to 1805 # retrieve `self._jobs` only once completed or in error, which (...) 1811 # timeouts before any other dispatched job has completed and 1812 # been added to `self._jobs` to be retrieved. KeyboardInterrupt:
# =====================================================
# ROBUST CROSS-VALIDATION WITH K-FOLD
# =====================================================
# Perform K-Fold cross-validation using optimized parameters
# This provides a more robust estimate of model performance than a single train/test split
print("π ROBUST K-FOLD CROSS-VALIDATION")
print("=" * 38)
# Initialize 5-fold cross-validation
# shuffle=True randomizes the data order for better validation
kf = KFold(n_splits=5, shuffle=True, random_state=42)
print(f"π Cross-validation setup:")
print(f" Number of folds: 5")
print(f" Training samples per fold: ~{len(X) * 0.8:.0f}")
print(f" Validation samples per fold: ~{len(X) * 0.2:.0f}")
# Initialize array to store out-of-fold predictions
# This will contain predictions for every sample in the training set
oof_predictions = np.zeros(len(train))
fold_scores = []
print(f"\nπ Training models across folds...")
# Perform K-Fold cross-validation
for fold, (train_idx, val_idx) in enumerate(kf.split(train), 1):
print(f"\n Fold {fold}/5:")
# Split data for current fold
X_fold_train, X_fold_val = X.iloc[train_idx], X.iloc[val_idx]
y_fold_train, y_fold_val = y.iloc[train_idx], y.iloc[val_idx]
print(f" Training samples: {len(X_fold_train)}")
print(f" Validation samples: {len(X_fold_val)}")
# Train XGBoost model with optimal parameters
model = xg.XGBRegressor(**best_params)
model.fit(X_fold_train, y_fold_train)
# Make predictions on validation set
y_pred = model.predict(X_fold_val)
# Store out-of-fold predictions
oof_predictions[val_idx] = y_pred
# Calculate and store fold performance
fold_rmse = root_mean_squared_error(y_fold_val, y_pred)
fold_scores.append(fold_rmse)
print(f" Fold RMSE: ${fold_rmse:,.2f}")
# Calculate final cross-validation performance
final_rmse = root_mean_squared_error(y, oof_predictions)
mean_fold_rmse = np.mean(fold_scores)
std_fold_rmse = np.std(fold_scores)
print(f"\nπ CROSS-VALIDATION RESULTS:")
print(f"=" * 30)
print(f" Final CV RMSE: ${final_rmse:,.2f}")
print(f" Mean fold RMSE: ${mean_fold_rmse:,.2f} Β± ${std_fold_rmse:,.2f}")
print(f" Best fold: ${min(fold_scores):,.2f}")
print(f" Worst fold: ${max(fold_scores):,.2f}")
print(f"\nπ‘ Performance Analysis:")
print(f" - Consistent performance across folds indicates stable model")
print(f" - Low standard deviation suggests good generalization")
print(f" - Ready for final predictions on test set")
# =====================================================
# FINAL MODEL TRAINING & COMPETITION SUBMISSION
# =====================================================
# Train final model on full dataset and generate competition predictions
print("π― FINAL MODEL TRAINING & PREDICTIONS")
print("=" * 40)
# Use the last trained model from cross-validation for predictions
# (Note: In production, you might retrain on full dataset)
print("π Generating predictions on test set...")
predictions = model.predict(X_test)
print(f" Test set size: {len(predictions)} houses")
print(f" Prediction range: ${predictions.min():,.0f} - ${predictions.max():,.0f}")
print(f" Median prediction: ${np.median(predictions):,.0f}")
# Create submission file in Kaggle competition format
print(f"\nπ Creating submission file...")
output = pd.DataFrame({
'Id': test['Id'], # House IDs from test set
'SalePrice': predictions # Our predicted sale prices
})
# Save submission file
submission_filename = 'submission_xgb.csv'
output.to_csv(submission_filename, index=False)
print(f"β
SUBMISSION FILE CREATED!")
print(f" Filename: {submission_filename}")
print(f" Format: Kaggle competition standard (Id, SalePrice)")
print(f" Records: {len(output)} predictions")
# Display first few predictions
print(f"\nπ Sample predictions:")
print(output.head(10))
print(f"\nπ Ready for Kaggle submission!")
print(f"π‘ Model Performance Summary:")
print(f" - Cross-validation RMSE: ${final_rmse:,.2f}")
print(f" - Hyperparameters optimized via GridSearch")
print(f" - Features engineered from correlation analysis")
print(f" - Outliers removed for better generalization")
πΉ 7. Alternative MethodΒΆ
# =====================================================
# ALTERNATIVE APPROACH - ONE-HOT ENCODING EXPERIMENT
# =====================================================
# Test an alternative preprocessing approach using one-hot encoding
# This creates binary features for each category, potentially capturing different patterns
print("π§ͺ ALTERNATIVE APPROACH - ONE-HOT ENCODING")
print("=" * 45)
print("π Trying different preprocessing strategy:")
print(" - One-hot encoding instead of label encoding")
print(" - May capture categorical relationships better")
print(" - Increases feature dimensionality significantly")
# Prepare target variable (same as before)
y = train["SalePrice"]
# Apply one-hot encoding to both datasets
# This creates binary (0/1) columns for each category in categorical features
print(f"\nπ Applying one-hot encoding...")
# Get_dummies automatically handles categorical features
X = pd.get_dummies(train.drop(columns=["SalePrice"]))
X_test = pd.get_dummies(test)
print(f" Original features: {train.shape[1] - 1}") # Subtract SalePrice
print(f" After one-hot encoding: {X.shape[1]} features")
print(f" Feature expansion: {X.shape[1] / (train.shape[1] - 1):.1f}x increase")
# Create new train/validation split
print(f"\nπ Creating train/validation split...")
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.25, random_state=SEED)
print(f" Training set: {X_train.shape[0]} samples Γ {X_train.shape[1]} features")
print(f" Validation set: {X_val.shape[0]} samples Γ {X_val.shape[1]} features")
# Train XGBoost with optimal parameters from previous grid search
print(f"\nπ§ Training XGBoost with one-hot encoded features...")
model = xg.XGBRegressor(
**best_params, # Use previously optimized parameters
random_state=SEED
)
# Train with validation monitoring
model.fit(X_train, y_train,
eval_set=[(X_train, y_train), (X_val, y_val)],
verbose=False) # Suppress training output for cleaner display
# Extract training history for visualization
results = model.evals_result()
# Plot training curves to monitor overfitting
print(f"\nπ Training Progress Visualization:")
plt.figure(figsize=(10,7))
plt.plot(results["validation_0"]["rmse"], label="Training RMSE", linewidth=2)
plt.plot(results["validation_1"]["rmse"], label="Validation RMSE", linewidth=2)
plt.xlabel("Number of Trees (Boosting Rounds)")
plt.ylabel("RMSE")
plt.title("XGBoost Training Progress - One-Hot Encoding Approach")
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
# Calculate performance metrics
best_validation_rmse = min(results["validation_1"]["rmse"])
training_rmse = min(results["validation_0"]["rmse"])
print(f"π ALTERNATIVE APPROACH RESULTS:")
print(f" Best validation RMSE: ${best_validation_rmse:,.2f}")
print(f" Training RMSE: ${training_rmse:,.2f}")
print(f" Overfitting gap: ${best_validation_rmse - training_rmse:,.2f}")
# Generate predictions and create submission
print(f"\nπ Generating alternative predictions...")
predictions = model.predict(X_test)
predictions_val = model.predict(X_val)
print(f" Test predictions: {len(predictions)} houses")
print(f" Validation RMSE: ${root_mean_squared_error(y_val, predictions_val):,.2f}")
# Create alternative submission file
output = pd.DataFrame({'Id': test['Id'], 'SalePrice': predictions})
alt_submission = 'submission_experiment.csv'
output.to_csv(alt_submission, index=False)
print(f"β
Alternative submission created: {alt_submission}")
print(f"\nπ‘ Comparison Summary:")
print(f" Label Encoding RMSE: ${final_rmse:,.2f}")
print(f" One-Hot Encoding RMSE: ${best_validation_rmse:,.2f}")
if best_validation_rmse < final_rmse:
print(f"π One-hot encoding approach performed better!")
else:
print(f"π Label encoding approach remains superior")
# =====================================================
# MODEL INTERPRETABILITY WITH SHAP ANALYSIS
# =====================================================
# Use SHAP (SHapley Additive exPlanations) to understand model behavior
# SHAP provides unified framework for interpreting machine learning model predictions
print("π MODEL INTERPRETABILITY WITH SHAP")
print("=" * 38)
print("π SHAP Analysis provides:")
print(" - Feature importance rankings")
print(" - Individual prediction explanations")
print(" - Feature interaction effects")
print(" - Model behavior insights")
# Initialize SHAP TreeExplainer for XGBoost models
# TreeExplainer is optimized for tree-based models like XGBoost
print(f"\nπ§ Initializing SHAP TreeExplainer...")
explainer = shap.TreeExplainer(model)
# Calculate SHAP values for validation set
# SHAP values represent each feature's contribution to individual predictions
print(f"π Computing SHAP values for {len(X_val)} validation samples...")
print(" (This may take a few moments for complex models)")
shap_values = explainer.shap_values(X_val)
print(f"β
SHAP analysis complete!")
print(f" SHAP values shape: {shap_values.shape}")
print(f" Expected value (baseline): ${explainer.expected_value:,.0f}")
# VISUALIZATION 1: Summary Plot
# Shows feature importance and impact direction for all features
print(f"\nπ Creating SHAP Summary Plot...")
print(" - Displays top features by importance")
print(" - Red points: high feature values")
print(" - Blue points: low feature values")
print(" - X-axis: SHAP value (impact on price prediction)")
plt.figure(figsize=(12, 8))
shap.summary_plot(shap_values, X_val, show=False)
plt.title("SHAP Summary Plot - Feature Importance & Impact Direction", fontsize=14, pad=20)
plt.tight_layout()
plt.show()
# VISUALIZATION 2: Individual Prediction Explanation
# Shows how each feature contributes to a specific prediction
print(f"\nπ― Individual Prediction Explanation:")
print(" - Explains prediction for first validation sample")
print(" - Shows feature contributions to final prediction")
print(" - Base value + feature contributions = final prediction")
sample_idx = 0
actual_price = y_val.iloc[sample_idx]
predicted_price = model.predict(X_val.iloc[[sample_idx]])[0]
print(f" Sample #{sample_idx}:")
print(f" Actual price: ${actual_price:,.0f}")
print(f" Predicted price: ${predicted_price:,.0f}")
print(f" Prediction error: ${abs(actual_price - predicted_price):,.0f}")
# Create force plot for individual prediction
plt.figure(figsize=(12, 6))
shap.force_plot(
explainer.expected_value,
shap_values[sample_idx],
X_val.iloc[sample_idx],
matplotlib=True,
show=False
)
plt.title(f"SHAP Force Plot - Individual Prediction Explanation (Sample {sample_idx})", fontsize=14)
plt.tight_layout()
plt.show()
print(f"\nπ‘ SHAP Analysis Insights:")
print(f" - Identifies which features drive high/low predictions")
print(f" - Reveals feature interactions and non-linear effects")
print(f" - Helps validate model logic against domain knowledge")
print(f" - Useful for debugging unexpected predictions")
print(f"\nπ Model interpretability analysis complete!")
print(f" Model predictions are now explainable and transparent")