Used Car Price Prediction — Regression Comparison
By Eldar · Published July 25, 2026
Compare Linear Regression, Random Forest, and Gradient Boosting models to predict used car prices from mileage, age, brand, fuel type, transmission, and engine size.
- regression
- price-prediction
- model-comparison
- scikit-learn
- eda
Inside this notebook
# 🚗 Used Car Price Prediction — Regression Model Comparison ## Goal Train a model to predict a used car's resale price from its **mileage, age, brand, fuel type, transmission, and engine size**, then compare several regression approaches. ## Dataset [Used Car Price Prediction Dataset](https://www.kaggle.com/datasets/taeefnajib/used-car-price-prediction-dataset) — 4,009 car listings with 13 attributes (Brand, Model, Model Year, Mileage, Fuel Type, Engine, Transmission, Price, and more). ## Approach (Branched) | Step | What happens | |------|-------------| | 1 | Load & inspect the data | | 2 | Feature engineering (Age, Engine Size, encode categoricals) | | 3 | Train/test split + scaling | | 4 | **Fork →** 3 branches (Linear Regression, Random Forest, Gradient Boosting) train in parallel | | 5 | **Converge →** Compare metrics, pick the winner | | 6 | Predict prices for 4 example cars + SHAP explanation | | 7 | Summary of findings |
# ⚙️ Install dependencies
import sys
!pip install -q kagglehub pandas numpy matplotlib seaborn scikit-learn shap# 📦 Load Dataset
import kagglehub
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# Download dataset from Kaggle
path = kagglehub.dataset_download("taeefnajib/used-car-price-prediction-dataset")
print("Dataset downloaded to:", path)
# Load CSV
df = pd.read_csv(f"{path}/used_cars.csv")
print(f"\nShape: {df.shape}")
print(f"\nColumns:\n{list(df.columns)}")
print(f"\nDtypes:\n{df.dtypes.value_counts()}")
print(f"\nFirst 5 rows:")
df.head()Dataset downloaded to: /root/.cache/kagglehub/datasets/taeefnajib/used-car-price-prediction-dataset/versions/1 Shape: (4009, 12) Columns: ['brand', 'model', 'model_year', 'milage', 'fuel_type', 'engine', 'transmission', 'ext_col', 'int_col', 'accident', 'clean_title', 'price'] Dtypes: object 11 int64 1 Name: count, dtype: int64 First 5 rows:
# 🔍 Inspect missing values & basic stats
print("=== Missing Values ===")
print(df.isnull().sum())
print("\n=== Summary Statistics (numeric) ===")
print(df.describe().round(2))
print("\n=== Unique values per categorical column ===")
for col in ['brand', 'fuel_type', 'transmission']:
print(f"{col}: {df[col].nunique()} unique | top 10: {df[col].value_counts().head(10).to_dict()}")
print("\n=== Price distribution ===")
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
sns.histplot(df['price'], bins=50, kde=True, ax=axes[0])
axes[0].set_title('Price Distribution')
sns.boxplot(x=df['price'], ax=axes[1])
axes[1].set_title('Price Boxplot')
…=== Missing Values ===
brand 0
model 0
model_year 0
milage 0
fuel_type 170
engine 0
transmission 0
ext_col 0
int_col 0
accident 113
clean_title 596
price 0
dtype: int64
=== Summary Statistics (numeric) ===
model_year
count 4009.00
mean 2015.52
std 6.10
min 1974.00
25% 2012.00
50% 2017.00
75% 2020.00
max 2024.00
=== Unique values per c…# 🔧 Feature Engineering
# 1. Clean price: remove $ and commas → numeric
df['price'] = df['price'].str.replace('$', '', regex=False).str.replace(',', '', regex=False)
df['price'] = pd.to_numeric(df['price'], errors='coerce')
# 2. Clean mileage: remove commas and ' mi.' suffix → numeric
df['milage'] = df['milage'].str.replace(',', '', regex=False).str.replace(' mi.', '', regex=False)
df['milage'] = pd.to_numeric(df['milage'], errors='coerce')
# 3. Model year → numeric
df['model_year'] = pd.to_numeric(df['model_year'], errors='coerce')
# 4. Derive Age from Model Year
current_year = 2026
df['age'] = current_year - df['model_year']
# 5. Parse Engine string → displacement in litres
…=== Missing values ===
price 0
milage 0
age 0
engine_size 217
fuel_type 170
transmission_simple 0
dtype: int64
Rows before dropna(price): 4009
Rows after dropna(price): 4009
Final shape: (4009, 15)
=== Sample ===
brand age milage fuel_type engine_size transmission_simple \
0 Ford 13 51000 E85 Flex Fuel 3.7 Automatic
1 Hyundai 5 34742 Gasoline…# 🎯 Train/Test Split & Preprocessing — Fork Point
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
# Define feature sets
numeric_features = ['age', 'milage', 'engine_size']
categorical_features = ['brand', 'fuel_type', 'transmission_simple']
X = df[numeric_features + categorical_features].copy()
y = df['price'].copy()
# Train/test split (80/20)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
…Train size: 3207 | Test size: 802 Processed feature count: 67 Feature names (first 10): ['age', 'milage', 'engine_size', 'brand_Alfa', 'brand_Aston', 'brand_Audi', 'brand_BMW', 'brand_Bentley', 'brand_Buick', 'brand_Cadillac'] ... Training set shape: (3207, 67)
# 💾 Save preprocessed data for experiment branches
import joblib
import json
# Save all preprocessing artifacts
joblib.dump(preprocessor, '/home/user/preprocessor.pkl')
np.save('/home/user/X_train_processed.npy', X_train_processed)
np.save('/home/user/X_test_processed.npy', X_test_processed)
np.save('/home/user/y_train.npy', y_train.values)
np.save('/home/user/y_test.npy', y_test.values)
with open('/home/user/feature_names.json', 'w') as f:
json.dump(feature_names, f)
# Also save original data for branches that want to do their own preprocessing
X_train.to_parquet('/home/user/X_train.parquet')
X_test.to_parquet('/home/user/X_test.parquet')
y_train.to_frame('price').to_parquet('/home/user/y_train.parquet')
…Preprocessed data saved to /home/user/ -rw-r--r-- 1 root root 17K Jul 25 21:26 /home/user/X_test.parquet -rw-r--r-- 1 root root 420K Jul 25 21:26 /home/user/X_test_processed.npy -rw-r--r-- 1 root root 48K Jul 25 21:26 /home/user/X_train.parquet -rw-r--r-- 1 root root 1.7M Jul 25 21:26 /home/user/X_train_processed.npy -rw-r--r-- 1 root root 1.2K Jul 25 21:26 /home/user/feature_names.json -rw-r--r-- 1 root root 110 Jul 25 21:15 /home/user/model_gb_metrics.json -rw-r--r-- 1 root root 1.7K…