Supervised Regression: 30-Model Comparison¶
Executive Summary¶
This notebook presents a supervised machine learning regression project using the diamonds dataset. The main objective is to compare 30 regression models under a consistent preprocessing, training, tuning, and evaluation framework.
The target variable is price, and the goal is to predict diamond prices from the available numerical and categorical features, such as carat, cut, colour, clarity, depth, table, and physical dimensions.
The project compares a wide range of model families, including baseline models, linear and regularized regressors, robust regressors, distance-based models, support vector models, tree-based models, ensemble models, boosting models, neural network models, and meta-estimators such as Voting and Stacking regressors.
The models are evaluated using multiple regression metrics, including MAE, MSE, RMSE, and R². Training time and prediction time are also recorded in order to compare not only predictive performance, but also computational cost and practical usability.
The project also includes hyperparameter tuning, prediction comparison tables, price range analysis, error analysis, permutation importance analysis of the best-performing model, and saved model/prediction files for faster reloading.
The purpose of this notebook is not only to find the best-performing model, but also to understand how different supervised regression model families behave on structured tabular data when tested under a common experimental framework.
In the final version of the project, ensemble-based, boosting-based, and meta-estimator models achieved the strongest overall performance. Based on the primary RMSE metric, the tuned Stacking Regressor achieved the best overall test performance, while the tuned Voting Regressor produced a very similar result with slightly lower test MAE.
The final results should be interpreted in the context of this single dataset and could be further strengthened by testing the same framework on additional regression datasets.
Final Key Results on the Test Set¶
- Best regression model: Tuned Stacking Regressor
- Test MAE: 264.66
- Test RMSE: 522.41
- Test R²: 0.9829
- Full notebook runtime: 11:17:53
The tuned Voting Regressor was a very close alternative, achieving a test MAE of 263.88, a test RMSE of 523.72, and a test R² score of 0.9828.
Research Question¶
How do different supervised regression model families compare on structured tabular data when they are trained, tuned, and evaluated under a consistent experimental framework?
More specifically, this project investigates how baseline, linear, regularized, robust, distance-based, support vector, tree-based, ensemble, boosting, neural network, and meta-estimator regression models perform when predicting diamond prices from the available numerical and categorical features.
The comparison focuses not only on predictive accuracy, but also on overfitting behaviour, training time, prediction time, tuning sensitivity, and practical usability.
The main questions explored in this notebook are:
- Which regression model or model family achieves the best predictive performance on the test set?
- Do ensemble and boosting models provide a clear improvement over simpler linear and tree-based models?
- How much does hyperparameter tuning improve model performance compared with the base models?
- Which models offer the best balance between accuracy, computational cost, and interpretability?
- Are there specific price ranges where the models perform better or worse?
Project Scope¶
This project focuses on supervised regression using structured tabular data. The dataset used in this notebook is the diamonds dataset, and the target variable is price.
The main purpose of the project is to compare a broad selection of regression model families under a consistent machine learning workflow. The comparison includes preprocessing, model training, prediction, evaluation, hyperparameter tuning, runtime measurement, and practical model comparison.
The project is intentionally broad rather than narrowly optimized for a single model. Instead of focusing only on achieving the lowest possible RMSE, the notebook explores how different types of regression models behave under similar conditions.
The scope of this notebook includes:
- preprocessing numerical and categorical features;
- training and evaluating multiple regression model families;
- comparing base models and tuned models;
- measuring predictive performance using MAE, MSE, RMSE, and R²;
- comparing training time and prediction time;
- analysing model behaviour across different diamond price ranges;
- inspecting feature importance or coefficients where appropriate;
- saving fitted models, tuned objects, predictions, and result tables for later reuse.
This project does not claim that the best-performing model in this notebook is universally the best regression model for all tabular datasets. The results are specific to the selected dataset, feature set, preprocessing choices, evaluation strategy, and hyperparameter search spaces used in this project.
Further work could strengthen the analysis by applying the same framework to additional datasets, using statistical significance testing between models, and adding more detailed interpretability methods such as SHAP analysis.
Dataset and Target Definition¶
This project uses the diamonds dataset, which contains information about diamonds and their physical, categorical, and price-related characteristics.
The regression target variable is:
price: the price of the diamond in US dollars.
The input features include both numerical and categorical variables, such as:
carat: the weight of the diamond;cut: the quality of the diamond cut;color: the diamond colour grade;clarity: the diamond clarity grade;depth: the total depth percentage;table: the width of the top of the diamond relative to the widest point;x,y,z: the physical dimensions of the diamond in millimetres.
The aim of the regression task is to predict the diamond price from the available explanatory features.
This is a supervised regression problem because the target variable is continuous. The models are trained to learn the relationship between the diamond characteristics and the final price.
The dataset is used as structured tabular data, which makes it suitable for comparing a wide range of traditional machine learning regression models, including linear models, tree-based models, ensemble models, boosting models, support vector models, distance-based models, and neural network models.
Experimental Design¶
This notebook follows a consistent supervised machine learning workflow in order to compare multiple regression models as fairly as possible.
The dataset is first loaded, inspected, and prepared for modelling. The target variable is price, while the remaining numerical and categorical columns are used as input features. The data is split into training and test sets using a fixed random state to make the results reproducible.
A preprocessing pipeline is applied to handle numerical and categorical features appropriately. Numerical features are scaled where required, while categorical features are encoded so that they can be used by machine learning models. Tree-based models are also evaluated using a suitable preprocessing strategy that avoids unnecessary scaling where it is not required.
Each model is first trained as a base model using a consistent evaluation function. The base models are compared using regression metrics such as MAE, MSE, RMSE, and R². Training time and prediction time are also measured.
After the base model comparison, selected models are tuned using cross-validation and predefined hyperparameter search spaces. In several cases, the search spaces are iteratively refined based on earlier tuning results in order to obtain a strong practical configuration for each model family. The aim is not to prove that the globally optimal hyperparameters have been found, but to compare the models after a reasonable and consistent tuning effort. The tuned models are then evaluated on the same test set and compared against their base versions.
For Voting and Stacking, the notebook uses a two-stage ensemble strategy. The initial Voting and Stacking regressors are built from selected untuned model pipelines. The tuned Voting and Stacking regressors are then built from the best estimators found by the earlier hyperparameter searches. No additional Voting weight search or Stacking final-estimator grid search is applied in the final version, because these extra searches were computationally expensive and produced only small improvements in previous runs.
Some baseline or reference models are not tuned. In this regression project, the Dummy Regressor and Linear Regression are used mainly as baseline/reference models. The Dummy Regressor provides a simple lower-bound benchmark, while Linear Regression provides a simple interpretable linear reference model.
Because these models do not require the same type of hyperparameter tuning as more flexible model families, their base model results are carried forward into the final tuned comparison tables. This allows the tuned models to be compared against clear baseline references under the same final reporting structure.
The final comparison focuses on several aspects:
- predictive performance on the test set;
- difference between base and tuned model performance;
- training time and prediction time;
- signs of overfitting or underfitting;
- model interpretability where possible;
- performance across different diamond price ranges;
- practical usefulness of each model family.
The purpose of this experimental design is to create a consistent and reusable framework for comparing regression models on structured tabular data.
Evaluation Metrics¶
The regression models in this notebook are evaluated using several complementary metrics. Using multiple metrics provides a more complete picture of model performance than relying on a single score.
The main evaluation metrics are:
MAE — Mean Absolute Error
Measures the average absolute difference between the actual and predicted diamond prices. It is easy to interpret because it is expressed in the same unit as the target variable.MSE — Mean Squared Error
Measures the average squared prediction error. Larger errors are penalised more heavily than smaller errors.RMSE — Root Mean Squared Error
Measures the square root of the mean squared error. Like MAE, it is expressed in the same unit as the target variable, but it gives more weight to larger errors. RMSE is used as one of the main comparison metrics in this project.R² — Coefficient of Determination
Measures how much of the variance in the target variable is explained by the model. A higher R² value indicates that the model explains more of the variation in diamond prices.
In this project, RMSE and R² are treated as the most important summary metrics. RMSE is useful because it shows the typical prediction error in price units, while R² shows how well the model explains the overall variation in the target variable.
Training time and prediction time are also recorded. These are not accuracy metrics, but they are important for comparing the practical usefulness of different model families. A model with slightly better predictive performance may not always be the best practical choice if it requires substantially more training time or computational resources.
Model Families Compared¶
This project compares a broad range of supervised regression model families. The aim is not only to identify the best individual model, but also to understand how different types of regression approaches behave on the same structured tabular dataset.
The models are grouped into the following broad families:
| Model family | Purpose in the comparison |
|---|---|
| Baseline models | Provide a simple reference point for evaluating whether more complex models add meaningful value. |
| Linear models | Test how well simple linear relationships explain the target variable. |
| Regularized linear models | Evaluate whether L1, L2, or combined regularization improves generalisation. |
| Robust linear models | Test models that are less sensitive to outliers or unusual observations. |
| Online / stochastic linear models | Evaluate faster linear approaches that can be useful for larger datasets. |
| Distance-based models | Test whether local similarity between observations is useful for price prediction. |
| Support vector models | Evaluate margin-based and kernel-based approaches for regression. |
| Tree-based models | Test non-linear models that can capture feature interactions without requiring linear assumptions. |
| Ensemble models | Evaluate whether combining multiple estimators improves predictive performance and stability. |
| Boosting models | Test sequential ensemble methods that often perform strongly on structured tabular data. |
| Neural network models | Evaluate whether a feed-forward neural network can learn useful non-linear relationships in the data. |
| Meta-estimators | Test whether combining several strong models through Voting or Stacking can improve final performance. |
This broad comparison helps show the strengths and weaknesses of different modelling approaches, including predictive accuracy, training cost, prediction speed, interpretability, and sensitivity to hyperparameter tuning.
In this project, special attention is given to whether more complex models such as boosting, voting, and stacking provide a meaningful improvement over simpler and more interpretable models.
Reproducibility Note¶
This notebook is designed to support both full execution and faster reloading of previously saved results.
When QUICK_LOAD = False, the notebook trains, tunes, evaluates, and saves the regression models from scratch. This provides the most accurate runtime information, but the full execution can take a significant amount of time depending on the computer hardware.
When QUICK_LOAD = True, the notebook attempts to reload previously saved fitted models, tuned search objects, predictions, and result files from the _exports folder. This makes it possible to review the project and regenerate many of the comparison outputs much faster, without retraining every model.
The saved notebook outputs represent results from completed runs. Before sharing the final version of this project, the notebook will be re-executed from the beginning with QUICK_LOAD = False in order to produce clean final outputs and accurate full runtime information.
To fully reproduce the fast-loading version of the notebook, the accompanying _exports folder should be provided together with the notebook. Without this folder, the notebook should either be run from scratch with QUICK_LOAD = False, or reviewed using the saved outputs already visible in the notebook.
from datetime import timedelta
import time
notebook_start_time = time.time()
import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning)
# Python ≥3.8 is required
import time
import sys
assert sys.version_info >= (3, 8)
# Scikit-Learn ≥ 1.8.0 is required
import sklearn
from packaging.version import parse
assert parse(sklearn.__version__) >= parse("1.8.0")
# Common imports
import numpy as np
import pandas as pd
# To plot pretty figures
import seaborn as sns
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rc('axes', labelsize=14)
mpl.rc('xtick', labelsize=12)
mpl.rc('ytick', labelsize=12)
# Precision options
np.set_printoptions(precision=2)
pd.options.display.float_format = '{:.2f}'.format
Portfolio export paths¶
from pathlib import Path
# ============================================================
# Portfolio export paths
# ============================================================
EXPORT_DIR = Path("_exports") / "regression_models"
REGRESSION_IMAGES_DIR = EXPORT_DIR / "images"
REGRESSION_RESULTS_DIR = EXPORT_DIR / "results"
REGRESSION_IMAGES_DIR.mkdir(parents=True, exist_ok=True)
REGRESSION_RESULTS_DIR.mkdir(parents=True, exist_ok=True)
FIG_DPI = 300
print("Regression export folder:", EXPORT_DIR.resolve())
print("Regression images:", REGRESSION_IMAGES_DIR.resolve())
print("Regression results:", REGRESSION_RESULTS_DIR.resolve())
Regression export folder: D:\WorkShop\Python\GitHub\Notes - ML\_exports\regression_models Regression images: D:\WorkShop\Python\GitHub\Notes - ML\_exports\regression_models\images Regression results: D:\WorkShop\Python\GitHub\Notes - ML\_exports\regression_models\results
Switches
These switches control whether the notebook loads saved files or trains the models again.
QUICK_LOAD = True
Load saved models, search objects, and predictions instead of fitting again.QUICK_LOAD = False
Fit the models again from scratch.SAVE_MODEL_ARTIFACTS = True
Save newly fitted models, search objects, and predictions. This only matters whenQUICK_LOAD = False.SAVE_PORTFOLIO_OUTPUTS = True
Save portfolio outputs such as images and CSV result tables into the export folder. This can be used independently from model training.
| Mode | QUICK_LOAD | SAVE_MODEL_ARTIFACTS |
|---|---|---|
| Fast run from saved files | True |
any |
| Train and save new files | False |
True |
| Train without saving | False |
False |
Important:¶
SAVE_MODEL_ARTIFACTS only has an effect when QUICK_LOAD = False.
When QUICK_LOAD = True, the notebook loads existing files and does not save new fitted models.
SAVE_PORTFOLIO_OUTPUTS controls the export of portfolio images and result CSV files. It can be used with either QUICK_LOAD = True or QUICK_LOAD = False.
QUICK_LOAD = False # True --> Load saved files instead of fitting the models again
SAVE_MODEL_ARTIFACTS = True # Only saves files when QUICK_LOAD = False
SAVE_PORTFOLIO_OUTPUTS = True # Save GitHub portfolio images and result CSV files
Shared configuration for model evaluation and tuning¶
from sklearn.model_selection import KFold # KFold cross-validator for splitting data into k folds during cross-validation
RANDOM_STATE = 42
SCORING = "neg_root_mean_squared_error" # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
N_JOBS = -1 # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
VERBOSE = 1 # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
ERROR_SCORE = "raise" # If a fit fails during CV/search, raise the error immediately
# cv parameter! # Default: None -> Common values: 3, 5, 10 - Number of cross-validation folds
KFOLD_3 = KFold(n_splits=3, shuffle=True, random_state=RANDOM_STATE) # 33.3% validation fold
KFOLD_5 = KFold(n_splits=5, shuffle=True, random_state=RANDOM_STATE) # 20% validation fold
KFOLD_10 = KFold(n_splits=10, shuffle=True, random_state=RANDOM_STATE) # 10% validation fold
Load the Dataset¶
full_diamonds = sns.load_dataset("diamonds")
full_diamonds.shape
(53940, 10)
Dataset Size Selection¶
The dataset size can be adjusted during development and testing to reduce runtime.
For the final experiment, the full diamonds dataset is used.
# Set the number of rows used in the project
# Use a small value for testing and the full dataset size for the final run
DATASET_SIZE = 53940
diamonds = (
full_diamonds
.sample(n=DATASET_SIZE, random_state=RANDOM_STATE)
.reset_index(drop=True)
)
diamonds.shape
(53940, 10)
Check the Dataset¶
Description of the features¶
- carat: weight of the diamond.
- cut: quality of the cut. Categories: Fair, Good, Very Good, Premium, Ideal.
- color: diamond colour grade, from D (best) to J (worst).
- clarity: measure of how clear the diamond is. Categories range from I1 (worst) to IF (best).
- depth: total depth percentage, calculated as
2 * z / (x + y). - table: width of the top of the diamond relative to its widest point.
- price: price in US dollars. This is the target variable for regression.
- x: length of the diamond in mm.
- y: width of the diamond in mm.
- z: depth of the diamond in mm.
diamonds.sample(5).sort_index()
| carat | cut | color | clarity | depth | table | price | x | y | z | |
|---|---|---|---|---|---|---|---|---|---|---|
| 25712 | 0.32 | Ideal | D | SI1 | 60.80 | 57.00 | 756 | 4.44 | 4.41 | 2.69 |
| 31814 | 1.50 | Premium | H | I1 | 61.10 | 59.00 | 3599 | 7.37 | 7.26 | 4.47 |
| 38839 | 1.23 | Ideal | H | VS1 | 62.10 | 54.00 | 7946 | 6.87 | 6.89 | 4.27 |
| 43210 | 0.70 | Very Good | D | SI1 | 61.10 | 58.00 | 2768 | 5.66 | 5.73 | 3.48 |
| 46666 | 0.34 | Ideal | G | IF | 62.10 | 54.00 | 974 | 4.48 | 4.50 | 2.79 |
diamonds.describe()
| carat | depth | table | price | x | y | z | |
|---|---|---|---|---|---|---|---|
| count | 53940.00 | 53940.00 | 53940.00 | 53940.00 | 53940.00 | 53940.00 | 53940.00 |
| mean | 0.80 | 61.75 | 57.46 | 3932.80 | 5.73 | 5.73 | 3.54 |
| std | 0.47 | 1.43 | 2.23 | 3989.44 | 1.12 | 1.14 | 0.71 |
| min | 0.20 | 43.00 | 43.00 | 326.00 | 0.00 | 0.00 | 0.00 |
| 25% | 0.40 | 61.00 | 56.00 | 950.00 | 4.71 | 4.72 | 2.91 |
| 50% | 0.70 | 61.80 | 57.00 | 2401.00 | 5.70 | 5.71 | 3.53 |
| 75% | 1.04 | 62.50 | 59.00 | 5324.25 | 6.54 | 6.54 | 4.04 |
| max | 5.01 | 79.00 | 95.00 | 18823.00 | 10.74 | 58.90 | 31.80 |
diamonds.info()
<class 'pandas.DataFrame'> RangeIndex: 53940 entries, 0 to 53939 Data columns (total 10 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 carat 53940 non-null float64 1 cut 53940 non-null category 2 color 53940 non-null category 3 clarity 53940 non-null category 4 depth 53940 non-null float64 5 table 53940 non-null float64 6 price 53940 non-null int64 7 x 53940 non-null float64 8 y 53940 non-null float64 9 z 53940 non-null float64 dtypes: category(3), float64(6), int64(1) memory usage: 3.0 MB
diamonds.isnull().sum()
carat 0 cut 0 color 0 clarity 0 depth 0 table 0 price 0 x 0 y 0 z 0 dtype: int64
Choose the target column¶
target_column = "price"
Plot the target¶
# Number of bins
price_bins = 200
# Maximum price
price_max = 19000
# Label of each bin
# Size of each bin on StratifiedShuffleSplit
price_cat_size = 500
# Create fixed price categories on the full dataset first
full_diamonds_with_bins = full_diamonds.copy()
full_diamonds_with_bins["target_cat"] = pd.cut(
full_diamonds_with_bins[target_column],
bins=np.arange(0, price_max + 1, price_cat_size),
labels=np.arange(price_cat_size, price_max + 1, price_cat_size),
include_lowest=True
)
# If DATASET_SIZE is smaller than the full dataset, create a test sample
# that keeps at least 2 rows from every available price category
MIN_ROWS_PER_BIN = 2
TEST_SIZE = 0.2
if DATASET_SIZE < len(full_diamonds_with_bins):
target_cat_counts = full_diamonds_with_bins["target_cat"].value_counts().sort_index()
valid_target_cats = target_cat_counts[target_cat_counts >= MIN_ROWS_PER_BIN].index
minimum_required_rows = max(
len(valid_target_cats) * MIN_ROWS_PER_BIN,
int(np.ceil(len(valid_target_cats) / TEST_SIZE))
)
if DATASET_SIZE < minimum_required_rows:
raise ValueError(
f"DATASET_SIZE is too small for stratified sampling with fixed price bins. "
f"Minimum required rows: {minimum_required_rows}, "
f"current DATASET_SIZE: {DATASET_SIZE}."
)
base_sample = (
full_diamonds_with_bins[
full_diamonds_with_bins["target_cat"].isin(valid_target_cats)
]
.groupby("target_cat", observed=True, group_keys=False)
.sample(n=MIN_ROWS_PER_BIN, random_state=RANDOM_STATE)
)
remaining_rows_needed = DATASET_SIZE - len(base_sample)
remaining_pool = full_diamonds_with_bins.drop(index=base_sample.index)
extra_sample = remaining_pool.sample(
n=remaining_rows_needed,
random_state=RANDOM_STATE
)
diamonds = (
pd.concat([base_sample, extra_sample], axis=0)
.sample(frac=1, random_state=RANDOM_STATE)
.reset_index(drop=True)
)
else:
diamonds = (
full_diamonds_with_bins
.sample(frac=1, random_state=RANDOM_STATE)
.reset_index(drop=True)
)
# Plot "price"
diamonds[target_column].hist(bins=price_bins, figsize=(12, 4))
plt.xlim(0, price_max)
plt.xticks(np.arange(0, price_max + 1, price_cat_size), rotation=90)
plt.xlabel(target_column)
plt.ylabel("Count per bin")
plt.grid(True, linestyle="--", alpha=0.5)
plt.show()
Train and Test Set¶
Prepare for StratifiedShuffleSplit making categories¶
# Check target categories
diamonds["target_cat"].value_counts().sort_index()
target_cat 500 1749 1000 12775 1500 5486 2000 4197 2500 3335 3000 2794 3500 2164 4000 2061 4500 2474 5000 2191 5500 1759 6000 1404 6500 1223 7000 1055 7500 891 8000 777 8500 618 9000 689 9500 564 10000 512 10500 488 11000 446 11500 453 12000 372 12500 349 13000 352 13500 303 14000 300 14500 276 15000 228 15500 254 16000 259 16500 212 17000 213 17500 224 18000 181 18500 210 19000 102 Name: count, dtype: int64
diamonds["target_cat"].value_counts().sort_index().plot.bar(
figsize=(12, 4),
width=0.95
)
plt.xlabel("Price category")
plt.ylabel("Count per bin")
plt.grid(True, linestyle="--", alpha=0.5)
plt.show()
Create a Train and Test Set¶
from sklearn.model_selection import StratifiedShuffleSplit
splitter = StratifiedShuffleSplit(
n_splits=1,
test_size=TEST_SIZE,
random_state=RANDOM_STATE
)
for train_index, test_index in splitter.split(diamonds, diamonds["target_cat"]):
train_set = diamonds.loc[train_index].reset_index(drop=True)
test_set = diamonds.loc[test_index].reset_index(drop=True)
Check the details of train_set and test_set¶
train_set.shape, test_set.shape
((43152, 11), (10788, 11))
# Compare the ratio of the full dataset, the train set and the test set
comparison_df = pd.DataFrame({
"full_ratio": diamonds["target_cat"].value_counts().sort_index() / len(diamonds),
"train_ratio": train_set["target_cat"].value_counts().sort_index() / len(train_set),
"test_ratio": test_set["target_cat"].value_counts().sort_index() / len(test_set),
})
comparison_df.head(10)
| full_ratio | train_ratio | test_ratio | |
|---|---|---|---|
| target_cat | |||
| 500 | 0.03 | 0.03 | 0.03 |
| 1000 | 0.24 | 0.24 | 0.24 |
| 1500 | 0.10 | 0.10 | 0.10 |
| 2000 | 0.08 | 0.08 | 0.08 |
| 2500 | 0.06 | 0.06 | 0.06 |
| 3000 | 0.05 | 0.05 | 0.05 |
| 3500 | 0.04 | 0.04 | 0.04 |
| 4000 | 0.04 | 0.04 | 0.04 |
| 4500 | 0.05 | 0.05 | 0.05 |
| 5000 | 0.04 | 0.04 | 0.04 |
Feature Engineering¶
# Define the function for feature engineering
def engineer_features(data: pd.DataFrame) -> pd.DataFrame:
# let's make a copy of the original dataset
engineered_data = data.copy()
# drop the previously created but unnecessary columns
engineered_data = engineered_data.drop(columns="target_cat")
# drop target_column as it is the label
engineered_data = engineered_data.drop(columns=target_column)
# # categorical features
# categorical_features = ["cut", "color", "clarity"]
# engineered_data[categorical_features] = engineered_data[categorical_features].astype('category')
return engineered_data
Create X_train, X_test, y_train, y_test¶
# Create X_train, X_test, y_train, y_test
X_train = engineer_features(train_set)
X_test = engineer_features(test_set)
y_train = train_set[target_column].copy()
y_test = test_set[target_column].copy()
train_set.head()
| carat | cut | color | clarity | depth | table | price | x | y | z | target_cat | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.52 | Ideal | G | VVS2 | 61.60 | 54.00 | 1958 | 5.17 | 5.22 | 3.20 | 2000 |
| 1 | 0.75 | Ideal | E | SI1 | 62.20 | 57.00 | 2985 | 5.82 | 5.86 | 3.63 | 3000 |
| 2 | 0.51 | Very Good | D | VS2 | 63.20 | 56.00 | 1722 | 5.04 | 5.08 | 3.20 | 2000 |
| 3 | 0.39 | Premium | F | SI1 | 61.30 | 62.00 | 860 | 4.71 | 4.68 | 2.88 | 1000 |
| 4 | 0.30 | Good | H | VS1 | 63.40 | 53.00 | 526 | 4.25 | 4.30 | 2.71 | 1000 |
X_train.head()
| carat | cut | color | clarity | depth | table | x | y | z | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.52 | Ideal | G | VVS2 | 61.60 | 54.00 | 5.17 | 5.22 | 3.20 |
| 1 | 0.75 | Ideal | E | SI1 | 62.20 | 57.00 | 5.82 | 5.86 | 3.63 |
| 2 | 0.51 | Very Good | D | VS2 | 63.20 | 56.00 | 5.04 | 5.08 | 3.20 |
| 3 | 0.39 | Premium | F | SI1 | 61.30 | 62.00 | 4.71 | 4.68 | 2.88 |
| 4 | 0.30 | Good | H | VS1 | 63.40 | 53.00 | 4.25 | 4.30 | 2.71 |
X_train.info()
<class 'pandas.DataFrame'> RangeIndex: 43152 entries, 0 to 43151 Data columns (total 9 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 carat 43152 non-null float64 1 cut 43152 non-null category 2 color 43152 non-null category 3 clarity 43152 non-null category 4 depth 43152 non-null float64 5 table 43152 non-null float64 6 x 43152 non-null float64 7 y 43152 non-null float64 8 z 43152 non-null float64 dtypes: category(3), float64(6) memory usage: 2.1 MB
Check missing values¶
## Look for rows with incomplete values
incomplete_rows = X_train[X_train.isna().any(axis=1)]
incomplete_rows
| carat | cut | color | clarity | depth | table | x | y | z |
|---|
X_train.isnull().sum()
carat 0 cut 0 color 0 clarity 0 depth 0 table 0 x 0 y 0 z 0 dtype: int64
Check numerical and categorical columns¶
# Separate numerical and categorical columns
num_attribs = X_train.select_dtypes(include=["int64", "float64"]).columns.tolist()
cat_attribs = X_train.select_dtypes(include=["object", "category"]).columns.tolist()
print(f"numerical columns:\n{num_attribs}\n")
print(f"categorical columns:\n{cat_attribs}")
numerical columns: ['carat', 'depth', 'table', 'x', 'y', 'z'] categorical columns: ['cut', 'color', 'clarity']
Preprocessing¶
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
Select test set rows for prediction comparison¶
# Use all test set rows for prediction comparison
sample_idx = X_test.index
Preprocessing for scaled models¶
scaled_preprocessing = ColumnTransformer([
("num", StandardScaler(), num_attribs),
("cat", OneHotEncoder(handle_unknown="ignore"), cat_attribs)
])
Preprocessing for tree-based models¶
tree_preprocessing = ColumnTransformer([
("num", "passthrough", num_attribs),
("cat", OneHotEncoder(handle_unknown="ignore"), cat_attribs)
])
Create evaluate_regression function¶
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
import numpy as np
# Define a function to evaluate a regression model
def evaluate_regression(
y_true,
y_pred,
model_name="Model",
y_train_true=None,
y_train_pred=None
):
mae = mean_absolute_error(y_true, y_pred)
mse = mean_squared_error(y_true, y_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_true, y_pred)
print(f"--- {model_name} ---")
print("TEST")
print(f"MAE: {mae:.2f}")
print(f"RMSE: {rmse:.2f}")
print(f"R²: {r2:.4f}")
results = {
"Model": model_name,
"MAE": mae,
"RMSE": rmse,
"R2": r2
}
# Optional train vs test fit check
if y_train_true is not None and y_train_pred is not None:
train_mae = mean_absolute_error(y_train_true, y_train_pred)
train_mse = mean_squared_error(y_train_true, y_train_pred)
train_rmse = np.sqrt(train_mse)
train_r2 = r2_score(y_train_true, y_train_pred)
rmse_gap = rmse - train_rmse
r2_gap = train_r2 - r2
print()
print("TRAIN")
print(f"MAE: {train_mae:.2f}")
print(f"RMSE: {train_rmse:.2f}")
print(f"R²: {train_r2:.4f}")
print()
print("GAP")
print(f"RMSE gap: {rmse_gap:.2f}")
print(f"R² gap: {r2_gap:.4f}")
results.update({
"Train_MAE": train_mae,
"Train_RMSE": train_rmse,
"Train_R2": train_r2,
"RMSE_gap": rmse_gap,
"R2_gap": r2_gap
})
return results
Load the saved files¶
Load fitted Base Models¶
if QUICK_LOAD:
import os
import joblib
import pandas as pd
models_dir = os.path.join("_exports", "regression_models", "fitted_base_models")
if not os.path.exists(models_dir):
raise FileNotFoundError(f"Model directory not found: {models_dir}")
loaded_fitted_base_models = {}
for filename in sorted(os.listdir(models_dir)):
if filename.endswith(".pkl"):
model_key = filename.replace("fitted_base_model_", "").replace(".pkl", "")
full_path = os.path.join(models_dir, filename)
loaded_fitted_base_models[model_key] = joblib.load(full_path)
if not loaded_fitted_base_models:
raise FileNotFoundError(
f"No fitted base model .pkl files were found in: {models_dir}"
)
loaded_fitted_base_models_df = pd.DataFrame({
"Model_Key": list(loaded_fitted_base_models.keys())
}).reset_index(drop=True)
Load Search Objects¶
if QUICK_LOAD:
import os
import joblib
import pandas as pd
searches_dir = os.path.join("_exports", "regression_models", "search_objects")
if not os.path.exists(searches_dir):
raise FileNotFoundError(f"Search directory not found: {searches_dir}")
loaded_searches = {}
for filename in sorted(os.listdir(searches_dir)):
if filename.endswith(".pkl"):
search_key = filename.replace("regression_search_", "").replace(".pkl", "")
full_path = os.path.join(searches_dir, filename)
loaded_searches[search_key] = joblib.load(full_path)
if not loaded_searches:
raise FileNotFoundError(
f"No search object .pkl files were found in: {searches_dir}"
)
loaded_searches_df = pd.DataFrame({
"Search_Key": list(loaded_searches.keys())
}).reset_index(drop=True)
Load Predictions¶
if QUICK_LOAD:
import os
import joblib
import pandas as pd
predictions_dir = os.path.join("_exports", "regression_models", "predictions")
if not os.path.exists(predictions_dir):
raise FileNotFoundError(f"Prediction directory not found: {predictions_dir}")
loaded_predictions = {}
for filename in sorted(os.listdir(predictions_dir)):
if filename.endswith(".pkl"):
prediction_key = filename.replace(".pkl", "") # remove extension only
full_path = os.path.join(predictions_dir, filename)
loaded_predictions[prediction_key] = joblib.load(full_path)
if not loaded_predictions:
raise FileNotFoundError(
f"No prediction .pkl files were found in: {predictions_dir}"
)
loaded_predictions_df = pd.DataFrame({
"Prediction_Key": list(loaded_predictions.keys()),
"Object_Type": [type(obj).__name__ for obj in loaded_predictions.values()],
"Length": [
len(obj) if hasattr(obj, "__len__") else None
for obj in loaded_predictions.values()
]
}).reset_index(drop=True)
display(loaded_predictions_df)
Baseline models¶
Dummy Regressor¶
The DummyRegressor is a simple baseline model that makes predictions without learning real relationships from the data.
start_fit = time.perf_counter()
from sklearn.dummy import DummyRegressor
dummy_param = {
# Most important parameters: strategy
"strategy": "mean" # Default: "mean" -> Common alternatives: "median", "quantile", "constant"
# "constant": 1000 # Default: None -> Only used if strategy="constant"
}
dummy_reg = Pipeline([
("preprocessing", tree_preprocessing),
("model", DummyRegressor(**dummy_param))
])
dummy_reg.fit(X_train, y_train)
Pipeline(steps=[('preprocessing',
ColumnTransformer(transformers=[('num', 'passthrough',
['carat', 'depth', 'table',
'x', 'y', 'z']),
('cat',
OneHotEncoder(handle_unknown='ignore'),
['cut', 'color',
'clarity'])])),
('model', DummyRegressor())])In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Parameters
Parameters
['carat', 'depth', 'table', 'x', 'y', 'z']
passthrough
['cut', 'color', 'clarity']
Parameters
Parameters
dummy_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(dummy_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:00
Features created by Dummy Regressor Model¶
# Number of input features used by the model
print("Number of input features:", dummy_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", dummy_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Dummy Regressor: predicting model...")
dummy_pred_train = dummy_reg.predict(X_train)
dummy_pred_test = dummy_reg.predict(X_test)
print("Dummy Regressor: predicted successfully.\n")
else:
print("Dummy Regressor: loading predicted model...")
dummy_pred_train = loaded_predictions["dummy_pred_train_file"]
dummy_pred_test = loaded_predictions["dummy_pred_test_file"]
print("Dummy Regressor: loaded successfully.\n")
dummy_results = evaluate_regression(
y_test,
dummy_pred_test,
model_name="Dummy Regressor",
y_train_true=y_train,
y_train_pred=dummy_pred_train
)
Dummy Regressor: predicting model... Dummy Regressor: predicted successfully. --- Dummy Regressor --- TEST MAE: 3031.98 RMSE: 3989.65 R²: -0.0000 TRAIN MAE: 3031.54 RMSE: 3989.34 R²: 0.0000 GAP RMSE gap: 0.31 R² gap: 0.0000
dummy_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(dummy_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Test set prediction comparison¶
dummy_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(dummy_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
dummy_sample_pred = pd.Series(dummy_pred_test, index=X_test.index).loc[sample_idx]
dummy_results_comparison["Error"] = dummy_results_comparison["Prediction"] - dummy_results_comparison["Original Label"]
dummy_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 |
| Error | 3361.89 | 3168.89 | 3105.89 | 2784.89 | 2576.89 | 2532.89 | 1263.89 | 1110.89 | -10.11 | -222.11 | -471.11 | -3737.11 | -4895.11 | -7928.11 | -12737.11 |
Add formatted timing and model type to the results¶
dummy_results["Model_type"] = "Baseline"
dummy_results["Fit_time"] = str(timedelta(seconds=int(dummy_fit_time)))
dummy_results["Pred_time"] = str(timedelta(seconds=int(dummy_predict_time)))
dummy_results["Tun_fit_time"] = np.nan
dummy_results["Tun_pred_time"] = np.nan
dummy_results["Full_model_time"] = str(timedelta(seconds=int(dummy_fit_time + dummy_predict_time)))
dummy_results["Tun_MAE"] = dummy_results["MAE"]
dummy_results["Tun_RMSE"] = dummy_results["RMSE"]
dummy_results["Tun_R2"] = dummy_results["R2"]
Linear / regularized models¶
Linear Regressor¶
The model tries to find which line best fits the data.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.linear_model import LinearRegression
lin_reg = Pipeline([
("preprocessing", scaled_preprocessing),
("model", LinearRegression())
])
print("Linear Regressor: fitting model...")
lin_reg.fit(X_train, y_train)
print("Linear Regressor: fitted successfully.")
else:
print("Linear Regressor: loading fitted model...")
lin_reg = loaded_fitted_base_models["linear"]
print("Linear Regressor: loaded successfully.")
Linear Regressor: fitting model... Linear Regressor: fitted successfully.
lin_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(lin_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:00
Features created by Linear Regressor Model¶
# Intercept of the linear regression model
print("Starting value of the model:", lin_reg.named_steps["model"].intercept_)
Starting value of the model: 3388.31030226263
# Coefficients learned for each transformed feature
print("Weights for each feature:\n", lin_reg.named_steps["model"].coef_)
Weights for each feature: [ 5.44e+03 -9.87e+01 -6.01e+01 -1.25e+03 -2.88e+00 -9.33e+00 -5.74e+02 -6.24e+00 2.60e+02 1.83e+02 1.37e+02 8.21e+02 6.20e+02 5.61e+02 3.50e+02 -1.55e+02 -6.42e+02 -1.56e+03 -3.67e+03 1.50e+03 -1.64e+02 -1.13e+03 7.49e+02 4.42e+02 1.16e+03 1.12e+03]
# Number of input features used by the model
print("Number of input features:", lin_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Rank of the training feature matrix
print("Rank of the training data:", lin_reg.named_steps["model"].rank_)
Rank of the training data: 23
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", lin_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
# Singular values of the training feature matrix
print("Singular values of the training data:\n", lin_reg.named_steps["model"].singular_)
Singular values of the training data: [4.14e+02 2.40e+02 1.82e+02 1.02e+02 9.94e+01 9.48e+01 9.41e+01 9.03e+01 8.76e+01 8.26e+01 8.10e+01 7.57e+01 6.72e+01 6.53e+01 6.34e+01 5.55e+01 4.93e+01 4.43e+01 4.29e+01 3.94e+01 3.71e+01 2.46e+01 2.40e+01 2.70e-13 7.60e-14 7.08e-14]
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Linear Regressor: predicting model...")
lin_pred_train = lin_reg.predict(X_train)
lin_pred_test = lin_reg.predict(X_test)
print("Linear Regressor: predicted successfully.\n")
else:
print("Linear Regressor: loading predicted model...")
lin_pred_train = loaded_predictions["lin_pred_train_file"]
lin_pred_test = loaded_predictions["lin_pred_test_file"]
print("Linear Regressor: loaded successfully.\n")
lin_results = evaluate_regression(
y_test,
lin_pred_test,
model_name="Linear Regressor",
y_train_true=y_train,
y_train_pred=lin_pred_train
)
Linear Regressor: predicting model... Linear Regressor: predicted successfully. --- Linear Regressor --- TEST MAE: 751.86 RMSE: 1191.28 R²: 0.9108 TRAIN MAE: 732.64 RMSE: 1114.62 R²: 0.9219 GAP RMSE gap: 76.66 R² gap: 0.0111
lin_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(lin_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Test set prediction comparison¶
lin_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(lin_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
lin_sample_pred = pd.Series(lin_pred_test, index=X_test.index).loc[sample_idx]
lin_results_comparison["Error"] = lin_results_comparison["Prediction"] - lin_results_comparison["Original Label"]
lin_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 182.28 | 1054.06 | 914.88 | 1617.37 | 1995.78 | 1957.88 | 2817.11 | 3445.18 | 3935.01 | 4568.26 | 4757.71 | 6874.30 | 12451.33 | 10144.17 | 11194.20 |
| Error | -388.72 | 290.06 | 87.88 | 469.37 | 639.78 | 557.88 | 148.11 | 623.18 | -7.99 | 413.26 | 353.71 | -795.70 | 3623.33 | -1716.83 | -5475.80 |
Add formatted timing and model type to the results¶
lin_results["Model_type"] = "Linear / regularized"
lin_results["Fit_time"] = str(timedelta(seconds=int(lin_fit_time)))
lin_results["Pred_time"] = str(timedelta(seconds=int(lin_predict_time)))
lin_results["Tun_fit_time"] = np.nan
lin_results["Tun_pred_time"] = np.nan
lin_results["Full_model_time"] = str(timedelta(seconds=int(lin_fit_time + lin_predict_time)))
lin_results["Tun_MAE"] = lin_results["MAE"]
lin_results["Tun_RMSE"] = lin_results["RMSE"]
lin_results["Tun_R2"] = lin_results["R2"]
Ridge Regressor¶
Ridge Regressor is a regularized version of Linear Regressor that helps reduce overfitting.
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.linear_model import Ridge
ridge_param = {
"alpha": 10.0 # Default: 1.0 -> From 0.1 to 100.0
}
ridge_reg = Pipeline([
("preprocessing", scaled_preprocessing),
("model", Ridge(**ridge_param))
])
print("Ridge Regressor: fitting model...")
ridge_reg.fit(X_train, y_train)
print("Ridge Regressor: fitted successfully.")
else:
print("Ridge Regressor: loading fitted model...")
ridge_reg = loaded_fitted_base_models["ridge"]
print("Ridge Regressor: loaded successfully.")
Ridge Regressor: fitting model... Ridge Regressor: fitted successfully.
ridge_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(ridge_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:00
Features created by Ridge Regressor Model¶
# Intercept of the ridge regression model
print("Starting value of the model:", ridge_reg.named_steps["model"].intercept_)
Starting value of the model: 3395.203718180953
# Coefficients learned for each transformed feature
print("Weights for each feature:\n", ridge_reg.named_steps["model"].coef_)
Weights for each feature: [ 5400.46 -97.26 -60.32 -1206.62 -5.98 -13.39 -576.95 -6.12 261.41 183.51 138.15 817.3 616.43 558.4 348.25 -154.93 -639.68 -1545.76 -3607.88 1484.39 -170.35 -1136.77 740.42 435.08 1148.81 1106.29]
# Number of input features used by the model
print("Number of input features:", ridge_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", ridge_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Ridge Regressor: predicting model...")
ridge_pred_train = ridge_reg.predict(X_train)
ridge_pred_test = ridge_reg.predict(X_test)
print("Ridge Regressor: predicted successfully.\n")
else:
print("Ridge Regressor: loading predicted model...")
ridge_pred_train = loaded_predictions["ridge_pred_train_file"]
ridge_pred_test = loaded_predictions["ridge_pred_test_file"]
print("Ridge Regressor: loaded successfully.\n")
ridge_results = evaluate_regression(
y_test,
ridge_pred_test,
model_name="Ridge Regressor",
y_train_true=y_train,
y_train_pred=ridge_pred_train
)
Ridge Regressor: predicting model... Ridge Regressor: predicted successfully. --- Ridge Regressor --- TEST MAE: 751.78 RMSE: 1191.18 R²: 0.9109 TRAIN MAE: 732.59 RMSE: 1114.68 R²: 0.9219 GAP RMSE gap: 76.51 R² gap: 0.0111
ridge_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(ridge_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import GridSearchCV
ridge_param_grid = {
"model__alpha": [1.0, 2.0, 5.0, 10.0, 15.0, 20.0, 30.0, 40.0, 50.0]
}
ridge_search = GridSearchCV(
ridge_reg, # The pipeline model to tune
param_grid=ridge_param_grid, # Default: required -> Dictionary with parameter values to test - Parameter values to test
scoring=SCORING, # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
cv=KFOLD_10, # Default: None -> Common values: 3, 5, 10 - Number of cross-validation folds
n_jobs=N_JOBS, # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
verbose=VERBOSE, # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
error_score=ERROR_SCORE # Default: np.nan -> Common values: np.nan, "raise" - If a fit fails during CV/search, raise the error immediately instead of silently assigning NaN to that candidate
)
print("Ridge Regressor: running grid search...")
ridge_search.fit(X_train, y_train)
print("Ridge Regressor: grid search completed successfully.")
else:
print("Ridge Regressor: loading search object...")
ridge_search = loaded_searches["ridge"]
print("Ridge Regressor: search object loaded successfully.")
Ridge Regressor: running grid search... Fitting 10 folds for each of 9 candidates, totalling 90 fits Ridge Regressor: grid search completed successfully.
ridge_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(ridge_tuning_fit_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:13
# Best parameter
ridge_search.best_params_
{'model__alpha': 50.0}
# Best model
ridge_best_tuning = ridge_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Ridge Regressor: predicting best model...")
ridge_best_pred_train = ridge_best_tuning.predict(X_train)
ridge_best_pred_test = ridge_best_tuning.predict(X_test)
print("Ridge Regressor: best model predicted successfully.\n")
else:
print("Ridge Regressor: loading best predicted model...")
ridge_best_pred_train = loaded_predictions["ridge_best_pred_train_file"]
ridge_best_pred_test = loaded_predictions["ridge_best_pred_test_file"]
print("Ridge Regressor: best model loaded successfully.\n")
ridge_best_results = evaluate_regression(
y_test,
ridge_best_pred_test,
"Best Ridge Regressor",
y_train_true=y_train,
y_train_pred=ridge_best_pred_train
)
Ridge Regressor: predicting best model... Ridge Regressor: best model predicted successfully. --- Best Ridge Regressor --- TEST MAE: 751.98 RMSE: 1191.68 R²: 0.9108 TRAIN MAE: 732.97 RMSE: 1115.87 R²: 0.9218 GAP RMSE gap: 75.81 R² gap: 0.0110
ridge_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(ridge_tuning_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Test set prediction comparison¶
ridge_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(ridge_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
ridge_sample_pred = pd.Series(ridge_best_pred_test, index=X_test.index).loc[sample_idx]
ridge_results_comparison["Error"] = ridge_results_comparison["Prediction"] - ridge_results_comparison["Original Label"]
ridge_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 181.06 | 1020.52 | 915.06 | 1556.39 | 1984.41 | 1925.29 | 2892.94 | 3482.85 | 3962.33 | 4587.15 | 4772.58 | 6889.61 | 12414.98 | 10124.59 | 11140.04 |
| Error | -389.94 | 256.52 | 88.06 | 408.39 | 628.41 | 525.29 | 223.94 | 660.85 | 19.33 | 432.15 | 368.58 | -780.39 | 3586.98 | -1736.41 | -5529.96 |
Add formatted timing and model type to the results¶
ridge_results["Model_type"] = "Linear / regularized"
ridge_results["Fit_time"] = str(timedelta(seconds=int(ridge_fit_time)))
ridge_results["Pred_time"] = str(timedelta(seconds=int(ridge_predict_time)))
ridge_results["Tun_fit_time"] = str(timedelta(seconds=int(ridge_tuning_fit_time)))
ridge_results["Tun_pred_time"] = str(timedelta(seconds=int(ridge_tuning_predict_time)))
ridge_results["Full_model_time"] = str(timedelta(seconds=int(ridge_fit_time + ridge_predict_time + ridge_tuning_fit_time + ridge_tuning_predict_time)))
ridge_results["Tun_MAE"] = ridge_best_results["MAE"]
ridge_results["Tun_RMSE"] = ridge_best_results["RMSE"]
ridge_results["Tun_R2"] = ridge_best_results["R2"]
ridge_results["Tun_Train_MAE"] = ridge_best_results["Train_MAE"]
ridge_results["Tun_Train_RMSE"] = ridge_best_results["Train_RMSE"]
ridge_results["Tun_Train_R2"] = ridge_best_results["Train_R2"]
Lasso Regressor¶
The Lasso Regressor is a regularized version of the Linear Regressor, which can reduce the weight of some features to zero.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.linear_model import Lasso
lasso_param = {
# Most important parameters: alpha
"alpha": 0.1, # Default: 1.0 -> From 0.01 to 10.0 - smaller alpha → weaker regularization
"max_iter": 10000 # Default: 1000 -> Increase if convergence warning appears
}
lasso_reg = Pipeline([
("preprocessing", scaled_preprocessing),
("model", Lasso(**lasso_param))
])
print("Lasso Regressor: fitting model...")
lasso_reg.fit(X_train, y_train)
print("Lasso Regressor: fitted successfully.")
else:
print("Lasso Regressor: loading fitted model...")
lasso_reg = loaded_fitted_base_models["lasso"]
print("Lasso Regressor: loaded successfully.")
Lasso Regressor: fitting model... Lasso Regressor: fitted successfully.
lasso_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(lasso_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:08
Features created by Lasso Regressor Model¶
# Intercept of the lasso regression model
print("Starting value of the model:", lasso_reg.named_steps["model"].intercept_)
Starting value of the model: 4402.749708468402
# Coefficients learned for each transformed feature
print("Weights for each feature:\n", lasso_reg.named_steps["model"].coef_)
Weights for each feature: [ 5.43e+03 -9.88e+01 -6.02e+01 -1.24e+03 -2.55e+00 -9.41e+00 -7.09e+02 -1.42e+02 1.23e+02 4.60e+01 0.00e+00 4.70e+02 2.69e+02 2.11e+02 0.00e+00 -5.04e+02 -9.91e+02 -1.90e+03 -4.19e+03 9.70e+02 -6.91e+02 -1.66e+03 2.20e+02 -8.50e+01 6.29e+02 5.87e+02]
# Number of input features used by the model
print("Number of input features:", lasso_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", lasso_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Lasso Regressor: predicting model...")
lasso_pred_train = lasso_reg.predict(X_train)
lasso_pred_test = lasso_reg.predict(X_test)
print("Lasso Regressor: predicted successfully.\n")
else:
print("Lasso Regressor: loading predicted model...")
lasso_pred_train = loaded_predictions["lasso_pred_train_file"]
lasso_pred_test = loaded_predictions["lasso_pred_test_file"]
print("Lasso Regressor: loaded successfully.\n")
lasso_results = evaluate_regression(
y_test,
lasso_pred_test,
model_name="Lasso Regressor",
y_train_true=y_train,
y_train_pred=lasso_pred_train
)
Lasso Regressor: predicting model... Lasso Regressor: predicted successfully. --- Lasso Regressor --- TEST MAE: 751.67 RMSE: 1191.26 R²: 0.9108 TRAIN MAE: 732.45 RMSE: 1114.62 R²: 0.9219 GAP RMSE gap: 76.63 R² gap: 0.0111
lasso_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(lasso_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import GridSearchCV
lasso_param_grid = {
"model__alpha": [0.01, 0.1, 1.0, 10.0],
"model__max_iter": [50000]
}
lasso_search = GridSearchCV(
lasso_reg, # The pipeline model to tune
param_grid=lasso_param_grid, # Default: required -> Dictionary with parameter values to test - Parameter values to test
scoring=SCORING, # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
cv=KFOLD_10, # Default: None -> Common values: 3, 5, 10 - Number of cross-validation folds
n_jobs=N_JOBS, # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
verbose=VERBOSE, # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
error_score=ERROR_SCORE # Default: np.nan -> Common values: np.nan, "raise" - If a fit fails during CV/search, raise the error immediately instead of silently assigning NaN to that candidate
)
print("Lasso Regressor: running grid search...")
lasso_search.fit(X_train, y_train)
print("Lasso Regressor: grid search completed successfully.")
else:
print("Lasso Regressor: loading search object...")
lasso_search = loaded_searches["lasso"]
print("Lasso Regressor: search object loaded successfully.")
Lasso Regressor: running grid search... Fitting 10 folds for each of 4 candidates, totalling 40 fits Lasso Regressor: grid search completed successfully.
lasso_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(lasso_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:06:54
# Best parameter
lasso_search.best_params_
{'model__alpha': 1.0, 'model__max_iter': 50000}
# Best model
lasso_best_tuning = lasso_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Lasso Regressor: predicting best model...")
lasso_best_pred_train = lasso_best_tuning.predict(X_train)
lasso_best_pred_test = lasso_best_tuning.predict(X_test)
print("Lasso Regressor: best model predicted successfully.\n")
else:
print("Lasso Regressor: loading best predicted model...")
lasso_best_pred_train = loaded_predictions["lasso_best_pred_train_file"]
lasso_best_pred_test = loaded_predictions["lasso_best_pred_test_file"]
print("Lasso Regressor: best model loaded successfully.\n")
lasso_best_results = evaluate_regression(
y_test,
lasso_best_pred_test,
"Best Lasso Regressor",
y_train_true=y_train,
y_train_pred=lasso_best_pred_train
)
Lasso Regressor: predicting best model... Lasso Regressor: best model predicted successfully. --- Best Lasso Regressor --- TEST MAE: 750.02 RMSE: 1191.18 R²: 0.9109 TRAIN MAE: 730.87 RMSE: 1114.79 R²: 0.9219 GAP RMSE gap: 76.39 R² gap: 0.0111
lasso_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(lasso_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:00
Test set prediction comparison¶
lasso_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(lasso_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
lasso_sample_pred = pd.Series(lasso_best_pred_test, index=X_test.index).loc[sample_idx]
lasso_results_comparison["Error"] = lasso_results_comparison["Prediction"] - lasso_results_comparison["Original Label"]
lasso_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 191.42 | 1038.13 | 916.62 | 1578.91 | 1977.09 | 1929.26 | 2844.24 | 3453.48 | 3932.24 | 4585.62 | 4756.97 | 6875.90 | 12437.02 | 10126.89 | 11159.35 |
| Error | -379.58 | 274.13 | 89.62 | 430.91 | 621.09 | 529.26 | 175.24 | 631.48 | -10.76 | 430.62 | 352.97 | -794.10 | 3609.02 | -1734.11 | -5510.65 |
Add formatted timing and model type to the results¶
lasso_results["Model_type"] = "Linear / regularized"
lasso_results["Fit_time"] = str(timedelta(seconds=int(lasso_fit_time)))
lasso_results["Pred_time"] = str(timedelta(seconds=int(lasso_predict_time)))
lasso_results["Tun_fit_time"] = str(timedelta(seconds=int(lasso_tuning_fit_time)))
lasso_results["Tun_pred_time"] = str(timedelta(seconds=int(lasso_tuning_predict_time)))
lasso_results["Full_model_time"] = str(timedelta(seconds=int(lasso_fit_time + lasso_predict_time + lasso_tuning_fit_time + lasso_tuning_predict_time)))
lasso_results["Tun_MAE"] = lasso_best_results["MAE"]
lasso_results["Tun_RMSE"] = lasso_best_results["RMSE"]
lasso_results["Tun_R2"] = lasso_best_results["R2"]
lasso_results["Tun_Train_MAE"] = lasso_best_results["Train_MAE"]
lasso_results["Tun_Train_RMSE"] = lasso_best_results["Train_RMSE"]
lasso_results["Tun_Train_R2"] = lasso_best_results["Train_R2"]
ElasticNet Regressor¶
The ElasticNet Regressor combines L1 and L2 regularization, so it balances feature selection and coefficient shrinkage.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.linear_model import ElasticNet
elastic_param = {
# Most important parameters: alpha, l1_ratio
"alpha": 0.1, # Default: 1.0 -> From 0.01 to 10.0 - smaller alpha → weaker regularization
"l1_ratio": 0.5, # Default: 0.5 -> From 0.0 to 1.0 - 0 = Ridge-like, 1 = Lasso-like
"max_iter": 5000 # Default: 1000 -> Increase if convergence warning appears
}
elastic_reg = Pipeline([
("preprocessing", scaled_preprocessing),
("model", ElasticNet(**elastic_param))
])
print("ElasticNet Regressor: fitting model...")
elastic_reg.fit(X_train, y_train)
print("ElasticNet Regressor: fitted successfully.")
else:
print("ElasticNet Regressor: loading fitted model...")
elastic_reg = loaded_fitted_base_models["elasticnet"]
print("ElasticNet Regressor: loaded successfully.")
ElasticNet Regressor: fitting model... ElasticNet Regressor: fitted successfully.
elastic_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(elastic_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:00
Features created by ElasticNet Regressor Model¶
# Intercept of the elastic net regression model
print("Starting value of the model:", elastic_reg.named_steps["model"].intercept_)
Starting value of the model: 3784.6919461254006
# Coefficients learned for each transformed feature
print("Weights for each feature:\n", elastic_reg.named_steps["model"].coef_)
Weights for each feature: [2773.88 -71.97 -83.14 530.5 313.81 263.04 -346.36 -62.73 205.79 106.53 95.77 389.2 292.52 279.83 212.67 -144.81 -353.96 -676.45 -752.94 480.41 -360.31 -918.21 314.05 150.97 522.81 561.23]
# Number of input features used by the model
print("Number of input features:", elastic_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", elastic_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("ElasticNet Regressor: predicting model...")
elastic_pred_train = elastic_reg.predict(X_train)
elastic_pred_test = elastic_reg.predict(X_test)
print("ElasticNet Regressor: predicted successfully.\n")
else:
print("ElasticNet Regressor: loading predicted model...")
elastic_pred_train = loaded_predictions["elastic_pred_train_file"]
elastic_pred_test = loaded_predictions["elastic_pred_test_file"]
print("ElasticNet Regressor: loaded successfully.\n")
elastic_results = evaluate_regression(
y_test,
elastic_pred_test,
model_name="ElasticNet Regressor",
y_train_true=y_train,
y_train_pred=elastic_pred_train
)
ElasticNet Regressor: predicting model... ElasticNet Regressor: predicted successfully. --- ElasticNet Regressor --- TEST MAE: 922.99 RMSE: 1384.66 R²: 0.8795 TRAIN MAE: 909.81 RMSE: 1340.40 R²: 0.8871 GAP RMSE gap: 44.26 R² gap: 0.0076
elastic_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(elastic_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import GridSearchCV
elastic_param_grid = {
"model__alpha": [0.01, 0.1, 1.0, 10.0],
"model__l1_ratio": [0.2, 0.5, 0.8]
}
elastic_search = GridSearchCV(
elastic_reg, # The pipeline model to tune
param_grid=elastic_param_grid, # Default: required -> Dictionary with parameter values to test - Parameter values to test
scoring=SCORING, # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
cv=KFOLD_10, # Default: None -> Common values: 3, 5, 10 - Number of cross-validation folds
n_jobs=N_JOBS, # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
verbose=VERBOSE, # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
error_score="raise" # Default: np.nan -> Common values: np.nan, "raise" - If a fit fails during CV/search, raise the error immediately instead of silently assigning NaN to that candidate
)
print("ElasticNet Regressor: running grid search...")
elastic_search.fit(X_train, y_train)
print("ElasticNet Regressor: grid search completed successfully.")
else:
print("ElasticNet Regressor: loading search object...")
elastic_search = loaded_searches["elasticnet"]
print("ElasticNet Regressor: search object loaded successfully.")
ElasticNet Regressor: running grid search... Fitting 10 folds for each of 12 candidates, totalling 120 fits ElasticNet Regressor: grid search completed successfully.
elastic_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(elastic_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:00:24
# Best parameters
elastic_search.best_params_
{'model__alpha': 0.01, 'model__l1_ratio': 0.8}
# Best model
elastic_best_tuning = elastic_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("ElasticNet Regressor: predicting best model...")
elastic_best_pred_train = elastic_best_tuning.predict(X_train)
elastic_best_pred_test = elastic_best_tuning.predict(X_test)
print("ElasticNet Regressor: best model predicted successfully.\n")
else:
print("ElasticNet Regressor: loading best predicted model...")
elastic_best_pred_train = loaded_predictions["elastic_best_pred_train_file"]
elastic_best_pred_test = loaded_predictions["elastic_best_pred_test_file"]
print("ElasticNet Regressor: best model loaded successfully.\n")
elastic_best_results = evaluate_regression(
y_test,
elastic_best_pred_test,
"Best ElasticNet Regressor",
y_train_true=y_train,
y_train_pred=elastic_best_pred_train
)
ElasticNet Regressor: predicting best model... ElasticNet Regressor: best model predicted successfully. --- Best ElasticNet Regressor --- TEST MAE: 752.81 RMSE: 1193.11 R²: 0.9106 TRAIN MAE: 734.06 RMSE: 1118.03 R²: 0.9215 GAP RMSE gap: 75.08 R² gap: 0.0109
elastic_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(elastic_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:00
Test set prediction comparison¶
elastic_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(elastic_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
elastic_sample_pred = pd.Series(elastic_best_pred_test, index=X_test.index).loc[sample_idx]
elastic_results_comparison["Error"] = elastic_results_comparison["Prediction"] - elastic_results_comparison["Original Label"]
elastic_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 180.91 | 998.05 | 915.48 | 1515.50 | 1976.06 | 1903.09 | 2943.72 | 3508.01 | 3980.30 | 4599.86 | 4782.63 | 6899.44 | 12390.99 | 10110.91 | 11102.68 |
| Error | -390.09 | 234.05 | 88.48 | 367.50 | 620.06 | 503.09 | 274.72 | 686.01 | 37.30 | 444.86 | 378.63 | -770.56 | 3562.99 | -1750.09 | -5567.32 |
Add formatted timing and model type to the results¶
elastic_results["Model_type"] = "Linear / regularized"
elastic_results["Fit_time"] = str(timedelta(seconds=int(elastic_fit_time)))
elastic_results["Pred_time"] = str(timedelta(seconds=int(elastic_predict_time)))
elastic_results["Tun_fit_time"] = str(timedelta(seconds=int(elastic_tuning_fit_time)))
elastic_results["Tun_pred_time"] = str(timedelta(seconds=int(elastic_tuning_predict_time)))
elastic_results["Full_model_time"] = str(timedelta(seconds=int(elastic_fit_time + elastic_predict_time + elastic_tuning_fit_time + elastic_tuning_predict_time)))
elastic_results["Tun_MAE"] = elastic_best_results["MAE"]
elastic_results["Tun_RMSE"] = elastic_best_results["RMSE"]
elastic_results["Tun_R2"] = elastic_best_results["R2"]
elastic_results["Tun_Train_MAE"] = elastic_best_results["Train_MAE"]
elastic_results["Tun_Train_RMSE"] = elastic_best_results["Train_RMSE"]
elastic_results["Tun_Train_R2"] = elastic_best_results["Train_R2"]
Bayesian Ridge Regressor¶
The BayesianRidge model is a linear Regressor model with built-in regularization based on Bayesian estimation.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.linear_model import BayesianRidge
bayes_ridge_param = {
# Most important parameters: alpha_1, alpha_2, lambda_1, lambda_2
"alpha_1": 1e-6, # Default: 1e-6 -> Usually left at default unless you want to change the priors
"alpha_2": 1e-6, # Default: 1e-6 -> Usually left at default unless you want to change the priors
"lambda_1": 1e-6, # Default: 1e-6 -> Usually left at default unless you want to change the priors
"lambda_2": 1e-6, # Default: 1e-6 -> Usually left at default unless you want to change the priors
"max_iter": 1000, # Default: 300 -> Increase if convergence warning appears
"tol": 0.001 # Default: 0.001 -> Smaller value means stricter stopping
}
bayes_ridge_reg = Pipeline([
("preprocessing", scaled_preprocessing),
("model", BayesianRidge(**bayes_ridge_param))
])
print("Bayesian Ridge Regressor: fitting model...")
bayes_ridge_reg.fit(X_train, y_train)
print("Bayesian Ridge Regressor: fitted successfully.")
else:
print("Bayesian Ridge Regressor: loading fitted model...")
bayes_ridge_reg = loaded_fitted_base_models["bayesian_ridge"]
print("Bayesian Ridge Regressor: loaded successfully.")
Bayesian Ridge Regressor: fitting model... Bayesian Ridge Regressor: fitted successfully.
bayes_ridge_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(bayes_ridge_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:00
Features created by Bayesian Ridge Regressor Model¶
# Intercept of the Bayesian ridge regression model
print("Starting value of the model:", bayes_ridge_reg.named_steps["model"].intercept_)
Starting value of the model: 3388.6652888316694
# Coefficients learned for each transformed feature
print("Weights for each feature:\n", bayes_ridge_reg.named_steps["model"].coef_)
Weights for each feature: [ 5.43e+03 -9.86e+01 -6.01e+01 -1.24e+03 -3.05e+00 -9.54e+00 -5.75e+02 -6.23e+00 2.61e+02 1.83e+02 1.37e+02 8.21e+02 6.20e+02 5.61e+02 3.50e+02 -1.55e+02 -6.42e+02 -1.55e+03 -3.67e+03 1.50e+03 -1.64e+02 -1.13e+03 7.48e+02 4.42e+02 1.16e+03 1.11e+03]
# Number of input features used by the model
print("Number of input features:", bayes_ridge_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", bayes_ridge_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Bayesian Ridge Regressor: predicting model...")
bayes_ridge_pred_train = bayes_ridge_reg.predict(X_train)
bayes_ridge_pred_test = bayes_ridge_reg.predict(X_test)
print("Bayesian Ridge Regressor: predicted successfully.\n")
else:
print("Bayesian Ridge Regressor: loading predicted model...")
bayes_ridge_pred_train = loaded_predictions["bayes_ridge_pred_train_file"]
bayes_ridge_pred_test = loaded_predictions["bayes_ridge_pred_test_file"]
print("Bayesian Ridge Regressor: loaded successfully.\n")
bayes_ridge_results = evaluate_regression(
y_test,
bayes_ridge_pred_test,
model_name="Bayesian Ridge Regressor",
y_train_true=y_train,
y_train_pred=bayes_ridge_pred_train
)
Bayesian Ridge Regressor: predicting model... Bayesian Ridge Regressor: predicted successfully. --- Bayesian Ridge Regressor --- TEST MAE: 751.86 RMSE: 1191.27 R²: 0.9108 TRAIN MAE: 732.63 RMSE: 1114.62 R²: 0.9219 GAP RMSE gap: 76.65 R² gap: 0.0111
bayes_ridge_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(bayes_ridge_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import GridSearchCV
bayes_ridge_param_grid = {
"model__max_iter": [300, 500, 1000],
"model__tol": [0.01, 0.001, 0.0001]
}
bayes_ridge_search = GridSearchCV(
bayes_ridge_reg, # The pipeline model to tune
param_grid=bayes_ridge_param_grid, # Default: required -> Dictionary with parameter values to test - Parameter values to test
scoring=SCORING, # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
cv=KFOLD_10, # Default: None -> Common values: 3, 5, 10 - Number of cross-validation folds
n_jobs=N_JOBS, # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
verbose=VERBOSE, # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
error_score=ERROR_SCORE # Default: np.nan -> Common values: np.nan, "raise" - If a fit fails during CV/search, raise the error immediately instead of silently assigning NaN to that candidate
)
print("Bayesian Ridge Regressor: running grid search...")
bayes_ridge_search.fit(X_train, y_train)
print("Bayesian Ridge Regressor: grid search completed successfully.")
else:
print("Bayesian Ridge Regressor: loading search object...")
bayes_ridge_search = loaded_searches["bayesian_ridge"]
print("Bayesian Ridge Regressor: search object loaded successfully.")
Bayesian Ridge Regressor: running grid search... Fitting 10 folds for each of 9 candidates, totalling 90 fits Bayesian Ridge Regressor: grid search completed successfully.
bayes_ridge_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(bayes_ridge_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:00:10
# Best parameters
bayes_ridge_search.best_params_
{'model__max_iter': 300, 'model__tol': 0.01}
# Best model
bayes_ridge_best_tuning = bayes_ridge_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Bayesian Ridge Regressor: predicting best model...")
bayes_ridge_best_pred_train = bayes_ridge_best_tuning.predict(X_train)
bayes_ridge_best_pred_test = bayes_ridge_best_tuning.predict(X_test)
print("Bayesian Ridge Regressor: best model predicted successfully.\n")
else:
print("Bayesian Ridge Regressor: loading best predicted model...")
bayes_ridge_best_pred_train = loaded_predictions["bayes_ridge_best_pred_train_file"]
bayes_ridge_best_pred_test = loaded_predictions["bayes_ridge_best_pred_test_file"]
print("Bayesian Ridge Regressor: best model loaded successfully.\n")
bayes_ridge_best_results = evaluate_regression(
y_test,
bayes_ridge_best_pred_test,
"Best Bayesian Ridge Regressor",
y_train_true=y_train,
y_train_pred=bayes_ridge_best_pred_train
)
Bayesian Ridge Regressor: predicting best model... Bayesian Ridge Regressor: best model predicted successfully. --- Best Bayesian Ridge Regressor --- TEST MAE: 751.86 RMSE: 1191.27 R²: 0.9108 TRAIN MAE: 732.63 RMSE: 1114.62 R²: 0.9219 GAP RMSE gap: 76.65 R² gap: 0.0111
bayes_ridge_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(bayes_ridge_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:00
Test set prediction comparison¶
bayes_ridge_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(bayes_ridge_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
bayes_ridge_sample_pred = pd.Series(bayes_ridge_best_pred_test, index=X_test.index).loc[sample_idx]
bayes_ridge_results_comparison["Error"] = bayes_ridge_results_comparison["Prediction"] - bayes_ridge_results_comparison["Original Label"]
bayes_ridge_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 182.26 | 1053.71 | 914.88 | 1616.71 | 1995.67 | 1957.54 | 2817.92 | 3445.58 | 3935.31 | 4568.47 | 4757.87 | 6874.47 | 12450.94 | 10143.97 | 11193.63 |
| Error | -388.74 | 289.71 | 87.88 | 468.71 | 639.67 | 557.54 | 148.92 | 623.58 | -7.69 | 413.47 | 353.87 | -795.53 | 3622.94 | -1717.03 | -5476.37 |
Add formatted timing and model type to the results¶
bayes_ridge_results["Model_type"] = "Linear / regularized"
bayes_ridge_results["Fit_time"] = str(timedelta(seconds=int(bayes_ridge_fit_time)))
bayes_ridge_results["Pred_time"] = str(timedelta(seconds=int(bayes_ridge_predict_time)))
bayes_ridge_results["Tun_fit_time"] = str(timedelta(seconds=int(bayes_ridge_tuning_fit_time)))
bayes_ridge_results["Tun_pred_time"] = str(timedelta(seconds=int(bayes_ridge_tuning_predict_time)))
bayes_ridge_results["Full_model_time"] = str(timedelta(seconds=int(bayes_ridge_fit_time + bayes_ridge_predict_time + bayes_ridge_tuning_fit_time + bayes_ridge_tuning_predict_time)))
bayes_ridge_results["Tun_MAE"] = bayes_ridge_best_results["MAE"]
bayes_ridge_results["Tun_RMSE"] = bayes_ridge_best_results["RMSE"]
bayes_ridge_results["Tun_R2"] = bayes_ridge_best_results["R2"]
bayes_ridge_results["Tun_Train_MAE"] = bayes_ridge_best_results["Train_MAE"]
bayes_ridge_results["Tun_Train_RMSE"] = bayes_ridge_best_results["Train_RMSE"]
bayes_ridge_results["Tun_Train_R2"] = bayes_ridge_best_results["Train_R2"]
Quantile Regressor¶
The QuantileRegressor model is a linear Regressor model that estimates a chosen quantile of the target distribution instead of the mean.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.linear_model import QuantileRegressor
quantile_param = {
# Most important parameters: quantile, alpha
"quantile": 0.5, # Default: 0.5 -> 0.5 = median regression, lower/higher values estimate lower/higher quantiles
"alpha": 0.0, # Default: 1.0 -> Smaller alpha = weaker regularization, 0.0 = no regularization
"solver": "highs" # Default: "highs" -> Recommended modern solver for linear programming
}
quantile_reg = Pipeline([
("preprocessing", scaled_preprocessing),
("model", QuantileRegressor(**quantile_param))
])
print("Quantile Regressor: fitting model...")
quantile_reg.fit(X_train, y_train)
print("Quantile Regressor: fitted successfully.")
else:
print("Quantile Regressor: loading fitted model...")
quantile_reg = loaded_fitted_base_models["quantile"]
print("Quantile Regressor: loaded successfully.")
Quantile Regressor: fitting model... Quantile Regressor: fitted successfully.
quantile_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(quantile_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:03:11
Features created by Quantile Regressor Model¶
# Intercept of the quantile regression model
print("Starting value of the model:", quantile_reg.named_steps["model"].intercept_)
Starting value of the model: 2305.809651153515
# Coefficients learned for each transformed feature
print("Weights for each feature:\n", quantile_reg.named_steps["model"].coef_)
Weights for each feature:
[ 6170.62 -117.95 -56.73 -1534.42 -605.01 -191.06 -396.13 -50.39
92.77 84.01 0. 1645.25 1507.89 1440.47 1376.08 1162.3
801.12 0. -2508.24 722.44 0. -689.06 452.49 325.52
606.09 581.12]
# Number of input features used by the model
print("Number of input features:", quantile_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", quantile_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Quantile Regressor: predicting model...")
quantile_pred_train = quantile_reg.predict(X_train)
quantile_pred_test = quantile_reg.predict(X_test)
print("Quantile Regressor: predicted successfully.\n")
else:
print("Quantile Regressor: loading predicted model...")
quantile_pred_train = loaded_predictions["quantile_pred_train_file"]
quantile_pred_test = loaded_predictions["quantile_pred_test_file"]
print("Quantile Regressor: loaded successfully.\n")
quantile_results = evaluate_regression(
y_test,
quantile_pred_test,
model_name="Quantile Regressor",
y_train_true=y_train,
y_train_pred=quantile_pred_train
)
Quantile Regressor: predicting model... Quantile Regressor: predicted successfully. --- Quantile Regressor --- TEST MAE: 657.62 RMSE: 1322.29 R²: 0.8902 TRAIN MAE: 638.64 RMSE: 1231.25 R²: 0.9047 GAP RMSE gap: 91.04 R² gap: 0.0146
quantile_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(quantile_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import GridSearchCV
quantile_param_grid = {
"model__quantile": [0.25, 0.5, 0.75],
"model__alpha": [0.0, 0.0001, 0.001, 0.01]
}
quantile_search = GridSearchCV(
quantile_reg, # The pipeline model to tune
param_grid=quantile_param_grid, # Default: required -> Dictionary with parameter values to test - Parameter values to test
scoring=SCORING, # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
cv=KFOLD_5, # Default: None -> Common values: 3, 5, 10 - Number of cross-validation folds
n_jobs=N_JOBS, # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
verbose=VERBOSE, # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
error_score=ERROR_SCORE # Default: np.nan -> Common values: np.nan, "raise" - If a fit fails during CV/search, raise the error immediately instead of silently assigning NaN to that candidate
)
print("Quantile Regressor: running grid search...")
quantile_search.fit(X_train, y_train)
print("Quantile Regressor: grid search completed successfully.")
else:
print("Quantile Regressor: loading search object...")
quantile_search = loaded_searches["quantile"]
print("Quantile Regressor: search object loaded successfully.")
Quantile Regressor: running grid search... Fitting 5 folds for each of 12 candidates, totalling 60 fits Quantile Regressor: grid search completed successfully.
quantile_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(quantile_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 1:04:26
# Best parameters
quantile_search.best_params_
{'model__alpha': 0.0, 'model__quantile': 0.5}
# Best model
quantile_best_tuning = quantile_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Quantile Regressor: predicting best model...")
quantile_best_pred_train = quantile_best_tuning.predict(X_train)
quantile_best_pred_test = quantile_best_tuning.predict(X_test)
print("Quantile Regressor: best model predicted successfully.\n")
else:
print("Quantile Regressor: loading best predicted model...")
quantile_best_pred_train = loaded_predictions["quantile_best_pred_train_file"]
quantile_best_pred_test = loaded_predictions["quantile_best_pred_test_file"]
print("Quantile Regressor: best model loaded successfully.\n")
quantile_best_results = evaluate_regression(
y_test,
quantile_best_pred_test,
"Best Quantile Regressor",
y_train_true=y_train,
y_train_pred=quantile_best_pred_train
)
Quantile Regressor: predicting best model... Quantile Regressor: best model predicted successfully. --- Best Quantile Regressor --- TEST MAE: 657.62 RMSE: 1322.29 R²: 0.8902 TRAIN MAE: 638.64 RMSE: 1231.25 R²: 0.9047 GAP RMSE gap: 91.04 R² gap: 0.0146
quantile_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(quantile_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:00
Test set prediction comparison¶
quantile_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(quantile_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
quantile_sample_pred = pd.Series(quantile_best_pred_test, index=X_test.index).loc[sample_idx]
quantile_results_comparison["Error"] = quantile_results_comparison["Prediction"] - quantile_results_comparison["Original Label"]
quantile_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 492.75 | 809.74 | 985.55 | 1146.59 | 1371.12 | 1403.38 | 3235.83 | 2887.61 | 3794.07 | 4317.66 | 4579.96 | 6050.80 | 12089.47 | 9219.13 | 10183.32 |
| Error | -78.25 | 45.74 | 158.55 | -1.41 | 15.12 | 3.38 | 566.83 | 65.61 | -148.93 | 162.66 | 175.96 | -1619.20 | 3261.47 | -2641.87 | -6486.68 |
Add formatted timing and model type to the results¶
quantile_results["Model_type"] = "Linear / quantile"
quantile_results["Fit_time"] = str(timedelta(seconds=int(quantile_fit_time)))
quantile_results["Pred_time"] = str(timedelta(seconds=int(quantile_predict_time)))
quantile_results["Tun_fit_time"] = str(timedelta(seconds=int(quantile_tuning_fit_time)))
quantile_results["Tun_pred_time"] = str(timedelta(seconds=int(quantile_tuning_predict_time)))
quantile_results["Full_model_time"] = str(timedelta(seconds=int(quantile_fit_time + quantile_predict_time + quantile_tuning_fit_time + quantile_tuning_predict_time)))
quantile_results["Tun_MAE"] = quantile_best_results["MAE"]
quantile_results["Tun_RMSE"] = quantile_best_results["RMSE"]
quantile_results["Tun_R2"] = quantile_best_results["R2"]
quantile_results["Tun_Train_MAE"] = quantile_best_results["Train_MAE"]
quantile_results["Tun_Train_RMSE"] = quantile_best_results["Train_RMSE"]
quantile_results["Tun_Train_R2"] = quantile_best_results["Train_R2"]
Robust linear models¶
Huber Regressor¶
The HuberRegressor is a linear model that is more robust to outliers than ordinary linear regressor.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.linear_model import HuberRegressor
huber_param = {
# Most important parameters: epsilon, alpha
"epsilon": 1.35, # Default: 1.35 -> From 1.1 to 2.0 - smaller epsilon → more sensitive to outliers
"alpha": 0.0001, # Default: 0.0001 -> From 0.00001 to 0.01 - smaller alpha → weaker regularization
"max_iter": 500, # Default: 100 -> Increase if convergence warning appears
"tol": 0.00001 # Default: 0.00001 -> Smaller value means stricter stopping
}
huber_reg = Pipeline([
("preprocessing", scaled_preprocessing),
("model", HuberRegressor(**huber_param))
])
print("Huber Regressor: fitting model...")
huber_reg.fit(X_train, y_train)
print("Huber Regressor: fitted successfully.")
else:
print("Huber Regressor: loading fitted model...")
huber_reg = loaded_fitted_base_models["huber"]
print("Huber Regressor: loaded successfully.")
Huber Regressor: fitting model... Huber Regressor: fitted successfully.
huber_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(huber_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:02
Features created by Huber Regressor Model¶
# Intercept of the Huber regressor model
print("Starting value of the model:", huber_reg.named_steps["model"].intercept_)
Starting value of the model: 2286.7131343011097
# Coefficients learned for each transformed feature
print("Weights for each feature:\n", huber_reg.named_steps["model"].coef_)
Weights for each feature: [ 6189.91 -132.46 -60.98 -2152.96 -36.19 -93.38 105.29 450.41 617.21 592.96 512.79 886.33 747.44 681.44 601.83 324.57 -63.64 -899.32 -2202.17 1135.3 286.33 -434.6 835. 675.43 1018.45 964.92]
# Number of input features used by the model
print("Number of input features:", huber_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", huber_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Huber Regressor: predicting model...")
huber_pred_train = huber_reg.predict(X_train)
huber_pred_test = huber_reg.predict(X_test)
print("Huber Regressor: predicted successfully.\n")
else:
print("Huber Regressor: loading predicted model...")
huber_pred_train = loaded_predictions["huber_pred_train_file"]
huber_pred_test = loaded_predictions["huber_pred_test_file"]
print("Huber Regressor: loaded successfully.\n")
huber_results = evaluate_regression(
y_test,
huber_pred_test,
model_name="Huber Regressor",
y_train_true=y_train,
y_train_pred=huber_pred_train
)
Huber Regressor: predicting model... Huber Regressor: predicted successfully. --- Huber Regressor --- TEST MAE: 662.10 RMSE: 1289.80 R²: 0.8955 TRAIN MAE: 642.26 RMSE: 1196.97 R²: 0.9100 GAP RMSE gap: 92.84 R² gap: 0.0145
huber_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(huber_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import RandomizedSearchCV
huber_param_dist = {
"model__epsilon": [3.0, 4.0, 5.0, 6.0, 6.9, 7.5, 8.0, 9.0],
"model__alpha": [0.01, 0.02, 0.03, 0.04, 0.05175, 0.06, 0.08, 0.1],
"model__max_iter": [500, 1000, 2000],
"model__tol": [0.00005, 0.000085, 0.0001, 0.0002, 0.0005]
}
huber_search = RandomizedSearchCV(
huber_reg, # The pipeline model to tune
param_distributions=huber_param_dist, # Dictionary with parameter values to test
n_iter=40, # Number of random combinations to try
scoring=SCORING, # Evaluation metric
cv=KFOLD_10, # Number of cross-validation folds
n_jobs=N_JOBS, # Parallel jobs
verbose=VERBOSE, # Show progress during fitting
random_state=RANDOM_STATE, # Reproducible random search
error_score=ERROR_SCORE # Raise error immediately if a fit fails
)
print("Huber Regressor: running randomized search...")
huber_search.fit(X_train, y_train)
print("Huber Regressor: randomized search completed successfully.")
else:
print("Huber Regressor: loading search object...")
huber_search = loaded_searches["huber"]
print("Huber Regressor: search object loaded successfully.")
Huber Regressor: running randomized search... Fitting 10 folds for each of 40 candidates, totalling 400 fits Huber Regressor: randomized search completed successfully.
huber_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(huber_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:07:29
# Best parameters
huber_search.best_params_
{'model__tol': 0.0001,
'model__max_iter': 500,
'model__epsilon': 6.9,
'model__alpha': 0.05175}
# Best model
huber_best_tuning = huber_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Huber Regressor: predicting best model...")
huber_best_pred_train = huber_best_tuning.predict(X_train)
huber_best_pred_test = huber_best_tuning.predict(X_test)
print("Huber Regressor: best model predicted successfully.\n")
else:
print("Huber Regressor: loading best predicted model...")
huber_best_pred_train = loaded_predictions["huber_best_pred_train_file"]
huber_best_pred_test = loaded_predictions["huber_best_pred_test_file"]
print("Huber Regressor: best model loaded successfully.\n")
huber_best_results = evaluate_regression(
y_test,
huber_best_pred_test,
"Best Huber Regressor",
y_train_true=y_train,
y_train_pred=huber_best_pred_train
)
Huber Regressor: predicting best model... Huber Regressor: best model predicted successfully. --- Best Huber Regressor --- TEST MAE: 749.74 RMSE: 1195.11 R²: 0.9103 TRAIN MAE: 730.20 RMSE: 1116.25 R²: 0.9217 GAP RMSE gap: 78.85 R² gap: 0.0114
huber_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(huber_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:00
Test set prediction comparison¶
huber_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(huber_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
huber_sample_pred = pd.Series(huber_best_pred_test, index=X_test.index).loc[sample_idx]
huber_results_comparison["Error"] = huber_results_comparison["Prediction"] - huber_results_comparison["Original Label"]
huber_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 187.80 | 1024.73 | 916.07 | 1531.19 | 1979.66 | 1916.40 | 2875.53 | 3466.55 | 3945.28 | 4582.00 | 4765.37 | 6884.38 | 12446.76 | 10129.92 | 11148.26 |
| Error | -383.20 | 260.73 | 89.07 | 383.19 | 623.66 | 516.40 | 206.53 | 644.55 | 2.28 | 427.00 | 361.37 | -785.62 | 3618.76 | -1731.08 | -5521.74 |
Add formatted timing and model type to the results¶
huber_results["Model_type"] = "Robust linear"
huber_results["Fit_time"] = str(timedelta(seconds=int(huber_fit_time)))
huber_results["Pred_time"] = str(timedelta(seconds=int(huber_predict_time)))
huber_results["Tun_fit_time"] = str(timedelta(seconds=int(huber_tuning_fit_time)))
huber_results["Tun_pred_time"] = str(timedelta(seconds=int(huber_tuning_predict_time)))
huber_results["Full_model_time"] = str(timedelta(seconds=int(huber_fit_time + huber_predict_time + huber_tuning_fit_time + huber_tuning_predict_time)))
huber_results["Tun_MAE"] = huber_best_results["MAE"]
huber_results["Tun_RMSE"] = huber_best_results["RMSE"]
huber_results["Tun_R2"] = huber_best_results["R2"]
huber_results["Tun_Train_MAE"] = huber_best_results["Train_MAE"]
huber_results["Tun_Train_RMSE"] = huber_best_results["Train_RMSE"]
huber_results["Tun_Train_R2"] = huber_best_results["Train_R2"]
RANSAC Regressor¶
The RANSAC Regressor is a robust regressor model that tries to fit the data while ignoring possible outliers.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.linear_model import LinearRegression, RANSACRegressor
ransac_param = {
# Most important parameters: min_samples, residual_threshold, max_trials
"min_samples": 0.5, # Default: None -> Common alternatives: 0.5 or an integer like 10
"residual_threshold": None, # Default: None -> Set a value if you want stricter outlier filtering
"max_trials": 100, # Default: 100 -> Increase if the model struggles to find a good fit
"stop_probability": 0.99 # Default: 0.99 -> Higher value means more confidence before stopping
}
ransac_reg = Pipeline([
("preprocessing", scaled_preprocessing),
("model", RANSACRegressor(
estimator=LinearRegression(),
random_state=RANDOM_STATE,
**ransac_param
))
])
print("RANSAC Regressor: fitting model...")
ransac_reg.fit(X_train, y_train)
print("RANSAC Regressor: fitted successfully.")
else:
print("RANSAC Regressor: loading fitted model...")
ransac_reg = loaded_fitted_base_models["ransac"]
print("RANSAC Regressor: loaded successfully.")
RANSAC Regressor: fitting model... RANSAC Regressor: fitted successfully.
ransac_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(ransac_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:03
Features created by RANSAC Regressor Model¶
# Number of input features used by the model
print("Number of input features:", ransac_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", ransac_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("RANSAC Regressor: predicting model...")
ransac_pred_train = ransac_reg.predict(X_train)
ransac_pred_test = ransac_reg.predict(X_test)
print("RANSAC Regressor: predicted successfully.\n")
else:
print("RANSAC Regressor: loading predicted model...")
ransac_pred_train = loaded_predictions["ransac_pred_train_file"]
ransac_pred_test = loaded_predictions["ransac_pred_test_file"]
print("RANSAC Regressor: loaded successfully.\n")
ransac_results = evaluate_regression(
y_test,
ransac_pred_test,
model_name="RANSAC Regressor",
y_train_true=y_train,
y_train_pred=ransac_pred_train
)
RANSAC Regressor: predicting model... RANSAC Regressor: predicted successfully. --- RANSAC Regressor --- TEST MAE: 674.05 RMSE: 1256.31 R²: 0.9008 TRAIN MAE: 654.06 RMSE: 1165.66 R²: 0.9146 GAP RMSE gap: 90.65 R² gap: 0.0138
ransac_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(ransac_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import RandomizedSearchCV
ransac_param_dist = {
"model__min_samples": [0.25, 0.3, 0.35, 0.4],
"model__max_trials": [200, 300],
"model__residual_threshold": [75, 85, 100, 125, 150],
"model__stop_probability": [0.99]
}
ransac_search = RandomizedSearchCV(
ransac_reg, # The pipeline model to tune
param_distributions=ransac_param_dist, # Dictionary with parameter values to test
n_iter=15, # Number of random combinations to try
scoring=SCORING, # Evaluation metric
cv=KFOLD_10, # Number of cross-validation folds
n_jobs=N_JOBS, # Parallel jobs
verbose=VERBOSE, # Show progress during fitting
random_state=RANDOM_STATE, # Reproducible random search
error_score=ERROR_SCORE # Raise error immediately if a fit fails
)
print("RANSAC Regressor: running randomized search...")
ransac_search.fit(X_train, y_train)
print("RANSAC Regressor: randomized search completed successfully.")
else:
print("RANSAC Regressor: loading search object...")
ransac_search = loaded_searches["ransac"]
print("RANSAC Regressor: search object loaded successfully.")
RANSAC Regressor: running randomized search... Fitting 10 folds for each of 15 candidates, totalling 150 fits RANSAC Regressor: randomized search completed successfully.
ransac_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(ransac_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:06:23
# Best parameters
ransac_search.best_params_
{'model__stop_probability': 0.99,
'model__residual_threshold': 100,
'model__min_samples': 0.3,
'model__max_trials': 300}
# Best model
ransac_best_tuning = ransac_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("RANSAC Regressor: predicting best model...")
ransac_best_pred_train = ransac_best_tuning.predict(X_train)
ransac_best_pred_test = ransac_best_tuning.predict(X_test)
print("RANSAC Regressor: best model predicted successfully.\n")
else:
print("RANSAC Regressor: loading best predicted model...")
ransac_best_pred_train = loaded_predictions["ransac_best_pred_train_file"]
ransac_best_pred_test = loaded_predictions["ransac_best_pred_test_file"]
print("RANSAC Regressor: best model loaded successfully.\n")
ransac_best_results = evaluate_regression(
y_test,
ransac_best_pred_test,
"Best RANSAC Regressor",
y_train_true=y_train,
y_train_pred=ransac_best_pred_train
)
RANSAC Regressor: predicting best model... RANSAC Regressor: best model predicted successfully. --- Best RANSAC Regressor --- TEST MAE: 741.22 RMSE: 1201.35 R²: 0.9093 TRAIN MAE: 720.41 RMSE: 1119.55 R²: 0.9212 GAP RMSE gap: 81.81 R² gap: 0.0119
ransac_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(ransac_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:00
Test set prediction comparison¶
ransac_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(ransac_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
ransac_sample_pred = pd.Series(ransac_best_pred_test, index=X_test.index).loc[sample_idx]
ransac_results_comparison["Error"] = ransac_results_comparison["Prediction"] - ransac_results_comparison["Original Label"]
ransac_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 224.98 | 1068.07 | 870.47 | 1579.49 | 1976.29 | 1962.80 | 2817.40 | 3359.07 | 3945.90 | 4461.99 | 4703.19 | 6781.56 | 12530.48 | 10049.67 | 11196.48 |
| Error | -346.02 | 304.07 | 43.47 | 431.49 | 620.29 | 562.80 | 148.40 | 537.07 | 2.90 | 306.99 | 299.19 | -888.44 | 3702.48 | -1811.33 | -5473.52 |
Add formatted timing and model type to the results¶
ransac_results["Model_type"] = "Robust linear"
ransac_results["Fit_time"] = str(timedelta(seconds=int(ransac_fit_time)))
ransac_results["Pred_time"] = str(timedelta(seconds=int(ransac_predict_time)))
ransac_results["Tun_fit_time"] = str(timedelta(seconds=int(ransac_tuning_fit_time)))
ransac_results["Tun_pred_time"] = str(timedelta(seconds=int(ransac_tuning_predict_time)))
ransac_results["Full_model_time"] = str(timedelta(seconds=int(ransac_fit_time + ransac_predict_time + ransac_tuning_fit_time + ransac_tuning_predict_time)))
ransac_results["Tun_MAE"] = ransac_best_results["MAE"]
ransac_results["Tun_RMSE"] = ransac_best_results["RMSE"]
ransac_results["Tun_R2"] = ransac_best_results["R2"]
ransac_results["Tun_Train_MAE"] = ransac_best_results["Train_MAE"]
ransac_results["Tun_Train_RMSE"] = ransac_best_results["Train_RMSE"]
ransac_results["Tun_Train_R2"] = ransac_best_results["Train_R2"]
Online linear models¶
SGD Regressor (Stochastic Gradient Descent)¶
The SGDRegressor is a linear model trained with stochastic gradient descent, which makes it suitable for larger datasets and faster training.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.linear_model import SGDRegressor
sgd_param = {
# Most important parameters: alpha, penalty, learning_rate, eta0
"alpha": 0.0001, # Default: 0.0001 -> From 0.00001 to 0.01 - smaller alpha → weaker regularization
"penalty": "l2", # Default: "l2" -> Common alternatives: "l1", "elasticnet"
"loss": "squared_error", # Default: "squared_error" -> Common alternatives: "huber", "epsilon_insensitive"
"learning_rate": "adaptive", # Default: "invscaling" -> Common alternatives: "optimal", "adaptive", "constant"
"eta0": 0.001, # Default: 0.01 -> Starting learning rate, mainly important for "constant", "invscaling", "adaptive"
"max_iter": 2000, # Default: 1000 -> Increase if convergence warning appears
"tol": 0.001 # Default: 0.001 -> Smaller value means stricter stopping
}
sgd_reg = Pipeline([
("preprocessing", scaled_preprocessing),
("model", SGDRegressor(random_state=RANDOM_STATE, **sgd_param))
])
print("SGD Regressor: fitting model...")
sgd_reg.fit(X_train, y_train)
print("SGD Regressor: fitted successfully.")
else:
print("SGD Regressor: loading fitted model...")
sgd_reg = loaded_fitted_base_models["sgd"]
print("SGD Regressor: loaded successfully.")
SGD Regressor: fitting model... SGD Regressor: fitted successfully.
sgd_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(sgd_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:01
Features created by SGD Regressor Model¶
# Intercept of the SGD regressor model
print("Starting value of the model:", sgd_reg.named_steps["model"].intercept_)
Starting value of the model: [2387.75]
# Coefficients learned for each transformed feature
print("Weights for each feature:\n", sgd_reg.named_steps["model"].coef_)
Weights for each feature: [ 5420.64 -98.24 -60.23 -1228.45 -5.73 -10.2 -147.53 422.93 690.15 612.72 566.41 1126.33 924.79 866.41 655.67 151.35 -334.74 -1245.13 -3373.62 1763.11 101.39 -867.4 1012.98 706.84 1421.94 1379.46]
# Number of input features used by the model
print("Number of input features:", sgd_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", sgd_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("SGD Regressor: predicting model...")
sgd_pred_train = sgd_reg.predict(X_train)
sgd_pred_test = sgd_reg.predict(X_test)
print("SGD Regressor: predicted successfully.\n")
else:
print("SGD Regressor: loading predicted model...")
sgd_pred_train = loaded_predictions["sgd_pred_train_file"]
sgd_pred_test = loaded_predictions["sgd_pred_test_file"]
print("SGD Regressor: loaded successfully.\n")
sgd_results = evaluate_regression(
y_test,
sgd_pred_test,
model_name="SGD Regressor",
y_train_true=y_train,
y_train_pred=sgd_pred_train
)
SGD Regressor: predicting model... SGD Regressor: predicted successfully. --- SGD Regressor --- TEST MAE: 751.84 RMSE: 1191.23 R²: 0.9108 TRAIN MAE: 732.63 RMSE: 1114.63 R²: 0.9219 GAP RMSE gap: 76.60 R² gap: 0.0111
sgd_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(sgd_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import RandomizedSearchCV
sgd_param_dist = {
"model__alpha": [0.0001, 0.001, 0.01],
"model__penalty": ["l2", "l1", "elasticnet"],
"model__loss": ["squared_error"],
"model__learning_rate": ["adaptive", "constant"],
"model__eta0": [0.001, 0.01],
"model__max_iter": [2000, 5000],
"model__tol": [0.0001, 0.001]
}
sgd_search = RandomizedSearchCV(
sgd_reg, # The pipeline model to tune
param_distributions=sgd_param_dist, # Default: required -> Dictionary with parameter values to test - Parameter values to test
n_iter=100, # Default: 10 -> Common values: 10, 20, 30, 50 - Number of random combinations to try
scoring=SCORING, # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
cv=KFOLD_10, # Default: None -> Common values: 3, 5, 10 - Number of cross-validation folds
n_jobs=N_JOBS, # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
verbose=VERBOSE, # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
random_state=RANDOM_STATE, # Default: None -> Common values: None, 42 - Reproducible random search
error_score=ERROR_SCORE # Default: np.nan -> Common values: np.nan, "raise" - If a fit fails during CV/search, raise the error immediately instead of silently assigning NaN to that candidate
)
print("SGD Regressor: running randomized search...")
sgd_search.fit(X_train, y_train)
print("SGD Regressor: randomized search completed successfully.")
else:
print("SGD Regressor: loading search object...")
sgd_search = loaded_searches["sgd"]
print("SGD Regressor: search object loaded successfully.")
SGD Regressor: running randomized search... Fitting 10 folds for each of 100 candidates, totalling 1000 fits SGD Regressor: randomized search completed successfully.
sgd_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(sgd_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:02:38
# Best parameters
sgd_search.best_params_
{'model__tol': 0.001,
'model__penalty': 'elasticnet',
'model__max_iter': 2000,
'model__loss': 'squared_error',
'model__learning_rate': 'adaptive',
'model__eta0': 0.001,
'model__alpha': 0.001}
# Best model
sgd_best_tuning = sgd_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("SGD Regressor: predicting best model...")
sgd_best_pred_train = sgd_best_tuning.predict(X_train)
sgd_best_pred_test = sgd_best_tuning.predict(X_test)
print("SGD Regressor: best model predicted successfully.\n")
else:
print("SGD Regressor: loading best predicted model...")
sgd_best_pred_train = loaded_predictions["sgd_best_pred_train_file"]
sgd_best_pred_test = loaded_predictions["sgd_best_pred_test_file"]
print("SGD Regressor: best model loaded successfully.\n")
sgd_best_results = evaluate_regression(
y_test,
sgd_best_pred_test,
"Best SGD Regressor",
y_train_true=y_train,
y_train_pred=sgd_best_pred_train
)
SGD Regressor: predicting best model... SGD Regressor: best model predicted successfully. --- Best SGD Regressor --- TEST MAE: 751.77 RMSE: 1191.36 R²: 0.9108 TRAIN MAE: 732.70 RMSE: 1115.31 R²: 0.9218 GAP RMSE gap: 76.05 R² gap: 0.0110
sgd_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(sgd_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:00
Test set prediction comparison¶
sgd_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(sgd_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
sgd_sample_pred = pd.Series(sgd_best_pred_test, index=X_test.index).loc[sample_idx]
sgd_results_comparison["Error"] = sgd_results_comparison["Prediction"] - sgd_results_comparison["Original Label"]
sgd_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 181.54 | 1028.76 | 914.61 | 1572.76 | 1986.60 | 1932.97 | 2873.12 | 3472.33 | 3955.30 | 4581.44 | 4768.44 | 6885.10 | 12423.64 | 10129.54 | 11153.88 |
| Error | -389.46 | 264.76 | 87.61 | 424.76 | 630.60 | 532.97 | 204.12 | 650.33 | 12.30 | 426.44 | 364.44 | -784.90 | 3595.64 | -1731.46 | -5516.12 |
Add formatted timing and model type to the results¶
sgd_results["Model_type"] = "Online linear"
sgd_results["Fit_time"] = str(timedelta(seconds=int(sgd_fit_time)))
sgd_results["Pred_time"] = str(timedelta(seconds=int(sgd_predict_time)))
sgd_results["Tun_fit_time"] = str(timedelta(seconds=int(sgd_tuning_fit_time)))
sgd_results["Tun_pred_time"] = str(timedelta(seconds=int(sgd_tuning_predict_time)))
sgd_results["Full_model_time"] = str(timedelta(seconds=int(sgd_fit_time + sgd_predict_time + sgd_tuning_fit_time + sgd_tuning_predict_time)))
sgd_results["Tun_MAE"] = sgd_best_results["MAE"]
sgd_results["Tun_RMSE"] = sgd_best_results["RMSE"]
sgd_results["Tun_R2"] = sgd_best_results["R2"]
sgd_results["Tun_Train_MAE"] = sgd_best_results["Train_MAE"]
sgd_results["Tun_Train_RMSE"] = sgd_best_results["Train_RMSE"]
sgd_results["Tun_Train_R2"] = sgd_best_results["Train_R2"]
Passive Aggressive Regressor¶
The PassiveAggressiveRegressor is an online linear model that updates itself aggressively when predictions are wrong.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.linear_model import SGDRegressor
pa_param = {
# Passive-Aggressive equivalent using SGDRegressor
"loss": "epsilon_insensitive", # Equivalent base loss for Passive-Aggressive regression
"penalty": None, # Matches PassiveAggressiveRegressor behaviour
"learning_rate": "pa1", # "pa1" = old epsilon_insensitive variant, "pa2" = old squared variant
"eta0": 1.0, # Equivalent of old parameter C
"max_iter": 2000, # Default: 1000 -> Increase if convergence warning appears
"tol": 0.001 # Default: 0.001 -> Smaller value means stricter stopping
}
pa_reg = Pipeline([
("preprocessing", scaled_preprocessing),
("model", SGDRegressor(random_state=RANDOM_STATE, **pa_param))
])
print("Passive Aggressive Regressor: fitting model...")
pa_reg.fit(X_train, y_train)
print("Passive Aggressive Regressor: fitted successfully.")
else:
print("Passive Aggressive Regressor: loading fitted model...")
pa_reg = loaded_fitted_base_models["passive_aggressive"]
print("Passive Aggressive Regressor: loaded successfully.")
Passive Aggressive Regressor: fitting model... Passive Aggressive Regressor: fitted successfully.
pa_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(pa_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:00
Features created by Passive Aggressive Regressor Model¶
# Intercept of the passive aggressive regressor model
print("Starting value of the model:", pa_reg.named_steps["model"].intercept_)
Starting value of the model: [2252.23]
# Coefficients learned for each transformed feature
print("Weights for each feature:\n", pa_reg.named_steps["model"].coef_)
Weights for each feature: [ 6.18e+03 -7.94e+01 -8.78e+01 -1.19e+03 -5.02e+02 -6.20e+02 1.27e+02 4.58e+02 5.84e+02 5.56e+02 5.28e+02 8.37e+02 7.09e+02 6.33e+02 5.33e+02 3.44e+02 -2.31e-01 -8.04e+02 -2.15e+03 1.08e+03 3.26e+02 -3.50e+02 7.99e+02 6.39e+02 9.52e+02 9.49e+02]
# Number of input features used by the model
print("Number of input features:", pa_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", pa_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Passive Aggressive Regressor: predicting model...")
pa_pred_train = pa_reg.predict(X_train)
pa_pred_test = pa_reg.predict(X_test)
print("Passive Aggressive Regressor: predicted successfully.\n")
else:
print("Passive Aggressive Regressor: loading predicted model...")
pa_pred_train = loaded_predictions["pa_pred_train_file"]
pa_pred_test = loaded_predictions["pa_pred_test_file"]
print("Passive Aggressive Regressor: loaded successfully.\n")
pa_results = evaluate_regression(
y_test,
pa_pred_test,
model_name="Passive Aggressive Regressor",
y_train_true=y_train,
y_train_pred=pa_pred_train
)
Passive Aggressive Regressor: predicting model... Passive Aggressive Regressor: predicted successfully. --- Passive Aggressive Regressor --- TEST MAE: 661.22 RMSE: 1323.48 R²: 0.8900 TRAIN MAE: 643.47 RMSE: 1238.61 R²: 0.9036 GAP RMSE gap: 84.87 R² gap: 0.0136
pa_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(pa_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import RandomizedSearchCV
pa_param_dist = {
"model__loss": ["epsilon_insensitive"],
"model__learning_rate": ["pa1", "pa2"],
"model__eta0": [0.001, 0.003, 0.005, 0.0085, 0.01, 0.03, 0.1, 0.3, 1.0, 3.0, 10.0],
"model__epsilon": [0.01, 0.03, 0.05, 0.1, 0.2, 0.3],
"model__penalty": [None, "l2", "l1", "elasticnet"],
"model__alpha": [0.00001, 0.0001, 0.001, 0.01],
"model__max_iter": [2000, 5000, 10000],
"model__tol": [0.00001, 0.0001, 0.001, 0.01],
"model__average": [False, True]
}
pa_search = RandomizedSearchCV(
pa_reg,
param_distributions=pa_param_dist,
n_iter=40,
scoring=SCORING,
cv=KFOLD_10,
n_jobs=N_JOBS,
verbose=VERBOSE,
random_state=RANDOM_STATE,
error_score=ERROR_SCORE
)
print("Passive Aggressive Regressor: running randomized search...")
pa_search.fit(X_train, y_train)
print("Passive Aggressive Regressor: randomized search completed successfully.")
else:
print("Passive Aggressive Regressor: loading search object...")
pa_search = loaded_searches["passive_aggressive"]
print("Passive Aggressive Regressor: search object loaded successfully.")
Passive Aggressive Regressor: running randomized search... Fitting 10 folds for each of 40 candidates, totalling 400 fits Passive Aggressive Regressor: randomized search completed successfully.
pa_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(pa_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:06:55
# Best parameters
pa_search.best_params_
{'model__tol': 1e-05,
'model__penalty': 'elasticnet',
'model__max_iter': 2000,
'model__loss': 'epsilon_insensitive',
'model__learning_rate': 'pa2',
'model__eta0': 0.001,
'model__epsilon': 0.01,
'model__average': True,
'model__alpha': 1e-05}
# Best model
pa_best_tuning = pa_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Passive Aggressive Regressor: predicting best model...")
pa_best_pred_train = pa_best_tuning.predict(X_train)
pa_best_pred_test = pa_best_tuning.predict(X_test)
print("Passive Aggressive Regressor: best model predicted successfully.\n")
else:
print("Passive Aggressive Regressor: loading best predicted model...")
pa_best_pred_train = loaded_predictions["pa_best_pred_train_file"]
pa_best_pred_test = loaded_predictions["pa_best_pred_test_file"]
print("Passive Aggressive Regressor: best model loaded successfully.\n")
pa_best_results = evaluate_regression(
y_test,
pa_best_pred_test,
"Best Passive Aggressive Regressor",
y_train_true=y_train,
y_train_pred=pa_best_pred_train
)
Passive Aggressive Regressor: predicting best model... Passive Aggressive Regressor: best model predicted successfully. --- Best Passive Aggressive Regressor --- TEST MAE: 749.59 RMSE: 1193.47 R²: 0.9105 TRAIN MAE: 730.42 RMSE: 1116.38 R²: 0.9217 GAP RMSE gap: 77.09 R² gap: 0.0112
pa_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(pa_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:00
Test set prediction comparison¶
pa_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(pa_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
pa_sample_pred = pd.Series(pa_best_pred_test, index=X_test.index).loc[sample_idx]
pa_results_comparison["Error"] = pa_results_comparison["Prediction"] - pa_results_comparison["Original Label"]
pa_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 184.08 | 1015.91 | 913.83 | 1536.16 | 1976.47 | 1910.84 | 2897.51 | 3470.87 | 3960.37 | 4576.95 | 4768.57 | 6877.09 | 12425.13 | 10115.73 | 11131.02 |
| Error | -386.92 | 251.91 | 86.83 | 388.16 | 620.47 | 510.84 | 228.51 | 648.87 | 17.37 | 421.95 | 364.57 | -792.91 | 3597.13 | -1745.27 | -5538.98 |
Add formatted timing and model type to the results¶
pa_results["Model_type"] = "Online linear"
pa_results["Fit_time"] = str(timedelta(seconds=int(pa_fit_time)))
pa_results["Pred_time"] = str(timedelta(seconds=int(pa_predict_time)))
pa_results["Tun_fit_time"] = str(timedelta(seconds=int(pa_tuning_fit_time)))
pa_results["Tun_pred_time"] = str(timedelta(seconds=int(pa_tuning_predict_time)))
pa_results["Full_model_time"] = str(timedelta(seconds=int(pa_fit_time + pa_predict_time + pa_tuning_fit_time + pa_tuning_predict_time)))
pa_results["Tun_MAE"] = pa_best_results["MAE"]
pa_results["Tun_RMSE"] = pa_best_results["RMSE"]
pa_results["Tun_R2"] = pa_best_results["R2"]
pa_results["Tun_Train_MAE"] = pa_best_results["Train_MAE"]
pa_results["Tun_Train_RMSE"] = pa_best_results["Train_RMSE"]
pa_results["Tun_Train_R2"] = pa_best_results["Train_R2"]
GLM / distribution-based linear models¶
Poisson Regressor¶
The PoissonRegressor model is a generalized linear Regressor model designed for non-negative target values with Poisson-like distribution.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.linear_model import PoissonRegressor
poisson_param = {
# Most important parameters: alpha
"alpha": 10.0, # Default: 1.0 -> Larger alpha = stronger regularization
"max_iter": 300, # Default: 100 -> Increase if convergence warning appears
"tol": 0.001 # Default: 1e-4 -> Smaller value means stricter stopping
}
poisson_reg = Pipeline([
("preprocessing", scaled_preprocessing),
("model", PoissonRegressor(**poisson_param))
])
print("Poisson Regressor: fitting model...")
poisson_reg.fit(X_train, y_train)
print("Poisson Regressor: fitted successfully.")
else:
print("Poisson Regressor: loading fitted model...")
poisson_reg = loaded_fitted_base_models["poisson"]
print("Poisson Regressor: loaded successfully.")
Poisson Regressor: fitting model... Poisson Regressor: fitted successfully.
poisson_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(poisson_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:03
Features created by Poisson Regressor Model¶
# Intercept of the Poisson regression model
print("Starting value of the model:", poisson_reg.named_steps["model"].intercept_)
Starting value of the model: 7.754143617050059
# Coefficients learned for each transformed feature
print("Weights for each feature:\n", poisson_reg.named_steps["model"].coef_)
Weights for each feature: [-0.36 0.08 0.01 1.33 0.02 0.06 -0.11 0.01 0.06 -0.01 0.05 0.21 0.16 0.13 0.05 -0.06 -0.17 -0.32 -0.65 0.37 -0.11 -0.29 0.11 0.04 0.29 0.24]
# Number of input features used by the model
print("Number of input features:", poisson_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", poisson_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Poisson Regressor: predicting model...")
poisson_pred_train = poisson_reg.predict(X_train)
poisson_pred_test = poisson_reg.predict(X_test)
print("Poisson Regressor: predicted successfully.\n")
else:
print("Poisson Regressor: loading predicted model...")
poisson_pred_train = loaded_predictions["poisson_pred_train_file"]
poisson_pred_test = loaded_predictions["poisson_pred_test_file"]
print("Poisson Regressor: loaded successfully.\n")
poisson_results = evaluate_regression(
y_test,
poisson_pred_test,
model_name="Poisson Regressor",
y_train_true=y_train,
y_train_pred=poisson_pred_train
)
Poisson Regressor: predicting model... Poisson Regressor: predicted successfully. --- Poisson Regressor --- TEST MAE: 455.73 RMSE: 907.80 R²: 0.9482 TRAIN MAE: 446.86 RMSE: 874.69 R²: 0.9519 GAP RMSE gap: 33.12 R² gap: 0.0037
poisson_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(poisson_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import GridSearchCV
poisson_param_grid = {
"model__alpha": [0.3, 0.5, 0.8, 1.0, 1.2, 1.5, 2.0, 2.5, 3.0],
"model__max_iter": [300],
"model__tol": [0.00005, 0.0001, 0.0002]
}
poisson_search = GridSearchCV(
poisson_reg,
param_grid=poisson_param_grid,
scoring=SCORING,
cv=KFOLD_10,
n_jobs=N_JOBS,
verbose=VERBOSE,
error_score=ERROR_SCORE
)
print("Poisson Regressor: running grid search...")
poisson_search.fit(X_train, y_train)
print("Poisson Regressor: grid search completed successfully.")
else:
print("Poisson Regressor: loading search object...")
poisson_search = loaded_searches["poisson"]
print("Poisson Regressor: search object loaded successfully.")
Poisson Regressor: running grid search... Fitting 10 folds for each of 27 candidates, totalling 270 fits Poisson Regressor: grid search completed successfully.
poisson_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(poisson_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:01:38
# Best parameters
poisson_search.best_params_
{'model__alpha': 3.0, 'model__max_iter': 300, 'model__tol': 5e-05}
# Best model
poisson_best_tuning = poisson_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Poisson Regressor: predicting best model...")
poisson_best_pred_train = poisson_best_tuning.predict(X_train)
poisson_best_pred_test = poisson_best_tuning.predict(X_test)
print("Poisson Regressor: best model predicted successfully.\n")
else:
print("Poisson Regressor: loading best predicted model...")
poisson_best_pred_train = loaded_predictions["poisson_best_pred_train_file"]
poisson_best_pred_test = loaded_predictions["poisson_best_pred_test_file"]
print("Poisson Regressor: best model loaded successfully.\n")
poisson_best_results = evaluate_regression(
y_test,
poisson_best_pred_test,
"Best Poisson Regressor",
y_train_true=y_train,
y_train_pred=poisson_best_pred_train
)
Poisson Regressor: predicting best model... Poisson Regressor: best model predicted successfully. --- Best Poisson Regressor --- TEST MAE: 444.73 RMSE: 875.40 R²: 0.9519 TRAIN MAE: 438.19 RMSE: 848.85 R²: 0.9547 GAP RMSE gap: 26.56 R² gap: 0.0029
poisson_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(poisson_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:00
Test set prediction comparison¶
poisson_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(poisson_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
poisson_sample_pred = pd.Series(poisson_best_pred_test, index=X_test.index).loc[sample_idx]
poisson_results_comparison["Error"] = poisson_results_comparison["Prediction"] - poisson_results_comparison["Original Label"]
poisson_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 734.15 | 839.24 | 987.88 | 1151.78 | 1568.63 | 1324.95 | 3159.49 | 3231.22 | 3660.59 | 3557.17 | 4267.82 | 6726.54 | 13142.66 | 11892.00 | 14623.60 |
| Error | 163.15 | 75.24 | 160.88 | 3.78 | 212.63 | -75.05 | 490.49 | 409.22 | -282.41 | -597.83 | -136.18 | -943.46 | 4314.66 | 31.00 | -2046.40 |
Add formatted timing and model type to the results¶
poisson_results["Model_type"] = "GLM / distribution-based linear"
poisson_results["Fit_time"] = str(timedelta(seconds=int(poisson_fit_time)))
poisson_results["Pred_time"] = str(timedelta(seconds=int(poisson_predict_time)))
poisson_results["Tun_fit_time"] = str(timedelta(seconds=int(poisson_tuning_fit_time)))
poisson_results["Tun_pred_time"] = str(timedelta(seconds=int(poisson_tuning_predict_time)))
poisson_results["Full_model_time"] = str(timedelta(seconds=int(poisson_fit_time + poisson_predict_time + poisson_tuning_fit_time + poisson_tuning_predict_time)))
poisson_results["Tun_MAE"] = poisson_best_results["MAE"]
poisson_results["Tun_RMSE"] = poisson_best_results["RMSE"]
poisson_results["Tun_R2"] = poisson_best_results["R2"]
poisson_results["Tun_Train_MAE"] = poisson_best_results["Train_MAE"]
poisson_results["Tun_Train_RMSE"] = poisson_best_results["Train_RMSE"]
poisson_results["Tun_Train_R2"] = poisson_best_results["Train_R2"]
Gamma Regressor¶
The GammaRegressor model is a generalized linear Regressor model designed for strictly positive target values with Gamma-like distribution.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.linear_model import GammaRegressor
gamma_param = {
# Most important parameters: alpha
"alpha": 1.0, # Default: 1.0 -> Larger alpha = stronger regularization
"max_iter": 100, # Default: 100 -> Increase if convergence warning appears
"tol": 0.0001 # Default: 1e-4 -> Smaller value means stricter stopping
}
gamma_reg = Pipeline([
("preprocessing", scaled_preprocessing),
("model", GammaRegressor(**gamma_param))
])
print("Gamma Regressor: fitting model...")
gamma_reg.fit(X_train, y_train)
print("Gamma Regressor: fitted successfully.")
else:
print("Gamma Regressor: loading fitted model...")
gamma_reg = loaded_fitted_base_models["gamma"]
print("Gamma Regressor: loaded successfully.")
Gamma Regressor: fitting model... Gamma Regressor: fitted successfully.
gamma_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(gamma_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:00
Features created by Gamma Regressor Model¶
# Intercept of the Gamma regression model
print("Starting value of the model:", gamma_reg.named_steps["model"].intercept_)
Starting value of the model: 7.859718756895826
# Coefficients learned for each transformed feature
print("Weights for each feature:\n", gamma_reg.named_steps["model"].coef_)
Weights for each feature: [ 0.2 -0. 0.01 0.19 0.19 0.18 -0. -0. 0. 0. -0. 0.01 0. 0.01 0.01 -0.01 -0.01 -0.01 -0.01 0. -0.01 -0.01 0.01 0. 0. 0.01]
# Number of input features used by the model
print("Number of input features:", gamma_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", gamma_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Gamma Regressor: predicting model...")
gamma_pred_train = gamma_reg.predict(X_train)
gamma_pred_test = gamma_reg.predict(X_test)
print("Gamma Regressor: predicted successfully.\n")
else:
print("Gamma Regressor: loading predicted model...")
gamma_pred_train = loaded_predictions["gamma_pred_train_file"]
gamma_pred_test = loaded_predictions["gamma_pred_test_file"]
print("Gamma Regressor: loaded successfully.\n")
gamma_results = evaluate_regression(
y_test,
gamma_pred_test,
model_name="Gamma Regressor",
y_train_true=y_train,
y_train_pred=gamma_pred_train
)
Gamma Regressor: predicting model... Gamma Regressor: predicted successfully. --- Gamma Regressor --- TEST MAE: 1097.17 RMSE: 3580.22 R²: 0.1947 TRAIN MAE: 4094.48 RMSE: 625005.31 R²: -24544.1262 GAP RMSE gap: -621425.09 R² gap: -24544.3209
gamma_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(gamma_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import GridSearchCV
gamma_param_grid = {
"model__alpha": [0.01, 1.0, 10.0, 100.0],
"model__max_iter": [100],
"model__tol": [10.0, 1.0, 0.1, 0.01, 0.001, 0.0001]
}
gamma_search = GridSearchCV(
gamma_reg, # The pipeline model to tune
param_grid=gamma_param_grid, # Default: required -> Dictionary with parameter values to test - Parameter values to test
scoring=SCORING, # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
cv=KFOLD_10, # Default: None -> Common values: 3, 5, 10 - Number of cross-validation folds
n_jobs=N_JOBS, # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
verbose=VERBOSE, # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
error_score=ERROR_SCORE # Default: np.nan -> Common values: np.nan, "raise" - If a fit fails during CV/search, raise the error immediately instead of silently assigning NaN to that candidate
)
print("Gamma Regressor: running grid search...")
gamma_search.fit(X_train, y_train)
print("Gamma Regressor: grid search completed successfully.")
else:
print("Gamma Regressor: loading search object...")
gamma_search = loaded_searches["gamma"]
print("Gamma Regressor: search object loaded successfully.")
Gamma Regressor: running grid search... Fitting 10 folds for each of 24 candidates, totalling 240 fits Gamma Regressor: grid search completed successfully.
gamma_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(gamma_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:00:11
# Best parameters
gamma_search.best_params_
{'model__alpha': 10.0, 'model__max_iter': 100, 'model__tol': 0.1}
# Best model
gamma_best_tuning = gamma_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Gamma Regressor: predicting best model...")
gamma_best_pred_train = gamma_best_tuning.predict(X_train)
gamma_best_pred_test = gamma_best_tuning.predict(X_test)
print("Gamma Regressor: best model predicted successfully.\n")
else:
print("Gamma Regressor: loading best predicted model...")
gamma_best_pred_train = loaded_predictions["gamma_best_pred_train_file"]
gamma_best_pred_test = loaded_predictions["gamma_best_pred_test_file"]
print("Gamma Regressor: best model loaded successfully.\n")
gamma_best_results = evaluate_regression(
y_test,
gamma_best_pred_test,
"Best Gamma Regressor",
y_train_true=y_train,
y_train_pred=gamma_best_pred_train
)
Gamma Regressor: predicting best model... Gamma Regressor: best model predicted successfully. --- Best Gamma Regressor --- TEST MAE: 2301.82 RMSE: 3228.21 R²: 0.3453 TRAIN MAE: 2304.66 RMSE: 3251.71 R²: 0.3356 GAP RMSE gap: -23.49 R² gap: -0.0097
gamma_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(gamma_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:00
Test set prediction comparison¶
gamma_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(gamma_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
gamma_sample_pred = pd.Series(gamma_best_pred_test, index=X_test.index).loc[sample_idx]
gamma_results_comparison["Error"] = gamma_results_comparison["Prediction"] - gamma_results_comparison["Original Label"]
gamma_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 2838.47 | 2732.86 | 2950.49 | 2887.57 | 2944.86 | 2944.05 | 4044.55 | 3605.20 | 3928.30 | 3847.35 | 4075.37 | 4189.67 | 5493.08 | 4803.32 | 4978.44 |
| Error | 2267.47 | 1968.86 | 2123.49 | 1739.57 | 1588.86 | 1544.05 | 1375.55 | 783.20 | -14.70 | -307.65 | -328.63 | -3480.33 | -3334.92 | -7057.68 | -11691.56 |
Add formatted timing and model type to the results¶
gamma_results["Model_type"] = "GLM / distribution-based linear"
gamma_results["Fit_time"] = str(timedelta(seconds=int(gamma_fit_time)))
gamma_results["Pred_time"] = str(timedelta(seconds=int(gamma_predict_time)))
gamma_results["Tun_fit_time"] = str(timedelta(seconds=int(gamma_tuning_fit_time)))
gamma_results["Tun_pred_time"] = str(timedelta(seconds=int(gamma_tuning_predict_time)))
gamma_results["Full_model_time"] = str(timedelta(seconds=int(gamma_fit_time + gamma_predict_time + gamma_tuning_fit_time + gamma_tuning_predict_time)))
gamma_results["Tun_MAE"] = gamma_best_results["MAE"]
gamma_results["Tun_RMSE"] = gamma_best_results["RMSE"]
gamma_results["Tun_R2"] = gamma_best_results["R2"]
gamma_results["Tun_Train_MAE"] = gamma_best_results["Train_MAE"]
gamma_results["Tun_Train_RMSE"] = gamma_best_results["Train_RMSE"]
gamma_results["Tun_Train_R2"] = gamma_best_results["Train_R2"]
Kernel / Distance-based models¶
SVR - Support Vector Regression (linear)¶
The linear SVR is a support vector regression model that tries to fit the data while allowing a small margin of error.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.svm import SVR
svr_linear_param = {
# Most important parameters: C, epsilon
"C": 10.0, # Default: 1.0 -> From 0.1 to 100.0 - smaller C → stronger regularization
"epsilon": 0.1, # Default: 0.1 -> From 0.01 to 1.0 - epsilon controls how much error is allowed without penalty
"tol": 0.001 # Default: 0.001 -> Smaller value means stricter stopping
}
svr_linear = Pipeline([
("preprocessing", scaled_preprocessing),
("model", SVR(kernel="linear", **svr_linear_param))
])
print("SVR (linear) Regressor: fitting model...")
svr_linear.fit(X_train, y_train)
print("SVR (linear) Regressor: fitted successfully.")
else:
print("SVR (linear) Regressor: loading fitted model...")
svr_linear = loaded_fitted_base_models["svr_linear"]
print("SVR (linear) Regressor: loaded successfully.")
SVR (linear) Regressor: fitting model... SVR (linear) Regressor: fitted successfully.
svr_linear_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(svr_linear_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:03:20
Features created by SVR (linear) Model¶
# Intercept of the linear SVR model
print("Starting value of the model:", svr_linear.named_steps["model"].intercept_)
Starting value of the model: [3365.11]
# Coefficients learned for each transformed feature
print("Weights for each feature:\n", svr_linear.named_steps["model"].coef_)
Weights for each feature:
[[ 5679.07 -60.08 -52.31 -903.73 -493.12 -547.71 -362.76 7.13
147.59 139.52 68.52 474.48 341. 283.76 214.57 22.6
-315.72 -1020.69 -1934.61 700.08 -12.47 -667.74 436.42 316.33
594.82 567.16]]
# Number of input features used by the model
print("Number of input features:", svr_linear.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", svr_linear.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("SVR (linear) Regressor: predicting model...")
svr_linear_pred_train = svr_linear.predict(X_train)
svr_linear_pred_test = svr_linear.predict(X_test)
print("SVR (linear) Regressor: predicted successfully.\n")
else:
print("SVR (linear) Regressor: loading predicted model...")
svr_linear_pred_train = loaded_predictions["svr_linear_pred_train_file"]
svr_linear_pred_test = loaded_predictions["svr_linear_pred_test_file"]
print("SVR (linear) Regressor: loaded successfully.\n")
svr_linear_results = evaluate_regression(
y_test,
svr_linear_pred_test,
model_name="SVR (linear)",
y_train_true=y_train,
y_train_pred=svr_linear_pred_train
)
SVR (linear) Regressor: predicting model... SVR (linear) Regressor: predicted successfully. --- SVR (linear) --- TEST MAE: 659.76 RMSE: 1330.26 R²: 0.8888 TRAIN MAE: 643.71 RMSE: 1254.48 R²: 0.9011 GAP RMSE gap: 75.78 R² gap: 0.0123
svr_linear_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(svr_linear_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:02:32
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import GridSearchCV
svr_linear_param_grid = {
"model__C": [1.0, 10.0, 50.0],
"model__epsilon": [0.05, 0.1, 0.2]
}
svr_linear_search = GridSearchCV(
svr_linear, # The pipeline model to tune
param_grid=svr_linear_param_grid, # Default: required -> Dictionary with parameter values to test - Parameter values to test
scoring=SCORING, # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
cv=KFOLD_3, # Default: None -> Common values: 3, 5, 10 - Number of cross-validation folds
n_jobs=N_JOBS, # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
verbose=VERBOSE, # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
error_score=ERROR_SCORE # Default: np.nan -> Common values: np.nan, "raise" - If a fit fails during CV/search, raise the error immediately instead of silently assigning NaN to that candidate
)
print("SVR (linear) Regressor: running grid search...")
svr_linear_search.fit(X_train, y_train)
print("SVR (linear) Regressor: grid search completed successfully.")
else:
print("SVR (linear) Regressor: loading search object...")
svr_linear_search = loaded_searches["svr_linear"]
print("SVR (linear) Regressor: search object loaded successfully.")
SVR (linear) Regressor: running grid search... Fitting 3 folds for each of 9 candidates, totalling 27 fits SVR (linear) Regressor: grid search completed successfully.
svr_linear_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(svr_linear_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:23:02
# Best parameters
svr_linear_search.best_params_
{'model__C': 50.0, 'model__epsilon': 0.1}
# Best model
svr_linear_best_tuning = svr_linear_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("SVR (linear) Regressor: predicting best model...")
svr_linear_best_pred_train = svr_linear_best_tuning.predict(X_train)
svr_linear_best_pred_test = svr_linear_best_tuning.predict(X_test)
print("SVR (linear) Regressor: predicted successfully.\n")
else:
print("SVR (linear) Regressor: loading best predicted model...")
svr_linear_best_pred_train = loaded_predictions["svr_linear_best_pred_train_file"]
svr_linear_best_pred_test = loaded_predictions["svr_linear_best_pred_test_file"]
print("SVR (linear) Regressor: loaded successfully.\n")
svr_linear_best_results = evaluate_regression(
y_test,
svr_linear_best_pred_test,
"Best SVR (linear)",
y_train_true=y_train,
y_train_pred=svr_linear_best_pred_train
)
SVR (linear) Regressor: predicting best model... SVR (linear) Regressor: predicted successfully. --- Best SVR (linear) --- TEST MAE: 657.20 RMSE: 1323.45 R²: 0.8900 TRAIN MAE: 638.97 RMSE: 1237.17 R²: 0.9038 GAP RMSE gap: 86.28 R² gap: 0.0139
svr_linear_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(svr_linear_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:02:32
Test set prediction comparison¶
svr_linear_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(svr_linear_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
svr_linear_sample_pred = pd.Series(svr_linear_best_pred_test, index=X_test.index).loc[sample_idx]
svr_linear_results_comparison["Error"] = svr_linear_results_comparison["Prediction"] - svr_linear_results_comparison["Original Label"]
svr_linear_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 487.12 | 801.09 | 989.37 | 1149.01 | 1378.53 | 1396.58 | 3267.48 | 2904.36 | 3805.16 | 4314.87 | 4570.36 | 6051.33 | 12029.63 | 9185.67 | 10131.90 |
| Error | -83.88 | 37.09 | 162.37 | 1.01 | 22.53 | -3.42 | 598.48 | 82.36 | -137.84 | 159.87 | 166.36 | -1618.67 | 3201.63 | -2675.33 | -6538.10 |
Add formatted timing and model type to the results¶
svr_linear_results["Model_type"] = "Kernel / Distance-based"
svr_linear_results["Fit_time"] = str(timedelta(seconds=int(svr_linear_fit_time)))
svr_linear_results["Pred_time"] = str(timedelta(seconds=int(svr_linear_predict_time)))
svr_linear_results["Tun_fit_time"] = str(timedelta(seconds=int(svr_linear_tuning_fit_time)))
svr_linear_results["Tun_pred_time"] = str(timedelta(seconds=int(svr_linear_tuning_predict_time)))
svr_linear_results["Full_model_time"] = str(timedelta(seconds=int(svr_linear_fit_time + svr_linear_predict_time + svr_linear_tuning_fit_time + svr_linear_tuning_predict_time)))
svr_linear_results["Tun_MAE"] = svr_linear_best_results["MAE"]
svr_linear_results["Tun_RMSE"] = svr_linear_best_results["RMSE"]
svr_linear_results["Tun_R2"] = svr_linear_best_results["R2"]
svr_linear_results["Tun_Train_MAE"] = svr_linear_best_results["Train_MAE"]
svr_linear_results["Tun_Train_RMSE"] = svr_linear_best_results["Train_RMSE"]
svr_linear_results["Tun_Train_R2"] = svr_linear_best_results["Train_R2"]
SVR - Support Vector Regression (RBF)¶
The RBF SVR is a support vector regression model that can learn more complex non-linear relationships in the data.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.svm import SVR
svr_rbf_param = {
# Most important parameters: C, gamma, epsilon
"C": 10.0, # Default: 1.0 -> From 0.1 to 100.0 - smaller C → stronger regularization
"gamma": "scale", # Default: "scale" -> Common alternative: "auto" - larger gamma → more complex model
"epsilon": 0.1, # Default: 0.1 -> From 0.01 to 1.0 - epsilon controls how much error is allowed without penalty
"tol": 0.001 # Default: 0.001 -> Smaller value means stricter stopping
}
svr_rbf = Pipeline([
("preprocessing", scaled_preprocessing),
("model", SVR(kernel="rbf", **svr_rbf_param))
])
print("SVR (RBF) Regressor: fitting model...")
svr_rbf.fit(X_train, y_train)
print("SVR (RBF) Regressor: fitted successfully.")
else:
print("SVR (RBF) Regressor: loading fitted model...")
svr_rbf = loaded_fitted_base_models["svr_rbf"]
print("SVR (RBF) Regressor: loaded successfully.")
SVR (RBF) Regressor: fitting model... SVR (RBF) Regressor: fitted successfully.
svr_rbf_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(svr_rbf_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:03:39
Features created by SVR (RBF) Model¶
# Number of input features used by the model
print("Number of input features:", svr_rbf.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", svr_rbf.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("SVR (RBF) Regressor: predicting model...")
svr_rbf_pred_train = svr_rbf.predict(X_train)
svr_rbf_pred_test = svr_rbf.predict(X_test)
print("SVR (RBF) Regressor: predicted successfully.\n")
else:
print("SVR (RBF) Regressor: loading predicted model...")
svr_rbf_pred_train = loaded_predictions["svr_rbf_pred_train_file"]
svr_rbf_pred_test = loaded_predictions["svr_rbf_pred_test_file"]
print("SVR (RBF) Regressor: loaded successfully.\n")
svr_rbf_results = evaluate_regression(
y_test,
svr_rbf_pred_test,
model_name="SVR (RBF)",
y_train_true=y_train,
y_train_pred=svr_rbf_pred_train
)
SVR (RBF) Regressor: predicting model... SVR (RBF) Regressor: predicted successfully. --- SVR (RBF) --- TEST MAE: 663.55 RMSE: 1456.31 R²: 0.8668 TRAIN MAE: 642.81 RMSE: 1390.55 R²: 0.8785 GAP RMSE gap: 65.76 R² gap: 0.0117
svr_rbf_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(svr_rbf_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:10:09
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import GridSearchCV
svr_rbf_param_grid = {
"model__C": [1000.0, 1100.0, 1200.0],
"model__gamma": ["scale"],
"model__epsilon": [0.15, 0.17, 0.2],
"model__tol": [0.01]
}
svr_rbf_search = GridSearchCV(
svr_rbf, # The pipeline model to tune
param_grid=svr_rbf_param_grid, # Dictionary with parameter values to test
scoring=SCORING, # Evaluation metric
cv=KFOLD_3, # Smaller outer CV to reduce cost
n_jobs=N_JOBS, # Parallel jobs
verbose=VERBOSE, # Show progress during fitting
error_score=ERROR_SCORE # Raise error immediately if a fit fails
)
print("SVR (RBF) Regressor: running grid search...")
svr_rbf_search.fit(X_train, y_train)
print("SVR (RBF) Regressor: grid search completed successfully.")
else:
print("SVR (RBF) Regressor: loading search object...")
svr_rbf_search = loaded_searches["svr_rbf"]
print("SVR (RBF) Regressor: search object loaded successfully.")
SVR (RBF) Regressor: running grid search... Fitting 3 folds for each of 9 candidates, totalling 27 fits SVR (RBF) Regressor: grid search completed successfully.
svr_rbf_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(svr_rbf_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:39:21
# Best parameters
svr_rbf_search.best_params_
{'model__C': 1200.0,
'model__epsilon': 0.2,
'model__gamma': 'scale',
'model__tol': 0.01}
# Best model
svr_rbf_best_tuning = svr_rbf_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("SVR (RBF) Regressor: predicting best model...")
svr_rbf_best_pred_train = svr_rbf_best_tuning.predict(X_train)
svr_rbf_best_pred_test = svr_rbf_best_tuning.predict(X_test)
print("SVR (RBF) Regressor: predicted successfully.\n")
else:
print("SVR (RBF) Regressor: loading best predicted model...")
svr_rbf_best_pred_train = loaded_predictions["svr_rbf_best_pred_train_file"]
svr_rbf_best_pred_test = loaded_predictions["svr_rbf_best_pred_test_file"]
print("SVR (RBF) Regressor: loaded successfully.\n")
svr_rbf_best_results = evaluate_regression(
y_test,
svr_rbf_best_pred_test,
"Best SVR (RBF)",
y_train_true=y_train,
y_train_pred=svr_rbf_best_pred_train
)
SVR (RBF) Regressor: predicting best model... SVR (RBF) Regressor: predicted successfully. --- Best SVR (RBF) --- TEST MAE: 318.52 RMSE: 667.51 R²: 0.9720 TRAIN MAE: 289.53 RMSE: 595.36 R²: 0.9777 GAP RMSE gap: 72.15 R² gap: 0.0057
svr_rbf_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(svr_rbf_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:10:08
Test set prediction comparison¶
svr_rbf_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(svr_rbf_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
svr_rbf_sample_pred = pd.Series(svr_rbf_best_pred_test, index=X_test.index).loc[sample_idx]
svr_rbf_results_comparison["Error"] = svr_rbf_results_comparison["Prediction"] - svr_rbf_results_comparison["Original Label"]
svr_rbf_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 614.66 | 738.00 | 943.57 | 1165.91 | 1417.14 | 1348.54 | 3307.70 | 3201.50 | 3755.62 | 3922.92 | 4465.54 | 7208.28 | 12590.64 | 11901.82 | 14050.26 |
| Error | 43.66 | -26.00 | 116.57 | 17.91 | 61.14 | -51.46 | 638.70 | 379.50 | -187.38 | -232.08 | 61.54 | -461.72 | 3762.64 | 40.82 | -2619.74 |
Add formatted timing and model type to the results¶
svr_rbf_results["Model_type"] = "Kernel / Distance-based"
svr_rbf_results["Fit_time"] = str(timedelta(seconds=int(svr_rbf_fit_time)))
svr_rbf_results["Pred_time"] = str(timedelta(seconds=int(svr_rbf_predict_time)))
svr_rbf_results["Tun_fit_time"] = str(timedelta(seconds=int(svr_rbf_tuning_fit_time)))
svr_rbf_results["Tun_pred_time"] = str(timedelta(seconds=int(svr_rbf_tuning_predict_time)))
svr_rbf_results["Full_model_time"] = str(timedelta(seconds=int(svr_rbf_fit_time + svr_rbf_predict_time + svr_rbf_tuning_fit_time + svr_rbf_tuning_predict_time)))
svr_rbf_results["Tun_MAE"] = svr_rbf_best_results["MAE"]
svr_rbf_results["Tun_RMSE"] = svr_rbf_best_results["RMSE"]
svr_rbf_results["Tun_R2"] = svr_rbf_best_results["R2"]
svr_rbf_results["Tun_Train_MAE"] = svr_rbf_best_results["Train_MAE"]
svr_rbf_results["Tun_Train_RMSE"] = svr_rbf_best_results["Train_RMSE"]
svr_rbf_results["Tun_Train_R2"] = svr_rbf_best_results["Train_R2"]
KNeighbors Regressor¶
The KNeighborsRegressor predicts the target value based on the values of the nearest training examples.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.neighbors import KNeighborsRegressor
knn_param = {
# Most important parameters: n_neighbors, weights, metric
"n_neighbors": 7, # Default: 5 -> Often worth trying values between 3 and 15
"weights": "distance", # Default: "uniform" -> Common alternative: "distance"
"metric": "minkowski", # Default: "minkowski" -> Common alternatives: "euclidean", "manhattan"
"p": 2 # Default: 2 -> 1 = Manhattan distance, 2 = Euclidean distance
}
knn_reg = Pipeline([
("preprocessing", scaled_preprocessing),
("model", KNeighborsRegressor(**knn_param))
])
print("KNeighbors Regressor: fitting model...")
knn_reg.fit(X_train, y_train)
print("KNeighbors Regressor: fitted successfully.")
else:
print("KNeighbors Regressor: loading fitted model...")
knn_reg = loaded_fitted_base_models["kneighbors"]
print("KNeighbors Regressor: loaded successfully.")
KNeighbors Regressor: fitting model... KNeighbors Regressor: fitted successfully.
knn_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(knn_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:00
Features created by KNeighbors Regressor Model¶
# Number of input features used by the model
print("Number of input features:", knn_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", knn_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("KNeighbors Regressor: predicting model...")
knn_pred_train = knn_reg.predict(X_train)
knn_pred_test = knn_reg.predict(X_test)
print("KNeighbors Regressor: predicted successfully.\n")
else:
print("KNeighbors Regressor: loading predicted model...")
knn_pred_train = loaded_predictions["knn_pred_train_file"]
knn_pred_test = loaded_predictions["knn_pred_test_file"]
print("KNeighbors Regressor: loaded successfully.\n")
knn_results = evaluate_regression(
y_test,
knn_pred_test,
model_name="KNeighbors Regressor",
y_train_true=y_train,
y_train_pred=knn_pred_train
)
KNeighbors Regressor: predicting model... KNeighbors Regressor: predicted successfully. --- KNeighbors Regressor --- TEST MAE: 387.51 RMSE: 792.30 R²: 0.9606 TRAIN MAE: 0.47 RMSE: 8.85 R²: 1.0000 GAP RMSE gap: 783.45 R² gap: 0.0394
knn_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(knn_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:07
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import GridSearchCV
knn_param_grid = {
"model__n_neighbors": [11, 15, 21, 31],
"model__weights": ["uniform"],
"model__p": [1, 2]
}
knn_search = GridSearchCV(
knn_reg,
param_grid=knn_param_grid,
scoring=SCORING,
cv=KFOLD_10,
n_jobs=N_JOBS,
verbose=VERBOSE,
error_score=ERROR_SCORE
)
print("KNeighbors Regressor: running grid search...")
knn_search.fit(X_train, y_train)
print("KNeighbors Regressor: grid search completed successfully.")
else:
print("KNeighbors Regressor: loading search object...")
knn_search = loaded_searches["kneighbors"]
print("KNeighbors Regressor: search object loaded successfully.")
KNeighbors Regressor: running grid search... Fitting 10 folds for each of 8 candidates, totalling 80 fits KNeighbors Regressor: grid search completed successfully.
knn_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(knn_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:01:08
# Best parameters
knn_search.best_params_
{'model__n_neighbors': 11, 'model__p': 1, 'model__weights': 'uniform'}
# Best model
knn_best_tuning = knn_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("KNeighbors Regressor: predicting best model...")
knn_best_pred_train = knn_best_tuning.predict(X_train)
knn_best_pred_test = knn_best_tuning.predict(X_test)
print("KNeighbors Regressor: best model predicted successfully.\n")
else:
print("KNeighbors Regressor: loading best predicted model...")
knn_best_pred_train = loaded_predictions["knn_best_pred_train_file"]
knn_best_pred_test = loaded_predictions["knn_best_pred_test_file"]
print("KNeighbors Regressor: best model loaded successfully.\n")
knn_best_results = evaluate_regression(
y_test,
knn_best_pred_test,
"Best KNeighbors Regressor",
y_train_true=y_train,
y_train_pred=knn_best_pred_train
)
KNeighbors Regressor: predicting best model... KNeighbors Regressor: best model predicted successfully. --- Best KNeighbors Regressor --- TEST MAE: 384.46 RMSE: 791.59 R²: 0.9606 TRAIN MAE: 332.91 RMSE: 662.87 R²: 0.9724 GAP RMSE gap: 128.72 R² gap: 0.0118
knn_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(knn_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:27
Test set prediction comparison¶
knn_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(knn_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
knn_sample_pred = pd.Series(knn_best_pred_test, index=X_test.index).loc[sample_idx]
knn_results_comparison["Error"] = knn_results_comparison["Prediction"] - knn_results_comparison["Original Label"]
knn_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 657.82 | 876.36 | 956.64 | 1057.64 | 1298.09 | 1433.64 | 3709.73 | 2676.18 | 4002.09 | 4285.91 | 4417.91 | 6788.91 | 11807.45 | 12141.91 | 12406.27 |
| Error | 86.82 | 112.36 | 129.64 | -90.36 | -57.91 | 33.64 | 1040.73 | -145.82 | 59.09 | 130.91 | 13.91 | -881.09 | 2979.45 | 280.91 | -4263.73 |
Add formatted timing and model type to the results¶
knn_results["Model_type"] = "Kernel / Distance-based"
knn_results["Fit_time"] = str(timedelta(seconds=int(knn_fit_time)))
knn_results["Pred_time"] = str(timedelta(seconds=int(knn_predict_time)))
knn_results["Tun_fit_time"] = str(timedelta(seconds=int(knn_tuning_fit_time)))
knn_results["Tun_pred_time"] = str(timedelta(seconds=int(knn_tuning_predict_time)))
knn_results["Full_model_time"] = str(timedelta(seconds=int(knn_fit_time + knn_predict_time + knn_tuning_fit_time + knn_tuning_predict_time)))
knn_results["Tun_MAE"] = knn_best_results["MAE"]
knn_results["Tun_RMSE"] = knn_best_results["RMSE"]
knn_results["Tun_R2"] = knn_best_results["R2"]
knn_results["Tun_Train_MAE"] = knn_best_results["Train_MAE"]
knn_results["Tun_Train_RMSE"] = knn_best_results["Train_RMSE"]
knn_results["Tun_Train_R2"] = knn_best_results["Train_R2"]
NuSVR Regressor¶
The NuSVR model is a support vector Regressor that uses the parameter nu to control the number of support vectors and training errors.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.svm import NuSVR
nusvr_param = {
# Most important parameters: nu, C, gamma
"nu": 0.5, # Default: 0.5 -> Controls support vectors / training errors
"C": 1.0, # Default: 1.0 -> Larger C = weaker regularization
"kernel": "rbf", # Default: "rbf" -> Common alternatives: "linear", "poly"
"gamma": "scale", # Default: "scale" -> Important for non-linear kernels
"tol": 0.001 # Default: 0.001 -> Smaller value means stricter stopping
}
nusvr_reg = Pipeline([
("preprocessing", scaled_preprocessing),
("model", NuSVR(**nusvr_param))
])
print("NuSVR Regressor: fitting model...")
nusvr_reg.fit(X_train, y_train)
print("NuSVR Regressor: fitted successfully.")
else:
print("NuSVR Regressor: loading fitted model...")
nusvr_reg = loaded_fitted_base_models["nusvr"]
print("NuSVR Regressor: loaded successfully.")
NuSVR Regressor: fitting model... NuSVR Regressor: fitted successfully.
nusvr_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(nusvr_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:04:46
Features created by NuSVR Regressor Model¶
# Intercept of the NuSVR model
print("Starting value of the model:", nusvr_reg.named_steps["model"].intercept_)
Starting value of the model: [3644.33]
# Dual coefficients learned by the model
print("Dual coefficients:\n", nusvr_reg.named_steps["model"].dual_coef_)
Dual coefficients: [[-1. -1. -1. ... -1. -1. 1.]]
# Number of support vectors used by the model
print("Number of support vectors:", len(nusvr_reg.named_steps["model"].support_))
Number of support vectors: 21589
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", nusvr_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("NuSVR Regressor: predicting model...")
nusvr_pred_train = nusvr_reg.predict(X_train)
nusvr_pred_test = nusvr_reg.predict(X_test)
print("NuSVR Regressor: predicted successfully.\n")
else:
print("NuSVR Regressor: loading predicted model...")
nusvr_pred_train = loaded_predictions["nusvr_pred_train_file"]
nusvr_pred_test = loaded_predictions["nusvr_pred_test_file"]
print("NuSVR Regressor: loaded successfully.\n")
nusvr_results = evaluate_regression(
y_test,
nusvr_pred_test,
model_name="NuSVR Regressor",
y_train_true=y_train,
y_train_pred=nusvr_pred_train
)
NuSVR Regressor: predicting model... NuSVR Regressor: predicted successfully. --- NuSVR Regressor --- TEST MAE: 1413.92 RMSE: 2766.31 R²: 0.5192 TRAIN MAE: 1409.10 RMSE: 2750.32 R²: 0.5247 GAP RMSE gap: 15.99 R² gap: 0.0055
nusvr_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(nusvr_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:04:59
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import GridSearchCV
nusvr_param_grid = {
"model__nu": [0.5],
"model__C": [30.0, 50.0, 70.0],
"model__gamma": [0.05, 0.1, 0.2],
"model__tol": [0.001]
}
nusvr_search = GridSearchCV(
nusvr_reg, # The pipeline model to tune
param_grid=nusvr_param_grid, # Default: required -> Dictionary with parameter values to test - Parameter values to test
scoring=SCORING, # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
cv=KFOLD_3, # Smaller CV is safer because kernel SVM models can be slow
n_jobs=N_JOBS, # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
verbose=VERBOSE, # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
error_score=ERROR_SCORE # Default: np.nan -> Common values: np.nan, "raise" - If a fit fails during CV/search, raise the error immediately instead of silently assigning NaN to that candidate
)
print("NuSVR Regressor: running grid search...")
nusvr_search.fit(X_train, y_train)
print("NuSVR Regressor: grid search completed successfully.")
else:
print("NuSVR Regressor: loading search object...")
nusvr_search = loaded_searches["nusvr"]
print("NuSVR Regressor: search object loaded successfully.")
NuSVR Regressor: running grid search... Fitting 3 folds for each of 9 candidates, totalling 27 fits NuSVR Regressor: grid search completed successfully.
nusvr_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(nusvr_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:27:54
# Best parameters
nusvr_search.best_params_
{'model__C': 70.0, 'model__gamma': 0.1, 'model__nu': 0.5, 'model__tol': 0.001}
# Best model
nusvr_best_tuning = nusvr_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("NuSVR Regressor: predicting best model...")
nusvr_best_pred_train = nusvr_best_tuning.predict(X_train)
nusvr_best_pred_test = nusvr_best_tuning.predict(X_test)
print("NuSVR Regressor: predicted successfully.\n")
else:
print("NuSVR Regressor: loading best predicted model...")
nusvr_best_pred_train = loaded_predictions["nusvr_best_pred_train_file"]
nusvr_best_pred_test = loaded_predictions["nusvr_best_pred_test_file"]
print("NuSVR Regressor: loaded successfully.\n")
nusvr_best_results = evaluate_regression(
y_test,
nusvr_best_pred_test,
"Best NuSVR Regressor",
y_train_true=y_train,
y_train_pred=nusvr_best_pred_train
)
NuSVR Regressor: predicting best model... NuSVR Regressor: predicted successfully. --- Best NuSVR Regressor --- TEST MAE: 444.34 RMSE: 950.57 R²: 0.9432 TRAIN MAE: 421.54 RMSE: 872.67 R²: 0.9521 GAP RMSE gap: 77.90 R² gap: 0.0089
nusvr_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(nusvr_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:05:02
Test set prediction comparison¶
nusvr_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(nusvr_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
nusvr_sample_pred = pd.Series(nusvr_best_pred_test, index=X_test.index).loc[sample_idx]
nusvr_results_comparison["Error"] = nusvr_results_comparison["Prediction"] - nusvr_results_comparison["Original Label"]
nusvr_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 674.85 | 800.24 | 902.94 | 1122.29 | 1340.58 | 1312.83 | 2847.33 | 3390.48 | 3710.10 | 3800.84 | 4494.70 | 6610.73 | 12852.19 | 11147.69 | 11872.74 |
| Error | 103.85 | 36.24 | 75.94 | -25.71 | -15.42 | -87.17 | 178.33 | 568.48 | -232.90 | -354.16 | 90.70 | -1059.27 | 4024.19 | -713.31 | -4797.26 |
Add formatted timing and model type to the results¶
nusvr_results["Model_type"] = "Kernel / Distance-based"
nusvr_results["Fit_time"] = str(timedelta(seconds=int(nusvr_fit_time)))
nusvr_results["Pred_time"] = str(timedelta(seconds=int(nusvr_predict_time)))
nusvr_results["Tun_fit_time"] = str(timedelta(seconds=int(nusvr_tuning_fit_time)))
nusvr_results["Tun_pred_time"] = str(timedelta(seconds=int(nusvr_tuning_predict_time)))
nusvr_results["Full_model_time"] = str(timedelta(seconds=int(nusvr_fit_time + nusvr_predict_time + nusvr_tuning_fit_time + nusvr_tuning_predict_time)))
nusvr_results["Tun_MAE"] = nusvr_best_results["MAE"]
nusvr_results["Tun_RMSE"] = nusvr_best_results["RMSE"]
nusvr_results["Tun_R2"] = nusvr_best_results["R2"]
nusvr_results["Tun_Train_MAE"] = nusvr_best_results["Train_MAE"]
nusvr_results["Tun_Train_RMSE"] = nusvr_best_results["Train_RMSE"]
nusvr_results["Tun_Train_R2"] = nusvr_best_results["Train_R2"]
Tree-based models¶
Decision Tree Regressor¶
The DecisionTreeRegressor splits the data into smaller groups and makes predictions based on tree-like decision rules.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.ensemble import RandomForestRegressor
from sklearn.tree import DecisionTreeRegressor
dt_param = {
# Most important parameters: max_depth, min_samples_split, min_samples_leaf
"max_depth": 10, # Default: None -> Often worth trying values between 3 and 30
"min_samples_split": 5, # Default: 2 -> Increase to reduce overfitting
"min_samples_leaf": 2, # Default: 1 -> Increase to make the tree more stable
"max_features": None # Default: None -> Common alternatives: "sqrt", "log2"
}
dt_reg = Pipeline([
("preprocessing", tree_preprocessing),
("model", DecisionTreeRegressor(random_state=RANDOM_STATE, **dt_param))
])
print("Decision Tree Regressor: fitting model...")
dt_reg.fit(X_train, y_train)
print("Decision Tree Regressor: fitted successfully.")
else:
print("Decision Tree Regressor: loading fitted model...")
dt_reg = loaded_fitted_base_models["decision_tree"]
print("Decision Tree Regressor: loaded successfully.")
Decision Tree Regressor: fitting model... Decision Tree Regressor: fitted successfully.
dt_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(dt_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:00
Features created by Decision Tree Regressor Model¶
# Number of input features used by the model
print("Number of input features:", dt_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", dt_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Decision Tree Regressor: predicting model...")
dt_pred_train = dt_reg.predict(X_train)
dt_pred_test = dt_reg.predict(X_test)
print("Decision Tree Regressor: predicted successfully.\n")
else:
print("Decision Tree Regressor: loading predicted model...")
dt_pred_train = loaded_predictions["dt_pred_train_file"]
dt_pred_test = loaded_predictions["dt_pred_test_file"]
print("Decision Tree Regressor: loaded successfully.\n")
dt_results = evaluate_regression(
y_test,
dt_pred_test,
model_name="Decision Tree Regressor",
y_train_true=y_train,
y_train_pred=dt_pred_train
)
Decision Tree Regressor: predicting model... Decision Tree Regressor: predicted successfully. --- Decision Tree Regressor --- TEST MAE: 408.04 RMSE: 763.02 R²: 0.9634 TRAIN MAE: 366.48 RMSE: 681.08 R²: 0.9709 GAP RMSE gap: 81.94 R² gap: 0.0074
dt_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(dt_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import GridSearchCV
dt_param_grid = {
"model__max_depth": [5, 10, 15, 20, None],
"model__min_samples_split": [2, 5, 10, 20],
"model__min_samples_leaf": [1, 2, 4, 8],
"model__max_features": [None, "sqrt", "log2"]
}
dt_search = GridSearchCV(
dt_reg, # The pipeline model to tune
param_grid=dt_param_grid, # Default: required -> Dictionary with parameter values to test - Parameter values to test
scoring=SCORING, # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
cv=KFOLD_10, # Default: None -> Common values: 3, 5, 10 - Number of cross-validation folds
n_jobs=N_JOBS, # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
verbose=VERBOSE, # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
error_score=ERROR_SCORE # Default: np.nan -> Common values: np.nan, "raise" - If a fit fails during CV/search, raise the error immediately instead of silently assigning NaN to that candidate
)
print("Decision Tree Regressor: running grid search...")
dt_search.fit(X_train, y_train)
print("Decision Tree Regressor: grid search completed successfully.")
else:
print("Decision Tree Regressor: loading search object...")
dt_search = loaded_searches["decision_tree"]
print("Decision Tree Regressor: search object loaded successfully.")
Decision Tree Regressor: running grid search... Fitting 10 folds for each of 240 candidates, totalling 2400 fits Decision Tree Regressor: grid search completed successfully.
dt_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(dt_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:01:59
# Best parameters
dt_search.best_params_
{'model__max_depth': 15,
'model__max_features': None,
'model__min_samples_leaf': 4,
'model__min_samples_split': 20}
# Best model
dt_best_tuning = dt_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Decision Tree Regressor: predicting best model...")
dt_best_pred_train = dt_best_tuning.predict(X_train)
dt_best_pred_test = dt_best_tuning.predict(X_test)
print("Decision Tree Regressor: best model predicted successfully.\n")
else:
print("Decision Tree Regressor: loading best predicted model...")
dt_best_pred_train = loaded_predictions["dt_best_pred_train_file"]
dt_best_pred_test = loaded_predictions["dt_best_pred_test_file"]
print("Decision Tree Regressor: best model loaded successfully.\n")
dt_best_results = evaluate_regression(
y_test,
dt_best_pred_test,
"Best Decision Tree Regressor",
y_train_true=y_train,
y_train_pred=dt_best_pred_train
)
Decision Tree Regressor: predicting best model... Decision Tree Regressor: best model predicted successfully. --- Best Decision Tree Regressor --- TEST MAE: 329.24 RMSE: 641.31 R²: 0.9742 TRAIN MAE: 246.23 RMSE: 469.78 R²: 0.9861 GAP RMSE gap: 171.53 R² gap: 0.0120
dt_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(dt_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:00
Test set prediction comparison¶
dt_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(dt_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
dt_sample_pred = pd.Series(dt_best_pred_test, index=X_test.index).loc[sample_idx]
dt_results_comparison["Error"] = dt_results_comparison["Prediction"] - dt_results_comparison["Original Label"]
dt_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 546.63 | 864.97 | 938.22 | 1018.91 | 1369.25 | 1282.20 | 2991.08 | 2718.19 | 3480.71 | 4160.11 | 4586.06 | 7515.60 | 10683.18 | 11015.27 | 15943.77 |
| Error | -24.37 | 100.97 | 111.22 | -129.09 | 13.25 | -117.80 | 322.08 | -103.81 | -462.29 | 5.11 | 182.06 | -154.40 | 1855.18 | -845.73 | -726.23 |
Add formatted timing and model type to the results¶
dt_results["Model_type"] = "Tree-based"
dt_results["Fit_time"] = str(timedelta(seconds=int(dt_fit_time)))
dt_results["Pred_time"] = str(timedelta(seconds=int(dt_predict_time)))
dt_results["Tun_fit_time"] = str(timedelta(seconds=int(dt_tuning_fit_time)))
dt_results["Tun_pred_time"] = str(timedelta(seconds=int(dt_tuning_predict_time)))
dt_results["Full_model_time"] = str(timedelta(seconds=int(dt_fit_time + dt_predict_time + dt_tuning_fit_time + dt_tuning_predict_time)))
dt_results["Tun_MAE"] = dt_best_results["MAE"]
dt_results["Tun_RMSE"] = dt_best_results["RMSE"]
dt_results["Tun_R2"] = dt_best_results["R2"]
dt_results["Tun_Train_MAE"] = dt_best_results["Train_MAE"]
dt_results["Tun_Train_RMSE"] = dt_best_results["Train_RMSE"]
dt_results["Tun_Train_R2"] = dt_best_results["Train_R2"]
Tree-based ensemble models¶
Random Forest Regressor¶
The RandomForestRegressor combines many decision trees and averages their predictions to improve accuracy and reduce overfitting.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.ensemble import RandomForestRegressor
rf_param = {
# Most important parameters: n_estimators, max_depth, min_samples_split, min_samples_leaf
"n_estimators": 300, # Default: 100 -> Often worth trying values between 100 and 500
"max_depth": None, # Default: None -> Often worth trying values between 5 and 30
"min_samples_split": 2, # Default: 2 -> Increase to reduce overfitting
"min_samples_leaf": 1, # Default: 1 -> Increase to make the trees more stable
"max_features": "sqrt" # Default: 1.0 -> Common alternatives: "sqrt", "log2"
}
rf_reg = Pipeline([
("preprocessing", tree_preprocessing),
("model", RandomForestRegressor(random_state=RANDOM_STATE, **rf_param))
])
print("Random Forest Regressor: fitting model...")
rf_reg.fit(X_train, y_train)
print("Random Forest Regressor: fitted successfully.")
else:
print("Random Forest Regressor: loading fitted model...")
rf_reg = loaded_fitted_base_models["random_forest"]
print("Random Forest Regressor: loaded successfully.")
Random Forest Regressor: fitting model... Random Forest Regressor: fitted successfully.
rf_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(rf_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:40
Features created by Random Forest Regressor Model¶
# Number of input features used by the model
print("Number of input features:", rf_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", rf_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Random Forest Regressor: predicting model...")
rf_pred_train = rf_reg.predict(X_train)
rf_pred_test = rf_reg.predict(X_test)
print("Random Forest Regressor: predicted successfully.\n")
else:
print("Random Forest Regressor: loading predicted model...")
rf_pred_train = loaded_predictions["rf_pred_train_file"]
rf_pred_test = loaded_predictions["rf_pred_test_file"]
print("Random Forest Regressor: loaded successfully.\n")
rf_results = evaluate_regression(
y_test,
rf_pred_test,
model_name="Random Forest Regressor",
y_train_true=y_train,
y_train_pred=rf_pred_train
)
Random Forest Regressor: predicting model... Random Forest Regressor: predicted successfully. --- Random Forest Regressor --- TEST MAE: 291.18 RMSE: 590.60 R²: 0.9781 TRAIN MAE: 103.58 RMSE: 208.16 R²: 0.9973 GAP RMSE gap: 382.44 R² gap: 0.0192
rf_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(rf_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:05
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import RandomizedSearchCV
rf_param_dist = {
"model__n_estimators": [100, 200, 300, 500],
"model__max_depth": [5, 10, 15, 20, 30, None],
"model__min_samples_split": [2, 5, 10, 20],
"model__min_samples_leaf": [1, 2, 4, 8],
"model__max_features": [1.0, "sqrt", "log2"]
}
rf_search = RandomizedSearchCV(
rf_reg, # The pipeline model to tune
param_distributions=rf_param_dist, # Default: required -> Dictionary with parameter values to test - Parameter values to test
n_iter=30, # Default: 10 -> Common values: 10, 20, 30, 50 - Number of random combinations to try
scoring=SCORING, # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
cv=KFOLD_5, # Default: None -> Common values: 3, 5, 10 - Number of cross-validation folds
n_jobs=N_JOBS, # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
verbose=VERBOSE, # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
random_state=RANDOM_STATE, # Default: None -> Common values: None, 42 - Reproducible random search
error_score=ERROR_SCORE # Default: np.nan -> Common values: np.nan, "raise" - If a fit fails during CV/search, raise the error immediately instead of silently assigning NaN to that candidate
)
print("Random Forest Regressor: running randomized search...")
rf_search.fit(X_train, y_train)
print("Random Forest Regressor: randomized search completed successfully.")
else:
print("Random Forest Regressor: loading search object...")
rf_search = loaded_searches["random_forest"]
print("Random Forest Regressor: search object loaded successfully.")
Random Forest Regressor: running randomized search... Fitting 5 folds for each of 30 candidates, totalling 150 fits Random Forest Regressor: randomized search completed successfully.
rf_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(rf_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:08:33
# Best parameters
rf_search.best_params_
{'model__n_estimators': 100,
'model__min_samples_split': 10,
'model__min_samples_leaf': 1,
'model__max_features': 1.0,
'model__max_depth': 20}
# Best model
rf_best_tuning = rf_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Random Forest Regressor: predicting best model...")
rf_best_pred_train = rf_best_tuning.predict(X_train)
rf_best_pred_test = rf_best_tuning.predict(X_test)
print("Random Forest Regressor: best model predicted successfully.\n")
else:
print("Random Forest Regressor: loading best predicted model...")
rf_best_pred_train = loaded_predictions["rf_best_pred_train_file"]
rf_best_pred_test = loaded_predictions["rf_best_pred_test_file"]
print("Random Forest Regressor: best model loaded successfully.\n")
rf_best_results = evaluate_regression(
y_test,
rf_best_pred_test,
"Best Random Forest Regressor",
y_train_true=y_train,
y_train_pred=rf_best_pred_train
)
Random Forest Regressor: predicting best model... Random Forest Regressor: best model predicted successfully. --- Best Random Forest Regressor --- TEST MAE: 278.87 RMSE: 559.44 R²: 0.9803 TRAIN MAE: 170.67 RMSE: 335.87 R²: 0.9929 GAP RMSE gap: 223.58 R² gap: 0.0126
rf_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(rf_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:01
Test set prediction comparison¶
rf_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(rf_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
rf_sample_pred = pd.Series(rf_best_pred_test, index=X_test.index).loc[sample_idx]
rf_results_comparison["Error"] = rf_results_comparison["Prediction"] - rf_results_comparison["Original Label"]
rf_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 595.48 | 774.27 | 862.19 | 1075.24 | 1335.37 | 1268.68 | 3009.42 | 2744.50 | 3433.16 | 4225.50 | 4463.89 | 7641.49 | 10777.77 | 12058.10 | 15315.19 |
| Error | 24.48 | 10.27 | 35.19 | -72.76 | -20.63 | -131.32 | 340.42 | -77.50 | -509.84 | 70.50 | 59.89 | -28.51 | 1949.77 | 197.10 | -1354.81 |
Add formatted timing and model type to the results¶
rf_results["Model_type"] = "Tree-based ensemble"
rf_results["Fit_time"] = str(timedelta(seconds=int(rf_fit_time)))
rf_results["Pred_time"] = str(timedelta(seconds=int(rf_predict_time)))
rf_results["Tun_fit_time"] = str(timedelta(seconds=int(rf_tuning_fit_time)))
rf_results["Tun_pred_time"] = str(timedelta(seconds=int(rf_tuning_predict_time)))
rf_results["Full_model_time"] = str(timedelta(seconds=int(rf_fit_time + rf_predict_time + rf_tuning_fit_time + rf_tuning_predict_time)))
rf_results["Tun_MAE"] = rf_best_results["MAE"]
rf_results["Tun_RMSE"] = rf_best_results["RMSE"]
rf_results["Tun_R2"] = rf_best_results["R2"]
rf_results["Tun_Train_MAE"] = rf_best_results["Train_MAE"]
rf_results["Tun_Train_RMSE"] = rf_best_results["Train_RMSE"]
rf_results["Tun_Train_R2"] = rf_best_results["Train_R2"]
Extra Trees Regressor¶
The ExtraTreesRegressor is an ensemble model that combines many randomized decision trees to make more stable predictions.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.ensemble import ExtraTreesRegressor
extra_trees_param = {
# Most important parameters: n_estimators, max_depth, min_samples_split, min_samples_leaf
"n_estimators": 300, # Default: 100 -> Often worth trying values between 100 and 500
"max_depth": None, # Default: None -> Often worth trying values between 5 and 30
"min_samples_split": 2, # Default: 2 -> Increase to reduce overfitting
"min_samples_leaf": 2, # Default: 1 -> Increase to make the trees more stable
"max_features": "sqrt" # Default: 1.0 -> Common alternatives: "sqrt", "log2"
}
extra_trees_reg = Pipeline([
("preprocessing", tree_preprocessing),
("model", ExtraTreesRegressor(random_state=RANDOM_STATE, **extra_trees_param))
])
print("Extra Trees Regressor: fitting model...")
extra_trees_reg.fit(X_train, y_train)
print("Extra Trees Regressor: fitted successfully.")
else:
print("Extra Trees Regressor: loading fitted model...")
extra_trees_reg = loaded_fitted_base_models["extra_trees"]
print("Extra Trees Regressor: loaded successfully.")
Extra Trees Regressor: fitting model... Extra Trees Regressor: fitted successfully.
extra_trees_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(extra_trees_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:22
Features created by Extra Trees Regressor Model¶
# Number of input features used by the model
print("Number of input features:", extra_trees_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", extra_trees_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Extra Trees Regressor: predicting model...")
extra_trees_pred_train = extra_trees_reg.predict(X_train)
extra_trees_pred_test = extra_trees_reg.predict(X_test)
print("Extra Trees Regressor: predicted successfully.\n")
else:
print("Extra Trees Regressor: loading predicted model...")
extra_trees_pred_train = loaded_predictions["extra_trees_pred_train_file"]
extra_trees_pred_test = loaded_predictions["extra_trees_pred_test_file"]
print("Extra Trees Regressor: loaded successfully.\n")
extra_trees_results = evaluate_regression(
y_test,
extra_trees_pred_test,
model_name="Extra Trees Regressor",
y_train_true=y_train,
y_train_pred=extra_trees_pred_train
)
Extra Trees Regressor: predicting model... Extra Trees Regressor: predicted successfully. --- Extra Trees Regressor --- TEST MAE: 320.02 RMSE: 618.24 R²: 0.9760 TRAIN MAE: 239.07 RMSE: 449.23 R²: 0.9873 GAP RMSE gap: 169.01 R² gap: 0.0113
extra_trees_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(extra_trees_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:04
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import RandomizedSearchCV
extra_trees_param_dist = {
"model__n_estimators": [100, 200, 300, 500],
"model__max_depth": [5, 10, 15, 20, 30, None],
"model__min_samples_split": [2, 5, 10, 20],
"model__min_samples_leaf": [1, 2, 4, 8],
"model__max_features": [1.0, "sqrt", "log2"]
}
extra_trees_search = RandomizedSearchCV(
extra_trees_reg, # The pipeline model to tune
param_distributions=extra_trees_param_dist, # Default: required -> Dictionary with parameter values to test - Parameter values to test
n_iter=30, # Default: 10 -> Common values: 10, 20, 30, 50 - Number of random combinations to try
scoring=SCORING, # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
cv=KFOLD_5, # Default: None -> Common values: 3, 5, 10 - Number of cross-validation folds
n_jobs=2, # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
pre_dispatch=2, # Default: '2*n_jobs' -> Common values: 1, 2, '2*n_jobs' - Limits how many jobs are sent out in advance, which can reduce memory usage
verbose=VERBOSE, # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
random_state=RANDOM_STATE, # Default: None -> Common values: None, 42 - Reproducible random search
error_score=ERROR_SCORE # Default: np.nan -> Common values: np.nan, "raise" - If a fit fails during CV/search, raise the error immediately instead of silently assigning NaN to that candidate
)
print("Extra Trees Regressor: running randomized search...")
extra_trees_search.fit(X_train, y_train)
print("Extra Trees Regressor: randomized search completed successfully.")
else:
print("Extra Trees Regressor: loading search object...")
extra_trees_search = loaded_searches["extra_trees"]
print("Extra Trees Regressor: search object loaded successfully.")
Extra Trees Regressor: running randomized search... Fitting 5 folds for each of 30 candidates, totalling 150 fits Extra Trees Regressor: randomized search completed successfully.
extra_trees_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(extra_trees_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:19:28
# Best parameters
extra_trees_search.best_params_
{'model__n_estimators': 100,
'model__min_samples_split': 10,
'model__min_samples_leaf': 1,
'model__max_features': 1.0,
'model__max_depth': 20}
# Best model
extra_trees_best_tuning = extra_trees_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Extra Trees Regressor: predicting best model...")
extra_trees_best_pred_train = extra_trees_best_tuning.predict(X_train)
extra_trees_best_pred_test = extra_trees_best_tuning.predict(X_test)
print("Extra Trees Regressor: best model predicted successfully.\n")
else:
print("Extra Trees Regressor: loading best predicted model...")
extra_trees_best_pred_train = loaded_predictions["extra_trees_best_pred_train_file"]
extra_trees_best_pred_test = loaded_predictions["extra_trees_best_pred_test_file"]
print("Extra Trees Regressor: best model loaded successfully.\n")
extra_trees_best_results = evaluate_regression(
y_test,
extra_trees_best_pred_test,
"Best Extra Trees Regressor",
y_train_true=y_train,
y_train_pred=extra_trees_best_pred_train
)
Extra Trees Regressor: predicting best model... Extra Trees Regressor: best model predicted successfully. --- Best Extra Trees Regressor --- TEST MAE: 274.34 RMSE: 542.91 R²: 0.9815 TRAIN MAE: 167.43 RMSE: 317.42 R²: 0.9937 GAP RMSE gap: 225.49 R² gap: 0.0122
extra_trees_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(extra_trees_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:01
Test set prediction comparison¶
extra_trees_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(extra_trees_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
extra_trees_sample_pred = pd.Series(extra_trees_best_pred_test, index=X_test.index).loc[sample_idx]
extra_trees_results_comparison["Error"] = extra_trees_results_comparison["Prediction"] - extra_trees_results_comparison["Original Label"]
extra_trees_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 605.09 | 801.38 | 899.26 | 1088.67 | 1316.88 | 1253.70 | 3033.42 | 2766.04 | 3599.58 | 4248.03 | 4426.43 | 7235.36 | 11459.84 | 11563.48 | 14764.40 |
| Error | 34.09 | 37.38 | 72.26 | -59.33 | -39.12 | -146.30 | 364.42 | -55.96 | -343.42 | 93.03 | 22.43 | -434.64 | 2631.84 | -297.52 | -1905.60 |
Add formatted timing and model type to the results¶
extra_trees_results["Model_type"] = "Tree-based ensemble"
extra_trees_results["Fit_time"] = str(timedelta(seconds=int(extra_trees_fit_time)))
extra_trees_results["Pred_time"] = str(timedelta(seconds=int(extra_trees_predict_time)))
extra_trees_results["Tun_fit_time"] = str(timedelta(seconds=int(extra_trees_tuning_fit_time)))
extra_trees_results["Tun_pred_time"] = str(timedelta(seconds=int(extra_trees_tuning_predict_time)))
extra_trees_results["Full_model_time"] = str(timedelta(seconds=int(extra_trees_fit_time + extra_trees_predict_time + extra_trees_tuning_fit_time + extra_trees_tuning_predict_time)))
extra_trees_results["Tun_MAE"] = extra_trees_best_results["MAE"]
extra_trees_results["Tun_RMSE"] = extra_trees_best_results["RMSE"]
extra_trees_results["Tun_R2"] = extra_trees_best_results["R2"]
extra_trees_results["Tun_Train_MAE"] = extra_trees_best_results["Train_MAE"]
extra_trees_results["Tun_Train_RMSE"] = extra_trees_best_results["Train_RMSE"]
extra_trees_results["Tun_Train_R2"] = extra_trees_best_results["Train_R2"]
Boosting ensemble models¶
AdaBoost Regressor¶
The AdaBoostRegressor combines many weak models and improves them step by step by focusing more on difficult training examples.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.ensemble import AdaBoostRegressor
ada_param = {
# Most important parameters: n_estimators, learning_rate, loss
"n_estimators": 100, # Default: 50 -> Often worth trying values between 25 and 500
"learning_rate": 0.1, # Default: 1.0 -> Often worth trying values between 0.01 and 1.0
"loss": "linear" # Default: "linear" -> Common alternatives: "square", "exponential"
}
ada_reg = Pipeline([
("preprocessing", tree_preprocessing),
("model", AdaBoostRegressor(random_state=RANDOM_STATE, **ada_param))
])
print("AdaBoost Regressor: fitting model...")
ada_reg.fit(X_train, y_train)
print("AdaBoost Regressor: fitted successfully.")
else:
print("AdaBoost Regressor: loading fitted model...")
ada_reg = loaded_fitted_base_models["adaboost"]
print("AdaBoost Regressor: loaded successfully.")
AdaBoost Regressor: fitting model... AdaBoost Regressor: fitted successfully.
ada_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(ada_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:11
Features created by AdaBoost Regressor Model¶
# Number of input features used by the model
print("Number of input features:", ada_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", ada_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("AdaBoost Regressor: predicting model...")
ada_pred_train = ada_reg.predict(X_train)
ada_pred_test = ada_reg.predict(X_test)
print("AdaBoost Regressor: predicted successfully.\n")
else:
print("AdaBoost Regressor: loading predicted model...")
ada_pred_train = loaded_predictions["ada_pred_train_file"]
ada_pred_test = loaded_predictions["ada_pred_test_file"]
print("AdaBoost Regressor: loaded successfully.\n")
ada_results = evaluate_regression(
y_test,
ada_pred_test,
model_name="AdaBoost Regressor",
y_train_true=y_train,
y_train_pred=ada_pred_train
)
AdaBoost Regressor: predicting model... AdaBoost Regressor: predicted successfully. --- AdaBoost Regressor --- TEST MAE: 889.86 RMSE: 1384.47 R²: 0.8796 TRAIN MAE: 877.80 RMSE: 1353.13 R²: 0.8850 GAP RMSE gap: 31.34 R² gap: 0.0054
ada_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(ada_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:01
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import RandomizedSearchCV
from sklearn.tree import DecisionTreeRegressor
ada_search_reg = Pipeline([
("preprocessing", tree_preprocessing),
("model", AdaBoostRegressor(
estimator=DecisionTreeRegressor(random_state=RANDOM_STATE),
random_state=RANDOM_STATE
))
])
ada_param_dist = {
"model__n_estimators": [25, 50, 100, 200, 300, 500],
"model__learning_rate": [0.01, 0.03, 0.05, 0.1, 0.2, 0.5, 1.0],
"model__loss": ["linear", "square", "exponential"],
"model__estimator__max_depth": [1, 2, 3, 4, 5, 6],
"model__estimator__min_samples_split": [2, 5, 10, 20],
"model__estimator__min_samples_leaf": [1, 2, 4, 8]
}
ada_search = RandomizedSearchCV(
ada_search_reg, # The pipeline model to tune
param_distributions=ada_param_dist, # Dictionary with parameter values to test
n_iter=40, # Number of random combinations to try
scoring=SCORING, # Evaluation metric
cv=KFOLD_5, # Number of cross-validation folds
n_jobs=N_JOBS, # Parallel jobs
verbose=VERBOSE, # Show progress during fitting
random_state=RANDOM_STATE, # Reproducible random search
error_score=ERROR_SCORE # Raise error immediately if a fit fails
)
print("AdaBoost Regressor: running randomized search...")
ada_search.fit(X_train, y_train)
print("AdaBoost Regressor: randomized search completed successfully.")
else:
print("AdaBoost Regressor: loading search object...")
ada_search = loaded_searches["adaboost"]
print("AdaBoost Regressor: search object loaded successfully.")
AdaBoost Regressor: running randomized search... Fitting 5 folds for each of 40 candidates, totalling 200 fits AdaBoost Regressor: randomized search completed successfully.
ada_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(ada_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:19:30
# Best parameters
ada_search.best_params_
{'model__n_estimators': 300,
'model__loss': 'exponential',
'model__learning_rate': 0.1,
'model__estimator__min_samples_split': 5,
'model__estimator__min_samples_leaf': 4,
'model__estimator__max_depth': 6}
# Best model
ada_best_tuning = ada_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("AdaBoost Regressor: predicting best model...")
ada_best_pred_train = ada_best_tuning.predict(X_train)
ada_best_pred_test = ada_best_tuning.predict(X_test)
print("AdaBoost Regressor: best model predicted successfully.\n")
else:
print("AdaBoost Regressor: loading best predicted model...")
ada_best_pred_train = loaded_predictions["ada_best_pred_train_file"]
ada_best_pred_test = loaded_predictions["ada_best_pred_test_file"]
print("AdaBoost Regressor: best model loaded successfully.\n")
ada_best_results = evaluate_regression(
y_test,
ada_best_pred_test,
"Best AdaBoost Regressor",
y_train_true=y_train,
y_train_pred=ada_best_pred_train
)
AdaBoost Regressor: predicting best model... AdaBoost Regressor: best model predicted successfully. --- Best AdaBoost Regressor --- TEST MAE: 543.39 RMSE: 843.12 R²: 0.9553 TRAIN MAE: 526.20 RMSE: 797.54 R²: 0.9600 GAP RMSE gap: 45.58 R² gap: 0.0047
ada_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(ada_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:04
Test set prediction comparison¶
ada_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(ada_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
ada_sample_pred = pd.Series(ada_best_pred_test, index=X_test.index).loc[sample_idx]
ada_results_comparison["Error"] = ada_results_comparison["Prediction"] - ada_results_comparison["Original Label"]
ada_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 854.64 | 735.27 | 960.77 | 827.00 | 1067.45 | 1018.77 | 4078.16 | 3320.33 | 4060.06 | 4106.89 | 5350.24 | 6713.44 | 11338.75 | 11486.76 | 14039.76 |
| Error | 283.64 | -28.73 | 133.77 | -321.00 | -288.55 | -381.23 | 1409.16 | 498.33 | 117.06 | -48.11 | 946.24 | -956.56 | 2510.75 | -374.24 | -2630.24 |
Add formatted timing and model type to the results¶
ada_results["Model_type"] = "Boosting ensemble"
ada_results["Fit_time"] = str(timedelta(seconds=int(ada_fit_time)))
ada_results["Pred_time"] = str(timedelta(seconds=int(ada_predict_time)))
ada_results["Tun_fit_time"] = str(timedelta(seconds=int(ada_tuning_fit_time)))
ada_results["Tun_pred_time"] = str(timedelta(seconds=int(ada_tuning_predict_time)))
ada_results["Full_model_time"] = str(timedelta(seconds=int(ada_fit_time + ada_predict_time + ada_tuning_fit_time + ada_tuning_predict_time)))
ada_results["Tun_MAE"] = ada_best_results["MAE"]
ada_results["Tun_RMSE"] = ada_best_results["RMSE"]
ada_results["Tun_R2"] = ada_best_results["R2"]
ada_results["Tun_Train_MAE"] = ada_best_results["Train_MAE"]
ada_results["Tun_Train_RMSE"] = ada_best_results["Train_RMSE"]
ada_results["Tun_Train_R2"] = ada_best_results["Train_R2"]
Gradient Boosting Regressor¶
The GradientBoostingRegressor is an ensemble model that builds trees step by step, where each new tree tries to correct the errors of the previous ones.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.ensemble import GradientBoostingRegressor
gbr_param = {
# Most important parameters: n_estimators, learning_rate, max_depth, subsample
"n_estimators": 200, # Default: 100 -> Often worth trying values between 100 and 1000
"learning_rate": 0.05, # Default: 0.1 -> From 0.01 to 0.8 - smaller learning_rate → slower but often better learning
"max_depth": 3, # Default: 3 -> Often worth trying values between 2 and 8
"subsample": 0.8, # Default: 1.0 -> From 0.5 to 1.0 - smaller value → more randomness
"loss": "squared_error" # Default: "squared_error" -> Common alternatives: "absolute_error", "huber", "quantile"
}
gbr_reg = Pipeline([
("preprocessing", tree_preprocessing),
("model", GradientBoostingRegressor(random_state=RANDOM_STATE, **gbr_param))
])
print("Gradient Boosting Regressor: fitting model...")
gbr_reg.fit(X_train, y_train)
print("Gradient Boosting Regressor: fitted successfully.")
else:
print("Gradient Boosting Regressor: loading fitted model...")
gbr_reg = loaded_fitted_base_models["gradient_boosting"]
print("Gradient Boosting Regressor: loaded successfully.")
Gradient Boosting Regressor: fitting model... Gradient Boosting Regressor: fitted successfully.
gbr_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(gbr_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:15
Features created by Gradient Boosting Regressor Model¶
# Number of input features used by the model
print("Number of input features:", gbr_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", gbr_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Gradient Boosting Regressor: predicting model...")
gbr_pred_train = gbr_reg.predict(X_train)
gbr_pred_test = gbr_reg.predict(X_test)
print("Gradient Boosting Regressor: predicted successfully.\n")
else:
print("Gradient Boosting Regressor: loading predicted model...")
gbr_pred_train = loaded_predictions["gbr_pred_train_file"]
gbr_pred_test = loaded_predictions["gbr_pred_test_file"]
print("Gradient Boosting Regressor: loaded successfully.\n")
gbr_results = evaluate_regression(
y_test,
gbr_pred_test,
model_name="Gradient Boosting Regressor",
y_train_true=y_train,
y_train_pred=gbr_pred_train
)
Gradient Boosting Regressor: predicting model... Gradient Boosting Regressor: predicted successfully. --- Gradient Boosting Regressor --- TEST MAE: 410.94 RMSE: 757.78 R²: 0.9639 TRAIN MAE: 394.16 RMSE: 711.06 R²: 0.9682 GAP RMSE gap: 46.72 R² gap: 0.0043
gbr_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(gbr_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import RandomizedSearchCV
gbr_param_dist = {
"model__n_estimators": [600, 700, 800, 862, 900, 1000],
"model__learning_rate": [0.03, 0.04, 0.046, 0.05, 0.06, 0.08],
"model__max_depth": [4, 5, 6, 7],
"model__subsample": [0.6, 0.65, 0.68, 0.7, 0.75, 0.8],
"model__loss": ["huber"]
}
gbr_search = RandomizedSearchCV(
gbr_reg, # The pipeline model to tune
param_distributions=gbr_param_dist, # Dictionary with parameter values to test
n_iter=20, # Number of random combinations to try
scoring=SCORING, # Evaluation metric
cv=KFOLD_5, # Number of cross-validation folds
n_jobs=N_JOBS, # Parallel jobs
verbose=VERBOSE, # Show progress during fitting
random_state=RANDOM_STATE, # Reproducible random search
error_score=ERROR_SCORE # Raise error immediately if a fit fails
)
print("Gradient Boosting Regressor: running randomized search...")
gbr_search.fit(X_train, y_train)
print("Gradient Boosting Regressor: randomized search completed successfully.")
else:
print("Gradient Boosting Regressor: loading search object...")
gbr_search = loaded_searches["gradient_boosting"]
print("Gradient Boosting Regressor: search object loaded successfully.")
Gradient Boosting Regressor: running randomized search... Fitting 5 folds for each of 20 candidates, totalling 100 fits Gradient Boosting Regressor: randomized search completed successfully.
gbr_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(gbr_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:38:50
# Best parameters
gbr_search.best_params_
{'model__subsample': 0.7,
'model__n_estimators': 1000,
'model__max_depth': 6,
'model__loss': 'huber',
'model__learning_rate': 0.046}
# Best model
gbr_best_tuning = gbr_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Gradient Boosting Regressor: predicting best model...")
gbr_best_pred_train = gbr_best_tuning.predict(X_train)
gbr_best_pred_test = gbr_best_tuning.predict(X_test)
print("Gradient Boosting Regressor: best model predicted successfully.\n")
else:
print("Gradient Boosting Regressor: loading best predicted model...")
gbr_best_pred_train = loaded_predictions["gbr_best_pred_train_file"]
gbr_best_pred_test = loaded_predictions["gbr_best_pred_test_file"]
print("Gradient Boosting Regressor: best model loaded successfully.\n")
gbr_best_results = evaluate_regression(
y_test,
gbr_best_pred_test,
"Best Gradient Boosting Regressor",
y_train_true=y_train,
y_train_pred=gbr_best_pred_train
)
Gradient Boosting Regressor: predicting best model... Gradient Boosting Regressor: best model predicted successfully. --- Best Gradient Boosting Regressor --- TEST MAE: 270.22 RMSE: 540.63 R²: 0.9816 TRAIN MAE: 191.96 RMSE: 363.18 R²: 0.9917 GAP RMSE gap: 177.45 R² gap: 0.0101
gbr_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(gbr_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:02
Test set prediction comparison¶
gbr_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(gbr_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
gbr_sample_pred = pd.Series(gbr_best_pred_test, index=X_test.index).loc[sample_idx]
gbr_results_comparison["Error"] = gbr_results_comparison["Prediction"] - gbr_results_comparison["Original Label"]
gbr_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 589.76 | 785.87 | 860.57 | 1082.45 | 1315.25 | 1431.13 | 2957.40 | 3004.22 | 3718.28 | 3992.32 | 4504.11 | 7183.18 | 11830.64 | 11768.39 | 15016.79 |
| Error | 18.76 | 21.87 | 33.57 | -65.55 | -40.75 | 31.13 | 288.40 | 182.22 | -224.72 | -162.68 | 100.11 | -486.82 | 3002.64 | -92.61 | -1653.21 |
Add formatted timing and model type to the results¶
gbr_results["Model_type"] = "Boosting ensemble"
gbr_results["Fit_time"] = str(timedelta(seconds=int(gbr_fit_time)))
gbr_results["Pred_time"] = str(timedelta(seconds=int(gbr_predict_time)))
gbr_results["Tun_fit_time"] = str(timedelta(seconds=int(gbr_tuning_fit_time)))
gbr_results["Tun_pred_time"] = str(timedelta(seconds=int(gbr_tuning_predict_time)))
gbr_results["Full_model_time"] = str(timedelta(seconds=int(gbr_fit_time + gbr_predict_time + gbr_tuning_fit_time + gbr_tuning_predict_time)))
gbr_results["Tun_MAE"] = gbr_best_results["MAE"]
gbr_results["Tun_RMSE"] = gbr_best_results["RMSE"]
gbr_results["Tun_R2"] = gbr_best_results["R2"]
gbr_results["Tun_Train_MAE"] = gbr_best_results["Train_MAE"]
gbr_results["Tun_Train_RMSE"] = gbr_best_results["Train_RMSE"]
gbr_results["Tun_Train_R2"] = gbr_best_results["Train_R2"]
Hist Gradient Boosting Regressor¶
The HistGradientBoostingRegressor is a fast boosting model that groups continuous values into bins to speed up training on larger datasets.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.ensemble import HistGradientBoostingRegressor
hgb_param = {
# Most important parameters: max_iter, learning_rate, max_depth, max_leaf_nodes, min_samples_leaf
"max_iter": 200, # Default: 100 -> Often worth trying values between 100 and 500
"learning_rate": 0.05, # Default: 0.1 -> From 0.01 to 0.3 - smaller learning_rate → slower but often better learning
"max_depth": None, # Default: None -> Common alternatives: 3 to 12 - smaller depth → less overfitting
"max_leaf_nodes": 31, # Default: 31 -> Often worth trying values between 15 and 127
"min_samples_leaf": 20 # Default: 20 -> Increase to make the model more stable
}
hgb_reg = Pipeline([
("preprocessing", tree_preprocessing),
("model", HistGradientBoostingRegressor(random_state=RANDOM_STATE, **hgb_param))
])
print("Hist Gradient Boosting Regressor: fitting model...")
hgb_reg.fit(X_train, y_train)
print("Hist Gradient Boosting Regressor: fitted successfully.")
else:
print("Hist Gradient Boosting Regressor: loading fitted model...")
hgb_reg = loaded_fitted_base_models["hist_gradient_boosting"]
print("Hist Gradient Boosting Regressor: loaded successfully.")
Hist Gradient Boosting Regressor: fitting model... Hist Gradient Boosting Regressor: fitted successfully.
hgb_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(hgb_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:03
Features created by Hist Gradient Boosting Regressor Model¶
# Number of input features used by the model
print("Number of input features:", hgb_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", hgb_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Hist Gradient Boosting Regressor: predicting model...")
hgb_pred_train = hgb_reg.predict(X_train)
hgb_pred_test = hgb_reg.predict(X_test)
print("Hist Gradient Boosting Regressor: predicted successfully.\n")
else:
print("Hist Gradient Boosting Regressor: loading predicted model...")
hgb_pred_train = loaded_predictions["hgb_pred_train_file"]
hgb_pred_test = loaded_predictions["hgb_pred_test_file"]
print("Hist Gradient Boosting Regressor: loaded successfully.\n")
hgb_results = evaluate_regression(
y_test,
hgb_pred_test,
model_name="Hist Gradient Boosting Regressor",
y_train_true=y_train,
y_train_pred=hgb_pred_train
)
Hist Gradient Boosting Regressor: predicting model... Hist Gradient Boosting Regressor: predicted successfully. --- Hist Gradient Boosting Regressor --- TEST MAE: 295.67 RMSE: 569.11 R²: 0.9797 TRAIN MAE: 271.53 RMSE: 494.89 R²: 0.9846 GAP RMSE gap: 74.22 R² gap: 0.0050
hgb_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(hgb_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import RandomizedSearchCV
hgb_param_dist = {
"model__max_iter": [100, 200, 300, 500],
"model__learning_rate": [0.01, 0.05, 0.1, 0.2],
"model__max_depth": [3, 5, 8, 12, None],
"model__max_leaf_nodes": [15, 31, 63, 127],
"model__min_samples_leaf": [10, 20, 30, 50]
}
hgb_search = RandomizedSearchCV(
hgb_reg, # The pipeline model to tune
param_distributions=hgb_param_dist, # Default: required -> Dictionary with parameter values to test - Parameter values to test
n_iter=20, # Default: 10 -> Common values: 10, 20, 30, 50 - Number of random combinations to try
scoring=SCORING, # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
cv=KFOLD_10, # Default: None -> Common values: 3, 5, 10 - Number of cross-validation folds
n_jobs=N_JOBS, # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
verbose=VERBOSE, # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
random_state=RANDOM_STATE, # Default: None -> Common values: None, 42 - Reproducible random search
error_score=ERROR_SCORE # Default: np.nan -> Common values: np.nan, "raise" - If a fit fails during CV/search, raise the error immediately instead of silently assigning NaN to that candidate
)
print("Hist Gradient Boosting Regressor: running randomized search...")
hgb_search.fit(X_train, y_train)
print("Hist Gradient Boosting Regressor: randomized search completed successfully.")
else:
print("Hist Gradient Boosting Regressor: loading search object...")
hgb_search = loaded_searches["hist_gradient_boosting"]
print("Hist Gradient Boosting Regressor: search object loaded successfully.")
Hist Gradient Boosting Regressor: running randomized search... Fitting 10 folds for each of 20 candidates, totalling 200 fits Hist Gradient Boosting Regressor: randomized search completed successfully.
hgb_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(hgb_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:02:12
# Best parameters
hgb_search.best_params_
{'model__min_samples_leaf': 10,
'model__max_leaf_nodes': 127,
'model__max_iter': 500,
'model__max_depth': 12,
'model__learning_rate': 0.05}
# Best model
hgb_best_tuning = hgb_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Hist Gradient Boosting Regressor: predicting best model...")
hgb_best_pred_train = hgb_best_tuning.predict(X_train)
hgb_best_pred_test = hgb_best_tuning.predict(X_test)
print("Hist Gradient Boosting Regressor: best model predicted successfully.\n")
else:
print("Hist Gradient Boosting Regressor: loading best predicted model...")
hgb_best_pred_train = loaded_predictions["hgb_best_pred_train_file"]
hgb_best_pred_test = loaded_predictions["hgb_best_pred_test_file"]
print("Hist Gradient Boosting Regressor: best model loaded successfully.\n")
hgb_best_results = evaluate_regression(
y_test,
hgb_best_pred_test,
"Best Hist Gradient Boosting Regressor",
y_train_true=y_train,
y_train_pred=hgb_best_pred_train
)
Hist Gradient Boosting Regressor: predicting best model... Hist Gradient Boosting Regressor: best model predicted successfully. --- Best Hist Gradient Boosting Regressor --- TEST MAE: 286.29 RMSE: 567.64 R²: 0.9798 TRAIN MAE: 234.41 RMSE: 420.41 R²: 0.9889 GAP RMSE gap: 147.23 R² gap: 0.0091
hgb_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(hgb_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:01
Test set prediction comparison¶
hgb_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(hgb_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
hgb_sample_pred = pd.Series(hgb_best_pred_test, index=X_test.index).loc[sample_idx]
hgb_results_comparison["Error"] = hgb_results_comparison["Prediction"] - hgb_results_comparison["Original Label"]
hgb_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 615.17 | 819.14 | 882.97 | 1056.07 | 1258.90 | 1290.38 | 3111.43 | 2894.48 | 3650.54 | 4046.04 | 4410.50 | 7523.40 | 10605.28 | 12028.61 | 15062.25 |
| Error | 44.17 | 55.14 | 55.97 | -91.93 | -97.10 | -109.62 | 442.43 | 72.48 | -292.46 | -108.96 | 6.50 | -146.60 | 1777.28 | 167.61 | -1607.75 |
Add formatted timing and model type to the results¶
hgb_results["Model_type"] = "Boosting ensemble"
hgb_results["Fit_time"] = str(timedelta(seconds=int(hgb_fit_time)))
hgb_results["Pred_time"] = str(timedelta(seconds=int(hgb_predict_time)))
hgb_results["Tun_fit_time"] = str(timedelta(seconds=int(hgb_tuning_fit_time)))
hgb_results["Tun_pred_time"] = str(timedelta(seconds=int(hgb_tuning_predict_time)))
hgb_results["Full_model_time"] = str(timedelta(seconds=int(hgb_fit_time + hgb_predict_time + hgb_tuning_fit_time + hgb_tuning_predict_time)))
hgb_results["Tun_MAE"] = hgb_best_results["MAE"]
hgb_results["Tun_RMSE"] = hgb_best_results["RMSE"]
hgb_results["Tun_R2"] = hgb_best_results["R2"]
hgb_results["Tun_Train_MAE"] = hgb_best_results["Train_MAE"]
hgb_results["Tun_Train_RMSE"] = hgb_best_results["Train_RMSE"]
hgb_results["Tun_Train_R2"] = hgb_best_results["Train_R2"]
XGBoost Regressor¶
The XGBoost Regressor is a powerful boosting model that builds trees step by step to improve prediction accuracy.
conda install -c conda-forge xgboost
pip install xgboost
start_fit = time.perf_counter()
if not QUICK_LOAD:
from xgboost import XGBRegressor
xgb_param = {
# Most important parameters: n_estimators, learning_rate, max_depth, subsample, colsample_bytree
"n_estimators": 100, # Default: 100 -> Often worth trying values between 100 and 500
"learning_rate": 0.1, # Default: 0.3 -> From 0.01 to 0.3 - smaller learning_rate → slower but often better learning
"max_depth": 6, # Default: 6 -> Often worth trying values between 3 and 10
"subsample": 1.0, # Default: 1.0 -> From 0.5 to 1.0 - smaller value → more randomness
"colsample_bytree": 1.0 # Default: 1.0 -> From 0.5 to 1.0 - smaller value → fewer features per tree
}
xgb_reg = Pipeline([
("preprocessing", tree_preprocessing),
("model", XGBRegressor(random_state=RANDOM_STATE, **xgb_param))
])
print("XGBoost Regressor: fitting model...")
xgb_reg.fit(X_train, y_train)
print("XGBoost Regressor: fitted successfully.")
else:
print("XGBoost Regressor: loading fitted model...")
xgb_reg = loaded_fitted_base_models["xgboost"]
print("XGBoost Regressor: loaded successfully.")
XGBoost Regressor: fitting model... XGBoost Regressor: fitted successfully.
xgb_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(xgb_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:00
Features created by XGBoost Regressor Model¶
# Number of input features used by the model
print("Number of input features:", xgb_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", xgb_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("XGBoost Regressor: predicting model...")
xgb_pred_train = xgb_reg.predict(X_train)
xgb_pred_test = xgb_reg.predict(X_test)
print("XGBoost Regressor: predicted successfully.\n")
else:
print("XGBoost Regressor: loading predicted model...")
xgb_pred_train = loaded_predictions["xgb_pred_train_file"]
xgb_pred_test = loaded_predictions["xgb_pred_test_file"]
print("XGBoost Regressor: loaded successfully.\n")
xgb_results = evaluate_regression(
y_test,
xgb_pred_test,
model_name="XGBoost Regressor",
y_train_true=y_train,
y_train_pred=xgb_pred_train
)
XGBoost Regressor: predicting model... XGBoost Regressor: predicted successfully. --- XGBoost Regressor --- TEST MAE: 298.71 RMSE: 580.68 R²: 0.9788 TRAIN MAE: 265.04 RMSE: 483.91 R²: 0.9853 GAP RMSE gap: 96.77 R² gap: 0.0065
xgb_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(xgb_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import RandomizedSearchCV
xgb_param_dist = {
"model__n_estimators": [100, 200, 300, 500],
"model__learning_rate": [0.01, 0.05, 0.1, 0.2],
"model__max_depth": [3, 4, 6, 8, 10],
"model__subsample": [0.6, 0.8, 1.0],
"model__colsample_bytree": [0.6, 0.8, 1.0],
"model__reg_lambda": [0, 1, 3, 5],
"model__reg_alpha": [0, 0.1, 1]
}
xgb_search = RandomizedSearchCV(
xgb_reg, # The pipeline model to tune
param_distributions=xgb_param_dist, # Default: required -> Dictionary with parameter values to test - Parameter values to test
n_iter=30, # Default: 10 -> Common values: 10, 20, 30, 50 - Number of random combinations to try
scoring=SCORING, # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
cv=KFOLD_10, # Default: None -> Common values: 3, 5, 10 - Number of cross-validation folds
n_jobs=N_JOBS, # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
verbose=VERBOSE, # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
random_state=RANDOM_STATE, # Default: None -> Common values: None, 42 - Reproducible random search
error_score=ERROR_SCORE # Default: np.nan -> Common values: np.nan, "raise" - If a fit fails during CV/search, raise the error immediately instead of silently assigning NaN to that candidate
)
print("XGBoost Regressor: running randomized search...")
xgb_search.fit(X_train, y_train)
print("XGBoost Regressor: randomized search completed successfully.")
else:
print("XGBoost Regressor: loading search object...")
xgb_search = loaded_searches["xgboost"]
print("XGBoost Regressor: search object loaded successfully.")
XGBoost Regressor: running randomized search... Fitting 10 folds for each of 30 candidates, totalling 300 fits XGBoost Regressor: randomized search completed successfully.
xgb_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(xgb_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:04:15
# Best parameters
xgb_search.best_params_
{'model__subsample': 0.8,
'model__reg_lambda': 0,
'model__reg_alpha': 0,
'model__n_estimators': 200,
'model__max_depth': 8,
'model__learning_rate': 0.05,
'model__colsample_bytree': 1.0}
# Best model
xgb_best_tuning = xgb_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("XGBoost Regressor: predicting best model...")
xgb_best_pred_train = xgb_best_tuning.predict(X_train)
xgb_best_pred_test = xgb_best_tuning.predict(X_test)
print("XGBoost Regressor: best model predicted successfully.\n")
else:
print("XGBoost Regressor: loading best predicted model...")
xgb_best_pred_train = loaded_predictions["xgb_best_pred_train_file"]
xgb_best_pred_test = loaded_predictions["xgb_best_pred_test_file"]
print("XGBoost Regressor: best model loaded successfully.\n")
xgb_best_results = evaluate_regression(
y_test,
xgb_best_pred_test,
"Best XGBoost Regressor",
y_train_true=y_train,
y_train_pred=xgb_best_pred_train
)
XGBoost Regressor: predicting best model... XGBoost Regressor: best model predicted successfully. --- Best XGBoost Regressor --- TEST MAE: 280.71 RMSE: 559.19 R²: 0.9804 TRAIN MAE: 211.66 RMSE: 365.56 R²: 0.9916 GAP RMSE gap: 193.63 R² gap: 0.0112
xgb_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(xgb_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:00
Test set prediction comparison¶
xgb_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(xgb_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
xgb_sample_pred = pd.Series(xgb_best_pred_test, index=X_test.index).loc[sample_idx]
xgb_results_comparison["Error"] = xgb_results_comparison["Prediction"] - xgb_results_comparison["Original Label"]
xgb_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 642.85 | 826.16 | 919.00 | 1021.83 | 1271.60 | 1348.60 | 2858.77 | 2924.44 | 3705.47 | 4020.53 | 4479.54 | 7123.75 | 11128.91 | 11908.25 | 15072.71 |
| Error | 71.85 | 62.16 | 92.00 | -126.17 | -84.40 | -51.40 | 189.77 | 102.44 | -237.53 | -134.47 | 75.54 | -546.25 | 2300.91 | 47.25 | -1597.29 |
Add formatted timing and model type to the results¶
xgb_results["Model_type"] = "Boosting ensemble"
xgb_results["Fit_time"] = str(timedelta(seconds=int(xgb_fit_time)))
xgb_results["Pred_time"] = str(timedelta(seconds=int(xgb_predict_time)))
xgb_results["Tun_fit_time"] = str(timedelta(seconds=int(xgb_tuning_fit_time)))
xgb_results["Tun_pred_time"] = str(timedelta(seconds=int(xgb_tuning_predict_time)))
xgb_results["Full_model_time"] = str(timedelta(seconds=int(xgb_fit_time + xgb_predict_time + xgb_tuning_fit_time + xgb_tuning_predict_time)))
xgb_results["Tun_MAE"] = xgb_best_results["MAE"]
xgb_results["Tun_RMSE"] = xgb_best_results["RMSE"]
xgb_results["Tun_R2"] = xgb_best_results["R2"]
xgb_results["Tun_Train_MAE"] = xgb_best_results["Train_MAE"]
xgb_results["Tun_Train_RMSE"] = xgb_best_results["Train_RMSE"]
xgb_results["Tun_Train_R2"] = xgb_best_results["Train_R2"]
LightGBM Regressor¶
The LightGBM Regressor is a fast boosting model that is especially effective on larger tabular datasets.
conda install -c conda-forge lightgbm
pip install lightgbm
start_fit = time.perf_counter()
import warnings
warnings.filterwarnings(
"ignore",
message=r"^X does not have valid feature names, but LGBMRegressor was fitted with feature names$",
category=UserWarning,
module=r"^sklearn\.utils\.validation$"
)
if not QUICK_LOAD:
from lightgbm import LGBMRegressor
lgbm_param = {
# Most important parameters: n_estimators, learning_rate, num_leaves, max_depth, subsample, colsample_bytree
"n_estimators": 100, # Default: 100 -> Often worth trying values between 100 and 500
"learning_rate": 0.1, # Default: 0.1 -> From 0.01 to 0.3 - smaller learning_rate → slower but often better learning
"num_leaves": 31, # Default: 31 -> Often worth trying values between 15 and 127
"max_depth": -1, # Default: -1 -> Common alternatives: 3 to 12 - smaller depth → less overfitting
"subsample": 1.0, # Default: 1.0 -> From 0.5 to 1.0 - smaller value → more randomness
"colsample_bytree": 1.0, # Default: 1.0 -> From 0.5 to 1.0 - smaller value → fewer features per tree
"verbosity": -1, # Default: 1 -> -1 hides LightGBM messages in the output
"force_col_wise": True # Default: False -> Forces column-wise training and removes the auto-choosing message
}
lgbm_reg = Pipeline([
("preprocessing", tree_preprocessing),
("model", LGBMRegressor(random_state=RANDOM_STATE, **lgbm_param))
])
print("LightGBM Regressor: fitting model...")
lgbm_reg.fit(X_train, y_train)
print("LightGBM Regressor: fitted successfully.")
else:
print("LightGBM Regressor: loading fitted model...")
lgbm_reg = loaded_fitted_base_models["lightgbm"]
print("LightGBM Regressor: loaded successfully.")
LightGBM Regressor: fitting model... LightGBM Regressor: fitted successfully.
lgbm_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(lgbm_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:00
Features created by LightGBM Regressor Model¶
# Number of input features used by the model
print("Number of input features:", lgbm_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", lgbm_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("LightGBM Regressor: predicting model...")
lgbm_pred_train = lgbm_reg.predict(X_train)
lgbm_pred_test = lgbm_reg.predict(X_test)
print("LightGBM Regressor: predicted successfully.\n")
else:
print("LightGBM Regressor: loading predicted model...")
lgbm_pred_train = loaded_predictions["lgbm_pred_train_file"]
lgbm_pred_test = loaded_predictions["lgbm_pred_test_file"]
print("LightGBM Regressor: loaded successfully.\n")
lgbm_results = evaluate_regression(
y_test,
lgbm_pred_test,
model_name="LightGBM Regressor",
y_train_true=y_train,
y_train_pred=lgbm_pred_train
)
LightGBM Regressor: predicting model... LightGBM Regressor: predicted successfully. --- LightGBM Regressor --- TEST MAE: 291.70 RMSE: 556.21 R²: 0.9806 TRAIN MAE: 267.43 RMSE: 481.77 R²: 0.9854 GAP RMSE gap: 74.44 R² gap: 0.0049
lgbm_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(lgbm_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import RandomizedSearchCV
lgbm_param_dist = {
"model__n_estimators": [100, 200, 300, 500],
"model__learning_rate": [0.01, 0.05, 0.1, 0.2],
"model__num_leaves": [15, 31, 63, 127],
"model__max_depth": [-1, 3, 5, 8, 12],
"model__subsample": [0.6, 0.8, 1.0],
"model__colsample_bytree": [0.6, 0.8, 1.0],
"model__reg_alpha": [0.0, 0.1, 1.0],
"model__reg_lambda": [0.0, 1.0, 3.0, 5.0]
}
lgbm_search = RandomizedSearchCV(
lgbm_reg, # The pipeline model to tune
param_distributions=lgbm_param_dist, # Default: required -> Dictionary with parameter values to test - Parameter values to test
n_iter=30, # Default: 10 -> Common values: 10, 20, 30, 50 - Number of random combinations to try
scoring=SCORING, # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
cv=KFOLD_10, # Default: None -> Common values: 3, 5, 10 - Number of cross-validation folds
n_jobs=N_JOBS, # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
verbose=VERBOSE, # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
random_state=RANDOM_STATE, # Default: None -> Common values: None, 42 - Reproducible random search
error_score=ERROR_SCORE # Default: np.nan -> Common values: np.nan, "raise" - If a fit fails during CV/search, raise the error immediately instead of silently assigning NaN to that candidate
)
print("LightGBM Regressor: running randomized search...")
lgbm_search.fit(X_train, y_train)
print("LightGBM Regressor: randomized search completed successfully.")
else:
print("LightGBM Regressor: loading search object...")
lgbm_search = loaded_searches["lightgbm"]
print("LightGBM Regressor: search object loaded successfully.")
LightGBM Regressor: running randomized search... Fitting 10 folds for each of 30 candidates, totalling 300 fits LightGBM Regressor: randomized search completed successfully.
lgbm_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(lgbm_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:03:20
# Best parameters
lgbm_search.best_params_
{'model__subsample': 0.8,
'model__reg_lambda': 0.0,
'model__reg_alpha': 0.0,
'model__num_leaves': 31,
'model__n_estimators': 500,
'model__max_depth': 12,
'model__learning_rate': 0.05,
'model__colsample_bytree': 1.0}
# Best model
lgbm_best_tuning = lgbm_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("LightGBM Regressor: predicting best model...")
lgbm_best_pred_train = lgbm_best_tuning.predict(X_train)
lgbm_best_pred_test = lgbm_best_tuning.predict(X_test)
print("LightGBM Regressor: best model predicted successfully.\n")
else:
print("LightGBM Regressor: loading best predicted model...")
lgbm_best_pred_train = loaded_predictions["lgbm_best_pred_train_file"]
lgbm_best_pred_test = loaded_predictions["lgbm_best_pred_test_file"]
print("LightGBM Regressor: best model loaded successfully.\n")
lgbm_best_results = evaluate_regression(
y_test,
lgbm_best_pred_test,
"Best LightGBM Regressor",
y_train_true=y_train,
y_train_pred=lgbm_best_pred_train
)
LightGBM Regressor: predicting best model... LightGBM Regressor: best model predicted successfully. --- Best LightGBM Regressor --- TEST MAE: 280.26 RMSE: 546.91 R²: 0.9812 TRAIN MAE: 239.95 RMSE: 430.82 R²: 0.9883 GAP RMSE gap: 116.09 R² gap: 0.0071
lgbm_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(lgbm_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:01
Test set prediction comparison¶
lgbm_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(lgbm_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
lgbm_sample_pred = pd.Series(lgbm_best_pred_test, index=X_test.index).loc[sample_idx]
lgbm_results_comparison["Error"] = lgbm_results_comparison["Prediction"] - lgbm_results_comparison["Original Label"]
lgbm_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 612.39 | 807.36 | 889.22 | 1075.63 | 1303.79 | 1415.74 | 3190.49 | 2698.53 | 3714.86 | 3996.38 | 4403.89 | 7328.76 | 11340.36 | 12153.15 | 14946.34 |
| Error | 41.39 | 43.36 | 62.22 | -72.37 | -52.21 | 15.74 | 521.49 | -123.47 | -228.14 | -158.62 | -0.11 | -341.24 | 2512.36 | 292.15 | -1723.66 |
Add formatted timing and model type to the results¶
lgbm_results["Model_type"] = "Boosting ensemble"
lgbm_results["Fit_time"] = str(timedelta(seconds=int(lgbm_fit_time)))
lgbm_results["Pred_time"] = str(timedelta(seconds=int(lgbm_predict_time)))
lgbm_results["Tun_fit_time"] = str(timedelta(seconds=int(lgbm_tuning_fit_time)))
lgbm_results["Tun_pred_time"] = str(timedelta(seconds=int(lgbm_tuning_predict_time)))
lgbm_results["Full_model_time"] = str(timedelta(seconds=int(lgbm_fit_time + lgbm_predict_time + lgbm_tuning_fit_time + lgbm_tuning_predict_time)))
lgbm_results["Tun_MAE"] = lgbm_best_results["MAE"]
lgbm_results["Tun_RMSE"] = lgbm_best_results["RMSE"]
lgbm_results["Tun_R2"] = lgbm_best_results["R2"]
lgbm_results["Tun_Train_MAE"] = lgbm_best_results["Train_MAE"]
lgbm_results["Tun_Train_RMSE"] = lgbm_best_results["Train_RMSE"]
lgbm_results["Tun_Train_R2"] = lgbm_best_results["Train_R2"]
CatBoost Regressor¶
The CatBoost Regressor is a boosting model that works especially well with tabular data and categorical features.
conda install -c conda-forge catboost
pip install catboost
start_fit = time.perf_counter()
if not QUICK_LOAD:
from catboost import CatBoostRegressor
cat_param = {
# Most important parameters: iterations, learning_rate, depth, l2_leaf_reg
"iterations": 1000, # Default: 1000 -> Often worth trying values between 100 and 1000
"learning_rate": None, # Default: auto -> Often worth trying values between 0.01 and 0.3
"depth": None, # Default: None (effectively auto/standard depth) -> Often worth trying values between 4 and 10
"l2_leaf_reg": 3.0, # Default: 3.0 -> Often worth trying values between 1.0 and 10.0
"loss_function": "RMSE", # Default: "RMSE" -> Common alternatives: "MAE", "Huber"
"allow_writing_files": False # Prevent creating the catboost_info folder and other training files
}
cat_reg = Pipeline([
("preprocessing", tree_preprocessing),
("model", CatBoostRegressor(random_state=RANDOM_STATE, verbose=0, **cat_param))
])
print("CatBoost Regressor: fitting model...")
cat_reg.fit(X_train, y_train)
print("CatBoost Regressor: fitted successfully.")
else:
print("CatBoost Regressor: loading fitted model...")
cat_reg = loaded_fitted_base_models["catboost"]
print("CatBoost Regressor: loaded successfully.")
CatBoost Regressor: fitting model... CatBoost Regressor: fitted successfully.
cat_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(cat_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:08
Features created by CatBoost Regressor Model¶
# Number of input features used by the model
print("Number of input features:", cat_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", cat_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("CatBoost Regressor: predicting model...")
cat_pred_train = cat_reg.predict(X_train)
cat_pred_test = cat_reg.predict(X_test)
print("CatBoost Regressor: predicted successfully.\n")
else:
print("CatBoost Regressor: loading predicted model...")
cat_pred_train = loaded_predictions["cat_pred_train_file"]
cat_pred_test = loaded_predictions["cat_pred_test_file"]
print("CatBoost Regressor: loaded successfully.\n")
cat_results = evaluate_regression(
y_test,
cat_pred_test,
model_name="CatBoost Regressor",
y_train_true=y_train,
y_train_pred=cat_pred_train
)
CatBoost Regressor: predicting model... CatBoost Regressor: predicted successfully. --- CatBoost Regressor --- TEST MAE: 301.92 RMSE: 565.78 R²: 0.9799 TRAIN MAE: 280.82 RMSE: 503.57 R²: 0.9841 GAP RMSE gap: 62.22 R² gap: 0.0042
cat_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(cat_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:00
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import RandomizedSearchCV
cat_param_dist = {
"model__iterations": [200, 500, 1000],
"model__learning_rate": [0.01, 0.03, 0.05, 0.1, 0.2],
"model__depth": [4, 6, 8, 10],
"model__l2_leaf_reg": [1.0, 3.0, 5.0, 10.0],
"model__loss_function": ["RMSE", "MAE"]
}
cat_search = RandomizedSearchCV(
cat_reg, # The pipeline model to tune
param_distributions=cat_param_dist, # Default: required -> Dictionary with parameter values to test - Parameter values to test
n_iter=20, # Default: 10 -> Common values: 10, 20, 30, 50 - Number of random combinations to try
scoring=SCORING, # Default: None -> Common alternatives: "r2", "neg_mean_absolute_error", "neg_mean_squared_error", "neg_root_mean_squared_error" - Evaluation metric
cv=KFOLD_5, # Default: None -> Common values: 3, 5, 10 - Number of cross-validation folds
n_jobs=N_JOBS, # Default: None -> Common values: None, 1, 2, 4, -1 // -1 = Use all CPU cores
verbose=0, # Default: 0 -> Common values: 0, 1, 2, 3 - Show progress during fitting
random_state=RANDOM_STATE, # Default: None -> Common values: None, 42 - Reproducible random search
error_score=ERROR_SCORE # Default: np.nan -> Common values: np.nan, "raise" - If a fit fails during CV/search, raise the error immediately instead of silently assigning NaN to that candidate
)
print("CatBoost Regressor: running randomized search...")
cat_search.fit(X_train, y_train)
print("CatBoost Regressor: randomized search completed successfully.")
else:
print("CatBoost Regressor: loading search object...")
cat_search = loaded_searches["catboost"]
print("CatBoost Regressor: search object loaded successfully.")
CatBoost Regressor: running randomized search... CatBoost Regressor: randomized search completed successfully.
cat_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(cat_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:09:46
# Best parameters
cat_search.best_params_
{'model__loss_function': 'RMSE',
'model__learning_rate': 0.03,
'model__l2_leaf_reg': 1.0,
'model__iterations': 1000,
'model__depth': 10}
# Best model
cat_best_tuning = cat_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("CatBoost Regressor: predicting best model...")
cat_best_pred_train = cat_best_tuning.predict(X_train)
cat_best_pred_test = cat_best_tuning.predict(X_test)
print("CatBoost Regressor: best model predicted successfully.\n")
else:
print("CatBoost Regressor: loading best predicted model...")
cat_best_pred_train = loaded_predictions["cat_best_pred_train_file"]
cat_best_pred_test = loaded_predictions["cat_best_pred_test_file"]
print("CatBoost Regressor: best model loaded successfully.\n")
cat_best_results = evaluate_regression(
y_test,
cat_best_pred_test,
"Best CatBoost Regressor",
y_train_true=y_train,
y_train_pred=cat_best_pred_train
)
CatBoost Regressor: predicting best model... CatBoost Regressor: best model predicted successfully. --- Best CatBoost Regressor --- TEST MAE: 275.41 RMSE: 539.24 R²: 0.9817 TRAIN MAE: 224.01 RMSE: 390.18 R²: 0.9904 GAP RMSE gap: 149.06 R² gap: 0.0087
cat_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(cat_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:00
Test set prediction comparison¶
cat_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(cat_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
cat_sample_pred = pd.Series(cat_best_pred_test, index=X_test.index).loc[sample_idx]
cat_results_comparison["Error"] = cat_results_comparison["Prediction"] - cat_results_comparison["Original Label"]
cat_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 584.16 | 794.15 | 899.40 | 1120.37 | 1310.87 | 1345.27 | 3170.53 | 2943.56 | 3676.56 | 3840.50 | 4502.36 | 7254.20 | 12081.78 | 11887.45 | 15044.23 |
| Error | 13.16 | 30.15 | 72.40 | -27.63 | -45.13 | -54.73 | 501.53 | 121.56 | -266.44 | -314.50 | 98.36 | -415.80 | 3253.78 | 26.45 | -1625.77 |
Add formatted timing and model type to the results¶
cat_results["Model_type"] = "Boosting ensemble"
cat_results["Fit_time"] = str(timedelta(seconds=int(cat_fit_time)))
cat_results["Pred_time"] = str(timedelta(seconds=int(cat_predict_time)))
cat_results["Tun_fit_time"] = str(timedelta(seconds=int(cat_tuning_fit_time)))
cat_results["Tun_pred_time"] = str(timedelta(seconds=int(cat_tuning_predict_time)))
cat_results["Full_model_time"] = str(timedelta(seconds=int(cat_fit_time + cat_predict_time + cat_tuning_fit_time + cat_tuning_predict_time)))
cat_results["Tun_MAE"] = cat_best_results["MAE"]
cat_results["Tun_RMSE"] = cat_best_results["RMSE"]
cat_results["Tun_R2"] = cat_best_results["R2"]
cat_results["Tun_Train_MAE"] = cat_best_results["Train_MAE"]
cat_results["Tun_Train_RMSE"] = cat_best_results["Train_RMSE"]
cat_results["Tun_Train_R2"] = cat_best_results["Train_R2"]
Neural models¶
MLP Regressor¶
The MLPRegressor is a neural network model that can learn more complex non-linear relationships in the data.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.neural_network import MLPRegressor
mlp_param = {
# Most important parameters: hidden_layer_sizes, activation, alpha, learning_rate_init, max_iter
"hidden_layer_sizes": (100, 50), # Default: (100,) -> Common alternatives: (50,), (100, 50), (200, 100)
"activation": "relu", # Default: "relu" -> Common alternatives: "tanh", "logistic"
"alpha": 0.0001, # Default: 0.0001 -> From 0.00001 to 0.01 - smaller alpha → weaker regularization
"learning_rate_init": 0.001, # Default: 0.001 -> From 0.0001 to 0.01 - smaller value → slower learning
"max_iter": 2000 # Default: 200 -> Increase if convergence warning appears
}
mlp_reg = Pipeline([
("preprocessing", scaled_preprocessing),
("model", MLPRegressor(random_state=RANDOM_STATE, **mlp_param))
])
print("MLP Regressor: fitting model...")
mlp_reg.fit(X_train, y_train)
print("MLP Regressor: fitted successfully.")
else:
print("MLP Regressor: loading fitted model...")
mlp_reg = loaded_fitted_base_models["mlp"]
print("MLP Regressor: loaded successfully.")
MLP Regressor: fitting model... MLP Regressor: fitted successfully.
mlp_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(mlp_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:12:01
Features created by MLP Regressor Model¶
# Number of input features used by the model
print("Number of input features:", mlp_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", mlp_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("MLP Regressor: predicting model...")
mlp_pred_train = mlp_reg.predict(X_train)
mlp_pred_test = mlp_reg.predict(X_test)
print("MLP Regressor: predicted successfully.\n")
else:
print("MLP Regressor: loading predicted model...")
mlp_pred_train = loaded_predictions["mlp_pred_train_file"]
mlp_pred_test = loaded_predictions["mlp_pred_test_file"]
print("MLP Regressor: loaded successfully.\n")
mlp_results = evaluate_regression(
y_test,
mlp_pred_test,
model_name="MLP Regressor",
y_train_true=y_train,
y_train_pred=mlp_pred_train
)
MLP Regressor: predicting model... MLP Regressor: predicted successfully. --- MLP Regressor --- TEST MAE: 299.39 RMSE: 563.93 R²: 0.9800 TRAIN MAE: 284.40 RMSE: 524.78 R²: 0.9827 GAP RMSE gap: 39.15 R² gap: 0.0027
mlp_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(mlp_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:02
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.model_selection import RandomizedSearchCV
mlp_param_dist = {
"model__hidden_layer_sizes": [(50,), (75,), (50, 25), (75, 50)],
"model__activation": ["tanh"],
"model__alpha": [0.0005, 0.001, 0.002, 0.003],
"model__learning_rate_init": [0.0015, 0.002, 0.003],
"model__max_iter": [3000],
"model__early_stopping": [False, True]
}
mlp_search = RandomizedSearchCV(
mlp_reg, # The pipeline model to tune
param_distributions=mlp_param_dist, # Dictionary with parameter values to test
n_iter=15, # Number of random combinations to try
scoring=SCORING, # Evaluation metric
cv=KFOLD_5, # Number of cross-validation folds
n_jobs=N_JOBS, # Parallel jobs
verbose=VERBOSE, # Show progress during fitting
random_state=RANDOM_STATE, # Reproducible random search
error_score=ERROR_SCORE # Raise error immediately if a fit fails
)
print("MLP Regressor: running randomized search...")
mlp_search.fit(X_train, y_train)
print("MLP Regressor: randomized search completed successfully.")
else:
print("MLP Regressor: loading search object...")
mlp_search = loaded_searches["mlp"]
print("MLP Regressor: search object loaded successfully.")
MLP Regressor: running randomized search... Fitting 5 folds for each of 15 candidates, totalling 75 fits MLP Regressor: randomized search completed successfully.
mlp_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(mlp_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 2:01:28
# Best parameters
mlp_search.best_params_
{'model__max_iter': 3000,
'model__learning_rate_init': 0.003,
'model__hidden_layer_sizes': (75,),
'model__early_stopping': False,
'model__alpha': 0.003,
'model__activation': 'tanh'}
# Best model
mlp_best_tuning = mlp_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("MLP Regressor: predicting best model...")
mlp_best_pred_train = mlp_best_tuning.predict(X_train)
mlp_best_pred_test = mlp_best_tuning.predict(X_test)
print("MLP Regressor: best model predicted successfully.\n")
else:
print("MLP Regressor: loading best predicted model...")
mlp_best_pred_train = loaded_predictions["mlp_best_pred_train_file"]
mlp_best_pred_test = loaded_predictions["mlp_best_pred_test_file"]
print("MLP Regressor: best model loaded successfully.\n")
mlp_best_results = evaluate_regression(
y_test,
mlp_best_pred_test,
"Best MLP Regressor",
y_train_true=y_train,
y_train_pred=mlp_best_pred_train
)
MLP Regressor: predicting best model... MLP Regressor: best model predicted successfully. --- Best MLP Regressor --- TEST MAE: 287.05 RMSE: 546.21 R²: 0.9813 TRAIN MAE: 260.99 RMSE: 487.42 R²: 0.9851 GAP RMSE gap: 58.79 R² gap: 0.0038
mlp_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(mlp_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:00
Test set prediction comparison¶
mlp_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(mlp_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
mlp_sample_pred = pd.Series(mlp_best_pred_test, index=X_test.index).loc[sample_idx]
mlp_results_comparison["Error"] = mlp_results_comparison["Prediction"] - mlp_results_comparison["Original Label"]
mlp_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 643.49 | 822.50 | 1025.35 | 1131.27 | 1430.79 | 1306.63 | 2909.18 | 3055.91 | 3731.91 | 4184.38 | 4384.40 | 7651.51 | 11505.61 | 11475.58 | 14354.44 |
| Error | 72.49 | 58.50 | 198.35 | -16.73 | 74.79 | -93.37 | 240.18 | 233.91 | -211.09 | 29.38 | -19.60 | -18.49 | 2677.61 | -385.42 | -2315.56 |
Add formatted timing and model type to the results¶
mlp_results["Model_type"] = "Neural"
mlp_results["Fit_time"] = str(timedelta(seconds=int(mlp_fit_time)))
mlp_results["Pred_time"] = str(timedelta(seconds=int(mlp_predict_time)))
mlp_results["Tun_fit_time"] = str(timedelta(seconds=int(mlp_tuning_fit_time)))
mlp_results["Tun_pred_time"] = str(timedelta(seconds=int(mlp_tuning_predict_time)))
mlp_results["Full_model_time"] = str(timedelta(seconds=int(mlp_fit_time + mlp_predict_time + mlp_tuning_fit_time + mlp_tuning_predict_time)))
mlp_results["Tun_MAE"] = mlp_best_results["MAE"]
mlp_results["Tun_RMSE"] = mlp_best_results["RMSE"]
mlp_results["Tun_R2"] = mlp_best_results["R2"]
mlp_results["Tun_Train_MAE"] = mlp_best_results["Train_MAE"]
mlp_results["Tun_Train_RMSE"] = mlp_best_results["Train_RMSE"]
mlp_results["Tun_Train_R2"] = mlp_best_results["Train_R2"]
Advanced ensemble models¶
Bagging Regressor¶
The BaggingRegressor trains multiple models on different random samples of the training data and averages their predictions.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.ensemble import BaggingRegressor
from sklearn.tree import DecisionTreeRegressor
bagging_param = {
# Most important parameters: n_estimators, max_samples, max_features
"n_estimators": 100, # Default: 10 -> Often worth trying values between 10 and 200
"max_samples": 0.8, # Default: 1.0 -> From 0.5 to 1.0 - smaller value → more randomness
"max_features": 1.0, # Default: 1.0 -> From 0.5 to 1.0 - smaller value → fewer features per model
"bootstrap": True # Default: True -> False means sampling without replacement
}
bagging_reg = Pipeline([
("preprocessing", tree_preprocessing),
("model", BaggingRegressor(
estimator=DecisionTreeRegressor(random_state=RANDOM_STATE),
random_state=RANDOM_STATE,
**bagging_param
))
])
print("Bagging Regressor: fitting model...")
bagging_reg.fit(X_train, y_train)
print("Bagging Regressor: fitted successfully.")
else:
print("Bagging Regressor: loading fitted model...")
bagging_reg = loaded_fitted_base_models["bagging"]
print("Bagging Regressor: loaded successfully.")
Bagging Regressor: fitting model... Bagging Regressor: fitted successfully.
bagging_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(bagging_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:00:44
Features created by Bagging Regressor Model¶
# Number of input features used by the model
print("Number of input features:", bagging_reg.named_steps["model"].n_features_in_)
Number of input features: 26
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", bagging_reg.named_steps["preprocessing"].get_feature_names_out())
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Bagging Regressor: predicting model...")
bagging_pred_train = bagging_reg.predict(X_train)
bagging_pred_test = bagging_reg.predict(X_test)
print("Bagging Regressor: predicted successfully.\n")
else:
print("Bagging Regressor: loading predicted model...")
bagging_pred_train = loaded_predictions["bagging_pred_train_file"]
bagging_pred_test = loaded_predictions["bagging_pred_test_file"]
print("Bagging Regressor: loaded successfully.\n")
bagging_results = evaluate_regression(
y_test,
bagging_pred_test,
model_name="Bagging Regressor",
y_train_true=y_train,
y_train_pred=bagging_pred_train
)
Bagging Regressor: predicting model... Bagging Regressor: predicted successfully. --- Bagging Regressor --- TEST MAE: 277.24 RMSE: 561.61 R²: 0.9802 TRAIN MAE: 121.53 RMSE: 248.55 R²: 0.9961 GAP RMSE gap: 313.06 R² gap: 0.0159
bagging_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(bagging_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:03
Hyperparameter Tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.ensemble import BaggingRegressor
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import RandomizedSearchCV
bagging_search_reg = Pipeline([
("preprocessing", tree_preprocessing),
("model", BaggingRegressor(
estimator=DecisionTreeRegressor(random_state=RANDOM_STATE),
random_state=RANDOM_STATE
))
])
bagging_param_dist = {
"model__n_estimators": [200, 300],
"model__max_samples": [0.6, 0.7, 0.8],
"model__max_features": [0.8, 1.0],
"model__bootstrap": [True],
"model__estimator__max_depth": [None, 15, 20, 25],
"model__estimator__min_samples_leaf": [1, 2, 4],
"model__estimator__min_samples_split": [2, 5, 10]
}
bagging_search = RandomizedSearchCV(
bagging_search_reg,
param_distributions=bagging_param_dist,
n_iter=20,
scoring=SCORING,
cv=KFOLD_5,
n_jobs=2,
pre_dispatch=2,
verbose=VERBOSE,
random_state=RANDOM_STATE,
error_score=ERROR_SCORE
)
print("Bagging Regressor: running randomized search...")
bagging_search.fit(X_train, y_train)
print("Bagging Regressor: randomized search completed successfully.")
else:
print("Bagging Regressor: loading search object...")
bagging_search = loaded_searches["bagging"]
print("Bagging Regressor: search object loaded successfully.")
Bagging Regressor: running randomized search... Fitting 5 folds for each of 20 candidates, totalling 100 fits Bagging Regressor: randomized search completed successfully.
bagging_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(bagging_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:36:40
# Best parameters
bagging_search.best_params_
{'model__n_estimators': 300,
'model__max_samples': 0.7,
'model__max_features': 1.0,
'model__estimator__min_samples_split': 2,
'model__estimator__min_samples_leaf': 1,
'model__estimator__max_depth': None,
'model__bootstrap': True}
# Best model
bagging_best_tuning = bagging_search.best_estimator_
Prediction on test set with Hyperparameter tuning¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Bagging Regressor: predicting best model...")
bagging_best_pred_train = bagging_best_tuning.predict(X_train)
bagging_best_pred_test = bagging_best_tuning.predict(X_test)
print("Bagging Regressor: best model predicted successfully.\n")
else:
print("Bagging Regressor: loading best predicted model...")
bagging_best_pred_train = loaded_predictions["bagging_best_pred_train_file"]
bagging_best_pred_test = loaded_predictions["bagging_best_pred_test_file"]
print("Bagging Regressor: best model loaded successfully.\n")
bagging_best_results = evaluate_regression(
y_test,
bagging_best_pred_test,
"Best Bagging Regressor",
y_train_true=y_train,
y_train_pred=bagging_best_pred_train
)
Bagging Regressor: predicting best model... Bagging Regressor: best model predicted successfully. --- Best Bagging Regressor --- TEST MAE: 276.32 RMSE: 557.92 R²: 0.9804 TRAIN MAE: 133.28 RMSE: 270.47 R²: 0.9954 GAP RMSE gap: 287.45 R² gap: 0.0150
bagging_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(bagging_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:11
Test set prediction comparison¶
bagging_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(bagging_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
bagging_sample_pred = pd.Series(bagging_best_pred_test, index=X_test.index).loc[sample_idx]
bagging_results_comparison["Error"] = bagging_results_comparison["Prediction"] - bagging_results_comparison["Original Label"]
bagging_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 591.10 | 775.90 | 848.77 | 1082.20 | 1337.62 | 1331.70 | 3070.66 | 2760.98 | 3498.34 | 4269.81 | 4453.90 | 7544.81 | 10824.47 | 12290.34 | 14944.14 |
| Error | 20.10 | 11.90 | 21.77 | -65.80 | -18.38 | -68.30 | 401.66 | -61.02 | -444.66 | 114.81 | 49.90 | -125.19 | 1996.47 | 429.34 | -1725.86 |
Add formatted timing and model type to the results¶
bagging_results["Model_type"] = "Advanced ensemble"
bagging_results["Fit_time"] = str(timedelta(seconds=int(bagging_fit_time)))
bagging_results["Pred_time"] = str(timedelta(seconds=int(bagging_predict_time)))
bagging_results["Tun_fit_time"] = str(timedelta(seconds=int(bagging_tuning_fit_time)))
bagging_results["Tun_pred_time"] = str(timedelta(seconds=int(bagging_tuning_predict_time)))
bagging_results["Full_model_time"] = str(timedelta(seconds=int(bagging_fit_time + bagging_predict_time + bagging_tuning_fit_time + bagging_tuning_predict_time)))
bagging_results["Tun_MAE"] = bagging_best_results["MAE"]
bagging_results["Tun_RMSE"] = bagging_best_results["RMSE"]
bagging_results["Tun_R2"] = bagging_best_results["R2"]
bagging_results["Tun_Train_MAE"] = bagging_best_results["Train_MAE"]
bagging_results["Tun_Train_RMSE"] = bagging_best_results["Train_RMSE"]
bagging_results["Tun_Train_R2"] = bagging_best_results["Train_R2"]
Voting and Stacking Ensemble Strategy¶
The initial Voting and Stacking regressors are built from the selected untuned model pipelines.
The tuned Voting and Stacking regressors are built from the best tuned estimators produced by the earlier hyperparameter searches. No additional grid search is applied to Voting weights or to the Stacking final estimator, because these extra searches were computationally expensive and produced only small improvements in previous runs.
This keeps the comparison clear:
- Initial Voting / Stacking: built from selected untuned model pipelines
- Tuned Voting / Stacking: built from the best estimators found by the earlier hyperparameter searches
from sklearn.base import clone
from sklearn.pipeline import Pipeline
from types import SimpleNamespace
import numpy as np
from catboost import CatBoostClassifier, CatBoostRegressor
# ============================================================
# Helper functions to reuse current tuned estimators safely
# ============================================================
CATBOOST_CLASSES = (CatBoostClassifier, CatBoostRegressor)
def make_python_native(value):
"""
Convert numpy scalar values into regular Python values.
This helps avoid sklearn.clone issues with some third-party estimators,
especially CatBoost models whose tuned parameters may contain numpy types.
"""
if isinstance(value, np.generic):
return value.item()
if isinstance(value, list):
return [make_python_native(v) for v in value]
if isinstance(value, tuple):
return tuple(make_python_native(v) for v in value)
if isinstance(value, dict):
return {
key: make_python_native(val)
for key, val in value.items()
}
return value
def rebuild_catboost_model(model):
"""
Rebuild a CatBoost model with Python-native parameter values.
This avoids sklearn.clone errors where CatBoost may internally modify
parameters such as learning_rate.
"""
params = model.get_params(deep=False)
params = {
key: make_python_native(value)
for key, value in params.items()
}
return model.__class__(**params)
def make_clone_safe_estimator(estimator):
"""
Return a clone-safe version of an estimator.
Normal sklearn-compatible estimators are cloned normally.
CatBoost estimators are rebuilt manually if needed.
Pipelines are handled step by step.
"""
# First try normal sklearn clone
try:
return clone(estimator)
except RuntimeError as error:
error_message = str(error)
if "Cannot clone object" not in error_message:
raise
# Pipeline case
if isinstance(estimator, Pipeline):
new_steps = []
for step_name, step_model in estimator.steps:
if isinstance(step_model, CATBOOST_CLASSES):
safe_step_model = rebuild_catboost_model(step_model)
else:
safe_step_model = clone(step_model)
new_steps.append((step_name, safe_step_model))
safe_pipeline = Pipeline(new_steps)
# Validate that the rebuilt pipeline can now be cloned
return clone(safe_pipeline)
# Standalone CatBoost case
if isinstance(estimator, CATBOOST_CLASSES):
safe_estimator = rebuild_catboost_model(estimator)
# Validate that the rebuilt estimator can now be cloned
return clone(safe_estimator)
# If we get here, the estimator failed clone but is not handled
raise RuntimeError(
"The estimator could not be cloned, and it is not a supported "
"CatBoost or Pipeline estimator."
)
def get_current_best_estimator(model_label, variable_name):
"""
Return a cloned estimator using the current best tuned configuration.
Supports:
- already extracted best estimators/pipelines
- search objects with .best_estimator_
This allows Voting and Stacking tuned versions to use the latest
tuned model configurations without running an additional grid search.
CatBoost estimators are handled safely because sklearn.clone may fail
on some CatBoost parameter values.
"""
estimator = globals().get(variable_name)
if estimator is None:
raise NameError(
f"{model_label}: {variable_name} was not found. "
"Run or load the tuning section for this model first."
)
if hasattr(estimator, "best_estimator_"):
estimator = estimator.best_estimator_
print(f"{model_label}: using tuned estimator from {variable_name}")
return make_clone_safe_estimator(estimator)
Voting Regressor¶
The VotingRegressor combines the predictions of multiple regression models and returns their average as the final prediction.
In this notebook, the initial Voting Regressor is built from selected untuned models, while the tuned Voting Regressor is built from the best estimators found by earlier hyperparameter searches.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.ensemble import VotingRegressor
from sklearn.base import clone
# ============================================================
# Base Voting Regressor
# ============================================================
# Built from base model pipelines, not tuned estimators.
voting_reg = VotingRegressor(
estimators=[
("cat", clone(cat_reg)),
("gbr", clone(gbr_reg)),
("extra_trees", clone(extra_trees_reg)),
("mlp", clone(mlp_reg)),
("lgbm", clone(lgbm_reg)),
("bagging", clone(bagging_reg)),
],
weights=None,
n_jobs=1
)
print("Voting Regressor: fitting base model...")
voting_reg.fit(X_train, y_train)
print("Voting Regressor: fitted successfully.")
else:
print("Voting Regressor: loading fitted model...")
voting_reg = loaded_fitted_base_models["voting"]
print("Voting Regressor: loaded successfully.")
Voting Regressor: fitting base model... Voting Regressor: fitted successfully.
voting_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(voting_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:13:18
Features created by Voting Regressor Model¶
# Number of input features used by the model
print("Number of input features:", voting_reg.n_features_in_)
Number of input features: 9
# Names of the transformed features after preprocessing
print(
"Feature names after preprocessing:\n",
voting_reg.estimators_[0].named_steps["preprocessing"].get_feature_names_out()
)
Feature names after preprocessing: ['num__carat' 'num__depth' 'num__table' 'num__x' 'num__y' 'num__z' 'cat__cut_Fair' 'cat__cut_Good' 'cat__cut_Ideal' 'cat__cut_Premium' 'cat__cut_Very Good' 'cat__color_D' 'cat__color_E' 'cat__color_F' 'cat__color_G' 'cat__color_H' 'cat__color_I' 'cat__color_J' 'cat__clarity_I1' 'cat__clarity_IF' 'cat__clarity_SI1' 'cat__clarity_SI2' 'cat__clarity_VS1' 'cat__clarity_VS2' 'cat__clarity_VVS1' 'cat__clarity_VVS2']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Voting Regressor: predicting model...")
voting_pred_train = voting_reg.predict(X_train)
voting_pred_test = voting_reg.predict(X_test)
print("Voting Regressor: predicted successfully.\n")
else:
print("Voting Regressor: loading predicted model...")
voting_pred_train = loaded_predictions["voting_pred_train_file"]
voting_pred_test = loaded_predictions["voting_pred_test_file"]
print("Voting Regressor: loaded successfully.\n")
voting_results = evaluate_regression(
y_test,
voting_pred_test,
model_name="Voting Regressor",
y_train_true=y_train,
y_train_pred=voting_pred_train
)
Voting Regressor: predicting model... Voting Regressor: predicted successfully. --- Voting Regressor --- TEST MAE: 287.70 RMSE: 556.54 R²: 0.9805 TRAIN MAE: 239.70 RMSE: 447.20 R²: 0.9874 GAP RMSE gap: 109.34 R² gap: 0.0069
voting_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(voting_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:10
Tuned Ensemble from Earlier Hyperparameter Searches¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.ensemble import VotingRegressor
from types import SimpleNamespace
# ============================================================
# Tuned Voting Regressor
# ============================================================
# Built directly from the current tuned base estimators.
# No additional GridSearchCV is used because weight tuning was slow
# and produced only small improvements in this project.
voting_best_tuning = VotingRegressor(
estimators=[
(
"cat",
get_current_best_estimator(
"CatBoost Regressor",
"cat_best_tuning"
)
),
(
"gbr",
get_current_best_estimator(
"Gradient Boosting Regressor",
"gbr_best_tuning"
)
),
(
"extra_trees",
get_current_best_estimator(
"Extra Trees Regressor",
"extra_trees_best_tuning"
)
),
(
"mlp",
get_current_best_estimator(
"MLP Regressor",
"mlp_best_tuning"
)
),
(
"lgbm",
get_current_best_estimator(
"LightGBM Regressor",
"lgbm_best_tuning"
)
),
(
"bagging",
get_current_best_estimator(
"Bagging Regressor",
"bagging_best_tuning"
)
),
],
weights=None,
n_jobs=1
)
print("Voting Regressor: fitting tuned-base model...")
voting_best_tuning.fit(X_train, y_train)
print("Voting Regressor: tuned-base model fitted successfully.")
# Search-like object to keep the existing notebook structure compatible
voting_search = SimpleNamespace(
best_estimator_=voting_best_tuning,
best_params_={
"base_estimators": [
"cat_best_tuning",
"gbr_best_tuning",
"extra_trees_best_tuning",
"mlp_best_tuning",
"lgbm_best_tuning",
"bagging_best_tuning"
],
"weights": None,
"additional_grid_search": False
}
)
else:
print("Voting Regressor: loading search-like object...")
voting_search = loaded_searches["voting"]
print("Voting Regressor: search-like object loaded successfully.")
CatBoost Regressor: using tuned estimator from cat_best_tuning Gradient Boosting Regressor: using tuned estimator from gbr_best_tuning Extra Trees Regressor: using tuned estimator from extra_trees_best_tuning MLP Regressor: using tuned estimator from mlp_best_tuning LightGBM Regressor: using tuned estimator from lgbm_best_tuning Bagging Regressor: using tuned estimator from bagging_best_tuning Voting Regressor: fitting tuned-base model... Voting Regressor: tuned-base model fitted successfully.
voting_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(voting_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:10:21
# Best parameters
voting_search.best_params_
{'base_estimators': ['cat_best_tuning',
'gbr_best_tuning',
'extra_trees_best_tuning',
'mlp_best_tuning',
'lgbm_best_tuning',
'bagging_best_tuning'],
'weights': None,
'additional_grid_search': False}
# Best model
voting_best_tuning = voting_search.best_estimator_
Prediction on test set with tuned ensemble¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Voting Regressor: predicting best model...")
voting_best_pred_train = voting_best_tuning.predict(X_train)
voting_best_pred_test = voting_best_tuning.predict(X_test)
print("Voting Regressor: best model predicted successfully.\n")
else:
print("Voting Regressor: loading best predicted model...")
voting_best_pred_train = loaded_predictions["voting_best_pred_train_file"]
voting_best_pred_test = loaded_predictions["voting_best_pred_test_file"]
print("Voting Regressor: best model loaded successfully.\n")
voting_best_results = evaluate_regression(
y_test,
voting_best_pred_test,
"Best Voting Regressor",
y_train_true=y_train,
y_train_pred=voting_best_pred_train
)
Voting Regressor: predicting best model... Voting Regressor: best model predicted successfully. --- Best Voting Regressor --- TEST MAE: 263.88 RMSE: 523.72 R²: 0.9828 TRAIN MAE: 192.36 RMSE: 357.40 R²: 0.9920 GAP RMSE gap: 166.32 R² gap: 0.0092
voting_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(voting_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:17
Test set prediction comparison¶
voting_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(voting_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
voting_sample_pred = pd.Series(voting_best_pred_test, index=X_test.index).loc[sample_idx]
voting_results_comparison["Error"] = voting_results_comparison["Prediction"] - voting_results_comparison["Original Label"]
voting_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 604.33 | 797.86 | 903.76 | 1096.77 | 1335.87 | 1347.36 | 3055.28 | 2871.54 | 3656.59 | 4088.57 | 4445.85 | 7366.30 | 11507.12 | 11856.40 | 14845.06 |
| Error | 33.33 | 33.86 | 76.76 | -51.23 | -20.13 | -52.64 | 386.28 | 49.54 | -286.41 | -66.43 | 41.85 | -303.70 | 2679.12 | -4.60 | -1824.94 |
Add formatted timing and model type to the results¶
voting_results["Model_type"] = "Advanced ensemble"
voting_results["Fit_time"] = str(timedelta(seconds=int(voting_fit_time)))
voting_results["Pred_time"] = str(timedelta(seconds=int(voting_predict_time)))
voting_results["Tun_fit_time"] = str(timedelta(seconds=int(voting_tuning_fit_time)))
voting_results["Tun_pred_time"] = str(timedelta(seconds=int(voting_tuning_predict_time)))
voting_results["Full_model_time"] = str(timedelta(seconds=int(voting_fit_time + voting_predict_time + voting_tuning_fit_time + voting_tuning_predict_time)))
voting_results["Tun_MAE"] = voting_best_results["MAE"]
voting_results["Tun_RMSE"] = voting_best_results["RMSE"]
voting_results["Tun_R2"] = voting_best_results["R2"]
voting_results["Tun_Train_MAE"] = voting_best_results["Train_MAE"]
voting_results["Tun_Train_RMSE"] = voting_best_results["Train_RMSE"]
voting_results["Tun_Train_R2"] = voting_best_results["Train_R2"]
Stacking Regressor¶
The StackingRegressor combines multiple regression models and trains a final meta-model to learn how to combine their predictions.
In this notebook, Ridge regression is used as the final estimator, and the tuned Stacking Regressor is built from the best estimators found by earlier hyperparameter searches.
start_fit = time.perf_counter()
if not QUICK_LOAD:
from sklearn.ensemble import StackingRegressor
from sklearn.linear_model import Ridge
from sklearn.base import clone
# ============================================================
# Base Stacking Regressor
# ============================================================
# Built from base model pipelines, not tuned estimators.
stacking_reg = StackingRegressor(
estimators=[
("cat", clone(cat_reg)),
("gbr", clone(gbr_reg)),
("extra_trees", clone(extra_trees_reg)),
("mlp", clone(mlp_reg)),
("lgbm", clone(lgbm_reg)),
("bagging", clone(bagging_reg)),
("xgb", clone(xgb_reg)),
],
final_estimator=Ridge(alpha=1.0),
cv=KFOLD_3,
passthrough=False,
n_jobs=1
)
print("Stacking Regressor: fitting base model...")
stacking_reg.fit(X_train, y_train)
print("Stacking Regressor: fitted successfully.")
else:
print("Stacking Regressor: loading fitted model...")
stacking_reg = loaded_fitted_base_models["stacking"]
print("Stacking Regressor: loaded successfully.")
Stacking Regressor: fitting base model... Stacking Regressor: fitted successfully.
stacking_fit_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(stacking_fit_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:47:53
Features created by Stacking Regressor Model¶
# Number of input features used by the model
print("Number of input features:", stacking_reg.n_features_in_)
Number of input features: 9
# Names of the transformed features after preprocessing
print("Feature names after preprocessing:\n", stacking_reg.get_feature_names_out())
Feature names after preprocessing: ['stackingregressor_cat' 'stackingregressor_gbr' 'stackingregressor_extra_trees' 'stackingregressor_mlp' 'stackingregressor_lgbm' 'stackingregressor_bagging' 'stackingregressor_xgb']
Prediction on test set¶
start_pred = time.perf_counter()
if not QUICK_LOAD:
print("Stacking Regressor: predicting model...")
stacking_pred_train = stacking_reg.predict(X_train)
stacking_pred_test = stacking_reg.predict(X_test)
print("Stacking Regressor: predicted successfully.\n")
else:
print("Stacking Regressor: loading predicted model...")
stacking_pred_train = loaded_predictions["stacking_pred_train_file"]
stacking_pred_test = loaded_predictions["stacking_pred_test_file"]
print("Stacking Regressor: loaded successfully.\n")
stacking_results = evaluate_regression(
y_test,
stacking_pred_test,
model_name="Stacking Regressor",
y_train_true=y_train,
y_train_pred=stacking_pred_train
)
Stacking Regressor: predicting model... Stacking Regressor: predicted successfully. --- Stacking Regressor --- TEST MAE: 273.48 RMSE: 534.10 R²: 0.9821 TRAIN MAE: 203.13 RMSE: 376.81 R²: 0.9911 GAP RMSE gap: 157.29 R² gap: 0.0090
stacking_predict_time = time.perf_counter() - start_pred
formatted_time = str(timedelta(seconds=int(stacking_predict_time)))
print(f"Predict time: {formatted_time}")
Predict time: 0:00:10
Tuned Ensemble from Earlier Hyperparameter Searches¶
start_time = time.perf_counter()
if not QUICK_LOAD:
from sklearn.ensemble import StackingRegressor
from sklearn.linear_model import Ridge
from types import SimpleNamespace
# ============================================================
# Tuned Stacking Regressor
# ============================================================
# Built directly from the current tuned base estimators.
# No additional GridSearchCV is used because final-estimator tuning
# was slow and produced only small improvements in this project.
stacking_best_tuning = StackingRegressor(
estimators=[
(
"cat",
get_current_best_estimator(
"CatBoost Regressor",
"cat_best_tuning"
)
),
(
"gbr",
get_current_best_estimator(
"Gradient Boosting Regressor",
"gbr_best_tuning"
)
),
(
"extra_trees",
get_current_best_estimator(
"Extra Trees Regressor",
"extra_trees_best_tuning"
)
),
(
"mlp",
get_current_best_estimator(
"MLP Regressor",
"mlp_best_tuning"
)
),
(
"lgbm",
get_current_best_estimator(
"LightGBM Regressor",
"lgbm_best_tuning"
)
),
(
"bagging",
get_current_best_estimator(
"Bagging Regressor",
"bagging_best_tuning"
)
),
(
"xgb",
get_current_best_estimator(
"XGBoost Regressor",
"xgb_best_tuning"
)
),
],
final_estimator=Ridge(alpha=1.0),
cv=KFOLD_3,
passthrough=False,
n_jobs=1
)
print("Stacking Regressor: fitting tuned-base model...")
stacking_best_tuning.fit(X_train, y_train)
print("Stacking Regressor: tuned-base model fitted successfully.")
# Search-like object to keep the existing notebook structure compatible
stacking_search = SimpleNamespace(
best_estimator_=stacking_best_tuning,
best_params_={
"base_estimators": [
"cat_best_tuning",
"gbr_best_tuning",
"extra_trees_best_tuning",
"mlp_best_tuning",
"lgbm_best_tuning",
"bagging_best_tuning",
"xgb_best_tuning"
],
"final_estimator": "Ridge(alpha=1.0)",
"cv": "KFOLD_3",
"passthrough": False,
"additional_grid_search": False
}
)
else:
print("Stacking Regressor: loading search-like object...")
stacking_search = loaded_searches["stacking"]
print("Stacking Regressor: search-like object loaded successfully.")
CatBoost Regressor: using tuned estimator from cat_best_tuning Gradient Boosting Regressor: using tuned estimator from gbr_best_tuning Extra Trees Regressor: using tuned estimator from extra_trees_best_tuning MLP Regressor: using tuned estimator from mlp_best_tuning LightGBM Regressor: using tuned estimator from lgbm_best_tuning Bagging Regressor: using tuned estimator from bagging_best_tuning XGBoost Regressor: using tuned estimator from xgb_best_tuning Stacking Regressor: fitting tuned-base model... Stacking Regressor: tuned-base model fitted successfully.
stacking_tuning_fit_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(stacking_tuning_fit_time)))
print(f"Tuning time: {formatted_time}")
Tuning time: 0:35:12
# Best parameters
stacking_search.best_params_
{'base_estimators': ['cat_best_tuning',
'gbr_best_tuning',
'extra_trees_best_tuning',
'mlp_best_tuning',
'lgbm_best_tuning',
'bagging_best_tuning',
'xgb_best_tuning'],
'final_estimator': 'Ridge(alpha=1.0)',
'cv': 'KFOLD_3',
'passthrough': False,
'additional_grid_search': False}
# Best model
stacking_best_tuning = stacking_search.best_estimator_
Prediction on test set with tuned ensemble¶
start_time = time.perf_counter()
if not QUICK_LOAD:
print("Stacking Regressor: predicting best model...")
stacking_best_pred_train = stacking_best_tuning.predict(X_train)
stacking_best_pred_test = stacking_best_tuning.predict(X_test)
print("Stacking Regressor: best model predicted successfully.\n")
else:
print("Stacking Regressor: loading best predicted model...")
stacking_best_pred_train = loaded_predictions["stacking_best_pred_train_file"]
stacking_best_pred_test = loaded_predictions["stacking_best_pred_test_file"]
print("Stacking Regressor: best model loaded successfully.\n")
stacking_best_results = evaluate_regression(
y_test,
stacking_best_pred_test,
"Best Stacking Regressor",
y_train_true=y_train,
y_train_pred=stacking_best_pred_train
)
Stacking Regressor: predicting best model... Stacking Regressor: best model predicted successfully. --- Best Stacking Regressor --- TEST MAE: 264.66 RMSE: 522.41 R²: 0.9829 TRAIN MAE: 206.71 RMSE: 384.31 R²: 0.9907 GAP RMSE gap: 138.09 R² gap: 0.0079
stacking_tuning_predict_time = time.perf_counter() - start_time
formatted_time = str(timedelta(seconds=int(stacking_tuning_predict_time)))
print(f"Tuning predict time: {formatted_time}")
Tuning predict time: 0:00:16
Test set prediction comparison¶
stacking_results_comparison = pd.DataFrame({
"Original Label": y_test.loc[sample_idx],
"Prediction": pd.Series(stacking_best_pred_test, index=X_test.index).loc[sample_idx]
})
### Save test set predictions for each model
stacking_sample_pred = pd.Series(stacking_best_pred_test, index=X_test.index).loc[sample_idx]
stacking_results_comparison["Error"] = stacking_results_comparison["Prediction"] - stacking_results_comparison["Original Label"]
stacking_results_comparison.sample(15, random_state=RANDOM_STATE).sort_values("Original Label").T.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
)
| 8048 | 9730 | 4593 | 3706 | 5458 | 8353 | 6130 | 510 | 10382 | 7331 | 6319 | 6725 | 10334 | 3614 | 7116 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 571.00 | 764.00 | 827.00 | 1148.00 | 1356.00 | 1400.00 | 2669.00 | 2822.00 | 3943.00 | 4155.00 | 4404.00 | 7670.00 | 8828.00 | 11861.00 | 16670.00 |
| Prediction | 604.74 | 796.99 | 924.62 | 1097.41 | 1346.71 | 1335.26 | 2995.15 | 2945.62 | 3690.96 | 4083.95 | 4442.45 | 7378.35 | 11649.81 | 11695.06 | 14767.11 |
| Error | 33.74 | 32.99 | 97.62 | -50.59 | -9.29 | -64.74 | 326.15 | 123.62 | -252.04 | -71.05 | 38.45 | -291.65 | 2821.81 | -165.94 | -1902.89 |
Add formatted timing and model type to the results¶
stacking_results["Model_type"] = "Advanced ensemble"
stacking_results["Fit_time"] = str(timedelta(seconds=int(stacking_fit_time)))
stacking_results["Pred_time"] = str(timedelta(seconds=int(stacking_predict_time)))
stacking_results["Tun_fit_time"] = str(timedelta(seconds=int(stacking_tuning_fit_time)))
stacking_results["Tun_pred_time"] = str(timedelta(seconds=int(stacking_tuning_predict_time)))
stacking_results["Full_model_time"] = str(timedelta(seconds=int(stacking_fit_time + stacking_predict_time + stacking_tuning_fit_time + stacking_tuning_predict_time)))
stacking_results["Tun_MAE"] = stacking_best_results["MAE"]
stacking_results["Tun_RMSE"] = stacking_best_results["RMSE"]
stacking_results["Tun_R2"] = stacking_best_results["R2"]
stacking_results["Tun_Train_MAE"] = stacking_best_results["Train_MAE"]
stacking_results["Tun_Train_RMSE"] = stacking_best_results["Train_RMSE"]
stacking_results["Tun_Train_R2"] = stacking_best_results["Train_R2"]
Tuned Regression Model Comparison¶
best_pred_candidates = {
# Baseline models
"Dummy Regressor": dummy_pred_test if "dummy_pred_test" in globals() else None,
# Linear / regularized models
"Linear Regressor": lin_pred_test if "lin_pred_test" in globals() else None,
"Ridge Regressor": ridge_best_pred_test if "ridge_best_pred_test" in globals() else None,
"Lasso Regressor": lasso_best_pred_test if "lasso_best_pred_test" in globals() else None,
"ElasticNet Regressor": elastic_best_pred_test if "elastic_best_pred_test" in globals() else None,
"Bayesian Ridge Regressor": bayes_ridge_best_pred_test if "bayes_ridge_best_pred_test" in globals() else None,
"Quantile Regressor": quantile_best_pred_test if "quantile_best_pred_test" in globals() else None,
# Robust linear models
"Huber Regressor": huber_best_pred_test if "huber_best_pred_test" in globals() else None,
"RANSAC Regressor": ransac_best_pred_test if "ransac_best_pred_test" in globals() else None,
# Online linear models
"SGD Regressor": sgd_best_pred_test if "sgd_best_pred_test" in globals() else None,
"Passive Aggressive Regressor": pa_best_pred_test if "pa_best_pred_test" in globals() else None,
# GLM / distribution-based linear models
"Poisson Regressor": poisson_best_pred_test if "poisson_best_pred_test" in globals() else None,
"Gamma Regressor": gamma_best_pred_test if "gamma_best_pred_test" in globals() else None,
# Kernel / distance-based models
"SVR (linear)": svr_linear_best_pred_test if "svr_linear_best_pred_test" in globals() else None,
"SVR (RBF)": svr_rbf_best_pred_test if "svr_rbf_best_pred_test" in globals() else None,
"KNeighbors Regressor": knn_best_pred_test if "knn_best_pred_test" in globals() else None,
"NuSVR Regressor": nusvr_best_pred_test if "nusvr_best_pred_test" in globals() else None,
# Tree-based models
"Decision Tree Regressor": dt_best_pred_test if "dt_best_pred_test" in globals() else None,
# Tree-based ensemble models
"Random Forest Regressor": rf_best_pred_test if "rf_best_pred_test" in globals() else None,
"Extra Trees Regressor": extra_trees_best_pred_test if "extra_trees_best_pred_test" in globals() else None,
# Boosting ensemble models
"AdaBoost Regressor": ada_best_pred_test if "ada_best_pred_test" in globals() else None,
"Gradient Boosting Regressor": gbr_best_pred_test if "gbr_best_pred_test" in globals() else None,
"Hist Gradient Boosting Regressor": hgb_best_pred_test if "hgb_best_pred_test" in globals() else None,
"XGBoost Regressor": xgb_best_pred_test if "xgb_best_pred_test" in globals() else None,
"LightGBM Regressor": lgbm_best_pred_test if "lgbm_best_pred_test" in globals() else None,
"CatBoost Regressor": cat_best_pred_test if "cat_best_pred_test" in globals() else None,
# Neural models
"MLP Regressor": mlp_best_pred_test if "mlp_best_pred_test" in globals() else None,
# Advanced ensemble models
"Bagging Regressor": bagging_best_pred_test if "bagging_best_pred_test" in globals() else None,
"Voting Regressor": voting_best_pred_test if "voting_best_pred_test" in globals() else None,
"Stacking Regressor": stacking_best_pred_test if "stacking_best_pred_test" in globals() else None
}
# Create results table only from variables that already exist
results_list = []
for var_name in [
# Baseline models
"dummy_results", # Dummy Regressor
# Linear / regularized models
"lin_results", # Linear Regressor
"ridge_results", # Ridge Regressor
"lasso_results", # Lasso Regressor
"elastic_results", # ElasticNet Regressor
"bayes_ridge_results", # Bayesian Ridge Regressor
"quantile_results", # Quantile Regressor
# Robust linear models
"huber_results", # Huber Regressor
"ransac_results", # RANSAC Regressor
# Online linear models
"sgd_results", # SGD Regressor (Stochastic Gradient Descent)
"pa_results", # Passive Aggressive Regressor
# GLM / distribution-based linear models
"poisson_results", # Poisson Regressor
"gamma_results", # Gamma Regressor
# Kernel / distance-based models
"svr_linear_results", # SVR - Support Vector Regression (linear)
"svr_rbf_results", # SVR - Support Vector Regression (RBF)
"knn_results", # KNeighbors Regressor
"nusvr_results", # NuSVR Regressor
# Tree-based models
"dt_results", # Decision Tree Regressor
# Tree-based ensemble models
"rf_results", # Random Forest Regressor
"extra_trees_results", # Extra Trees Regressor
# Boosting ensemble models
"ada_results", # AdaBoost Regressor
"gbr_results", # Gradient Boosting Regressor
"hgb_results", # Hist Gradient Boosting Regressor
"xgb_results", # XGBoost Regressor
"lgbm_results", # LightGBM Regressor
"cat_results", # CatBoost Regressor
# Neural models
"mlp_results", # MLP Regressor
# Advanced ensemble models
"bagging_results", # Bagging Regressor
"voting_results", # Voting Regressor
"stacking_results" # Stacking Regressor
]:
if var_name in globals():
results_list.append(globals()[var_name])
results_df = pd.DataFrame(results_list)
results_df_sorted = results_df.sort_values("Tun_RMSE").reset_index(drop=True)
results_df_sorted = results_df_sorted[
[
"Model",
"Model_type",
"Train_MAE",
"Train_RMSE",
"Train_R2",
"MAE",
"RMSE",
"R2",
"Fit_time",
"Pred_time",
"Tun_Train_MAE",
"Tun_Train_RMSE",
"Tun_Train_R2",
"Tun_MAE",
"Tun_RMSE",
"Tun_R2",
"Tun_fit_time",
"Tun_pred_time",
"Full_model_time"
]
]
### Add total row for time columns
time_columns = [
"Fit_time",
"Pred_time",
"Tun_fit_time",
"Tun_pred_time",
"Full_model_time"
]
numeric_columns = [
"MAE",
"Train_MAE",
"RMSE",
"Train_RMSE",
"R2",
"Train_R2",
"Tun_MAE",
"Tun_Train_MAE",
"Tun_RMSE",
"Tun_Train_RMSE",
"Tun_R2",
"Tun_Train_R2"
]
# Calculate time sums safely from the original table
time_sums = {}
for col in time_columns:
td_series = pd.to_timedelta(results_df_sorted[col], errors="coerce").dropna()
time_sums[col] = sum(td_series, pd.Timedelta(0))
# Create final table
results_df_with_total = results_df_sorted.copy()
results_df_with_total.loc["Total"] = np.nan
results_df_with_total.loc["Total", "Model"] = "Total"
results_df_with_total.loc["Total", "Model_type"] = ""
# Insert summed times into the Total row
for col in time_columns:
results_df_with_total.loc["Total", col] = str(time_sums[col]).replace("0 days ", "")
format_dict = {col: "{:.2f}" for col in numeric_columns}
format_dict["R2"] = "{:.4f}"
format_dict["Train_R2"] = "{:.4f}"
format_dict["Tun_R2"] = "{:.4f}"
format_dict["Tun_Train_R2"] = "{:.4f}"
red_columns = [
"Train_MAE",
"Train_RMSE",
"Train_R2"
]
dark_red_columns = [
"MAE",
"RMSE",
"R2"
]
green_columns = [
"Tun_Train_MAE",
"Tun_Train_RMSE",
"Tun_Train_R2"
]
dark_green_columns = [
"Tun_MAE",
"Tun_RMSE",
"Tun_R2"
]
white_columns = [
"Model",
"Model_type"
]
light_grey_columns = [
"Fit_time",
"Pred_time",
"Tun_fit_time",
"Tun_pred_time"
]
grey_columns = [
"Full_model_time"
]
results_df_with_total["Model"] = results_df_with_total["Model"].str.replace(" Regressor", "", regex=False)
results_df_with_total.style.format(
format_dict,
na_rep=""
).set_properties(
**{"text-align": "center"}
).set_properties(
subset=pd.IndexSlice[:, green_columns],
**{"background-color": "#e5fddb"}
).set_properties(
subset=pd.IndexSlice[:, dark_green_columns],
**{"background-color": "#d7f5cc"}
).set_properties(
subset=pd.IndexSlice[:, red_columns],
**{"background-color": "#fef1f1"}
).set_properties(
subset=pd.IndexSlice[:, dark_red_columns],
**{"background-color": "#f8e6e6"}
).set_properties(
subset=pd.IndexSlice[:, white_columns],
**{"background-color": "#ffffff"}
).set_properties(
subset=pd.IndexSlice[:, light_grey_columns],
**{"background-color": "#f0f0f0"}
).set_properties(
subset=pd.IndexSlice[:, grey_columns],
**{"background-color": "#e3e3e3"}
).set_properties(
subset=pd.IndexSlice[["Total"], :],
**{"background-color": "#e3e3e3"}
).map_index(
lambda v: "background-color: #e3e3e3" if v == "Total" else "background-color: #ffffff",
axis=0
).set_properties(
subset=pd.IndexSlice[:, ["Model"]],
**{
"white-space": "nowrap",
"text-align": "left",
"font-weight": "bold"
}
).set_properties(
subset=pd.IndexSlice[:, ["Model_type"]],
**{
"white-space": "nowrap",
"text-align": "left"
}
).set_table_styles(
[
{
"selector": "th.col_heading",
"props": [("text-align", "left")]
},
{
"selector": "th, td",
"props": [
("border", "1px solid #d0d0d0")
]
},
{
"selector": "td.col0",
"props": [
("text-align", "left !important"),
("white-space", "nowrap")
]
},
{
"selector": "th.col_heading.level0.col0",
"props": [
("text-align", "left !important"),
("white-space", "nowrap")
]
},
{
"selector": "table",
"props": [
("border-collapse", "collapse")
]
},
{
"selector": "tbody tr:nth-child(even) td",
"props": [("filter", "brightness(0.985)")]
},
{
"selector": "tbody tr:nth-child(even) th",
"props": [("filter", "brightness(0.985)")]
},
{
"selector": "tbody tr:hover td",
"props": [("filter", "brightness(0.94)")]
},
{
"selector": "tbody tr:hover th",
"props": [("filter", "brightness(0.94)")]
}
],
overwrite=False
).hide(axis="index")
| Model | Model_type | Train_MAE | Train_RMSE | Train_R2 | MAE | RMSE | R2 | Fit_time | Pred_time | Tun_Train_MAE | Tun_Train_RMSE | Tun_Train_R2 | Tun_MAE | Tun_RMSE | Tun_R2 | Tun_fit_time | Tun_pred_time | Full_model_time |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Stacking | Advanced ensemble | 203.13 | 376.81 | 0.9911 | 273.48 | 534.10 | 0.9821 | 0:47:53 | 0:00:10 | 206.71 | 384.31 | 0.9907 | 264.66 | 522.41 | 0.9829 | 0:35:12 | 0:00:16 | 1:23:33 |
| Voting | Advanced ensemble | 239.70 | 447.20 | 0.9874 | 287.70 | 556.54 | 0.9805 | 0:13:18 | 0:00:10 | 192.36 | 357.40 | 0.9920 | 263.88 | 523.72 | 0.9828 | 0:10:21 | 0:00:17 | 0:24:08 |
| CatBoost | Boosting ensemble | 280.82 | 503.57 | 0.9841 | 301.92 | 565.78 | 0.9799 | 0:00:08 | 0:00:00 | 224.01 | 390.18 | 0.9904 | 275.41 | 539.24 | 0.9817 | 0:09:46 | 0:00:00 | 0:09:55 |
| Gradient Boosting | Boosting ensemble | 394.16 | 711.06 | 0.9682 | 410.94 | 757.78 | 0.9639 | 0:00:15 | 0:00:00 | 191.96 | 363.18 | 0.9917 | 270.22 | 540.63 | 0.9816 | 0:38:50 | 0:00:02 | 0:39:08 |
| Extra Trees | Tree-based ensemble | 239.07 | 449.23 | 0.9873 | 320.02 | 618.24 | 0.9760 | 0:00:22 | 0:00:04 | 167.43 | 317.42 | 0.9937 | 274.34 | 542.91 | 0.9815 | 0:19:28 | 0:00:01 | 0:19:56 |
| MLP | Neural | 284.40 | 524.78 | 0.9827 | 299.39 | 563.93 | 0.9800 | 0:12:01 | 0:00:02 | 260.99 | 487.42 | 0.9851 | 287.05 | 546.21 | 0.9813 | 2:01:28 | 0:00:00 | 2:13:32 |
| LightGBM | Boosting ensemble | 267.43 | 481.77 | 0.9854 | 291.70 | 556.21 | 0.9806 | 0:00:00 | 0:00:00 | 239.95 | 430.82 | 0.9883 | 280.26 | 546.91 | 0.9812 | 0:03:20 | 0:00:01 | 0:03:22 |
| Bagging | Advanced ensemble | 121.53 | 248.55 | 0.9961 | 277.24 | 561.61 | 0.9802 | 0:00:44 | 0:00:03 | 133.28 | 270.47 | 0.9954 | 276.32 | 557.92 | 0.9804 | 0:36:40 | 0:00:11 | 0:37:40 |
| XGBoost | Boosting ensemble | 265.04 | 483.91 | 0.9853 | 298.71 | 580.68 | 0.9788 | 0:00:00 | 0:00:00 | 211.66 | 365.56 | 0.9916 | 280.71 | 559.19 | 0.9804 | 0:04:15 | 0:00:00 | 0:04:16 |
| Random Forest | Tree-based ensemble | 103.58 | 208.16 | 0.9973 | 291.18 | 590.60 | 0.9781 | 0:00:40 | 0:00:05 | 170.67 | 335.87 | 0.9929 | 278.87 | 559.44 | 0.9803 | 0:08:33 | 0:00:01 | 0:09:21 |
| Hist Gradient Boosting | Boosting ensemble | 271.53 | 494.89 | 0.9846 | 295.67 | 569.11 | 0.9797 | 0:00:03 | 0:00:00 | 234.41 | 420.41 | 0.9889 | 286.29 | 567.64 | 0.9798 | 0:02:12 | 0:00:01 | 0:02:17 |
| Decision Tree | Tree-based | 366.48 | 681.08 | 0.9709 | 408.04 | 763.02 | 0.9634 | 0:00:00 | 0:00:00 | 246.23 | 469.78 | 0.9861 | 329.24 | 641.31 | 0.9742 | 0:01:59 | 0:00:00 | 0:02:00 |
| SVR (RBF) | Kernel / Distance-based | 642.81 | 1390.55 | 0.8785 | 663.55 | 1456.31 | 0.8668 | 0:03:39 | 0:10:09 | 289.53 | 595.36 | 0.9777 | 318.52 | 667.51 | 0.9720 | 0:39:21 | 0:10:08 | 1:03:19 |
| KNeighbors | Kernel / Distance-based | 0.47 | 8.85 | 1.0000 | 387.51 | 792.30 | 0.9606 | 0:00:00 | 0:00:07 | 332.91 | 662.87 | 0.9724 | 384.46 | 791.59 | 0.9606 | 0:01:08 | 0:00:27 | 0:01:43 |
| AdaBoost | Boosting ensemble | 877.80 | 1353.13 | 0.8850 | 889.86 | 1384.47 | 0.8796 | 0:00:11 | 0:00:01 | 526.20 | 797.54 | 0.9600 | 543.39 | 843.12 | 0.9553 | 0:19:30 | 0:00:04 | 0:19:48 |
| Poisson | GLM / distribution-based linear | 446.86 | 874.69 | 0.9519 | 455.73 | 907.80 | 0.9482 | 0:00:03 | 0:00:00 | 438.19 | 848.85 | 0.9547 | 444.73 | 875.40 | 0.9519 | 0:01:38 | 0:00:00 | 0:01:41 |
| NuSVR | Kernel / Distance-based | 1409.10 | 2750.32 | 0.5247 | 1413.92 | 2766.31 | 0.5192 | 0:04:46 | 0:04:59 | 421.54 | 872.67 | 0.9521 | 444.34 | 950.57 | 0.9432 | 0:27:54 | 0:05:02 | 0:42:43 |
| Lasso | Linear / regularized | 732.45 | 1114.62 | 0.9219 | 751.67 | 1191.26 | 0.9108 | 0:00:08 | 0:00:00 | 730.87 | 1114.79 | 0.9219 | 750.02 | 1191.18 | 0.9109 | 0:06:54 | 0:00:00 | 0:07:03 |
| Bayesian Ridge | Linear / regularized | 732.63 | 1114.62 | 0.9219 | 751.86 | 1191.27 | 0.9108 | 0:00:00 | 0:00:00 | 732.63 | 1114.62 | 0.9219 | 751.86 | 1191.27 | 0.9108 | 0:00:10 | 0:00:00 | 0:00:10 |
| Linear | Linear / regularized | 732.64 | 1114.62 | 0.9219 | 751.86 | 1191.28 | 0.9108 | 0:00:00 | 0:00:00 | 751.86 | 1191.28 | 0.9108 | 0:00:00 | |||||
| SGD | Online linear | 732.63 | 1114.63 | 0.9219 | 751.84 | 1191.23 | 0.9108 | 0:00:01 | 0:00:00 | 732.70 | 1115.31 | 0.9218 | 751.77 | 1191.36 | 0.9108 | 0:02:38 | 0:00:00 | 0:02:40 |
| Ridge | Linear / regularized | 732.59 | 1114.68 | 0.9219 | 751.78 | 1191.18 | 0.9109 | 0:00:00 | 0:00:00 | 732.97 | 1115.87 | 0.9218 | 751.98 | 1191.68 | 0.9108 | 0:00:13 | 0:00:00 | 0:00:13 |
| ElasticNet | Linear / regularized | 909.81 | 1340.40 | 0.8871 | 922.99 | 1384.66 | 0.8795 | 0:00:00 | 0:00:00 | 734.06 | 1118.03 | 0.9215 | 752.81 | 1193.11 | 0.9106 | 0:00:24 | 0:00:00 | 0:00:25 |
| Passive Aggressive | Online linear | 643.47 | 1238.61 | 0.9036 | 661.22 | 1323.48 | 0.8900 | 0:00:00 | 0:00:00 | 730.42 | 1116.38 | 0.9217 | 749.59 | 1193.47 | 0.9105 | 0:06:55 | 0:00:00 | 0:06:56 |
| Huber | Robust linear | 642.26 | 1196.97 | 0.9100 | 662.10 | 1289.80 | 0.8955 | 0:00:02 | 0:00:00 | 730.20 | 1116.25 | 0.9217 | 749.74 | 1195.11 | 0.9103 | 0:07:29 | 0:00:00 | 0:07:32 |
| RANSAC | Robust linear | 654.06 | 1165.66 | 0.9146 | 674.05 | 1256.31 | 0.9008 | 0:00:03 | 0:00:00 | 720.41 | 1119.55 | 0.9212 | 741.22 | 1201.35 | 0.9093 | 0:06:23 | 0:00:00 | 0:06:27 |
| Quantile | Linear / quantile | 638.64 | 1231.25 | 0.9047 | 657.62 | 1322.29 | 0.8902 | 0:03:11 | 0:00:00 | 638.64 | 1231.25 | 0.9047 | 657.62 | 1322.29 | 0.8902 | 1:04:26 | 0:00:00 | 1:07:39 |
| SVR (linear) | Kernel / Distance-based | 643.71 | 1254.48 | 0.9011 | 659.76 | 1330.26 | 0.8888 | 0:03:20 | 0:02:32 | 638.97 | 1237.17 | 0.9038 | 657.20 | 1323.45 | 0.8900 | 0:23:02 | 0:02:32 | 0:31:28 |
| Gamma | GLM / distribution-based linear | 4094.48 | 625005.31 | -24544.1262 | 1097.17 | 3580.22 | 0.1947 | 0:00:00 | 0:00:00 | 2304.66 | 3251.71 | 0.3356 | 2301.82 | 3228.21 | 0.3453 | 0:00:11 | 0:00:00 | 0:00:11 |
| Dummy | Baseline | 3031.54 | 3989.34 | 0.0000 | 3031.98 | 3989.65 | -0.0000 | 0:00:00 | 0:00:00 | 3031.98 | 3989.65 | -0.0000 | 0:00:00 | |||||
| Total | 01:30:48 | 00:18:22 | 08:20:20 | 00:19:03 | 10:29:06 |
Export Final Regression Results and Model Comparison Plot¶
# ============================================================
# Export tuned regression model comparison table as CSV
# ============================================================
output_path = REGRESSION_RESULTS_DIR / "supervised_regression_tuned_model_comparison_table.csv"
tuned_regression_model_comparison_table = results_df_with_total.copy()
if SAVE_PORTFOLIO_OUTPUTS:
tuned_regression_model_comparison_table.to_csv(
output_path,
index=False
)
print("Saved:", output_path)
Saved: _exports\regression_models\results\supervised_regression_tuned_model_comparison_table.csv
# ============================================================
# Export tuned regression model comparison plot
# ============================================================
model_comparison_plot_df = results_df_sorted.copy()
model_comparison_plot_df = model_comparison_plot_df.dropna(
subset=["Tun_RMSE"]
).copy()
model_comparison_plot_df["Model_Display"] = (
model_comparison_plot_df["Model"]
.astype(str)
.str.replace(" Regressor", "", regex=False)
)
model_comparison_plot_df = model_comparison_plot_df.sort_values(
"Tun_RMSE",
ascending=True
)
plt.figure(figsize=(12, max(7, 0.38 * len(model_comparison_plot_df))))
plt.barh(
model_comparison_plot_df["Model_Display"][::-1],
model_comparison_plot_df["Tun_RMSE"][::-1]
)
plt.xlabel("Tuned RMSE")
plt.ylabel("Model")
plt.title("Tuned Regression Model Comparison")
plt.grid(axis="x", linestyle="--", alpha=0.4)
plt.tight_layout()
if SAVE_PORTFOLIO_OUTPUTS:
plt.savefig(
REGRESSION_IMAGES_DIR / "supervised_regression_model_comparison.png",
dpi=FIG_DPI,
bbox_inches="tight"
)
plt.show()
from sklearn.metrics import root_mean_squared_error, r2_score
best_model_name = results_df_sorted.loc[0, "Model"]
best_y_pred = best_pred_candidates.get(best_model_name)
if best_y_pred is not None:
print("Best model:", best_model_name)
print("Best model test RMSE:", root_mean_squared_error(y_test, best_y_pred))
print("Best model test R2:", r2_score(y_test, best_y_pred))
else:
print(f"No saved prediction found for: {best_model_name}")
Best model: Stacking Regressor Best model test RMSE: 522.4074749766904 Best model test R2: 0.9828545638547189
Prediction Comparison on Selected Test Samples¶
### Final comparison table for sample predictions
sample_pred_candidates = [
# Baseline models
("Dummy Regressor", "dummy_sample_pred"),
# Linear / regularized models
("Linear Regressor", "lin_sample_pred"),
("Ridge Regressor", "ridge_sample_pred"),
("Lasso Regressor", "lasso_sample_pred"),
("ElasticNet Regressor", "elastic_sample_pred"),
("Bayesian Ridge Regressor", "bayes_ridge_sample_pred"),
("Quantile Regressor", "quantile_sample_pred"),
# Robust linear models
("Huber Regressor", "huber_sample_pred"),
("RANSAC Regressor", "ransac_sample_pred"),
# Online linear models
("SGD Regressor", "sgd_sample_pred"),
("Passive Aggressive Regressor", "pa_sample_pred"),
# GLM / distribution-based linear models
("Poisson Regressor", "poisson_sample_pred"),
("Gamma Regressor", "gamma_sample_pred"),
# Kernel / distance-based models
("SVR (linear)", "svr_linear_sample_pred"),
("SVR (RBF)", "svr_rbf_sample_pred"),
("NuSVR Regressor", "nusvr_sample_pred"),
("KNeighbors Regressor", "knn_sample_pred"),
# Tree-based models
("Decision Tree Regressor", "dt_sample_pred"),
# Tree-based ensemble models
("Random Forest Regressor", "rf_sample_pred"),
("Extra Trees Regressor", "extra_trees_sample_pred"),
# Boosting ensemble models
("AdaBoost Regressor", "ada_sample_pred"),
("Gradient Boosting Regressor", "gbr_sample_pred"),
("Hist Gradient Boosting Regressor", "hgb_sample_pred"),
("XGBoost Regressor", "xgb_sample_pred"),
("LightGBM Regressor", "lgbm_sample_pred"),
("CatBoost Regressor", "cat_sample_pred"),
# Neural models
("MLP Regressor", "mlp_sample_pred"),
# Advanced ensemble models
("Bagging Regressor", "bagging_sample_pred"),
("Voting Regressor", "voting_sample_pred"),
("Stacking Regressor", "stacking_sample_pred")
]
comparison_dict = {
"Original Label": y_test.loc[sample_idx]
}
for model_name, var_name in sample_pred_candidates:
if var_name in globals():
comparison_dict[model_name] = pd.Series(globals()[var_name]).reindex(sample_idx)
final_sample_comparison_df = pd.DataFrame(comparison_dict).T
### Reorder comparison table by Tun_RMSE ranking
ordered_models = ["Original Label"] + [
model_name
for model_name in results_df_sorted["Model"]
if model_name in final_sample_comparison_df.index
]
final_sample_comparison_df = final_sample_comparison_df.reindex(ordered_models)
sorted_df = final_sample_comparison_df.sort_values("Original Label", axis=1)
label_values = sorted_df.loc["Original Label"]
thresholds = [500, 750, 1000, 2000, 3000, 4000, 5000, 7500, 10000, 12500, 15000, 17500]
selected_cols = []
for threshold in thresholds:
matching_positions = np.where(label_values.to_numpy() >= threshold)[0]
if len(matching_positions) > 0:
start = matching_positions[0]
selected_cols.extend(sorted_df.columns[start:start+2])
selected_cols = list(dict.fromkeys(selected_cols)) # remove duplicates, keep order
selected_df = sorted_df[selected_cols].copy()
# Remove " Regressor" from index labels
selected_df.index = selected_df.index.str.replace(" Regressor", "", regex=False)
selected_df.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966; text-align: left; white-space: nowrap;"
if v == "Original Label"
else "text-align: left; white-space: nowrap;",
axis=0
) \
.set_table_styles(
[
{
"selector": "th.row_heading",
"props": [
("text-align", "left"),
("white-space", "nowrap")
]
}
],
overwrite=False
)
| 5872 | 7115 | 4786 | 3903 | 10066 | 9954 | 6963 | 8201 | 726 | 10772 | 4194 | 4446 | 1967 | 3861 | 1038 | 4503 | 10182 | 5305 | 2532 | 5912 | 2950 | 6704 | 9048 | 329 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 500.00 | 500.00 | 750.00 | 750.00 | 1000.00 | 1000.00 | 2000.00 | 2000.00 | 3001.00 | 3001.00 | 4001.00 | 4001.00 | 5000.00 | 5000.00 | 7503.00 | 7504.00 | 10000.00 | 10004.00 | 12500.00 | 12502.00 | 15017.00 | 15030.00 | 17504.00 | 17509.00 |
| Stacking | 514.58 | 594.82 | 798.73 | 919.64 | 1060.06 | 971.49 | 2396.09 | 1528.78 | 3042.09 | 3243.95 | 4387.46 | 3904.71 | 5329.89 | 4769.95 | 7629.48 | 7538.63 | 9593.46 | 9972.90 | 17743.07 | 12615.69 | 16978.51 | 17175.33 | 17287.24 | 16212.88 |
| Voting | 519.89 | 584.62 | 796.47 | 895.76 | 1054.56 | 986.03 | 2363.95 | 1539.18 | 3083.73 | 3249.40 | 4429.76 | 3898.07 | 5381.53 | 4837.08 | 7613.18 | 7463.92 | 9499.86 | 10071.79 | 17720.44 | 12654.85 | 17126.17 | 17140.08 | 17368.99 | 16032.32 |
| CatBoost | 492.91 | 568.39 | 785.84 | 894.60 | 1085.08 | 996.92 | 2244.93 | 1701.76 | 3058.92 | 3290.15 | 4446.97 | 3901.47 | 5420.84 | 5059.76 | 7625.33 | 7668.14 | 9528.13 | 10235.02 | 17765.94 | 12526.20 | 17034.23 | 16810.86 | 17638.16 | 16065.87 |
| Gradient Boosting | 494.79 | 542.49 | 809.33 | 860.93 | 1025.91 | 970.61 | 2346.51 | 1493.76 | 3178.16 | 3351.92 | 4412.94 | 3858.93 | 5462.72 | 4708.77 | 7559.00 | 7537.70 | 9662.76 | 9843.49 | 18769.15 | 12881.56 | 17366.03 | 16983.30 | 17031.04 | 15511.57 |
| Extra Trees | 545.09 | 641.78 | 805.46 | 889.02 | 1038.93 | 1014.85 | 2467.06 | 1462.85 | 3011.07 | 3062.59 | 4344.38 | 3851.96 | 5444.24 | 4757.00 | 7801.34 | 7346.52 | 9364.27 | 10133.31 | 17231.21 | 12800.46 | 16764.65 | 17166.48 | 17556.33 | 16632.49 |
| MLP | 540.56 | 664.31 | 814.87 | 1022.25 | 1108.33 | 949.58 | 2509.14 | 1529.97 | 2927.06 | 3225.90 | 4350.08 | 3972.81 | 5109.61 | 4659.68 | 7624.28 | 7608.00 | 9722.22 | 9767.90 | 17120.90 | 12294.59 | 16541.77 | 17402.52 | 17043.95 | 16548.84 |
| LightGBM | 545.72 | 449.61 | 781.20 | 868.31 | 1059.86 | 1020.40 | 2208.98 | 1530.21 | 3195.40 | 3423.08 | 4448.23 | 3904.48 | 5415.31 | 4962.68 | 7469.05 | 7392.90 | 9495.16 | 10273.71 | 18311.54 | 12679.85 | 17704.60 | 17444.51 | 17478.45 | 15615.66 |
| Bagging | 500.29 | 641.14 | 782.13 | 839.48 | 1009.27 | 963.82 | 2407.06 | 1516.53 | 3131.79 | 3142.78 | 4575.98 | 3898.77 | 5436.44 | 4874.60 | 7600.08 | 7230.25 | 9226.65 | 10177.29 | 17123.91 | 12746.46 | 17345.76 | 17032.80 | 17466.01 | 15819.46 |
| XGBoost | 512.55 | 609.06 | 843.94 | 909.32 | 1039.15 | 991.88 | 2302.22 | 1567.17 | 3092.96 | 3146.49 | 4376.83 | 3880.76 | 5351.58 | 4783.10 | 7301.20 | 7468.68 | 9086.98 | 10250.64 | 17350.14 | 12727.72 | 17544.82 | 16579.54 | 17835.42 | 17503.64 |
| Random Forest | 489.01 | 635.94 | 772.93 | 853.84 | 1013.64 | 959.01 | 2397.25 | 1486.36 | 3098.72 | 3089.63 | 4492.00 | 3899.81 | 5460.35 | 4894.49 | 7510.79 | 7164.21 | 9246.92 | 10100.12 | 16962.01 | 12741.44 | 17294.09 | 17042.17 | 17443.39 | 16314.65 |
| Hist Gradient Boosting | 534.32 | 571.81 | 769.70 | 875.37 | 1057.88 | 996.30 | 2296.44 | 1520.24 | 3200.05 | 3357.97 | 4668.61 | 3855.64 | 5262.60 | 4735.77 | 7476.73 | 7380.83 | 9217.75 | 10208.79 | 16932.17 | 12749.06 | 17801.61 | 16034.81 | 17546.95 | 16771.56 |
| Decision Tree | 497.58 | 648.33 | 833.44 | 938.22 | 1026.83 | 1005.51 | 2642.50 | 1506.80 | 3144.86 | 2991.08 | 4342.10 | 4068.23 | 5395.05 | 4835.03 | 7247.33 | 7024.81 | 8990.91 | 9998.59 | 17808.68 | 12365.85 | 17028.00 | 17057.25 | 17091.10 | 15795.40 |
| SVR (RBF) | 510.97 | 576.57 | 732.62 | 911.51 | 1052.18 | 941.23 | 2231.79 | 1580.13 | 3080.10 | 3190.09 | 4388.21 | 3898.79 | 5419.67 | 5155.14 | 7477.26 | 7474.43 | 10001.22 | 10263.76 | 15391.04 | 12148.48 | 16753.98 | 16634.38 | 18379.99 | 16606.36 |
| KNeighbors | 590.82 | 667.91 | 887.82 | 955.09 | 1047.64 | 1017.45 | 2527.45 | 1617.09 | 3268.36 | 3132.09 | 4471.27 | 3814.18 | 5311.64 | 6370.00 | 7058.91 | 7409.00 | 9680.45 | 10087.55 | 15476.45 | 12390.82 | 17374.18 | 16451.18 | 17053.82 | 14785.55 |
| AdaBoost | 685.90 | 936.78 | 937.89 | 956.74 | 769.31 | 936.52 | 2426.49 | 1936.28 | 4021.11 | 4048.45 | 5825.39 | 5256.33 | 5881.71 | 4111.92 | 8890.08 | 8695.53 | 9594.17 | 10511.97 | 15408.74 | 12454.87 | 15345.61 | 14943.73 | 15740.44 | 15399.36 |
| Poisson | 573.00 | 475.04 | 936.55 | 956.74 | 1205.68 | 983.19 | 2280.37 | 1954.99 | 2400.28 | 2493.60 | 4119.95 | 3889.46 | 5506.60 | 5054.15 | 8443.13 | 8319.22 | 10471.82 | 9968.11 | 20008.08 | 12425.46 | 16332.99 | 18566.02 | 22163.07 | 16651.77 |
| NuSVR | 537.38 | 611.26 | 751.49 | 893.23 | 1113.51 | 994.36 | 2203.20 | 1680.86 | 2544.31 | 2679.61 | 4573.71 | 4000.92 | 5803.52 | 5153.26 | 8730.76 | 8193.30 | 9721.55 | 9716.99 | 12401.12 | 11175.27 | 15282.79 | 15291.51 | 17214.21 | 14146.79 |
| Lasso | -375.23 | -1735.76 | 651.76 | 945.23 | 1669.42 | 1361.04 | 2801.74 | 1946.79 | 2447.58 | 2616.74 | 4974.01 | 4387.38 | 5876.33 | 5466.99 | 8834.93 | 8852.65 | 9958.90 | 8753.58 | 19200.28 | 10629.16 | 14649.51 | 21050.19 | 17891.77 | 14291.64 |
| Bayesian Ridge | -378.21 | -1759.32 | 646.54 | 944.07 | 1690.39 | 1381.18 | 2797.32 | 1941.06 | 2421.96 | 2588.68 | 4947.36 | 4369.47 | 5871.08 | 5484.15 | 8814.36 | 8840.35 | 9954.41 | 8759.14 | 19248.73 | 10651.35 | 14683.77 | 21112.90 | 17927.29 | 14324.69 |
| Linear | -377.82 | -1759.64 | 646.46 | 944.11 | 1690.67 | 1381.49 | 2797.40 | 1940.84 | 2421.33 | 2588.03 | 4946.93 | 4369.24 | 5870.79 | 5484.09 | 8814.01 | 8840.22 | 9954.84 | 8759.18 | 19251.37 | 10651.76 | 14684.93 | 21115.41 | 17928.86 | 14325.81 |
| SGD | -405.12 | -1736.61 | 652.02 | 941.20 | 1670.46 | 1359.95 | 2791.63 | 1955.48 | 2465.52 | 2633.14 | 4976.63 | 4384.75 | 5890.58 | 5488.14 | 8837.51 | 8848.13 | 9924.16 | 8755.81 | 19067.84 | 10622.58 | 14604.64 | 20942.35 | 17820.23 | 14247.73 |
| Ridge | -413.50 | -1727.83 | 654.20 | 940.74 | 1664.45 | 1352.85 | 2790.28 | 1961.37 | 2481.48 | 2649.15 | 4987.89 | 4390.58 | 5897.78 | 5489.88 | 8847.03 | 8852.39 | 9914.46 | 8755.27 | 19007.65 | 10612.35 | 14577.41 | 20882.14 | 17782.09 | 14221.12 |
| ElasticNet | -436.70 | -1704.81 | 659.82 | 938.91 | 1646.55 | 1334.03 | 2785.68 | 1975.16 | 2522.90 | 2690.99 | 5015.93 | 4405.01 | 5916.16 | 5493.18 | 8869.25 | 8860.80 | 9887.26 | 8752.20 | 18848.33 | 10585.40 | 14505.74 | 20726.95 | 17683.76 | 14151.07 |
| Passive Aggressive | -416.18 | -1720.50 | 652.11 | 939.30 | 1654.91 | 1345.71 | 2778.95 | 1953.60 | 2481.84 | 2653.08 | 4980.89 | 4388.80 | 5896.22 | 5478.88 | 8857.14 | 8857.13 | 9903.48 | 8740.37 | 19006.84 | 10603.95 | 14580.61 | 20902.60 | 17788.80 | 14216.20 |
| Huber | -404.71 | -1728.74 | 652.44 | 942.90 | 1665.49 | 1354.41 | 2784.90 | 1954.56 | 2467.64 | 2639.69 | 4980.51 | 4382.13 | 5894.78 | 5478.76 | 8860.35 | 8865.87 | 9924.46 | 8753.84 | 19094.93 | 10618.52 | 14619.40 | 20974.25 | 17840.05 | 14259.54 |
| RANSAC | -223.74 | -1649.50 | 670.05 | 903.88 | 1628.33 | 1399.33 | 2788.69 | 1910.37 | 2437.48 | 2562.43 | 4973.61 | 4365.20 | 5787.74 | 5433.45 | 8891.43 | 8889.65 | 9909.83 | 8634.31 | 19499.85 | 10683.23 | 14798.55 | 21502.19 | 18112.81 | 14399.12 |
| Quantile | 111.80 | -684.02 | 635.91 | 1023.82 | 1170.41 | 1161.58 | 2713.18 | 1721.47 | 2825.16 | 2948.79 | 4910.24 | 4481.76 | 5511.88 | 4622.47 | 8585.25 | 8691.46 | 9671.20 | 7551.31 | 19677.42 | 9991.32 | 14435.52 | 21879.07 | 17790.10 | 13766.25 |
| SVR (linear) | 110.31 | -676.16 | 644.11 | 1024.14 | 1164.70 | 1149.45 | 2710.17 | 1729.34 | 2842.32 | 2961.39 | 4922.96 | 4478.34 | 5504.08 | 4621.57 | 8588.87 | 8666.43 | 9618.01 | 7529.41 | 19458.51 | 9954.64 | 14327.90 | 21695.33 | 17660.02 | 13663.76 |
| Gamma | 2738.00 | 2944.90 | 2919.27 | 2945.32 | 2821.02 | 2927.33 | 3463.84 | 3305.21 | 3944.67 | 3853.26 | 4247.70 | 4096.35 | 4243.58 | 3892.99 | 4901.56 | 4947.43 | 4943.59 | 4476.60 | 6894.97 | 5001.68 | 5916.48 | 7362.31 | 6395.88 | 5598.35 |
| Dummy | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 | 3932.89 |
# ============================================================
# Export selected test sample predictions as CSV
# ============================================================
if SAVE_PORTFOLIO_OUTPUTS:
output_path = REGRESSION_RESULTS_DIR / "supervised_regression_selected_test_sample_predictions.csv"
selected_test_sample_predictions = selected_df.copy()
selected_test_sample_predictions.to_csv(
output_path,
index=True,
index_label="Model"
)
print("Saved:", output_path)
Saved: _exports\regression_models\results\supervised_regression_selected_test_sample_predictions.csv
Prediction Comparison for the Best Regression Model¶
comparison_model_name = results_df_sorted.loc[0, "Model"] # Default: best model from Tun_RMSE ranking
comparison_model_name = comparison_model_name.replace(" Regressor", "")
# comparison_model_name = "Lasso" # Example: change later if needed
original_row = selected_df.loc[["Original Label"]]
model_row = selected_df.loc[[comparison_model_name]]
error_row = (model_row.iloc[0] - original_row.iloc[0]).to_frame().T
error_row.index = ["Discrepancy"]
comparison_df = pd.concat([original_row, model_row, error_row])
comparison_df.style.format("{:.2f}") \
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{"background-color": "#ffd966"}
) \
.map_index(
lambda v: "background-color: #ffd966" if v == "Original Label" else "",
axis=0
) \
.set_table_styles(
[
{
"selector": "th.row_heading",
"props": [
("text-align", "left"),
("white-space", "nowrap")
]
}
],
overwrite=False
)
| 5872 | 7115 | 4786 | 3903 | 10066 | 9954 | 6963 | 8201 | 726 | 10772 | 4194 | 4446 | 1967 | 3861 | 1038 | 4503 | 10182 | 5305 | 2532 | 5912 | 2950 | 6704 | 9048 | 329 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Original Label | 500.00 | 500.00 | 750.00 | 750.00 | 1000.00 | 1000.00 | 2000.00 | 2000.00 | 3001.00 | 3001.00 | 4001.00 | 4001.00 | 5000.00 | 5000.00 | 7503.00 | 7504.00 | 10000.00 | 10004.00 | 12500.00 | 12502.00 | 15017.00 | 15030.00 | 17504.00 | 17509.00 |
| Stacking | 514.58 | 594.82 | 798.73 | 919.64 | 1060.06 | 971.49 | 2396.09 | 1528.78 | 3042.09 | 3243.95 | 4387.46 | 3904.71 | 5329.89 | 4769.95 | 7629.48 | 7538.63 | 9593.46 | 9972.90 | 17743.07 | 12615.69 | 16978.51 | 17175.33 | 17287.24 | 16212.88 |
| Discrepancy | 14.58 | 94.82 | 48.73 | 169.64 | 60.06 | -28.51 | 396.09 | -471.22 | 41.09 | 242.95 | 386.46 | -96.29 | 329.89 | -230.05 | 126.48 | 34.63 | -406.54 | -31.10 | 5243.07 | 113.69 | 1961.51 | 2145.33 | -216.76 | -1296.12 |
Prediction range and difference summary¶
# This summary is based on final_sample_comparison_df.
# final_sample_comparison_df contains tuned/best model predictions for the selected test samples.
# Dummy and plain Linear Regression are included as non-tuned baseline/reference models.
# --- 1) Calculate diff_df ---
diff_df = final_sample_comparison_df.sub(
final_sample_comparison_df.loc["Original Label"],
axis=1
)
# --- 2) Min and Max values ---
min_values = final_sample_comparison_df.T.min()
max_values = final_sample_comparison_df.T.max()
# --- 3) Basic min-max summary DF ---
min_max_summary_df = pd.DataFrame({
"Min_Value": min_values,
"Max_Value": max_values
})
# --- 4) Range calculation ---
min_max_summary_df["Range"] = (
min_max_summary_df["Max_Value"] - min_max_summary_df["Min_Value"]
)
# --- 5) Max and average negative / positive deviations ---
min_max_summary_df["Max_Neg_Diff"] = diff_df.where(diff_df < 0).min(axis=1)
min_max_summary_df["Avg_Neg_Diff"] = diff_df.where(diff_df < 0).mean(axis=1)
min_max_summary_df["Max_Pos_Diff"] = diff_df.where(diff_df > 0).max(axis=1)
min_max_summary_df["Avg_Pos_Diff"] = diff_df.where(diff_df > 0).mean(axis=1)
# Fixing the Original Label row
min_max_summary_df.loc["Original Label", "Max_Neg_Diff"] = 0
min_max_summary_df.loc["Original Label", "Avg_Neg_Diff"] = 0
min_max_summary_df.loc["Original Label", "Max_Pos_Diff"] = 0
min_max_summary_df.loc["Original Label", "Avg_Pos_Diff"] = 0
# Optional: if a model has no negative or no positive deviations at all
min_max_summary_df[["Max_Neg_Diff", "Avg_Neg_Diff", "Max_Pos_Diff", "Avg_Pos_Diff"]] = (
min_max_summary_df[["Max_Neg_Diff", "Avg_Neg_Diff", "Max_Pos_Diff", "Avg_Pos_Diff"]].fillna(0)
)
# --- 6) Assigning model_type with map() ---
model_type_map = (
results_df_with_total[["Model", "Model_type"]]
.assign(Model=lambda df: df["Model"].str.replace(" Regressor", "", regex=False))
.drop_duplicates(subset="Model")
.set_index("Model")["Model_type"]
)
min_max_summary_df.index = min_max_summary_df.index.str.replace(" Regressor", "", regex=False)
min_max_summary_df.insert(
0,
"Model_type",
min_max_summary_df.index.map(model_type_map)
)
# Empty the Model_type field of the Original Label row.
min_max_summary_df.loc["Original Label", "Model_type"] = ""
# --- 7) Stylized display ---
numeric_cols = min_max_summary_df.select_dtypes(include=["float", "int"]).columns
styled_output = (
min_max_summary_df.style
.format(
{col: "{:.2f}" for col in numeric_cols},
na_rep="—"
)
# Model type column
.set_properties(
subset=pd.IndexSlice[:, ["Model_type"]],
**{
"text-align": "left",
"white-space": "nowrap",
"background-color": "#eeeeee",
"font-weight": "bold"
}
)
# Min / Max value columns
.set_properties(
subset=pd.IndexSlice[:, ["Min_Value", "Max_Value"]],
**{
"background-color": "#f3f3f3",
"text-align": "right"
}
)
# Range column
.set_properties(
subset=pd.IndexSlice[:, ["Range"]],
**{
"background-color": "#e8f1ff",
"text-align": "right",
"font-weight": "bold"
}
)
# Negative deviation columns
.set_properties(
subset=pd.IndexSlice[:, ["Max_Neg_Diff", "Avg_Neg_Diff"]],
**{
"background-color": "#f8e6e6",
"text-align": "right"
}
)
# Positive deviation columns
.set_properties(
subset=pd.IndexSlice[:, ["Max_Pos_Diff", "Avg_Pos_Diff"]],
**{
"background-color": "#e5fddb",
"text-align": "right"
}
)
# Original Label reference row
.set_properties(
subset=pd.IndexSlice[["Original Label"], :],
**{
"background-color": "#ffd966",
"font-weight": "bold"
}
)
# Row index styling
.map_index(
lambda v: (
"background-color: #ffd966; "
"font-weight: bold; "
"text-align: left; "
"white-space: nowrap;"
)
if v == "Original Label"
else (
"text-align: left; "
"white-space: nowrap;"
),
axis=0
)
# Table style
.set_table_styles(
[
{
"selector": "th.row_heading",
"props": [
("text-align", "left"),
("white-space", "nowrap")
]
},
{
"selector": "th.col_heading",
"props": [
("text-align", "left"),
("white-space", "nowrap")
]
},
{
"selector": "th, td",
"props": [
("border", "1px solid #d0d0d0"),
("padding", "6px 8px")
]
},
{
"selector": "table",
"props": [
("border-collapse", "collapse")
]
},
{
"selector": "tbody tr:nth-child(even) td",
"props": [
("filter", "brightness(0.97)")
]
},
{
"selector": "tbody tr:hover td",
"props": [
("filter", "brightness(0.90)")
]
}
],
overwrite=False
)
)
styled_output
| Model_type | Min_Value | Max_Value | Range | Max_Neg_Diff | Avg_Neg_Diff | Max_Pos_Diff | Avg_Pos_Diff | |
|---|---|---|---|---|---|---|---|---|
| Original Label | 326.00 | 18818.00 | 18492.00 | 0.00 | 0.00 | 0.00 | 0.00 | |
| Stacking | Advanced ensemble | 285.75 | 18058.28 | 17772.53 | -6428.21 | -264.97 | 5395.78 | 264.37 |
| Voting | Advanced ensemble | 330.72 | 18058.13 | 17727.41 | -6371.67 | -272.43 | 5220.44 | 256.19 |
| CatBoost | Boosting ensemble | 306.44 | 19052.58 | 18746.13 | -7190.62 | -284.20 | 5265.94 | 267.34 |
| Gradient Boosting | Boosting ensemble | 293.70 | 18769.15 | 18475.46 | -6645.99 | -271.92 | 6269.15 | 268.63 |
| Extra Trees | Tree-based ensemble | 369.39 | 18197.95 | 17828.56 | -6309.59 | -280.00 | 5246.35 | 269.11 |
| MLP | Neural | -135.71 | 17501.55 | 17637.27 | -6527.76 | -295.32 | 7546.78 | 279.68 |
| LightGBM | Boosting ensemble | 309.16 | 18311.54 | 18002.38 | -6035.83 | -290.38 | 8426.61 | 271.00 |
| Bagging | Advanced ensemble | 373.55 | 17943.50 | 17569.95 | -6441.34 | -276.74 | 5197.97 | 275.98 |
| XGBoost | Boosting ensemble | 337.34 | 19375.97 | 19038.63 | -7742.23 | -294.96 | 9182.61 | 267.90 |
| Random Forest | Tree-based ensemble | 374.93 | 17960.65 | 17585.72 | -6364.62 | -280.43 | 5095.91 | 277.35 |
| Hist Gradient Boosting | Boosting ensemble | 416.36 | 18028.92 | 17612.56 | -6342.00 | -299.30 | 12092.65 | 274.51 |
| Decision Tree | Tree-based | 361.00 | 18322.60 | 17961.60 | -5995.15 | -333.59 | 5714.70 | 325.88 |
| SVR (RBF) | Kernel / Distance-based | 300.12 | 19695.45 | 19395.33 | -12011.55 | -316.53 | 5046.97 | 320.53 |
| KNeighbors | Kernel / Distance-based | 412.91 | 17622.45 | 17209.55 | -11516.73 | -431.36 | 8586.27 | 342.67 |
| AdaBoost | Boosting ensemble | 685.89 | 17015.70 | 16329.81 | -6542.08 | -520.06 | 10390.75 | 557.15 |
| Poisson | GLM / distribution-based linear | 0.71 | 30590.95 | 30590.24 | -13687.25 | -520.12 | 13444.95 | 390.92 |
| NuSVR | Kernel / Distance-based | 399.14 | 17393.45 | 16994.31 | -12806.44 | -543.86 | 8421.90 | 356.56 |
| Lasso | Linear / regularized | -3491.08 | 39968.71 | 43459.79 | -10774.33 | -921.34 | 21950.71 | 637.35 |
| Bayesian Ridge | Linear / regularized | -3617.51 | 40086.40 | 43703.91 | -10736.31 | -921.35 | 22068.40 | 640.01 |
| Linear | Linear / regularized | -3621.64 | 40091.31 | 43712.96 | -10735.91 | -921.35 | 22073.31 | 640.01 |
| SGD | Online linear | -3342.36 | 39745.78 | 43088.14 | -10764.21 | -923.23 | 21727.78 | 638.88 |
| Ridge | Linear / regularized | -3250.28 | 39627.74 | 42878.02 | -10774.00 | -926.80 | 21609.74 | 637.63 |
| ElasticNet | Linear / regularized | -3211.06 | 39312.82 | 42523.87 | -10800.46 | -926.15 | 21294.82 | 639.03 |
| Passive Aggressive | Online linear | -3228.95 | 39784.81 | 43013.76 | -10809.06 | -922.25 | 21766.81 | 635.47 |
| Huber | Robust linear | -3244.22 | 40064.47 | 43308.69 | -10810.41 | -917.28 | 22046.47 | 639.17 |
| RANSAC | Robust linear | -3392.07 | 41405.69 | 44797.77 | -10959.46 | -909.57 | 23387.69 | 630.93 |
| Quantile | Linear / quantile | -12784.01 | 43691.17 | 56475.18 | -14859.01 | -846.41 | 25673.17 | 473.46 |
| SVR (linear) | Kernel / Distance-based | -12012.12 | 43289.15 | 55301.27 | -14087.12 | -853.24 | 25271.15 | 467.03 |
| Gamma | GLM / distribution-based linear | 1664.16 | 13196.77 | 11532.61 | -14400.69 | -3542.47 | 11121.77 | 1599.92 |
| Dummy | Baseline | 3932.89 | 3932.89 | 0.00 | -14885.11 | -4161.87 | 3606.89 | 2384.75 |
# ============================================================
# Export prediction range and difference summary
# ============================================================
prediction_range_difference_summary = min_max_summary_df.copy()
if SAVE_PORTFOLIO_OUTPUTS:
prediction_range_difference_summary.to_csv(
REGRESSION_RESULTS_DIR / "supervised_regression_prediction_range_difference_summary.csv",
index=True
)
print(
"Saved:",
REGRESSION_RESULTS_DIR / "supervised_regression_prediction_range_difference_summary.csv"
)
Saved: _exports\regression_models\results\supervised_regression_prediction_range_difference_summary.csv
Best Model Performance by Price Range¶
# Number of bins
price_bins = 500
# Maximum price
price_max = 19000
# Label of each bin
# Size of each bin on StratifiedShuffleSplit
price_cat_size = 1500
# Plot "price"
diamonds[target_column].hist(bins=price_bins, figsize=(12, 4))
plt.xlim(0, price_max)
plt.xticks(np.arange(0, price_max + 1, price_cat_size), rotation=90)
plt.xlabel(target_column)
plt.ylabel("Count per bin")
plt.grid(True, linestyle="--", alpha=0.5)
plt.show()
diamonds["target_cat"] = pd.cut(
diamonds[target_column],
bins=np.arange(0, price_max + 1, price_cat_size),
labels=np.arange(price_cat_size, price_max + 1, price_cat_size),
include_lowest=True
)
diamonds["target_cat"].value_counts().sort_index().plot.bar(
figsize=(12, 4),
width=0.95
)
plt.xlabel("Price")
plt.ylabel("Count per bin")
plt.grid(True, linestyle="--", alpha=0.5)
plt.show()
# Create tuned models table only from variables that already exist
TUNED_MODELS = {}
model_candidates = [
# Baseline models
("Dummy Regressor", "dummy_reg"),
# Linear / regularized models
("Linear Regressor", "lin_reg"),
("Ridge Regressor", "ridge_best_tuning"),
("Lasso Regressor", "lasso_best_tuning"),
("ElasticNet Regressor", "elastic_best_tuning"),
("Bayesian Ridge Regressor", "bayes_ridge_best_tuning"),
("Quantile Regressor", "quantile_best_tuning"),
# Robust linear models
("Huber Regressor", "huber_best_tuning"),
("RANSAC Regressor", "ransac_best_tuning"),
# Online linear models
("SGD Regressor", "sgd_best_tuning"),
("Passive Aggressive Regressor", "pa_best_tuning"),
# GLM / distribution-based linear models
("Poisson Regressor", "poisson_best_tuning"),
("Gamma Regressor", "gamma_best_tuning"),
# SVR / distance-based models
("SVR (linear)", "svr_linear_best_tuning"),
("SVR (RBF)", "svr_rbf_best_tuning"),
("KNeighbors Regressor", "knn_best_tuning"),
("NuSVR Regressor", "nusvr_best_tuning"),
# Tree-based models
("Decision Tree Regressor", "dt_best_tuning"),
# Tree-based ensemble models
("Random Forest Regressor", "rf_best_tuning"),
("Extra Trees Regressor", "extra_trees_best_tuning"),
# Boosting ensemble models
("AdaBoost Regressor", "ada_best_tuning"),
("Gradient Boosting Regressor", "gbr_best_tuning"),
("Hist Gradient Boosting Regressor", "hgb_best_tuning"),
("XGBoost Regressor", "xgb_best_tuning"),
("LightGBM Regressor", "lgbm_best_tuning"),
("CatBoost Regressor", "cat_best_tuning"),
# Neural models
("MLP Regressor", "mlp_best_tuning"),
# Advanced ensemble models
("Bagging Regressor", "bagging_best_tuning"),
("Voting Regressor", "voting_best_tuning"),
("Stacking Regressor", "stacking_best_tuning")
]
for model_name, var_name in model_candidates:
if var_name in globals():
TUNED_MODELS[model_name] = globals()[var_name]
best_model = TUNED_MODELS[best_model_name]
import pandas as pd
import numpy as np
from sklearn.metrics import mean_absolute_error, root_mean_squared_error
best_y_pred = best_model.predict(X_test)
range_eval_df = pd.DataFrame({
"actual": y_test,
"predicted": best_y_pred
}).copy()
range_eval_df = range_eval_df.reset_index(drop=True)
actual_price_max = int(diamonds[target_column].max())
bin_edges = np.arange(0, price_max + price_cat_size, price_cat_size)
bin_labels = [
f"{bin_edges[i]}-{bin_edges[i+1]}"
for i in range(len(bin_edges) - 2)
] + [
f"{bin_edges[-2]}-{actual_price_max}"
]
range_eval_df["target_cat"] = pd.cut(
range_eval_df["actual"],
bins=bin_edges,
labels=bin_labels,
include_lowest=True,
right=True
)
# Remove unused empty categories
range_eval_df["target_cat"] = range_eval_df["target_cat"].cat.remove_unused_categories()
# Row-level percentage errors
range_eval_df["abs_pct_error"] = (
np.abs(range_eval_df["predicted"] - range_eval_df["actual"])
/ range_eval_df["actual"] * 100
)
range_eval_df["signed_pct_error"] = (
(range_eval_df["predicted"] - range_eval_df["actual"])
/ range_eval_df["actual"] * 100
)
def calculate_range_metrics(group):
y_true = group["actual"]
y_pred = group["predicted"]
mean_actual = y_true.mean()
mean_predicted = y_pred.mean()
mean_bias = (y_pred - y_true).mean()
mae = mean_absolute_error(y_true, y_pred)
rmse = root_mean_squared_error(y_true, y_pred)
mae_pct = (mae / mean_actual * 100) if mean_actual != 0 else np.nan
rmse_pct = (rmse / mean_actual * 100) if mean_actual != 0 else np.nan
mape = group["abs_pct_error"].mean()
mean_relative_error_pct = group["signed_pct_error"].mean()
return pd.Series({
"count": len(group),
"mean_actual": mean_actual,
"mean_predicted": mean_predicted,
"mean_bias": mean_bias,
"MAE": mae,
"RMSE": rmse,
"MAE_pct": mae_pct,
"RMSE_pct": rmse_pct,
"MAPE_pct": mape,
"mean_relative_error_pct": mean_relative_error_pct
})
range_metrics_df = (
range_eval_df
.dropna(subset=["target_cat"])
.groupby("target_cat", observed=True)
.apply(calculate_range_metrics)
.reset_index()
)
range_metrics_df
| target_cat | count | mean_actual | mean_predicted | mean_bias | MAE | RMSE | MAE_pct | RMSE_pct | MAPE_pct | mean_relative_error_pct | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0-1500 | 4002.00 | 841.46 | 846.38 | 4.92 | 61.05 | 81.10 | 7.25 | 9.64 | 7.37 | 1.01 |
| 1 | 1500-3000 | 2065.00 | 2185.42 | 2213.90 | 28.48 | 142.86 | 232.15 | 6.54 | 10.62 | 6.46 | 1.27 |
| 2 | 3000-4500 | 1340.00 | 3774.41 | 3843.90 | 69.49 | 267.86 | 376.99 | 7.10 | 9.99 | 7.13 | 1.82 |
| 3 | 4500-6000 | 1071.00 | 5166.04 | 5175.51 | 9.46 | 325.95 | 470.74 | 6.31 | 9.11 | 6.28 | 0.20 |
| 4 | 6000-7500 | 633.00 | 6691.58 | 6736.58 | 45.00 | 450.84 | 681.95 | 6.74 | 10.19 | 6.76 | 0.72 |
| 5 | 7500-9000 | 417.00 | 8216.30 | 8265.90 | 49.60 | 499.39 | 727.78 | 6.08 | 8.86 | 6.10 | 0.61 |
| 6 | 9000-10500 | 313.00 | 9709.33 | 9742.84 | 33.51 | 682.47 | 968.96 | 7.03 | 9.98 | 7.03 | 0.36 |
| 7 | 10500-12000 | 254.00 | 11211.44 | 11241.73 | 30.28 | 728.98 | 1095.13 | 6.50 | 9.77 | 6.50 | 0.26 |
| 8 | 12000-13500 | 201.00 | 12733.15 | 13165.56 | 432.40 | 1104.42 | 1520.21 | 8.67 | 11.94 | 8.68 | 3.41 |
| 9 | 13500-15000 | 161.00 | 14201.34 | 14260.36 | 59.02 | 841.52 | 1123.02 | 5.93 | 7.91 | 5.92 | 0.41 |
| 10 | 15000-16500 | 145.00 | 15747.39 | 15391.24 | -356.15 | 816.10 | 1041.52 | 5.18 | 6.61 | 5.17 | -2.20 |
| 11 | 16500-18000 | 124.00 | 17212.06 | 16165.40 | -1046.66 | 1074.32 | 1275.49 | 6.24 | 7.41 | 6.23 | -6.07 |
| 12 | 18000-18823 | 62.00 | 18385.98 | 16669.15 | -1716.83 | 1716.83 | 1961.04 | 9.34 | 10.67 | 9.31 | -9.31 |
# ============================================================
# Export best model performance by price range as CSV
# ============================================================
if SAVE_PORTFOLIO_OUTPUTS:
output_path = REGRESSION_RESULTS_DIR / "supervised_regression_best_model_performance_by_price_range.csv"
best_model_performance_by_price_range = range_metrics_df.copy()
best_model_performance_by_price_range.to_csv(
output_path,
index=False
)
print("Saved:", output_path)
Saved: _exports\regression_models\results\supervised_regression_best_model_performance_by_price_range.csv
High-Value Prediction Performance¶
high_value_mask = y_test > 16000
print("Best Model:", best_model_name)
print("RMSE for high values:", root_mean_squared_error(y_test[high_value_mask], best_y_pred[high_value_mask]))
print("Mean true high value:", y_test[high_value_mask].mean())
print("Mean pred high value:", best_y_pred[high_value_mask].mean())
Best Model: Stacking Regressor RMSE for high values: 1487.6193364562034 Mean true high value: 17363.684210526317 Mean pred high value: 16172.427284286647
Actual vs Predicted Price Distributions by Model Group¶
# Calculate bin counts for the true target values
counts_y_test, bin_edges = np.histogram(y_test, bins=price_bins)
# Calculate bin centers for the plot x-axis
bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2
# Define model groups
model_groups = {
"Baseline Models": [
("dummy_pred_test", "Dummy Regressor"),
],
"Linear / Regularized Models": [
("lin_pred_test", "Linear Regressor"),
("ridge_best_pred_test", "Ridge Regressor"),
("lasso_best_pred_test", "Lasso Regressor"),
("elastic_best_pred_test", "ElasticNet Regressor"),
("bayes_ridge_best_pred_test", "Bayesian Ridge Regressor"),
("quantile_best_pred_test", "Quantile Regressor"),
],
"Robust Linear Models": [
("huber_best_pred_test", "Huber Regressor"),
("ransac_best_pred_test", "RANSAC Regressor"),
],
"Online Linear Models": [
("sgd_best_pred_test", "SGD Regressor"),
("pa_best_pred_test", "Passive Aggressive Regressor"),
],
"GLM / Distribution-Based Linear Models": [
("poisson_best_pred_test", "Poisson Regressor"),
("gamma_best_pred_test", "Gamma Regressor"),
],
"Kernel / Distance-Based Models": [
("svr_linear_best_pred_test", "SVR (linear)"),
("svr_rbf_best_pred_test", "SVR (RBF)"),
("nusvr_best_pred_test", "NuSVR Regressor"),
("knn_best_pred_test", "KNeighbors Regressor"),
],
"Tree-Based Models": [
("dt_best_pred_test", "Decision Tree Regressor"),
("rf_best_pred_test", "Random Forest Regressor"),
("extra_trees_best_pred_test", "Extra Trees Regressor"),
],
"AdaBoost Regressor": [
("ada_best_pred_test", "AdaBoost Regressor"),
],
"Boosting Ensemble Models": [
("gbr_best_pred_test", "Gradient Boosting Regressor"),
("hgb_best_pred_test", "Hist Gradient Boosting Regressor"),
("xgb_best_pred_test", "XGBoost Regressor"),
("lgbm_best_pred_test", "LightGBM Regressor"),
("cat_best_pred_test", "CatBoost Regressor"),
],
"Neural Model": [
("mlp_best_pred_test", "MLP Regressor"),
],
"Advanced Ensemble Models": [
("bagging_best_pred_test", "Bagging Regressor"),
("voting_best_pred_test", "Voting Regressor"),
("stacking_best_pred_test", "Stacking Regressor"),
],
}
# Keep only groups that contain at least one existing prediction variable
available_groups = {}
for group_name, models in model_groups.items():
existing_models = []
for var_name, label in models:
if var_name in globals() and globals()[var_name] is not None:
existing_models.append((var_name, label))
if existing_models:
available_groups[group_name] = existing_models
# Create subplots only for groups that have available variables
n_groups = len(available_groups)
if n_groups == 0:
print("No prediction variables were found.")
else:
fig, axes = plt.subplots(n_groups, 1, figsize=(12, 4 * n_groups), sharex=True)
# If only one subplot exists, convert axes to a list
if n_groups == 1:
axes = [axes]
# Plot each model group
for ax, (group_name, models) in zip(axes, available_groups.items()):
# Plot the true target distribution
ax.plot(bin_centers, counts_y_test, label="y_test", linewidth=1.2)
# Plot each available model in the current group
for var_name, label in models:
counts_pred, _ = np.histogram(globals()[var_name], bins=price_bins)
ax.plot(bin_centers, counts_pred, label=label, linewidth=0.6)
ax.set_title(group_name, fontsize=16)
ax.set_xlim(0, price_max)
ax.set_ylabel("Count per bin", fontsize=12)
ax.set_xlabel("Price", fontsize=12)
ax.grid(True, linestyle="--", alpha=0.5)
ax.legend()
# Format the shared x-axis
axes[-1].set_xticks(np.arange(0, price_max + 1, price_cat_size))
axes[-1].tick_params(axis="x", rotation=90)
axes[-1].set_xlabel(target_column)
plt.suptitle("Actual vs Predicted Price Distributions by Model Group", fontsize=20, y=1.003)
plt.tight_layout()
if SAVE_PORTFOLIO_OUTPUTS:
fig.savefig(
REGRESSION_IMAGES_DIR / "supervised_regression_actual_vs_predicted_distributions_by_model_group.png",
dpi=FIG_DPI,
bbox_inches="tight"
)
plt.show()
Prediction Error Analysis¶
Actual vs Predicted Values for the Best Regression Model¶
This plot shows how close the model’s predictions are to the real values.
- The closer the points are to the diagonal line, the better the model.
- If the points spread out more at higher values, the model performs worse there.
best_prediction_errors = best_y_pred - y_test
top_model_names = [
model_name
for model_name in results_df_sorted["Model"]
if model_name in best_pred_candidates and best_pred_candidates[model_name] is not None
]
import math
model_numbers = len(results_df_with_total) - 1
plt_columns = 4
plt_rows = math.ceil(model_numbers / plt_columns)
figsize_width = 15
figsize_height = plt_rows * (23 / 7)
plt.figure(figsize=(6, 5.5))
plt.scatter(y_test, best_y_pred, alpha=0.2)
plt.plot(
[y_test.min(), y_test.max()],
[y_test.min(), y_test.max()],
linestyle="--",
color='red'
)
plt.title(f"Actual vs Predicted - {best_model_name}", fontsize=16, y=1.003)
plt.xlabel("Actual Values", fontsize=12)
plt.ylabel("Predicted Values", fontsize=12)
plt.grid(alpha=0.3)
plt.xticks(np.linspace(0, 20000, 9))
plt.yticks(np.linspace(0, 20000, 9))
plt.tick_params(axis="x", labelrotation=90)
plt.ylim([0, 20000])
plt.xlim([0, 20000])
plt.tight_layout()
if SAVE_PORTFOLIO_OUTPUTS:
plt.savefig(
REGRESSION_IMAGES_DIR / "supervised_regression_actual_vs_predicted_best_model.png",
dpi=FIG_DPI,
bbox_inches="tight"
)
plt.show()
Actual vs Predicted Values for All Tuned Regression Models¶
#fig, axes = plt.subplots(7, 4, figsize=(15, 25))
fig, axes = plt.subplots(plt_rows, plt_columns, figsize=(figsize_width, figsize_height+5))
axes = axes.ravel()
for ax, model_name in zip(axes, top_model_names):
y_pred = best_pred_candidates[model_name]
ax.scatter(y_test, y_pred, alpha=0.3, s=12)
ax.plot(
[y_test.min(), y_test.max()],
[y_test.min(), y_test.max()],
linestyle="--",
color='red'
)
ax.set_title(model_name, fontsize=16)
ax.set_xlabel("Actual Values", fontsize=12)
ax.set_ylabel("Predicted Values", fontsize=12)
ax.grid(alpha=0.3)
ax.set_xticks(np.linspace(0, 20000, 9))
ax.set_yticks(np.linspace(0, 20000, 9))
ax.tick_params(axis="x", labelrotation=90)
ax.set_ylim([0, 20000])
ax.set_xlim([0, 20000])
for ax in axes[len(top_model_names):]:
ax.axis("off")
plt.suptitle("Actual vs Predicted Values for All Tuned Regression Models", fontsize=20, y=1.003)
plt.tight_layout()
if SAVE_PORTFOLIO_OUTPUTS:
fig.savefig(
REGRESSION_IMAGES_DIR / "supervised_regression_actual_vs_predicted_all_models.png",
dpi=FIG_DPI,
bbox_inches="tight"
)
plt.show()
Residual Plot for the Best Regression Model¶
This plot shows the errors made by the model.
- In a good case, the points are randomly scattered around 0.
- If a visible pattern appears, the model is not capturing something well.
# Classical residual definition:
# residual = actual - predicted
best_residuals = y_test - best_y_pred
plt.figure(figsize=(7, 7))
plt.scatter(
best_y_pred,
best_residuals,
alpha=0.2
)
plt.axhline(y=0, linestyle="--", color="red")
plt.title(f"Residual Plot - {best_model_name}", fontsize=18, y=1.003)
plt.xlabel("Predicted Values", fontsize=14)
plt.ylabel("Residuals (Actual - Predicted)", fontsize=14)
plt.xlim([0, 20000])
plt.ylim([-16000, 16000])
plt.xticks(np.linspace(0, 20000, 9))
plt.yticks(np.linspace(-15000, 15000, 13))
plt.tick_params(axis="x", labelrotation=90)
plt.grid(alpha=0.3)
plt.tight_layout()
if SAVE_PORTFOLIO_OUTPUTS:
plt.savefig(
REGRESSION_IMAGES_DIR / "supervised_regression_residuals_best_model.png",
dpi=FIG_DPI,
bbox_inches="tight"
)
plt.show()
Residual Plots for All Tuned Regression Models¶
fig, axes = plt.subplots(plt_rows, plt_columns, figsize=(figsize_width, figsize_height * 1.15))
axes = axes.ravel()
for ax, model_name in zip(axes, top_model_names):
y_pred = best_pred_candidates[model_name]
# Classical residual definition:
# residual = actual - predicted
model_residuals = y_test - y_pred
ax.scatter(y_pred, model_residuals, alpha=0.3, s=12)
ax.axhline(y=0, linestyle="--", color="red")
ax.set_title(model_name, fontsize=14)
ax.tick_params(axis="both", labelsize=8, length=3, width=0.4)
ax.set_xlabel("Predicted Values", fontsize=10)
ax.set_ylabel("Residuals", fontsize=10)
ax.set_ylim(-16000, 16000)
ax.set_xlim(0, 20000)
ax.set_xticks(np.linspace(0, 20000, 9))
ax.set_yticks(np.linspace(-15000, 15000, 13))
ax.tick_params(axis="x", labelrotation=90)
ax.grid(alpha=0.3)
for ax in axes[len(top_model_names):]:
ax.axis("off")
plt.suptitle("Residual Plots for All Tuned Regression Models", fontsize=20, y=1.003)
plt.tight_layout()
if SAVE_PORTFOLIO_OUTPUTS:
fig.savefig(
REGRESSION_IMAGES_DIR / "supervised_regression_residuals_all_models.png",
dpi=FIG_DPI,
bbox_inches="tight"
)
plt.show()
Residual Distribution for the Best Regression Model¶
This plot shows how often small or large errors happen.
- In a good case, the values are centered around 0.
- If the distribution is very skewed or very wide, that is an interesting sign.
# Classical residual definition:
# residual = actual - predicted
best_residuals = y_test - best_y_pred
bin_edges = np.linspace(-5000, 5000, 500)
plt.hist(best_residuals, bins=bin_edges)
plt.title(f"Residual Distribution - {best_model_name}", fontsize=16, y=1.003)
plt.xlabel("Residuals (Actual - Predicted)", fontsize=12)
plt.ylabel("Frequency", fontsize=12)
plt.ylim([0, 800])
plt.xlim([-5000, 5000])
plt.yticks(np.linspace(0, 800, 9))
plt.xticks(np.linspace(-5000, 5000, 11))
plt.tick_params(axis="x", labelrotation=90)
plt.grid(axis="y", alpha=0.3)
plt.tight_layout()
if SAVE_PORTFOLIO_OUTPUTS:
plt.savefig(
REGRESSION_IMAGES_DIR / "supervised_regression_error_distribution_best_model.png",
dpi=FIG_DPI,
bbox_inches="tight"
)
plt.show()
Residual Distributions for All Tuned Regression Models¶
fig, axes = plt.subplots(plt_rows, plt_columns, figsize=(figsize_width, figsize_height))
axes = axes.ravel()
bin_edges = np.linspace(-5000, 5000, 500) # evenly spaced bin edges between -5000 and 5000
for ax, model_name in zip(axes, top_model_names):
y_pred = best_pred_candidates[model_name]
# Classical residual definition:
# residual = actual - predicted
model_residuals = y_test - y_pred
ax.hist(model_residuals, bins=bin_edges)
ax.set_title(model_name, fontsize=14)
ax.set_xlabel("Residuals (Actual - Predicted)", fontsize=12)
ax.set_ylabel("Frequency", fontsize=12)
ax.set_ylim([0, 800])
ax.set_xlim([-5000, 5000])
ax.set_yticks(np.linspace(0, 800, 9))
ax.set_xticks(np.linspace(-5000, 5000, 11))
ax.tick_params(axis="x", labelrotation=90)
ax.grid(axis="y", alpha=0.3)
for ax in axes[len(top_model_names):]:
ax.axis("off")
plt.suptitle("Residual Distributions for All Tuned Regression Models", fontsize=20, y=1.003)
plt.tight_layout()
if SAVE_PORTFOLIO_OUTPUTS:
fig.savefig(
REGRESSION_IMAGES_DIR / "supervised_regression_error_distribution_all_models.png",
dpi=FIG_DPI,
bbox_inches="tight"
)
plt.show()
best_params summary¶
# Create best searches table only from variables that already exist
BEST_SEARCHES = {}
search_candidates = [
# Linear / regularized models
("Ridge Regressor", "ridge_search"),
("Lasso Regressor", "lasso_search"),
("ElasticNet Regressor", "elastic_search"),
("Bayesian Ridge Regressor", "bayes_ridge_search"),
("Quantile Regressor", "quantile_search"),
# Robust linear models
("Huber Regressor", "huber_search"),
("RANSAC Regressor", "ransac_search"),
# Online linear models
("SGD Regressor", "sgd_search"),
("Passive Aggressive Regressor", "pa_search"),
# GLM / distribution-based linear models
("Poisson Regressor", "poisson_search"),
("Gamma Regressor", "gamma_search"),
# SVR / distance-based models
("SVR (linear)", "svr_linear_search"),
("SVR (RBF)", "svr_rbf_search"),
("NuSVR Regressor", "nusvr_search"),
("KNeighbors Regressor", "knn_search"),
# Tree-based models
("Decision Tree Regressor", "dt_search"),
# Tree-based ensemble models
("Random Forest Regressor", "rf_search"),
("Extra Trees Regressor", "extra_trees_search"),
# Boosting ensemble models
("AdaBoost Regressor", "ada_search"),
("Gradient Boosting Regressor", "gbr_search"),
("Hist Gradient Boosting Regressor", "hgb_search"),
("XGBoost Regressor", "xgb_search"),
("LightGBM Regressor", "lgbm_search"),
("CatBoost Regressor", "cat_search"),
# Neural models
("MLP Regressor", "mlp_search"),
# Advanced ensemble models
("Bagging Regressor", "bagging_search"),
("Voting Regressor", "voting_search"),
("Stacking Regressor", "stacking_search")
]
for model_name, var_name in search_candidates:
if var_name in globals():
BEST_SEARCHES[model_name] = globals()[var_name]
best_params_summary = []
for model_name, model_obj in BEST_SEARCHES.items():
best_params_summary.append({
"Model": model_name,
"Best_Params": model_obj.best_params_
})
best_params_summary_df = pd.DataFrame(best_params_summary)
best_params_summary_df = best_params_summary_df.merge(
results_df_sorted[["Model", "Tun_RMSE", "Tun_R2"]],
on="Model",
how="left"
)
best_params_summary_df = best_params_summary_df[
["Model", "Tun_RMSE", "Tun_R2", "Best_Params"]
].sort_values("Tun_RMSE", ascending=True).reset_index(drop=True)
# Remove " Regressor" from Model and move it to index
best_params_summary_df["Model"] = best_params_summary_df["Model"].str.replace(" Regressor", "", regex=False)
best_params_summary_df = best_params_summary_df.set_index("Model")
best_params_summary_df.index.name = None
display(
best_params_summary_df.style
.format({
"Tun_RMSE": "{:.2f}",
"Tun_R2": "{:.4f}"
})
.map_index(
lambda v: "text-align: left; white-space: nowrap;",
axis=0
)
.set_table_styles([
{
"selector": "th.row_heading",
"props": [
("text-align", "left"),
("white-space", "nowrap")
]
},
], overwrite=False)
)
| Tun_RMSE | Tun_R2 | Best_Params | |
|---|---|---|---|
| Stacking | 522.41 | 0.9829 | {'base_estimators': ['cat_best_tuning', 'gbr_best_tuning', 'extra_trees_best_tuning', 'mlp_best_tuning', 'lgbm_best_tuning', 'bagging_best_tuning', 'xgb_best_tuning'], 'final_estimator': 'Ridge(alpha=1.0)', 'cv': 'KFOLD_3', 'passthrough': False, 'additional_grid_search': False} |
| Voting | 523.72 | 0.9828 | {'base_estimators': ['cat_best_tuning', 'gbr_best_tuning', 'extra_trees_best_tuning', 'mlp_best_tuning', 'lgbm_best_tuning', 'bagging_best_tuning'], 'weights': None, 'additional_grid_search': False} |
| CatBoost | 539.24 | 0.9817 | {'model__loss_function': 'RMSE', 'model__learning_rate': 0.03, 'model__l2_leaf_reg': 1.0, 'model__iterations': 1000, 'model__depth': 10} |
| Gradient Boosting | 540.63 | 0.9816 | {'model__subsample': 0.7, 'model__n_estimators': 1000, 'model__max_depth': 6, 'model__loss': 'huber', 'model__learning_rate': 0.046} |
| Extra Trees | 542.91 | 0.9815 | {'model__n_estimators': 100, 'model__min_samples_split': 10, 'model__min_samples_leaf': 1, 'model__max_features': 1.0, 'model__max_depth': 20} |
| MLP | 546.21 | 0.9813 | {'model__max_iter': 3000, 'model__learning_rate_init': 0.003, 'model__hidden_layer_sizes': (75,), 'model__early_stopping': False, 'model__alpha': 0.003, 'model__activation': 'tanh'} |
| LightGBM | 546.91 | 0.9812 | {'model__subsample': 0.8, 'model__reg_lambda': 0.0, 'model__reg_alpha': 0.0, 'model__num_leaves': 31, 'model__n_estimators': 500, 'model__max_depth': 12, 'model__learning_rate': 0.05, 'model__colsample_bytree': 1.0} |
| Bagging | 557.92 | 0.9804 | {'model__n_estimators': 300, 'model__max_samples': 0.7, 'model__max_features': 1.0, 'model__estimator__min_samples_split': 2, 'model__estimator__min_samples_leaf': 1, 'model__estimator__max_depth': None, 'model__bootstrap': True} |
| XGBoost | 559.19 | 0.9804 | {'model__subsample': 0.8, 'model__reg_lambda': 0, 'model__reg_alpha': 0, 'model__n_estimators': 200, 'model__max_depth': 8, 'model__learning_rate': 0.05, 'model__colsample_bytree': 1.0} |
| Random Forest | 559.44 | 0.9803 | {'model__n_estimators': 100, 'model__min_samples_split': 10, 'model__min_samples_leaf': 1, 'model__max_features': 1.0, 'model__max_depth': 20} |
| Hist Gradient Boosting | 567.64 | 0.9798 | {'model__min_samples_leaf': 10, 'model__max_leaf_nodes': 127, 'model__max_iter': 500, 'model__max_depth': 12, 'model__learning_rate': 0.05} |
| Decision Tree | 641.31 | 0.9742 | {'model__max_depth': 15, 'model__max_features': None, 'model__min_samples_leaf': 4, 'model__min_samples_split': 20} |
| SVR (RBF) | 667.51 | 0.9720 | {'model__C': 1200.0, 'model__epsilon': 0.2, 'model__gamma': 'scale', 'model__tol': 0.01} |
| KNeighbors | 791.59 | 0.9606 | {'model__n_neighbors': 11, 'model__p': 1, 'model__weights': 'uniform'} |
| AdaBoost | 843.12 | 0.9553 | {'model__n_estimators': 300, 'model__loss': 'exponential', 'model__learning_rate': 0.1, 'model__estimator__min_samples_split': 5, 'model__estimator__min_samples_leaf': 4, 'model__estimator__max_depth': 6} |
| Poisson | 875.40 | 0.9519 | {'model__alpha': 3.0, 'model__max_iter': 300, 'model__tol': 5e-05} |
| NuSVR | 950.57 | 0.9432 | {'model__C': 70.0, 'model__gamma': 0.1, 'model__nu': 0.5, 'model__tol': 0.001} |
| Lasso | 1191.18 | 0.9109 | {'model__alpha': 1.0, 'model__max_iter': 50000} |
| Bayesian Ridge | 1191.27 | 0.9108 | {'model__max_iter': 300, 'model__tol': 0.01} |
| SGD | 1191.36 | 0.9108 | {'model__tol': 0.001, 'model__penalty': 'elasticnet', 'model__max_iter': 2000, 'model__loss': 'squared_error', 'model__learning_rate': 'adaptive', 'model__eta0': 0.001, 'model__alpha': 0.001} |
| Ridge | 1191.68 | 0.9108 | {'model__alpha': 50.0} |
| ElasticNet | 1193.11 | 0.9106 | {'model__alpha': 0.01, 'model__l1_ratio': 0.8} |
| Passive Aggressive | 1193.47 | 0.9105 | {'model__tol': 1e-05, 'model__penalty': 'elasticnet', 'model__max_iter': 2000, 'model__loss': 'epsilon_insensitive', 'model__learning_rate': 'pa2', 'model__eta0': 0.001, 'model__epsilon': 0.01, 'model__average': True, 'model__alpha': 1e-05} |
| Huber | 1195.11 | 0.9103 | {'model__tol': 0.0001, 'model__max_iter': 500, 'model__epsilon': 6.9, 'model__alpha': 0.05175} |
| RANSAC | 1201.35 | 0.9093 | {'model__stop_probability': 0.99, 'model__residual_threshold': 100, 'model__min_samples': 0.3, 'model__max_trials': 300} |
| Quantile | 1322.29 | 0.8902 | {'model__alpha': 0.0, 'model__quantile': 0.5} |
| SVR (linear) | 1323.45 | 0.8900 | {'model__C': 50.0, 'model__epsilon': 0.1} |
| Gamma | 3228.21 | 0.3453 | {'model__alpha': 10.0, 'model__max_iter': 100, 'model__tol': 0.1} |
Permutation Importance for the Best Regression Model¶
from sklearn.inspection import permutation_importance
# Permutation importance is used for the best model regardless of model type.
# This gives a consistent, model-agnostic explanation based on the original input features.
perm = permutation_importance(
best_model,
X_test,
y_test,
scoring="neg_root_mean_squared_error",
n_repeats=10,
random_state=RANDOM_STATE,
n_jobs=1
)
importance_values = perm.importances_mean
importance_std = perm.importances_std
importance_df = pd.DataFrame({
"Feature": X_test.columns,
"Importance": importance_values,
"Std": importance_std,
"Abs_Importance": np.abs(importance_values)
}).sort_values("Abs_Importance", ascending=False).reset_index(drop=True)
# Add percentage importance
importance_df["Importance_pct"] = (
importance_df["Abs_Importance"] / importance_df["Abs_Importance"].sum() * 100
)
importance_df["Importance_pct"] = (
importance_df["Importance_pct"]
.round(2)
.astype(str) + "%"
)
print(f"Model explanation type: permutation importance ({best_model_name})")
importance_df
Model explanation type: permutation importance (Stacking Regressor)
| Feature | Importance | Std | Abs_Importance | Importance_pct | |
|---|---|---|---|---|---|
| 0 | carat | 3797.79 | 14.17 | 3797.79 | 49.93% |
| 1 | y | 1412.38 | 8.21 | 1412.38 | 18.57% |
| 2 | clarity | 1180.82 | 14.98 | 1180.82 | 15.52% |
| 3 | color | 801.72 | 10.27 | 801.72 | 10.54% |
| 4 | x | 178.79 | 3.22 | 178.79 | 2.35% |
| 5 | z | 173.77 | 2.31 | 173.77 | 2.28% |
| 6 | cut | 39.78 | 1.88 | 39.78 | 0.52% |
| 7 | depth | 12.64 | 0.77 | 12.64 | 0.17% |
| 8 | table | 9.19 | 0.91 | 9.19 | 0.12% |
if importance_df is not None:
top_importance_df = importance_df.sort_values("Importance", ascending=True)
top_importance_df.plot.barh(
x="Feature",
y="Importance",
figsize=(10, 5),
width=0.75,
legend=False
)
plt.xlabel("Permutation Importance")
plt.ylabel("Feature")
plt.title(f"Permutation Importance - {best_model_name}")
plt.grid(axis="x", linestyle="--", alpha=0.7)
plt.tight_layout()
plt.tight_layout()
if SAVE_PORTFOLIO_OUTPUTS:
plt.savefig(
REGRESSION_IMAGES_DIR / "supervised_regression_permutation_importance.png",
dpi=FIG_DPI,
bbox_inches="tight"
)
plt.show()
print("Original input features:", len(X_test.columns))
print("Permutation importance values:", len(importance_values))
print("Explanation is based on original input features.")
Original input features: 9 Permutation importance values: 9 Explanation is based on original input features.
Log-Target Mini Experiment¶
This is a small extra test to check whether the model works better when the target values are transformed to a log scale.
The model is trained on log1p(y_train) instead of the original target values.
After prediction, the results are transformed back to the original scale with expm1().
This experiment is useful when the target values are highly skewed or when a few very large values may affect the model too much.
At the end, the normal version and the log-target version are compared using RMSE and R² on the original scale.
start_fit = time.perf_counter()
from sklearn.base import clone
import numpy as np
# Log-Target Mini Experiment
log_model = clone(best_model)
y_train_log = np.log1p(y_train)
log_model.fit(X_train, y_train_log)
# Log-model predictions
y_pred_log = log_model.predict(X_test)
y_pred_from_log = np.expm1(y_pred_log)
# Normal-model predictions
normal_pred = best_model.predict(X_test)
# Metrics on original target scale
log_rmse = root_mean_squared_error(y_test, y_pred_from_log)
log_r2 = r2_score(y_test, y_pred_from_log)
normal_rmse = root_mean_squared_error(y_test, normal_pred)
normal_r2 = r2_score(y_test, normal_pred)
print("Model:", best_model_name)
print("Normal target RMSE:", normal_rmse)
print("Normal target R2:", normal_r2)
print("Log target RMSE:", log_rmse)
print("Log target R2:", log_r2)
Model: Stacking Regressor Normal target RMSE: 522.4074749766904 Normal target R2: 0.9828545638547189 Log target RMSE: 530.6912843634028 Log target R2: 0.9823065027644141
log_target_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(log_target_time)))
print(f"Fit time: {formatted_time}")
Fit time: 0:17:57
Saving and Loading the Models¶
Save all the Fitted Models¶
start_fit = time.perf_counter()
if not QUICK_LOAD and SAVE_MODEL_ARTIFACTS:
import os
import re
import joblib
import pandas as pd
os.makedirs(os.path.join("_exports", "regression_models", "fitted_models"), exist_ok=True)
def normalize_model_name(name):
name = str(name).lower()
name = name.replace(" regressor", "")
name = name.replace("(", "")
name = name.replace(")", "")
name = name.replace("/", "_")
name = name.replace("-", " ")
name = re.sub(r"[^a-z0-9_ ]+", "", name)
name = re.sub(r"\s+", " ", name).strip()
return name
# Build normalized lookup from TUNED_MODELS
normalized_tuned_models = {
normalize_model_name(model_name): model_object
for model_name, model_object in TUNED_MODELS.items()
}
saved_models_info = []
for model_name in results_df_with_total["Model"].drop_duplicates():
normalized_name = normalize_model_name(model_name)
if normalized_name not in normalized_tuned_models:
continue
model_object = normalized_tuned_models[normalized_name]
safe_model_name = normalized_name.replace(" ", "_")
model_filename = os.path.join("_exports", "regression_models", "fitted_models", f"fitted_model_{safe_model_name}.pkl")
joblib.dump(model_object, model_filename)
file_size_bytes = os.path.getsize(model_filename)
file_size_kb = file_size_bytes / 1024
file_size_mb = file_size_bytes / (1024 * 1024)
saved_models_info.append({
"Model": model_name,
"Size_Bytes": file_size_bytes,
# "Size_KB": file_size_kb,
"Size_MB": file_size_mb,
"File": model_filename
})
saved_models_df = pd.DataFrame(saved_models_info).reset_index(drop=True)
if not QUICK_LOAD and SAVE_MODEL_ARTIFACTS:
saved_models_display_df = (
saved_models_df[
["Model", "Size_Bytes", "Size_MB", "File"]
]
.sort_values("Model")
.reset_index(drop=True)
)
styled_saved_models_df = (
saved_models_display_df.style
.format({
"Size_Bytes": "{:,.0f}",
"Size_MB": "{:,.2f}"
})
.set_properties(
subset=["Model", "File"],
**{
"text-align": "left",
"white-space": "nowrap"
}
)
.set_table_styles([
{
"selector": "th.col_heading",
"props": [("text-align", "left")]
}
], overwrite=False)
)
display(styled_saved_models_df)
elif QUICK_LOAD:
print("Saving skipped because QUICK_LOAD = True.")
elif not SAVE_MODEL_ARTIFACTS:
print("Saving skipped because SAVE_MODEL_ARTIFACTS = False.")
| Model | Size_Bytes | Size_MB | File | |
|---|---|---|---|---|
| 0 | AdaBoost | 2,750,889 | 2.62 | _exports\regression_models\fitted_models\fitted_model_adaboost.pkl |
| 1 | Bagging | 827,306,985 | 788.98 | _exports\regression_models\fitted_models\fitted_model_bagging.pkl |
| 2 | Bayesian Ridge | 9,913 | 0.01 | _exports\regression_models\fitted_models\fitted_model_bayesian_ridge.pkl |
| 3 | CatBoost | 16,483,032 | 15.72 | _exports\regression_models\fitted_models\fitted_model_catboost.pkl |
| 4 | Decision Tree | 338,963 | 0.32 | _exports\regression_models\fitted_models\fitted_model_decision_tree.pkl |
| 5 | Dummy | 3,201 | 0.00 | _exports\regression_models\fitted_models\fitted_model_dummy.pkl |
| 6 | ElasticNet | 3,864 | 0.00 | _exports\regression_models\fitted_models\fitted_model_elasticnet.pkl |
| 7 | Extra Trees | 94,575,306 | 90.19 | _exports\regression_models\fitted_models\fitted_model_extra_trees.pkl |
| 8 | Gamma | 4,089 | 0.00 | _exports\regression_models\fitted_models\fitted_model_gamma.pkl |
| 9 | Gradient Boosting | 7,950,454 | 7.58 | _exports\regression_models\fitted_models\fitted_model_gradient_boosting.pkl |
| 10 | Hist Gradient Boosting | 2,097,689 | 2.00 | _exports\regression_models\fitted_models\fitted_model_hist_gradient_boosting.pkl |
| 11 | Huber | 47,017 | 0.04 | _exports\regression_models\fitted_models\fitted_model_huber.pkl |
| 12 | KNeighbors | 9,324,566 | 8.89 | _exports\regression_models\fitted_models\fitted_model_kneighbors.pkl |
| 13 | Lasso | 3,848 | 0.00 | _exports\regression_models\fitted_models\fitted_model_lasso.pkl |
| 14 | LightGBM | 1,400,589 | 1.34 | _exports\regression_models\fitted_models\fitted_model_lightgbm.pkl |
| 15 | Linear | 3,977 | 0.00 | _exports\regression_models\fitted_models\fitted_model_linear.pkl |
| 16 | MLP | 90,801 | 0.09 | _exports\regression_models\fitted_models\fitted_model_mlp.pkl |
| 17 | NuSVR | 4,957,214 | 4.73 | _exports\regression_models\fitted_models\fitted_model_nusvr.pkl |
| 18 | Passive Aggressive | 4,765 | 0.00 | _exports\regression_models\fitted_models\fitted_model_passive_aggressive.pkl |
| 19 | Poisson | 4,089 | 0.00 | _exports\regression_models\fitted_models\fitted_model_poisson.pkl |
| 20 | Quantile | 3,737 | 0.00 | _exports\regression_models\fitted_models\fitted_model_quantile.pkl |
| 21 | RANSAC | 47,689 | 0.05 | _exports\regression_models\fitted_models\fitted_model_ransac.pkl |
| 22 | Random Forest | 71,422,410 | 68.11 | _exports\regression_models\fitted_models\fitted_model_random_forest.pkl |
| 23 | Ridge | 3,769 | 0.00 | _exports\regression_models\fitted_models\fitted_model_ridge.pkl |
| 24 | SGD | 4,047 | 0.00 | _exports\regression_models\fitted_models\fitted_model_sgd.pkl |
| 25 | SVR (RBF) | 9,827,064 | 9.37 | _exports\regression_models\fitted_models\fitted_model_svr_rbf.pkl |
| 26 | SVR (linear) | 9,840,760 | 9.38 | _exports\regression_models\fitted_models\fitted_model_svr_linear.pkl |
| 27 | Stacking | 950,675,728 | 906.64 | _exports\regression_models\fitted_models\fitted_model_stacking.pkl |
| 28 | Voting | 947,864,400 | 903.95 | _exports\regression_models\fitted_models\fitted_model_voting.pkl |
| 29 | XGBoost | 2,811,310 | 2.68 | _exports\regression_models\fitted_models\fitted_model_xgboost.pkl |
Load all the Fitted Models¶
if not QUICK_LOAD and SAVE_MODEL_ARTIFACTS:
import os
import joblib
import pandas as pd
models_dir = os.path.join("_exports", "regression_models", "fitted_models")
if not os.path.exists(models_dir):
raise FileNotFoundError(f"Model directory not found: {models_dir}")
loaded_models = {}
for filename in sorted(os.listdir(models_dir)):
if filename.endswith(".pkl"):
model_key = filename.replace("fitted_model_", "").replace(".pkl", "")
full_path = os.path.join(models_dir, filename)
loaded_models[model_key] = joblib.load(full_path)
if not loaded_models:
raise FileNotFoundError(
f"No fitted model .pkl files were found in: {models_dir}"
)
loaded_models_df = pd.DataFrame({
"Model_Key": list(loaded_models.keys())
}).reset_index(drop=True)
display(loaded_models_df)
| Model_Key | |
|---|---|
| 0 | adaboost |
| 1 | bagging |
| 2 | bayesian_ridge |
| 3 | catboost |
| 4 | decision_tree |
| 5 | dummy |
| 6 | elasticnet |
| 7 | extra_trees |
| 8 | gamma |
| 9 | gradient_boosting |
| 10 | hist_gradient_boosting |
| 11 | huber |
| 12 | kneighbors |
| 13 | lasso |
| 14 | lightgbm |
| 15 | linear |
| 16 | mlp |
| 17 | nusvr |
| 18 | passive_aggressive |
| 19 | poisson |
| 20 | quantile |
| 21 | random_forest |
| 22 | ransac |
| 23 | ridge |
| 24 | sgd |
| 25 | stacking |
| 26 | svr_linear |
| 27 | svr_rbf |
| 28 | voting |
| 29 | xgboost |
if not QUICK_LOAD and SAVE_MODEL_ARTIFACTS:
# Access the loaded Voting model
display(loaded_models["voting"])
elif QUICK_LOAD:
print("Loading skipped because QUICK_LOAD = True.")
elif not SAVE_MODEL_ARTIFACTS:
print("Loading skipped because SAVE_MODEL_ARTIFACTS = False.")
VotingRegressor(estimators=[('cat',
Pipeline(steps=[('preprocessing',
ColumnTransformer(transformers=[('num',
'passthrough',
['carat',
'depth',
'table',
'x',
'y',
'z']),
('cat',
OneHotEncoder(handle_unknown='ignore'),
['cut',
'color',
'clarity'])])),
('model',
CatBoostRegressor(allow_writing_files=False, depth=10, iterations=1000, l2_leaf_reg=1.0, learning_rate=0.03, l...
('bagging',
Pipeline(steps=[('preprocessing',
ColumnTransformer(transformers=[('num',
'passthrough',
['carat',
'depth',
'table',
'x',
'y',
'z']),
('cat',
OneHotEncoder(handle_unknown='ignore'),
['cut',
'color',
'clarity'])])),
('model',
BaggingRegressor(estimator=DecisionTreeRegressor(random_state=42),
max_samples=0.7,
n_estimators=300,
random_state=42))]))],
n_jobs=1)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Parameters
Parameters
['carat', 'depth', 'table', 'x', 'y', 'z']
passthrough
['cut', 'color', 'clarity']
Parameters
CatBoostRegressor(allow_writing_files=False, depth=10, iterations=1000, l2_leaf_reg=1.0, learning_rate=0.03, loss_function='RMSE', random_state=42, verbose=0)
Parameters
['carat', 'depth', 'table', 'x', 'y', 'z']
passthrough
['cut', 'color', 'clarity']
Parameters
Parameters
Parameters
['carat', 'depth', 'table', 'x', 'y', 'z']
passthrough
['cut', 'color', 'clarity']
Parameters
Parameters
Parameters
['carat', 'depth', 'table', 'x', 'y', 'z']
Parameters
['cut', 'color', 'clarity']
Parameters
Parameters
Parameters
['carat', 'depth', 'table', 'x', 'y', 'z']
passthrough
['cut', 'color', 'clarity']
Parameters
Parameters
| boosting_type | 'gbdt' | |
| num_leaves | 31 | |
| max_depth | 12 | |
| learning_rate | 0.05 | |
| n_estimators | 500 | |
| subsample_for_bin | 200000 | |
| objective | None | |
| class_weight | None | |
| min_split_gain | 0.0 | |
| min_child_weight | 0.001 | |
| min_child_samples | 20 | |
| subsample | 0.8 | |
| subsample_freq | 0 | |
| colsample_bytree | 1.0 | |
| reg_alpha | 0.0 | |
| reg_lambda | 0.0 | |
| random_state | 42 | |
| n_jobs | None | |
| importance_type | 'split' | |
| verbosity | -1 | |
| force_col_wise | True |
Parameters
['carat', 'depth', 'table', 'x', 'y', 'z']
passthrough
['cut', 'color', 'clarity']
Parameters
Parameters
DecisionTreeRegressor(random_state=42)
Parameters
Saving and Loading the Search Objects¶
Save all the Search Objects¶
if not QUICK_LOAD and SAVE_MODEL_ARTIFACTS:
import os
import re
import joblib
import pandas as pd
os.makedirs("_exports", exist_ok=True)
os.makedirs(os.path.join("_exports", "regression_models", "search_objects"), exist_ok=True)
def normalize_model_name(name):
name = str(name).lower()
name = name.replace(" regressor", "")
name = name.replace("(", "")
name = name.replace(")", "")
name = name.replace("/", "_")
name = name.replace("-", " ")
name = re.sub(r"[^a-z0-9_ ]+", "", name)
name = re.sub(r"\s+", " ", name).strip()
return name
# Build normalized lookup from search objects
normalized_search_objects = {}
# 1) If you already have a dictionary of search objects, use it here
if "BEST_SEARCHES" in globals():
normalized_search_objects = {
normalize_model_name(model_name): search_object
for model_name, search_object in BEST_SEARCHES.items()
}
# 2) Fallback: build the lookup from variables ending with _search
elif any(var_name.endswith("_search") for var_name in globals()):
normalized_search_objects = {
normalize_model_name(var_name.replace("_search", "")): search_object
for var_name, search_object in globals().items()
if var_name.endswith("_search")
}
saved_searches_info = []
for model_name in results_df_with_total["Model"].drop_duplicates():
normalized_name = normalize_model_name(model_name)
if normalized_name not in normalized_search_objects:
continue
search_object = normalized_search_objects[normalized_name]
safe_model_name = normalized_name.replace(" ", "_")
search_filename = os.path.join(
"_exports",
"regression_models",
"search_objects",
f"regression_search_{safe_model_name}.pkl"
)
joblib.dump(search_object, search_filename)
file_size_bytes = os.path.getsize(search_filename)
file_size_kb = file_size_bytes / 1024
file_size_mb = file_size_bytes / (1024 * 1024)
saved_searches_info.append({
"Model": model_name,
"Size_Bytes": file_size_bytes,
# "Size_KB": file_size_kb,
"Size_MB": file_size_mb,
"File": search_filename
})
saved_searches_df = pd.DataFrame(saved_searches_info).reset_index(drop=True)
if not QUICK_LOAD and SAVE_MODEL_ARTIFACTS:
saved_searches_display_df = (
saved_searches_df
.sort_values("Model")
.reset_index(drop=True)
)
styled_saved_searches_df = (
saved_searches_display_df.style
.format({
"Size_Bytes": "{:,.0f}",
"Size_MB": "{:,.2f}"
})
.set_properties(
subset=["Model", "File"],
**{
"text-align": "left",
"white-space": "nowrap"
}
)
.set_table_styles([
{
"selector": "th.col_heading",
"props": [("text-align", "left")]
}
], overwrite=False)
)
display(styled_saved_searches_df)
elif QUICK_LOAD:
print("Saving skipped because QUICK_LOAD = True.")
elif not SAVE_MODEL_ARTIFACTS:
print("Saving skipped because SAVE_MODEL_ARTIFACTS = False.")
| Model | Size_Bytes | Size_MB | File | |
|---|---|---|---|---|
| 0 | AdaBoost | 2,771,567 | 2.64 | _exports\regression_models\search_objects\regression_search_adaboost.pkl |
| 1 | Bagging | 827,324,831 | 789.00 | _exports\regression_models\search_objects\regression_search_bagging.pkl |
| 2 | Bayesian Ridge | 22,483 | 0.02 | _exports\regression_models\search_objects\regression_search_bayesian_ridge.pkl |
| 3 | CatBoost | 17,608,063 | 16.79 | _exports\regression_models\search_objects\regression_search_catboost.pkl |
| 4 | Decision Tree | 493,807 | 0.47 | _exports\regression_models\search_objects\regression_search_decision_tree.pkl |
| 5 | ElasticNet | 11,023 | 0.01 | _exports\regression_models\search_objects\regression_search_elasticnet.pkl |
| 6 | Extra Trees | 481,833,623 | 459.51 | _exports\regression_models\search_objects\regression_search_extra_trees.pkl |
| 7 | Gamma | 13,759 | 0.01 | _exports\regression_models\search_objects\regression_search_gamma.pkl |
| 8 | Gradient Boosting | 8,228,063 | 7.85 | _exports\regression_models\search_objects\regression_search_gradient_boosting.pkl |
| 9 | Hist Gradient Boosting | 2,845,775 | 2.71 | _exports\regression_models\search_objects\regression_search_hist_gradient_boosting.pkl |
| 10 | Huber | 103,903 | 0.10 | _exports\regression_models\search_objects\regression_search_huber.pkl |
| 11 | KNeighbors | 18,651,599 | 17.79 | _exports\regression_models\search_objects\regression_search_kneighbors.pkl |
| 12 | Lasso | 9,551 | 0.01 | _exports\regression_models\search_objects\regression_search_lasso.pkl |
| 13 | LightGBM | 1,708,375 | 1.63 | _exports\regression_models\search_objects\regression_search_lightgbm.pkl |
| 14 | MLP | 361,003 | 0.34 | _exports\regression_models\search_objects\regression_search_mlp.pkl |
| 15 | NuSVR | 9,886,067 | 9.43 | _exports\regression_models\search_objects\regression_search_nusvr.pkl |
| 16 | Passive Aggressive | 20,879 | 0.02 | _exports\regression_models\search_objects\regression_search_passive_aggressive.pkl |
| 17 | Poisson | 14,331 | 0.01 | _exports\regression_models\search_objects\regression_search_poisson.pkl |
| 18 | Quantile | 9,999 | 0.01 | _exports\regression_models\search_objects\regression_search_quantile.pkl |
| 19 | RANSAC | 99,499 | 0.09 | _exports\regression_models\search_objects\regression_search_ransac.pkl |
| 20 | Random Forest | 1,173,455,399 | 1,119.09 | _exports\regression_models\search_objects\regression_search_random_forest.pkl |
| 21 | Ridge | 10,067 | 0.01 | _exports\regression_models\search_objects\regression_search_ridge.pkl |
| 22 | SGD | 32,415 | 0.03 | _exports\regression_models\search_objects\regression_search_sgd.pkl |
| 23 | SVR (RBF) | 19,667,827 | 18.76 | _exports\regression_models\search_objects\regression_search_svr_rbf.pkl |
| 24 | SVR (linear) | 19,684,211 | 18.77 | _exports\regression_models\search_objects\regression_search_svr_linear.pkl |
| 25 | Stacking | 950,676,077 | 906.64 | _exports\regression_models\search_objects\regression_search_stacking.pkl |
| 26 | Voting | 947,864,692 | 903.95 | _exports\regression_models\search_objects\regression_search_voting.pkl |
| 27 | XGBoost | 3,273,543 | 3.12 | _exports\regression_models\search_objects\regression_search_xgboost.pkl |
Load all the Search Objects¶
if not QUICK_LOAD and SAVE_MODEL_ARTIFACTS:
import os
import joblib
import pandas as pd
searches_dir = os.path.join("_exports", "regression_models", "search_objects")
if not os.path.exists(searches_dir):
raise FileNotFoundError(f"Search directory not found: {searches_dir}")
loaded_searches = {}
for filename in sorted(os.listdir(searches_dir)):
if filename.endswith(".pkl"):
search_key = filename.replace("regression_search_", "").replace(".pkl", "")
full_path = os.path.join(searches_dir, filename)
loaded_searches[search_key] = joblib.load(full_path)
if not loaded_searches:
raise FileNotFoundError(
f"No search object .pkl files were found in: {searches_dir}"
)
loaded_searches_table = pd.DataFrame({
"Search_Key": list(loaded_searches.keys()),
"Object_Type": [type(search).__name__ for search in loaded_searches.values()]
}).reset_index(drop=True)
display(loaded_searches_table)
| Search_Key | Object_Type | |
|---|---|---|
| 0 | adaboost | RandomizedSearchCV |
| 1 | bagging | RandomizedSearchCV |
| 2 | bayesian_ridge | GridSearchCV |
| 3 | catboost | RandomizedSearchCV |
| 4 | decision_tree | GridSearchCV |
| 5 | elasticnet | GridSearchCV |
| 6 | extra_trees | RandomizedSearchCV |
| 7 | gamma | GridSearchCV |
| 8 | gradient_boosting | RandomizedSearchCV |
| 9 | hist_gradient_boosting | RandomizedSearchCV |
| 10 | huber | RandomizedSearchCV |
| 11 | kneighbors | GridSearchCV |
| 12 | lasso | GridSearchCV |
| 13 | lightgbm | RandomizedSearchCV |
| 14 | mlp | RandomizedSearchCV |
| 15 | nusvr | GridSearchCV |
| 16 | passive_aggressive | RandomizedSearchCV |
| 17 | poisson | GridSearchCV |
| 18 | quantile | GridSearchCV |
| 19 | random_forest | RandomizedSearchCV |
| 20 | ransac | RandomizedSearchCV |
| 21 | ridge | GridSearchCV |
| 22 | sgd | RandomizedSearchCV |
| 23 | stacking | SimpleNamespace |
| 24 | svr_linear | GridSearchCV |
| 25 | svr_rbf | GridSearchCV |
| 26 | voting | SimpleNamespace |
| 27 | xgboost | RandomizedSearchCV |
if not QUICK_LOAD and SAVE_MODEL_ARTIFACTS:
# Access the loaded Voting search object
display(loaded_searches["voting"])
elif QUICK_LOAD:
print("Loading skipped because QUICK_LOAD = True.")
elif not SAVE_MODEL_ARTIFACTS:
print("Loading skipped because SAVE_MODEL_ARTIFACTS = False.")
namespace(best_estimator_=VotingRegressor(estimators=[('cat',
Pipeline(steps=[('preprocessing',
ColumnTransformer(transformers=[('num',
'passthrough',
['carat',
'depth',
'table',
'x',
'y',
'z']),
('cat',
OneHotEncoder(handle_unknown='ignore'),
['cut',
'color',
'clarity'])])),
('model',
CatBoostRegressor(allow_writing_files=False, depth=10, iterations=1000, l2_leaf_reg=1.0, learning_rate=0.03, l...
('bagging',
Pipeline(steps=[('preprocessing',
ColumnTransformer(transformers=[('num',
'passthrough',
['carat',
'depth',
'table',
'x',
'y',
'z']),
('cat',
OneHotEncoder(handle_unknown='ignore'),
['cut',
'color',
'clarity'])])),
('model',
BaggingRegressor(estimator=DecisionTreeRegressor(random_state=42),
max_samples=0.7,
n_estimators=300,
random_state=42))]))],
n_jobs=1),
best_params_={'base_estimators': ['cat_best_tuning',
'gbr_best_tuning',
'extra_trees_best_tuning',
'mlp_best_tuning',
'lgbm_best_tuning',
'bagging_best_tuning'],
'weights': None,
'additional_grid_search': False})
Saving and Loading the Base Models¶
Save all the Base Models¶
if not QUICK_LOAD and SAVE_MODEL_ARTIFACTS:
import os
import joblib
import pandas as pd
os.makedirs("_exports", exist_ok=True)
os.makedirs(os.path.join("_exports", "regression_models", "fitted_base_models"), exist_ok=True)
model_variable_map = {
"Dummy": "dummy_reg",
"Linear": "lin_reg",
"Ridge": "ridge_reg",
"Lasso": "lasso_reg",
"ElasticNet": "elastic_reg",
"Bayesian Ridge": "bayes_ridge_reg",
"Quantile": "quantile_reg",
"Huber": "huber_reg",
"RANSAC": "ransac_reg",
"SGD": "sgd_reg",
"Passive Aggressive": "pa_reg",
"Poisson": "poisson_reg",
"Gamma": "gamma_reg",
"SVR (linear)": "svr_linear",
"SVR (RBF)": "svr_rbf",
"KNeighbors": "knn_reg",
"NuSVR": "nusvr_reg",
"Decision Tree": "dt_reg",
"Random Forest": "rf_reg",
"Extra Trees": "extra_trees_reg",
"AdaBoost": "ada_reg",
"Gradient Boosting": "gbr_reg",
"Hist Gradient Boosting": "hgb_reg",
"XGBoost": "xgb_reg",
"LightGBM": "lgbm_reg",
"CatBoost": "cat_reg",
"MLP": "mlp_reg",
"Bagging": "bagging_reg",
"Voting": "voting_reg",
"Stacking": "stacking_reg",
}
saved_fitted_base_models_info = []
for model_name in results_df_with_total["Model"].drop_duplicates():
if model_name not in model_variable_map:
continue
var_name = model_variable_map[model_name]
if var_name not in globals():
continue
fitted_model = globals()[var_name]
safe_model_name = (
model_name.lower()
.replace(" ", "_")
.replace("(", "")
.replace(")", "")
.replace("/", "_")
)
model_filename = os.path.join(
"_exports",
"regression_models",
"fitted_base_models",
f"fitted_base_model_{safe_model_name}.pkl"
)
joblib.dump(fitted_model, model_filename)
file_size_bytes = os.path.getsize(model_filename)
file_size_mb = file_size_bytes / (1024 * 1024)
saved_fitted_base_models_info.append({
"Model": model_name,
"Size_Bytes": file_size_bytes,
"Size_MB": file_size_mb,
"File": model_filename
})
saved_fitted_base_models_df = pd.DataFrame(saved_fitted_base_models_info).reset_index(drop=True)
if not QUICK_LOAD and SAVE_MODEL_ARTIFACTS:
saved_fitted_base_models_display_df = (
saved_fitted_base_models_df
.sort_values("Model")
.reset_index(drop=True)
)
styled_saved_fitted_base_models_df = (
saved_fitted_base_models_display_df.style
.format({
"Size_Bytes": "{:,.0f}",
"Size_MB": "{:,.2f}"
})
.set_properties(
subset=["Model", "File"],
**{
"text-align": "left",
"white-space": "nowrap"
}
)
.set_table_styles([
{
"selector": "th.col_heading",
"props": [("text-align", "left")]
}
], overwrite=False)
)
display(styled_saved_fitted_base_models_df)
elif QUICK_LOAD:
print("Saving skipped because QUICK_LOAD = True.")
elif not SAVE_MODEL_ARTIFACTS:
print("Saving skipped because SAVE_MODEL_ARTIFACTS = False.")
| Model | Size_Bytes | Size_MB | File | |
|---|---|---|---|---|
| 0 | AdaBoost | 143,289 | 0.14 | _exports\regression_models\fitted_base_models\fitted_base_model_adaboost.pkl |
| 1 | Bagging | 300,621,529 | 286.70 | _exports\regression_models\fitted_base_models\fitted_base_model_bagging.pkl |
| 2 | Bayesian Ridge | 9,913 | 0.01 | _exports\regression_models\fitted_base_models\fitted_base_model_bayesian_ridge.pkl |
| 3 | CatBoost | 1,120,637 | 1.07 | _exports\regression_models\fitted_base_models\fitted_base_model_catboost.pkl |
| 4 | Decision Tree | 110,003 | 0.10 | _exports\regression_models\fitted_base_models\fitted_base_model_decision_tree.pkl |
| 5 | Dummy | 3,201 | 0.00 | _exports\regression_models\fitted_base_models\fitted_base_model_dummy.pkl |
| 6 | ElasticNet | 3,848 | 0.00 | _exports\regression_models\fitted_base_models\fitted_base_model_elasticnet.pkl |
| 7 | Extra Trees | 387,253,594 | 369.31 | _exports\regression_models\fitted_base_models\fitted_base_model_extra_trees.pkl |
| 8 | Gamma | 4,089 | 0.00 | _exports\regression_models\fitted_base_models\fitted_base_model_gamma.pkl |
| 9 | Gradient Boosting | 273,878 | 0.26 | _exports\regression_models\fitted_base_models\fitted_base_model_gradient_boosting.pkl |
| 10 | Hist Gradient Boosting | 744,225 | 0.71 | _exports\regression_models\fitted_base_models\fitted_base_model_hist_gradient_boosting.pkl |
| 11 | Huber | 47,017 | 0.04 | _exports\regression_models\fitted_base_models\fitted_base_model_huber.pkl |
| 12 | KNeighbors | 9,324,566 | 8.89 | _exports\regression_models\fitted_base_models\fitted_base_model_kneighbors.pkl |
| 13 | Lasso | 3,848 | 0.00 | _exports\regression_models\fitted_base_models\fitted_base_model_lasso.pkl |
| 14 | LightGBM | 298,507 | 0.28 | _exports\regression_models\fitted_base_models\fitted_base_model_lightgbm.pkl |
| 15 | Linear | 3,977 | 0.00 | _exports\regression_models\fitted_base_models\fitted_base_model_linear.pkl |
| 16 | MLP | 266,929 | 0.25 | _exports\regression_models\fitted_base_models\fitted_base_model_mlp.pkl |
| 17 | NuSVR | 4,926,686 | 4.70 | _exports\regression_models\fitted_base_models\fitted_base_model_nusvr.pkl |
| 18 | Passive Aggressive | 4,031 | 0.00 | _exports\regression_models\fitted_base_models\fitted_base_model_passive_aggressive.pkl |
| 19 | Poisson | 4,089 | 0.00 | _exports\regression_models\fitted_base_models\fitted_base_model_poisson.pkl |
| 20 | Quantile | 3,737 | 0.00 | _exports\regression_models\fitted_base_models\fitted_base_model_quantile.pkl |
| 21 | RANSAC | 47,689 | 0.05 | _exports\regression_models\fitted_base_models\fitted_base_model_ransac.pkl |
| 22 | Random Forest | 1,102,028,266 | 1,050.98 | _exports\regression_models\fitted_base_models\fitted_base_model_random_forest.pkl |
| 23 | Ridge | 3,769 | 0.00 | _exports\regression_models\fitted_base_models\fitted_base_model_ridge.pkl |
| 24 | SGD | 4,047 | 0.00 | _exports\regression_models\fitted_base_models\fitted_base_model_sgd.pkl |
| 25 | SVR (RBF) | 9,838,702 | 9.38 | _exports\regression_models\fitted_base_models\fitted_base_model_svr_rbf.pkl |
| 26 | SVR (linear) | 9,841,902 | 9.39 | _exports\regression_models\fitted_base_models\fitted_base_model_svr_linear.pkl |
| 27 | Stacking | 690,337,760 | 658.36 | _exports\regression_models\fitted_base_models\fitted_base_model_stacking.pkl |
| 28 | Voting | 689,884,176 | 657.92 | _exports\regression_models\fitted_base_models\fitted_base_model_voting.pkl |
| 29 | XGBoost | 453,566 | 0.43 | _exports\regression_models\fitted_base_models\fitted_base_model_xgboost.pkl |
Load all the Base Models¶
if not QUICK_LOAD and SAVE_MODEL_ARTIFACTS:
import os
import joblib
import pandas as pd
models_dir = os.path.join("_exports", "regression_models", "fitted_base_models")
if not os.path.exists(models_dir):
raise FileNotFoundError(f"Model directory not found: {models_dir}")
loaded_fitted_base_models = {}
for filename in sorted(os.listdir(models_dir)):
if filename.endswith(".pkl"):
model_key = filename.replace("fitted_base_model_", "").replace(".pkl", "")
full_path = os.path.join(models_dir, filename)
loaded_fitted_base_models[model_key] = joblib.load(full_path)
if not loaded_fitted_base_models:
raise FileNotFoundError(
f"No fitted base model .pkl files were found in: {models_dir}"
)
loaded_fitted_base_models_df = pd.DataFrame({
"Model_Key": list(loaded_fitted_base_models.keys())
}).reset_index(drop=True)
display(loaded_fitted_base_models_df)
| Model_Key | |
|---|---|
| 0 | adaboost |
| 1 | bagging |
| 2 | bayesian_ridge |
| 3 | catboost |
| 4 | decision_tree |
| 5 | dummy |
| 6 | elasticnet |
| 7 | extra_trees |
| 8 | gamma |
| 9 | gradient_boosting |
| 10 | hist_gradient_boosting |
| 11 | huber |
| 12 | kneighbors |
| 13 | lasso |
| 14 | lightgbm |
| 15 | linear |
| 16 | mlp |
| 17 | nusvr |
| 18 | passive_aggressive |
| 19 | poisson |
| 20 | quantile |
| 21 | random_forest |
| 22 | ransac |
| 23 | ridge |
| 24 | sgd |
| 25 | stacking |
| 26 | svr_linear |
| 27 | svr_rbf |
| 28 | voting |
| 29 | xgboost |
if not QUICK_LOAD and SAVE_MODEL_ARTIFACTS:
# Access the loaded fitted base Voting model
display(loaded_fitted_base_models["voting"])
elif QUICK_LOAD:
print("Loading skipped because QUICK_LOAD = True.")
elif not SAVE_MODEL_ARTIFACTS:
print("Loading skipped because SAVE_MODEL_ARTIFACTS = False.")
VotingRegressor(estimators=[('cat',
Pipeline(steps=[('preprocessing',
ColumnTransformer(transformers=[('num',
'passthrough',
['carat',
'depth',
'table',
'x',
'y',
'z']),
('cat',
OneHotEncoder(handle_unknown='ignore'),
['cut',
'color',
'clarity'])])),
('model',
CatBoostRegressor(allow_writing_files=False, iterations=1000, l2_leaf_reg=3.0, loss_function='RMSE', random_s...
('bagging',
Pipeline(steps=[('preprocessing',
ColumnTransformer(transformers=[('num',
'passthrough',
['carat',
'depth',
'table',
'x',
'y',
'z']),
('cat',
OneHotEncoder(handle_unknown='ignore'),
['cut',
'color',
'clarity'])])),
('model',
BaggingRegressor(estimator=DecisionTreeRegressor(random_state=42),
max_samples=0.8,
n_estimators=100,
random_state=42))]))],
n_jobs=1)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Parameters
Parameters
['carat', 'depth', 'table', 'x', 'y', 'z']
passthrough
['cut', 'color', 'clarity']
Parameters
CatBoostRegressor(allow_writing_files=False, iterations=1000, l2_leaf_reg=3.0, loss_function='RMSE', random_state=42, verbose=0)
Parameters
['carat', 'depth', 'table', 'x', 'y', 'z']
passthrough
['cut', 'color', 'clarity']
Parameters
Parameters
Parameters
['carat', 'depth', 'table', 'x', 'y', 'z']
passthrough
['cut', 'color', 'clarity']
Parameters
Parameters
Parameters
['carat', 'depth', 'table', 'x', 'y', 'z']
Parameters
['cut', 'color', 'clarity']
Parameters
Parameters
Parameters
['carat', 'depth', 'table', 'x', 'y', 'z']
passthrough
['cut', 'color', 'clarity']
Parameters
Parameters
| boosting_type | 'gbdt' | |
| num_leaves | 31 | |
| max_depth | -1 | |
| learning_rate | 0.1 | |
| n_estimators | 100 | |
| subsample_for_bin | 200000 | |
| objective | None | |
| class_weight | None | |
| min_split_gain | 0.0 | |
| min_child_weight | 0.001 | |
| min_child_samples | 20 | |
| subsample | 1.0 | |
| subsample_freq | 0 | |
| colsample_bytree | 1.0 | |
| reg_alpha | 0.0 | |
| reg_lambda | 0.0 | |
| random_state | 42 | |
| n_jobs | None | |
| importance_type | 'split' | |
| verbosity | -1 | |
| force_col_wise | True |
Parameters
['carat', 'depth', 'table', 'x', 'y', 'z']
passthrough
['cut', 'color', 'clarity']
Parameters
Parameters
DecisionTreeRegressor(random_state=42)
Parameters
Saving and Loading a list of Predictions¶
Save all Predictions¶
if not QUICK_LOAD and SAVE_MODEL_ARTIFACTS:
import os
import re
import joblib
import pandas as pd
# Ensure export directory exists
EXPORT_DIR = os.path.join("_exports", "regression_models", "predictions")
os.makedirs(EXPORT_DIR, exist_ok=True)
def normalize_name(name: str) -> str:
"""Normalize prediction name for safe filenames."""
name = str(name).lower()
name = name.replace("(", "").replace(")", "")
name = name.replace("/", "_")
name = name.replace("-", " ")
name = re.sub(r"[^a-z0-9_ ]+", "", name)
name = re.sub(r"\s+", " ", name).strip()
return name
# Prediction variables to save
prediction_names = [
# Baseline / simple reference models
"dummy_pred_train",
"dummy_pred_test",
"lin_pred_train",
"lin_pred_test",
# Linear / regularized models
"ridge_pred_train",
"ridge_pred_test",
"ridge_best_pred_train",
"ridge_best_pred_test",
"lasso_pred_train",
"lasso_pred_test",
"lasso_best_pred_train",
"lasso_best_pred_test",
"elastic_pred_train",
"elastic_pred_test",
"elastic_best_pred_train",
"elastic_best_pred_test",
"bayes_ridge_pred_train",
"bayes_ridge_pred_test",
"bayes_ridge_best_pred_train",
"bayes_ridge_best_pred_test",
# Robust linear models
"huber_pred_train",
"huber_pred_test",
"huber_best_pred_train",
"huber_best_pred_test",
"ransac_pred_train",
"ransac_pred_test",
"ransac_best_pred_train",
"ransac_best_pred_test",
# Online / stochastic linear models
"sgd_pred_train",
"sgd_pred_test",
"sgd_best_pred_train",
"sgd_best_pred_test",
"pa_pred_train",
"pa_pred_test",
"pa_best_pred_train",
"pa_best_pred_test",
# SVR / kernel-based models
"svr_linear_pred_train",
"svr_linear_pred_test",
"svr_linear_best_pred_train",
"svr_linear_best_pred_test",
"svr_rbf_pred_train",
"svr_rbf_pred_test",
"svr_rbf_best_pred_train",
"svr_rbf_best_pred_test",
"nusvr_pred_train",
"nusvr_pred_test",
"nusvr_best_pred_train",
"nusvr_best_pred_test",
# Distance-based models
"knn_pred_train",
"knn_pred_test",
"knn_best_pred_train",
"knn_best_pred_test",
# Tree-based models
"dt_pred_train",
"dt_pred_test",
"dt_best_pred_train",
"dt_best_pred_test",
"rf_pred_train",
"rf_pred_test",
"rf_best_pred_train",
"rf_best_pred_test",
"extra_trees_pred_train",
"extra_trees_pred_test",
"extra_trees_best_pred_train",
"extra_trees_best_pred_test",
# Ensemble models
"bagging_pred_train",
"bagging_pred_test",
"bagging_best_pred_train",
"bagging_best_pred_test",
"voting_pred_train",
"voting_pred_test",
"voting_best_pred_train",
"voting_best_pred_test",
"stacking_pred_train",
"stacking_pred_test",
"stacking_best_pred_train",
"stacking_best_pred_test",
# Boosting models
"ada_pred_train",
"ada_pred_test",
"ada_best_pred_train",
"ada_best_pred_test",
"gbr_pred_train",
"gbr_pred_test",
"gbr_best_pred_train",
"gbr_best_pred_test",
"hgb_pred_train",
"hgb_pred_test",
"hgb_best_pred_train",
"hgb_best_pred_test",
"xgb_pred_train",
"xgb_pred_test",
"xgb_best_pred_train",
"xgb_best_pred_test",
"lgbm_pred_train",
"lgbm_pred_test",
"lgbm_best_pred_train",
"lgbm_best_pred_test",
"cat_pred_train",
"cat_pred_test",
"cat_best_pred_train",
"cat_best_pred_test",
# Neural network model
"mlp_pred_train",
"mlp_pred_test",
"mlp_best_pred_train",
"mlp_best_pred_test",
# Distribution / GLM / other models
"poisson_pred_train",
"poisson_pred_test",
"poisson_best_pred_train",
"poisson_best_pred_test",
"gamma_pred_train",
"gamma_pred_test",
"gamma_best_pred_train",
"gamma_best_pred_test",
"quantile_pred_train",
"quantile_pred_test",
"quantile_best_pred_train",
"quantile_best_pred_test",
]
# Check that all listed variables exist before saving
missing_predictions = [name for name in prediction_names if name not in globals()]
if missing_predictions:
raise NameError(
"The following prediction variables are missing and cannot be saved:\n"
+ "\n".join(missing_predictions)
)
# Build the prediction dictionary from the variable names
PREDICTIONS = {
name: globals()[name]
for name in prediction_names
}
saved_preds_info = []
for pred_name, pred_array in PREDICTIONS.items():
normalized = normalize_name(pred_name)
safe_name = normalized.replace(" ", "_")
filename = os.path.join(EXPORT_DIR, f"{safe_name}_file.pkl")
joblib.dump(pred_array, filename)
file_size_bytes = os.path.getsize(filename)
file_size_mb = file_size_bytes / (1024 * 1024)
saved_preds_info.append({
"Prediction": pred_name,
"Size_Bytes": file_size_bytes,
"Size_MB": file_size_mb,
"File": filename
})
saved_predictions_df = pd.DataFrame(saved_preds_info).reset_index(drop=True)
if not QUICK_LOAD and SAVE_MODEL_ARTIFACTS:
if "saved_predictions_df" not in globals():
raise NameError(
"saved_predictions_df does not exist. "
"Run the prediction saving cell before displaying the saved predictions table."
)
saved_predictions_display_df = (
saved_predictions_df[
["Prediction", "Size_Bytes", "Size_MB", "File"]
]
.sort_values("Prediction")
.reset_index(drop=True)
.copy()
)
styled_saved_predictions_df = (
saved_predictions_display_df.style
.format({
"Size_Bytes": "{:,.0f}",
"Size_MB": "{:,.2f}"
})
.set_properties(
subset=["Prediction", "File"],
**{
"text-align": "left",
"white-space": "nowrap"
}
)
.set_table_styles([
{
"selector": "th.col_heading",
"props": [("text-align", "left")]
}
], overwrite=False)
)
display(styled_saved_predictions_df)
elif QUICK_LOAD:
print("Saving skipped because QUICK_LOAD = True.")
elif not SAVE_MODEL_ARTIFACTS:
print("Saving skipped because SAVE_MODEL_ARTIFACTS = False.")
| Prediction | Size_Bytes | Size_MB | File | |
|---|---|---|---|---|
| 0 | ada_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\ada_best_pred_test_file.pkl |
| 1 | ada_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\ada_best_pred_train_file.pkl |
| 2 | ada_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\ada_pred_test_file.pkl |
| 3 | ada_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\ada_pred_train_file.pkl |
| 4 | bagging_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\bagging_best_pred_test_file.pkl |
| 5 | bagging_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\bagging_best_pred_train_file.pkl |
| 6 | bagging_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\bagging_pred_test_file.pkl |
| 7 | bagging_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\bagging_pred_train_file.pkl |
| 8 | bayes_ridge_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\bayes_ridge_best_pred_test_file.pkl |
| 9 | bayes_ridge_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\bayes_ridge_best_pred_train_file.pkl |
| 10 | bayes_ridge_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\bayes_ridge_pred_test_file.pkl |
| 11 | bayes_ridge_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\bayes_ridge_pred_train_file.pkl |
| 12 | cat_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\cat_best_pred_test_file.pkl |
| 13 | cat_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\cat_best_pred_train_file.pkl |
| 14 | cat_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\cat_pred_test_file.pkl |
| 15 | cat_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\cat_pred_train_file.pkl |
| 16 | dt_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\dt_best_pred_test_file.pkl |
| 17 | dt_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\dt_best_pred_train_file.pkl |
| 18 | dt_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\dt_pred_test_file.pkl |
| 19 | dt_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\dt_pred_train_file.pkl |
| 20 | dummy_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\dummy_pred_test_file.pkl |
| 21 | dummy_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\dummy_pred_train_file.pkl |
| 22 | elastic_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\elastic_best_pred_test_file.pkl |
| 23 | elastic_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\elastic_best_pred_train_file.pkl |
| 24 | elastic_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\elastic_pred_test_file.pkl |
| 25 | elastic_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\elastic_pred_train_file.pkl |
| 26 | extra_trees_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\extra_trees_best_pred_test_file.pkl |
| 27 | extra_trees_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\extra_trees_best_pred_train_file.pkl |
| 28 | extra_trees_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\extra_trees_pred_test_file.pkl |
| 29 | extra_trees_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\extra_trees_pred_train_file.pkl |
| 30 | gamma_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\gamma_best_pred_test_file.pkl |
| 31 | gamma_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\gamma_best_pred_train_file.pkl |
| 32 | gamma_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\gamma_pred_test_file.pkl |
| 33 | gamma_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\gamma_pred_train_file.pkl |
| 34 | gbr_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\gbr_best_pred_test_file.pkl |
| 35 | gbr_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\gbr_best_pred_train_file.pkl |
| 36 | gbr_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\gbr_pred_test_file.pkl |
| 37 | gbr_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\gbr_pred_train_file.pkl |
| 38 | hgb_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\hgb_best_pred_test_file.pkl |
| 39 | hgb_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\hgb_best_pred_train_file.pkl |
| 40 | hgb_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\hgb_pred_test_file.pkl |
| 41 | hgb_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\hgb_pred_train_file.pkl |
| 42 | huber_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\huber_best_pred_test_file.pkl |
| 43 | huber_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\huber_best_pred_train_file.pkl |
| 44 | huber_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\huber_pred_test_file.pkl |
| 45 | huber_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\huber_pred_train_file.pkl |
| 46 | knn_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\knn_best_pred_test_file.pkl |
| 47 | knn_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\knn_best_pred_train_file.pkl |
| 48 | knn_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\knn_pred_test_file.pkl |
| 49 | knn_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\knn_pred_train_file.pkl |
| 50 | lasso_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\lasso_best_pred_test_file.pkl |
| 51 | lasso_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\lasso_best_pred_train_file.pkl |
| 52 | lasso_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\lasso_pred_test_file.pkl |
| 53 | lasso_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\lasso_pred_train_file.pkl |
| 54 | lgbm_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\lgbm_best_pred_test_file.pkl |
| 55 | lgbm_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\lgbm_best_pred_train_file.pkl |
| 56 | lgbm_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\lgbm_pred_test_file.pkl |
| 57 | lgbm_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\lgbm_pred_train_file.pkl |
| 58 | lin_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\lin_pred_test_file.pkl |
| 59 | lin_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\lin_pred_train_file.pkl |
| 60 | mlp_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\mlp_best_pred_test_file.pkl |
| 61 | mlp_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\mlp_best_pred_train_file.pkl |
| 62 | mlp_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\mlp_pred_test_file.pkl |
| 63 | mlp_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\mlp_pred_train_file.pkl |
| 64 | nusvr_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\nusvr_best_pred_test_file.pkl |
| 65 | nusvr_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\nusvr_best_pred_train_file.pkl |
| 66 | nusvr_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\nusvr_pred_test_file.pkl |
| 67 | nusvr_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\nusvr_pred_train_file.pkl |
| 68 | pa_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\pa_best_pred_test_file.pkl |
| 69 | pa_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\pa_best_pred_train_file.pkl |
| 70 | pa_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\pa_pred_test_file.pkl |
| 71 | pa_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\pa_pred_train_file.pkl |
| 72 | poisson_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\poisson_best_pred_test_file.pkl |
| 73 | poisson_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\poisson_best_pred_train_file.pkl |
| 74 | poisson_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\poisson_pred_test_file.pkl |
| 75 | poisson_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\poisson_pred_train_file.pkl |
| 76 | quantile_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\quantile_best_pred_test_file.pkl |
| 77 | quantile_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\quantile_best_pred_train_file.pkl |
| 78 | quantile_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\quantile_pred_test_file.pkl |
| 79 | quantile_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\quantile_pred_train_file.pkl |
| 80 | ransac_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\ransac_best_pred_test_file.pkl |
| 81 | ransac_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\ransac_best_pred_train_file.pkl |
| 82 | ransac_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\ransac_pred_test_file.pkl |
| 83 | ransac_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\ransac_pred_train_file.pkl |
| 84 | rf_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\rf_best_pred_test_file.pkl |
| 85 | rf_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\rf_best_pred_train_file.pkl |
| 86 | rf_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\rf_pred_test_file.pkl |
| 87 | rf_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\rf_pred_train_file.pkl |
| 88 | ridge_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\ridge_best_pred_test_file.pkl |
| 89 | ridge_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\ridge_best_pred_train_file.pkl |
| 90 | ridge_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\ridge_pred_test_file.pkl |
| 91 | ridge_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\ridge_pred_train_file.pkl |
| 92 | sgd_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\sgd_best_pred_test_file.pkl |
| 93 | sgd_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\sgd_best_pred_train_file.pkl |
| 94 | sgd_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\sgd_pred_test_file.pkl |
| 95 | sgd_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\sgd_pred_train_file.pkl |
| 96 | stacking_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\stacking_best_pred_test_file.pkl |
| 97 | stacking_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\stacking_best_pred_train_file.pkl |
| 98 | stacking_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\stacking_pred_test_file.pkl |
| 99 | stacking_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\stacking_pred_train_file.pkl |
| 100 | svr_linear_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\svr_linear_best_pred_test_file.pkl |
| 101 | svr_linear_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\svr_linear_best_pred_train_file.pkl |
| 102 | svr_linear_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\svr_linear_pred_test_file.pkl |
| 103 | svr_linear_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\svr_linear_pred_train_file.pkl |
| 104 | svr_rbf_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\svr_rbf_best_pred_test_file.pkl |
| 105 | svr_rbf_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\svr_rbf_best_pred_train_file.pkl |
| 106 | svr_rbf_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\svr_rbf_pred_test_file.pkl |
| 107 | svr_rbf_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\svr_rbf_pred_train_file.pkl |
| 108 | voting_best_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\voting_best_pred_test_file.pkl |
| 109 | voting_best_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\voting_best_pred_train_file.pkl |
| 110 | voting_pred_test | 86,529 | 0.08 | _exports\regression_models\predictions\voting_pred_test_file.pkl |
| 111 | voting_pred_train | 345,441 | 0.33 | _exports\regression_models\predictions\voting_pred_train_file.pkl |
| 112 | xgb_best_pred_test | 43,377 | 0.04 | _exports\regression_models\predictions\xgb_best_pred_test_file.pkl |
| 113 | xgb_best_pred_train | 172,833 | 0.16 | _exports\regression_models\predictions\xgb_best_pred_train_file.pkl |
| 114 | xgb_pred_test | 43,377 | 0.04 | _exports\regression_models\predictions\xgb_pred_test_file.pkl |
| 115 | xgb_pred_train | 172,833 | 0.16 | _exports\regression_models\predictions\xgb_pred_train_file.pkl |
Load all Predictions¶
if not QUICK_LOAD and SAVE_MODEL_ARTIFACTS:
import os
import joblib
import pandas as pd
predictions_dir = os.path.join("_exports", "regression_models", "predictions")
if not os.path.exists(predictions_dir):
raise FileNotFoundError(f"Prediction directory not found: {predictions_dir}")
loaded_predictions = {}
for filename in sorted(os.listdir(predictions_dir)):
if filename.endswith(".pkl"):
prediction_key = filename.replace(".pkl", "") # remove extension only
full_path = os.path.join(predictions_dir, filename)
loaded_predictions[prediction_key] = joblib.load(full_path)
if not loaded_predictions:
raise FileNotFoundError(
f"No prediction .pkl files were found in: {predictions_dir}"
)
loaded_predictions_df = pd.DataFrame({
"Prediction_Key": list(loaded_predictions.keys())
}).reset_index(drop=True)
# Display all rows
with pd.option_context("display.max_rows", None):
display(loaded_predictions_df)
| Prediction_Key | |
|---|---|
| 0 | ada_best_pred_test_file |
| 1 | ada_best_pred_train_file |
| 2 | ada_pred_test_file |
| 3 | ada_pred_train_file |
| 4 | bagging_best_pred_test_file |
| 5 | bagging_best_pred_train_file |
| 6 | bagging_pred_test_file |
| 7 | bagging_pred_train_file |
| 8 | bayes_ridge_best_pred_test_file |
| 9 | bayes_ridge_best_pred_train_file |
| 10 | bayes_ridge_pred_test_file |
| 11 | bayes_ridge_pred_train_file |
| 12 | cat_best_pred_test_file |
| 13 | cat_best_pred_train_file |
| 14 | cat_pred_test_file |
| 15 | cat_pred_train_file |
| 16 | dt_best_pred_test_file |
| 17 | dt_best_pred_train_file |
| 18 | dt_pred_test_file |
| 19 | dt_pred_train_file |
| 20 | dummy_pred_test_file |
| 21 | dummy_pred_train_file |
| 22 | elastic_best_pred_test_file |
| 23 | elastic_best_pred_train_file |
| 24 | elastic_pred_test_file |
| 25 | elastic_pred_train_file |
| 26 | extra_trees_best_pred_test_file |
| 27 | extra_trees_best_pred_train_file |
| 28 | extra_trees_pred_test_file |
| 29 | extra_trees_pred_train_file |
| 30 | gamma_best_pred_test_file |
| 31 | gamma_best_pred_train_file |
| 32 | gamma_pred_test_file |
| 33 | gamma_pred_train_file |
| 34 | gbr_best_pred_test_file |
| 35 | gbr_best_pred_train_file |
| 36 | gbr_pred_test_file |
| 37 | gbr_pred_train_file |
| 38 | hgb_best_pred_test_file |
| 39 | hgb_best_pred_train_file |
| 40 | hgb_pred_test_file |
| 41 | hgb_pred_train_file |
| 42 | huber_best_pred_test_file |
| 43 | huber_best_pred_train_file |
| 44 | huber_pred_test_file |
| 45 | huber_pred_train_file |
| 46 | knn_best_pred_test_file |
| 47 | knn_best_pred_train_file |
| 48 | knn_pred_test_file |
| 49 | knn_pred_train_file |
| 50 | lasso_best_pred_test_file |
| 51 | lasso_best_pred_train_file |
| 52 | lasso_pred_test_file |
| 53 | lasso_pred_train_file |
| 54 | lgbm_best_pred_test_file |
| 55 | lgbm_best_pred_train_file |
| 56 | lgbm_pred_test_file |
| 57 | lgbm_pred_train_file |
| 58 | lin_pred_test_file |
| 59 | lin_pred_train_file |
| 60 | mlp_best_pred_test_file |
| 61 | mlp_best_pred_train_file |
| 62 | mlp_pred_test_file |
| 63 | mlp_pred_train_file |
| 64 | nusvr_best_pred_test_file |
| 65 | nusvr_best_pred_train_file |
| 66 | nusvr_pred_test_file |
| 67 | nusvr_pred_train_file |
| 68 | pa_best_pred_test_file |
| 69 | pa_best_pred_train_file |
| 70 | pa_pred_test_file |
| 71 | pa_pred_train_file |
| 72 | poisson_best_pred_test_file |
| 73 | poisson_best_pred_train_file |
| 74 | poisson_pred_test_file |
| 75 | poisson_pred_train_file |
| 76 | quantile_best_pred_test_file |
| 77 | quantile_best_pred_train_file |
| 78 | quantile_pred_test_file |
| 79 | quantile_pred_train_file |
| 80 | ransac_best_pred_test_file |
| 81 | ransac_best_pred_train_file |
| 82 | ransac_pred_test_file |
| 83 | ransac_pred_train_file |
| 84 | rf_best_pred_test_file |
| 85 | rf_best_pred_train_file |
| 86 | rf_pred_test_file |
| 87 | rf_pred_train_file |
| 88 | ridge_best_pred_test_file |
| 89 | ridge_best_pred_train_file |
| 90 | ridge_pred_test_file |
| 91 | ridge_pred_train_file |
| 92 | sgd_best_pred_test_file |
| 93 | sgd_best_pred_train_file |
| 94 | sgd_pred_test_file |
| 95 | sgd_pred_train_file |
| 96 | stacking_best_pred_test_file |
| 97 | stacking_best_pred_train_file |
| 98 | stacking_pred_test_file |
| 99 | stacking_pred_train_file |
| 100 | svr_linear_best_pred_test_file |
| 101 | svr_linear_best_pred_train_file |
| 102 | svr_linear_pred_test_file |
| 103 | svr_linear_pred_train_file |
| 104 | svr_rbf_best_pred_test_file |
| 105 | svr_rbf_best_pred_train_file |
| 106 | svr_rbf_pred_test_file |
| 107 | svr_rbf_pred_train_file |
| 108 | voting_best_pred_test_file |
| 109 | voting_best_pred_train_file |
| 110 | voting_pred_test_file |
| 111 | voting_pred_train_file |
| 112 | xgb_best_pred_test_file |
| 113 | xgb_best_pred_train_file |
| 114 | xgb_pred_test_file |
| 115 | xgb_pred_train_file |
if not QUICK_LOAD and SAVE_MODEL_ARTIFACTS:
# Access the loaded SVR linear prediction
display(loaded_predictions["svr_linear_pred_train_file"])
elif QUICK_LOAD:
print("Loading skipped because QUICK_LOAD = True.")
elif not SAVE_MODEL_ARTIFACTS:
print("Loading skipped because SAVE_MODEL_ARTIFACTS = False.")
array([1982.66, 3062.82, 1843.93, ..., 3005.29, 4744.91, 9841.8 ])
saving_loading_files_time = time.perf_counter() - start_fit
formatted_time = str(timedelta(seconds=int(saving_loading_files_time)))
print(f"Saving and loading files time: {formatted_time}")
Saving and loading files time: 0:23:13
Practical Heuristic Comparison of Regressor Models¶
Meaning of the Practical Comparison Table¶
This practical comparison table provides a project-based overview of the regression models used in this notebook.
The Released column refers to the approximate historical origin of the algorithm, model family, or main method behind the estimator. It is included to show the development of supervised machine learning methods over time. These years are historical reference points, not necessarily the release dates of the specific scikit-learn, XGBoost, LightGBM, or CatBoost implementations used in this project.
The practical scores are based on a 1–10 scale and reflect the behaviour observed in this notebook. They are intended as practical guidance for this project, not as universal theoretical rankings.
Meaning of the Practical Rating Scales¶
All numerical practical ratings use a 1–10 scale, where 10 represents the more favourable result or characteristic.
| Column | Meaning |
|---|---|
| Performance (1–10) | 10 = very strong predictive performance in this project |
| Fit Speed (1–10) | 10 = very fast model fitting / training in this project |
| Prediction Speed (1–10) | 10 = very fast prediction in this project |
| Memory Efficiency (1–10) | 10 = relatively lightweight model behaviour and smaller model size |
| Overfitting Resistance (1–10) | 10 = lower tendency to overfit |
| Scaling Robustness (1–10) | 10 = less sensitive to feature scaling |
The ratings are intended as practical, project-specific comparisons rather than universal rankings. Performance and speed ratings are informed directly by the results observed in this project, while some other ratings also reflect general characteristics of the model families and their behaviour in the implemented workflow.
Non-numerical columns provide additional practical context:
| Column | Meaning |
|---|---|
| Released | Approximate year the estimator became available in scikit-learn, or in its relevant external library where applicable |
| Need Scaling? | Whether feature scaling is required, recommended, not required, or depends on the model setup |
| External Library? | Whether the model requires an external machine learning library outside core scikit-learn |
| Supports Continued Training? | Whether the estimator supports some form of continued, incremental, or warm-start training |
| Continue Training Method | The mechanism used for continued training, where supported |
| Short Description | A short practical summary of the model type |
Released refers to the approximate year the estimator became available in the scikit-learn ecosystem, rather than the historical origin of the underlying statistical or machine learning method. For models provided by external libraries, such as XGBoost, LightGBM and CatBoost, the year refers to the relevant external implementation.
from IPython.display import Markdown, display
# ============================================================
# Practical comparison table
# Single source of truth:
# - displayed as Markdown
# - exported as CSV
# ============================================================
practical_comparison_markdown = r"""
| Model | Released | Performance | Fit Speed | Prediction Speed | Memory Efficiency | Overfitting Resistance | Scaling Robustness | Need Scaling? | External Library? | Can Continue Training? | Continue Training Method | Short Description |
|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---|
| Linear Regressor | 2010 | 3 | 10 | 10 | 9 | 9 | 8 | Not required | No | No | - | Simple linear model |
| Ridge Regressor | 2010 | 3 | 10 | 10 | 9 | 10 | 5 | Yes / recommended | No | No | - | L2-regularized linear model |
| Lasso Regressor | 2010 | 3 | 6 | 10 | 8 | 9 | 4 | Yes / recommended | No | No | - | L1 regularization + feature selection |
| ElasticNet Regressor | 2010 | 3 | 10 | 10 | 8 | 9 | 3 | Yes / recommended | No | No | - | Combination of L1 and L2 |
| Bayesian Ridge Regressor | 2010 | 3 | 10 | 10 | 8 | 10 | 5 | Yes / recommended | No | No | - | Bayesian regularized linear model |
| RANSAC Regressor | 2010 | 3 | 6 | 10 | 7 | 8 | 7 | Not required | No | No | - | Robust model against outliers |
| SGD Regressor | 2010 | 3 | 8 | 10 | 10 | 7 | 3 | Yes | No | Yes | `partial_fit(X, y)` | Gradient-based online linear model |
| SVR (linear) | 2010 | 2 | 3 | 3 | 7 | 8 | 3 | Yes | No | No | - | Linear margin-based regressor |
| SVR (RBF) | 2010 | 7 | 2 | 1 | 4 | 4 | 1 | Yes | No | No | - | Kernel-based nonlinear regressor |
| KNeighbors Regressor | 2010 | 6 | 9 | 7 | 3 | 3 | 2 | Yes | No | No | - | Distance-based model that stores the training data |
| NuSVR Regressor | 2010 | 4 | 3 | 2 | 4 | 4 | 1 | Yes | No | No | - | Nu-based kernel SVR, usually strong but very slow on large datasets |
| Decision Tree Regressor | 2010 | 7 | 8 | 10 | 8 | 1 | 10 | No | No | No | - | Tree-based model |
| Random Forest Regressor | 2010 | 8 | 5 | 10 | 4 | 7 | 10 | No | No | No | - | Bagged ensemble of decision trees |
| AdaBoost Regressor | 2010 | 5 | 4 | 10 | 7 | 5 | 9 | No | No | No | - | Boosting regressor that corrects errors step by step |
| Bagging Regressor | 2010 | 8 | 3 | 9 | 5 | 6 | 10 | Depends | No | No | - | Average of multiple bootstrap-trained models |
| Dummy Regressor | 2013 | 1 | 10 | 10 | 10 | 10 | 10 | No | No | No | - | Naive baseline, for example predicts the mean |
| Extra Trees Regressor | 2013 | 9 | 4 | 10 | 4 | 7 | 10 | No | No | No | - | Strongly randomized tree ensemble |
| XGBoost Regressor | 2014 | 8 | 7 | 10 | 4 | 5 | 9 | No | Yes (xgboost) | Yes | `fit(X, y, xgb_model=old_model)` | Strong regularized boosting |
| Huber Regressor | 2016 | 3 | 6 | 10 | 8 | 9 | 4 | Yes / recommended | No | No | - | Robust linear regressor for outliers |
| Gradient Boosting Regressor | 2016 | 9 | 3 | 10 | 6 | 5 | 9 | No | No | No | - | Classic boosting model |
| MLP Regressor | 2016 | 9 | 1 | 10 | 5 | 3 | 2 | Yes | No | Yes | `partial_fit(X, y)` | Neural network for regressor |
| Passive Aggressive Regressor | 2017 | 3 | 6 | 10 | 10 | 6 | 3 | Yes | No | Yes | `partial_fit(X, y)` | Online linear model with aggressive updates |
| LightGBM Regressor | 2017 | 9 | 8 | 10 | 6 | 5 | 9 | No | Yes (lightgbm) | Yes | `fit(X, y, init_model=old_model)` | Very fast boosting for large datasets |
| CatBoost Regressor | 2017 | 9 | 5 | 10 | 5 | 6 | 9 | No | Yes (catboost) | Yes | `fit(X, y, init_model=old_model)` | Strong boosting, also good with categorical data |
| Voting Regressor | 2019 | 10 | 4 | 8 | 6 | 6 | 5 | Depends | No | No | - | Average of predictions from multiple regressors |
| Stacking Regressor | 2019 | 10 | 2 | 8 | 4 | 5 | 5 | Depends | No | No | - | Multi-level ensemble meta-model |
| Poisson Regressor | 2020 | 5 | 8 | 10 | 9 | 9 | 5 | Yes / recommended | No | No | - | GLM for strictly positive count-like targets with log link |
| Gamma Regressor | 2020 | 1 | 10 | 10 | 9 | 9 | 5 | Yes / recommended | No | No | - | GLM for strictly positive right-skewed targets with log link |
| Hist Gradient Boosting Regressor | 2020 | 8 | 8 | 10 | 6 | 6 | 9 | No | No | No | - | Fast boosting for larger datasets |
| Quantile Regressor | 2021 | 2 | 2 | 10 | 8 | 9 | 4 | Yes / recommended | No | No | - | Linear model that predicts conditional quantiles, robust to outliers |
"""
# Display the table as Markdown
display(Markdown(practical_comparison_markdown))
# Convert Markdown table to DataFrame
def markdown_table_to_dataframe(markdown_table):
lines = [
line.strip()
for line in markdown_table.strip().splitlines()
if line.strip().startswith("|")
]
cleaned_rows = []
for line in lines:
inner = line.strip().strip("|").strip()
# Skip Markdown separator row, for example:
# |:---|:---:|:---:|
separator_check = (
inner
.replace("|", "")
.replace(":", "")
.replace("-", "")
.replace(" ", "")
)
if separator_check == "":
continue
columns = [col.strip() for col in inner.split("|")]
cleaned_rows.append(columns)
header = cleaned_rows[0]
rows = cleaned_rows[1:]
return pd.DataFrame(rows, columns=header)
practical_comparison_df = markdown_table_to_dataframe(
practical_comparison_markdown
)
# Export as CSV
if SAVE_PORTFOLIO_OUTPUTS:
output_path = (
REGRESSION_RESULTS_DIR
/ "supervised_regression_practical_comparison.csv"
)
practical_comparison_df.to_csv(
output_path,
index=False,
encoding="utf-8-sig"
)
print("Saved:", output_path)
| Model | Released | Performance | Fit Speed | Prediction Speed | Memory Efficiency | Overfitting Resistance | Scaling Robustness | Need Scaling? | External Library? | Can Continue Training? | Continue Training Method | Short Description |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Linear Regressor | 2010 | 3 | 10 | 10 | 9 | 9 | 8 | Not required | No | No | - | Simple linear model |
| Ridge Regressor | 2010 | 3 | 10 | 10 | 9 | 10 | 5 | Yes / recommended | No | No | - | L2-regularized linear model |
| Lasso Regressor | 2010 | 3 | 6 | 10 | 8 | 9 | 4 | Yes / recommended | No | No | - | L1 regularization + feature selection |
| ElasticNet Regressor | 2010 | 3 | 10 | 10 | 8 | 9 | 3 | Yes / recommended | No | No | - | Combination of L1 and L2 |
| Bayesian Ridge Regressor | 2010 | 3 | 10 | 10 | 8 | 10 | 5 | Yes / recommended | No | No | - | Bayesian regularized linear model |
| RANSAC Regressor | 2010 | 3 | 6 | 10 | 7 | 8 | 7 | Not required | No | No | - | Robust model against outliers |
| SGD Regressor | 2010 | 3 | 8 | 10 | 10 | 7 | 3 | Yes | No | Yes | partial_fit(X, y) |
Gradient-based online linear model |
| SVR (linear) | 2010 | 2 | 3 | 3 | 7 | 8 | 3 | Yes | No | No | - | Linear margin-based regressor |
| SVR (RBF) | 2010 | 7 | 2 | 1 | 4 | 4 | 1 | Yes | No | No | - | Kernel-based nonlinear regressor |
| KNeighbors Regressor | 2010 | 6 | 9 | 7 | 3 | 3 | 2 | Yes | No | No | - | Distance-based model that stores the training data |
| NuSVR Regressor | 2010 | 4 | 3 | 2 | 4 | 4 | 1 | Yes | No | No | - | Nu-based kernel SVR, usually strong but very slow on large datasets |
| Decision Tree Regressor | 2010 | 7 | 8 | 10 | 8 | 1 | 10 | No | No | No | - | Tree-based model |
| Random Forest Regressor | 2010 | 8 | 5 | 10 | 4 | 7 | 10 | No | No | No | - | Bagged ensemble of decision trees |
| AdaBoost Regressor | 2010 | 5 | 4 | 10 | 7 | 5 | 9 | No | No | No | - | Boosting regressor that corrects errors step by step |
| Bagging Regressor | 2010 | 8 | 3 | 9 | 5 | 6 | 10 | Depends | No | No | - | Average of multiple bootstrap-trained models |
| Dummy Regressor | 2013 | 1 | 10 | 10 | 10 | 10 | 10 | No | No | No | - | Naive baseline, for example predicts the mean |
| Extra Trees Regressor | 2013 | 9 | 4 | 10 | 4 | 7 | 10 | No | No | No | - | Strongly randomized tree ensemble |
| XGBoost Regressor | 2014 | 8 | 7 | 10 | 4 | 5 | 9 | No | Yes (xgboost) | Yes | fit(X, y, xgb_model=old_model) |
Strong regularized boosting |
| Huber Regressor | 2016 | 3 | 6 | 10 | 8 | 9 | 4 | Yes / recommended | No | No | - | Robust linear regressor for outliers |
| Gradient Boosting Regressor | 2016 | 9 | 3 | 10 | 6 | 5 | 9 | No | No | No | - | Classic boosting model |
| MLP Regressor | 2016 | 9 | 1 | 10 | 5 | 3 | 2 | Yes | No | Yes | partial_fit(X, y) |
Neural network for regressor |
| Passive Aggressive Regressor | 2017 | 3 | 6 | 10 | 10 | 6 | 3 | Yes | No | Yes | partial_fit(X, y) |
Online linear model with aggressive updates |
| LightGBM Regressor | 2017 | 9 | 8 | 10 | 6 | 5 | 9 | No | Yes (lightgbm) | Yes | fit(X, y, init_model=old_model) |
Very fast boosting for large datasets |
| CatBoost Regressor | 2017 | 9 | 5 | 10 | 5 | 6 | 9 | No | Yes (catboost) | Yes | fit(X, y, init_model=old_model) |
Strong boosting, also good with categorical data |
| Voting Regressor | 2019 | 10 | 4 | 8 | 6 | 6 | 5 | Depends | No | No | - | Average of predictions from multiple regressors |
| Stacking Regressor | 2019 | 10 | 2 | 8 | 4 | 5 | 5 | Depends | No | No | - | Multi-level ensemble meta-model |
| Poisson Regressor | 2020 | 5 | 8 | 10 | 9 | 9 | 5 | Yes / recommended | No | No | - | GLM for strictly positive count-like targets with log link |
| Gamma Regressor | 2020 | 1 | 10 | 10 | 9 | 9 | 5 | Yes / recommended | No | No | - | GLM for strictly positive right-skewed targets with log link |
| Hist Gradient Boosting Regressor | 2020 | 8 | 8 | 10 | 6 | 6 | 9 | No | No | No | - | Fast boosting for larger datasets |
| Quantile Regressor | 2021 | 2 | 2 | 10 | 8 | 9 | 4 | Yes / recommended | No | No | - | Linear model that predicts conditional quantiles, robust to outliers |
Saved: _exports\regression_models\results\supervised_regression_practical_comparison.csv
This table is a practical, experience-based summary of the model families tested in this project. It is intended to support interpretation and model selection, but it should not be treated as a formal statistical ranking.
Python Environment Package Versions¶
import sys
import importlib
print("Python:", sys.version)
print("=" * (len(sys.version) + 8))
package_groups = {
"Core ML / data:": [
("NumPy", "numpy"),
("Pandas", "pandas"),
("SciPy", "scipy"),
("scikit-learn", "sklearn"),
("Statsmodels", "statsmodels"),
("Joblib", "joblib"),
("threadpoolctl", "threadpoolctl"),
],
"Visualization / notebook helpers:": [
("Matplotlib", "matplotlib"),
("Seaborn", "seaborn"),
("OpenPyXL", "openpyxl"),
],
"Boosting libraries:": [
("XGBoost", "xgboost"),
("LightGBM", "lightgbm"),
("CatBoost", "catboost"),
],
"Hyperparameter tuning:": [
("scikit-optimize", "skopt"),
],
"Jupyter-related packages actually used by the environment:": [
("IPyKernel", "ipykernel"),
("JupyterLab", "jupyterlab"),
("Notebook", "notebook"),
],
}
for group_name, packages in package_groups.items():
print(f"\n{group_name}")
for display_name, import_name in packages:
try:
module = importlib.import_module(import_name)
version = getattr(module, "__version__", "no __version__ attribute")
print(f" - {display_name}: {version}")
except ImportError:
print(f" - {display_name}: not installed")
Python: 3.11.14 | packaged by conda-forge | (main, Jan 26 2026, 23:39:55) [MSC v.1944 64 bit (AMD64)] ===================================================================================================== Core ML / data: - NumPy: 1.26.4 - Pandas: 3.0.2 - SciPy: 1.17.1 - scikit-learn: 1.8.0 - Statsmodels: 0.14.6 - Joblib: 1.5.3 - threadpoolctl: 3.6.0 Visualization / notebook helpers: - Matplotlib: 3.10.8 - Seaborn: 0.13.2 - OpenPyXL: 3.1.5 Boosting libraries: - XGBoost: 3.2.0 - LightGBM: 4.6.0 - CatBoost: 1.2.10 Hyperparameter tuning: - scikit-optimize: 0.10.2 Jupyter-related packages actually used by the environment: - IPyKernel: 7.2.0 - JupyterLab: 4.5.6 - Notebook: 7.5.5
System informations¶
import platform
import psutil
import sys
print("Execution Environment Summary")
print("=" * 30)
print(f"OS: {platform.system()} {platform.release()}")
print(f"Architecture: {platform.architecture()[0]}")
print(f"Python: {sys.version.split()[0]}")
print()
print("CPU")
print("-" * 30)
print(f"Processor: {platform.processor()}")
print(f"Physical cores: {psutil.cpu_count(logical=False)}")
print(f"Logical cores: {psutil.cpu_count(logical=True)}")
cpu_freq = psutil.cpu_freq()
if cpu_freq:
print(f"Current frequency: {cpu_freq.current:.2f} MHz")
print(f"Max frequency: {cpu_freq.max:.2f} MHz")
print()
vm = psutil.virtual_memory()
print("Memory")
print("-" * 30)
print(f"Total RAM: {vm.total / (1024**3):.2f} GB")
print(f"Available RAM: {vm.available / (1024**3):.2f} GB")
print(f"RAM usage: {vm.percent}%")
Execution Environment Summary ============================== OS: Windows 10 Architecture: 64bit Python: 3.11.14 CPU ------------------------------ Processor: AMD64 Family 21 Model 1 Stepping 2, AuthenticAMD Physical cores: 4 Logical cores: 8 Current frequency: 3600.00 MHz Max frequency: 3600.00 MHz Memory ------------------------------ Total RAM: 15.96 GB Available RAM: 1.51 GB RAM usage: 90.5%
notebook_running_time = time.time() - notebook_start_time
from datetime import timedelta
formatted_time = str(timedelta(seconds=int(notebook_running_time)))
print(f"Total notebook execution time: {formatted_time}")
from datetime import datetime
today = datetime.today()
print(f'Date: {today.strftime("%Y/%m/%d")}')
Total notebook execution time: 11:17:53 Date: 2026/08/21
Practical Model Selection: Performance, Runtime and File Size¶
Model selection is not only about predictive accuracy. In practical machine learning workflows, runtime, prediction speed, file size, memory use, and deployment complexity can also influence which model is the best choice.
The saved fitted model file sizes add a useful practical perspective to the final comparison. They give an approximate indication of how large each trained model artifact is when saved and reused. This is important because a highly accurate model may not always be the most practical option if it is slow to train, slow to predict, large to store, or more complex to deploy.
Search object sizes are not used for practical deployment comparison because search objects can contain cross-validation metadata, parameter search results, and additional information that would normally not be needed when deploying only the final fitted model.
In this project, the tuned Stacking Regressor achieved the best overall RMSE, while the tuned Voting Regressor produced a very similar result. However, both models are ensemble-based and can be more complex or larger than some individual models. From a practical point of view, tuned LightGBM and tuned CatBoost are especially attractive alternatives because they achieved strong predictive performance while remaining simpler than the largest ensemble combinations.
This shows that the best model depends on the practical goal. If the priority is the lowest possible RMSE, the tuned Stacking Regressor is the best choice in this project. If the priority is a strong balance between accuracy, training time, prediction speed, saved model size, and deployment simplicity, tuned LightGBM or tuned CatBoost may be more practical choices.
Final Model Comparison¶
The final regression comparison shows that the strongest overall performance was achieved by ensemble-based, boosting-based, and meta-estimator models. Based on the primary RMSE metric, the best-performing model was the tuned Stacking Regressor.
The tuned Stacking Regressor achieved the best overall test RMSE, with an RMSE of 522.41 and an R² score of 0.9829. It also achieved a strong test MAE of 264.66. This suggests that combining several strong tuned regressors through a meta-model can slightly improve predictive performance on this structured tabular regression task.
The tuned Voting Regressor was extremely close behind, achieving a test RMSE of 523.72, an R² score of 0.9828, and the lowest test MAE among the top models at 263.88. This makes Voting a very strong practical alternative, especially when a simpler ensemble structure is preferred.
Among the individual non-meta models, tuned CatBoost, tuned Gradient Boosting, tuned Extra Trees, tuned MLP, and tuned LightGBM all produced highly competitive results. This confirms that tree-based ensemble, boosting, and flexible non-linear methods are highly effective for this type of structured regression problem.
Linear, regularized linear, robust, distance-based, and support vector models provided useful baseline and comparison points. Although most of these models were less competitive than the strongest ensemble and boosting models, they remain valuable because they are often easier to interpret or useful for understanding the performance gain achieved by more complex models.
The final model selection should not be based only on a single metric. RMSE and R² are especially important in this project, but MAE, training time, prediction time, train-test gap, interpretability, tuning complexity, and practical usability also provide important context.
Overall, the project shows that ensemble and boosting approaches provide the strongest predictive performance on this diamond price prediction task, while simpler models remain useful as interpretable and computationally efficient baselines.
Limitations and Future Work¶
Although this project compares a broad range of regression models, the results should be interpreted with several limitations in mind.
First, the analysis is based on a single structured tabular dataset. Therefore, the results show how the models behave on this particular diamond price prediction task, but they do not prove that the same model family would perform best on all regression problems.
Second, the hyperparameter tuning process uses predefined and, in some cases, iteratively refined search spaces. These search spaces were designed to be practical and computationally manageable, and the aim was to obtain strong model configurations for fairer comparison. However, they may not include every possible strong configuration for each model. Some models could potentially achieve better results with larger, more specialized, or more computationally expensive tuning strategies.
Third, the tuned Voting and Stacking regressors are built from the best estimators found by earlier hyperparameter searches. No additional Voting weight search or Stacking final-estimator grid search is applied in the final version, because these extra searches were computationally expensive and produced only small improvements in previous runs. This makes the workflow more practical, but it also means that the ensemble configurations are not exhaustively optimised.
Fourth, the project focuses mainly on predictive performance, runtime, residual analysis, price range behaviour, and practical model comparison. Although permutation importance is applied to the best-performing model, the project does not yet include a full interpretability analysis using methods such as SHAP across all suitable models.
Fifth, the comparison uses a fixed train-test split and cross-validation strategy. While this provides a consistent experimental framework, repeated cross-validation or statistical significance testing could be used to evaluate whether the performance differences between the top models are statistically meaningful.
Finally, some models are more computationally expensive than others. A model with slightly better performance may not always be the most practical choice if the improvement is small compared with the additional training time, tuning cost, or model complexity.
Future work could strengthen this project by:
- applying the same benchmarking framework to additional regression datasets;
- using repeated cross-validation or statistical significance testing to compare top models;
- adding SHAP or more detailed permutation importance analysis for deeper model interpretation;
- analysing model errors in more detail across different price segments;
- testing alternative feature engineering strategies;
- comparing the results with additional modern tabular modelling approaches;
- testing alternative ensemble configurations for Voting and Stacking;
- turning the notebook into a shorter technical report or paper-style summary.
Conclusion¶
This project compared approximately 30 supervised regression models on the diamonds dataset using a consistent preprocessing, training, tuning, and evaluation framework.
The task was to predict diamond prices from the available numerical and categorical features, including carat, cut, colour, clarity, depth, table, and physical dimensions.
The final results show that ensemble-based, boosting-based, and meta-estimator approaches were the strongest model families for this diamond price prediction task. Based on the primary RMSE metric, the best-performing model was the tuned Stacking Regressor.
The tuned Stacking Regressor achieved the best overall test performance, with an RMSE of 522.41, an MAE of 264.66, and an R² score of 0.9829. This suggests that combining several strong tuned regressors through a meta-model can slightly improve predictive performance on this structured tabular dataset.
The tuned Voting Regressor also performed extremely strongly, achieving an RMSE of 523.72, an MAE of 263.88, and an R² score of 0.9828. Although it was slightly behind the tuned Stacking Regressor in RMSE, it achieved the lowest MAE among the top models and remains a very strong ensemble alternative.
Among the individual non-meta models, tuned CatBoost, tuned Gradient Boosting, tuned Extra Trees, tuned MLP, and tuned LightGBM produced highly competitive results. This confirms that tree-based ensemble, boosting, and flexible non-linear methods are highly effective for this type of structured regression problem.
Simpler models, including linear, regularized linear, robust, distance-based, and support vector models, were generally less competitive than the strongest ensemble and boosting methods. However, they remain valuable as baselines because they are often easier to interpret, computationally cheaper, or useful for understanding how much performance is gained by using more complex models.
The project also shows why regression models should not be compared using only one metric. RMSE and R² were especially important in this notebook, but MAE, train-test gap, residual behaviour, price range performance, runtime, interpretability, and practical usability all provide important additional context.
The saved fitted model file sizes add another practical perspective to the comparison. Large ensemble models can provide excellent predictive performance, but they may also create larger model artifacts and require more storage or deployment resources. In contrast, some individual models, such as tuned LightGBM or tuned CatBoost, may offer a better practical balance between predictive performance, training time, prediction speed, and saved model size.
In summary, the tuned Stacking Regressor was the best overall model based on the primary RMSE metric. The tuned Voting Regressor was the closest ensemble alternative, producing a very similar result and the lowest MAE among the top models. Among the individual non-meta models, tuned CatBoost, tuned Gradient Boosting, tuned Extra Trees, and tuned LightGBM were among the strongest predictive alternatives. From a practical deployment perspective, tuned LightGBM and tuned CatBoost are especially attractive because they provide strong performance without the same level of model size and ensemble complexity as the largest models.
Overall, this notebook provides a broad supervised regression benchmark on structured tabular data. It also creates a reusable framework that can be extended to additional datasets, deeper interpretability analysis, alternative feature engineering strategies, additional ensemble experiments, and a shorter paper-style technical report.