best-model-toolkit 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
best_model_toolkit
|
|
3
|
+
===================
|
|
4
|
+
|
|
5
|
+
Utilities for training, tuning (GridSearchCV) and comparing scikit-learn /
|
|
6
|
+
XGBoost models for regression and classification tasks, together with
|
|
7
|
+
ready-made plots of performance metrics (R^2, MAE, RMSE, F1, precision,
|
|
8
|
+
recall, PR curves, etc).
|
|
9
|
+
|
|
10
|
+
Main function:
|
|
11
|
+
|
|
12
|
+
from best_model_toolkit import best_model
|
|
13
|
+
|
|
14
|
+
results = best_model(
|
|
15
|
+
features=X, target=y,
|
|
16
|
+
mode="regression", # or "classification"
|
|
17
|
+
grid="Yes", # whether to run GridSearchCV
|
|
18
|
+
df=df, hue=None,
|
|
19
|
+
pairplot_type="heatmap", # "heatmap" | "top_corr" | "full" | "none"
|
|
20
|
+
)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from .core import (
|
|
24
|
+
best_model,
|
|
25
|
+
test_model_reg,
|
|
26
|
+
test_model_class,
|
|
27
|
+
grid_reg,
|
|
28
|
+
grid_class,
|
|
29
|
+
plot_r2,
|
|
30
|
+
plot_score_reg,
|
|
31
|
+
plot_mae,
|
|
32
|
+
plot_rmse,
|
|
33
|
+
plot_score,
|
|
34
|
+
plot_f1_score,
|
|
35
|
+
plot_score_precision,
|
|
36
|
+
plot_score_recall,
|
|
37
|
+
plot_score_ap,
|
|
38
|
+
plot_score_generic,
|
|
39
|
+
plot_curve_generic,
|
|
40
|
+
plot_cv_r2,
|
|
41
|
+
plot_mae_mse,
|
|
42
|
+
plot_cv_metrics,
|
|
43
|
+
plot_pair_grid_ref,
|
|
44
|
+
plot_pair_grid_class,
|
|
45
|
+
plot_corr_heatmap,
|
|
46
|
+
plot_pair_grid_top_corr,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
__version__ = "0.2.0"
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
"best_model",
|
|
53
|
+
"test_model_reg",
|
|
54
|
+
"test_model_class",
|
|
55
|
+
"grid_reg",
|
|
56
|
+
"grid_class",
|
|
57
|
+
"plot_r2",
|
|
58
|
+
"plot_score_reg",
|
|
59
|
+
"plot_mae",
|
|
60
|
+
"plot_rmse",
|
|
61
|
+
"plot_score",
|
|
62
|
+
"plot_f1_score",
|
|
63
|
+
"plot_score_precision",
|
|
64
|
+
"plot_score_recall",
|
|
65
|
+
"plot_score_ap",
|
|
66
|
+
"plot_score_generic",
|
|
67
|
+
"plot_curve_generic",
|
|
68
|
+
"plot_cv_r2",
|
|
69
|
+
"plot_mae_mse",
|
|
70
|
+
"plot_cv_metrics",
|
|
71
|
+
"plot_pair_grid_ref",
|
|
72
|
+
"plot_pair_grid_class",
|
|
73
|
+
"plot_corr_heatmap",
|
|
74
|
+
"plot_pair_grid_top_corr",
|
|
75
|
+
]
|
|
@@ -0,0 +1,1016 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Core functions for training, tuning and comparing scikit-learn / XGBoost
|
|
4
|
+
regression and classification models, with built-in plotting of
|
|
5
|
+
performance metrics (R^2, MAE, RMSE, F1, precision, recall, PR curves, etc).
|
|
6
|
+
|
|
7
|
+
This module was originally written as a Google Colab notebook and has been
|
|
8
|
+
packaged for reuse via pip.
|
|
9
|
+
"""
|
|
10
|
+
import numpy as np
|
|
11
|
+
from math import sqrt
|
|
12
|
+
import matplotlib.pyplot as plt
|
|
13
|
+
import matplotlib
|
|
14
|
+
import pandas as pd
|
|
15
|
+
import seaborn as sns
|
|
16
|
+
import xgboost as xgb
|
|
17
|
+
from scipy import stats
|
|
18
|
+
from sklearn import linear_model
|
|
19
|
+
from sklearn import svm
|
|
20
|
+
from sklearn.ensemble import AdaBoostClassifier
|
|
21
|
+
from sklearn.ensemble import AdaBoostRegressor
|
|
22
|
+
from sklearn.ensemble import GradientBoostingClassifier
|
|
23
|
+
from sklearn.ensemble import GradientBoostingRegressor
|
|
24
|
+
from sklearn.ensemble import RandomForestClassifier
|
|
25
|
+
from sklearn.ensemble import RandomForestRegressor
|
|
26
|
+
from sklearn.linear_model import LinearRegression
|
|
27
|
+
from sklearn.linear_model import LogisticRegression
|
|
28
|
+
from sklearn.linear_model import Ridge
|
|
29
|
+
from sklearn.metrics import *
|
|
30
|
+
from sklearn.metrics import ConfusionMatrixDisplay
|
|
31
|
+
from sklearn.metrics import make_scorer, accuracy_score, precision_score, recall_score, f1_score
|
|
32
|
+
from sklearn.model_selection import GridSearchCV
|
|
33
|
+
from sklearn.model_selection import train_test_split
|
|
34
|
+
from sklearn.naive_bayes import GaussianNB
|
|
35
|
+
from sklearn.neighbors import KNeighborsClassifier
|
|
36
|
+
from sklearn.neighbors import KNeighborsRegressor
|
|
37
|
+
from sklearn.preprocessing import OneHotEncoder
|
|
38
|
+
from sklearn.tree import DecisionTreeClassifier
|
|
39
|
+
from sklearn.tree import DecisionTreeRegressor
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
Functions for testing and evaluating regression models, as well as for plotting the performance metrics such as R-squared, MAE, and RMSE, using Matplotlib """
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_model_reg(model, model_name, x_train, y_train, x_test, y_test):
|
|
46
|
+
"""
|
|
47
|
+
Args:
|
|
48
|
+
model: a machine learning model to be tested
|
|
49
|
+
model_name:a string containing the name of the model
|
|
50
|
+
x_train: a table of features for the training set
|
|
51
|
+
y_train: a table of labels for the training set
|
|
52
|
+
x_test: a table of features for the test set
|
|
53
|
+
y_test: a table of labels for the test set
|
|
54
|
+
|
|
55
|
+
Returns: dictionary of scores
|
|
56
|
+
"""
|
|
57
|
+
# Train the model using the training data
|
|
58
|
+
model.fit(x_train, y_train)
|
|
59
|
+
|
|
60
|
+
# Use the trained model to make predictions on the testing and training data
|
|
61
|
+
y_test_pred = model.predict(x_test)
|
|
62
|
+
y_train_pred = model.predict(x_train)
|
|
63
|
+
|
|
64
|
+
# Compute R^2 scores for both training and testing data
|
|
65
|
+
r2_train = r2_score(y_train, y_train_pred)
|
|
66
|
+
r2_test = r2_score(y_test, y_test_pred)
|
|
67
|
+
|
|
68
|
+
# Compute mean absolute error (MAE) scores for both training and testing data
|
|
69
|
+
mae_train = mean_absolute_error(y_train, y_train_pred)
|
|
70
|
+
mae_test = mean_absolute_error(y_test, y_test_pred)
|
|
71
|
+
|
|
72
|
+
# Compute root mean squared error (RMSE) scores for both training and testing data
|
|
73
|
+
rmse_train = sqrt(mean_squared_error(y_train, y_train_pred))
|
|
74
|
+
rmse_test = sqrt(mean_squared_error(y_test, y_test_pred))
|
|
75
|
+
|
|
76
|
+
# Store the scores in a dictionary
|
|
77
|
+
scores = {
|
|
78
|
+
'r2': {'train': r2_train, 'val': r2_test},
|
|
79
|
+
'rmse': {'train': rmse_train, 'val': rmse_test},
|
|
80
|
+
'mae': {'train': mae_train, 'val': mae_test}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Plot the predicted vs. ground truth values for the testing data
|
|
84
|
+
plt.scatter(y_test, y_test_pred, s=5)
|
|
85
|
+
plt.xlabel('Original Data')
|
|
86
|
+
plt.ylabel('Predicted')
|
|
87
|
+
plt.title(model_name)
|
|
88
|
+
plt.show()
|
|
89
|
+
plt.close()
|
|
90
|
+
|
|
91
|
+
return scores
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def plot_r2(model_names, model_scores_t, model_scores_v):
|
|
95
|
+
"""
|
|
96
|
+
Args:
|
|
97
|
+
model_names: the names of the models being compared
|
|
98
|
+
model_scores_t: the training scores for each model
|
|
99
|
+
model_scores_v: the validation scores for each model
|
|
100
|
+
|
|
101
|
+
Returns: The function does not have a Returns section, so it does not return anything
|
|
102
|
+
"""
|
|
103
|
+
# Plot the R^2 scores for the training and validation data for each model
|
|
104
|
+
plt.plot(model_names, model_scores_t, '*', label='training')
|
|
105
|
+
plt.plot(model_names, model_scores_v, '*', label='validation')
|
|
106
|
+
plt.ylabel(r'$R^2$')
|
|
107
|
+
plt.xticks(rotation=45, ha='right')
|
|
108
|
+
plt.ylim(-0.1, 1)
|
|
109
|
+
plt.legend()
|
|
110
|
+
plt.show()
|
|
111
|
+
plt.close()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def plot_score_reg(model_names, model_scores_t, model_scores_v):
|
|
115
|
+
"""
|
|
116
|
+
Args:
|
|
117
|
+
model_names: names of the models being compared
|
|
118
|
+
model_scores_t: the training scores for each model
|
|
119
|
+
model_scores_v: the validation scores for each model
|
|
120
|
+
|
|
121
|
+
Returns: The function does not have a Returns section, so it does not return anything
|
|
122
|
+
"""
|
|
123
|
+
# Plot a generic score (e.g. R^2, accuracy) for the training and validation data for each model
|
|
124
|
+
plt.plot(model_names, model_scores_t, '*', label='training')
|
|
125
|
+
plt.plot(model_names, model_scores_v, '*', label='validation')
|
|
126
|
+
plt.ylabel('score')
|
|
127
|
+
plt.xticks(rotation=45, ha='right')
|
|
128
|
+
plt.legend()
|
|
129
|
+
plt.show()
|
|
130
|
+
plt.close()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def plot_mae(model_names, model_scores_t, model_scores_v):
|
|
134
|
+
"""
|
|
135
|
+
Args:
|
|
136
|
+
model_names: the names of the models being compared
|
|
137
|
+
model_scores_t: the training scores for each model
|
|
138
|
+
model_scores_v: the validation scores for each model
|
|
139
|
+
|
|
140
|
+
Returns: The function does not have a Returns section, so it does not return anything
|
|
141
|
+
"""
|
|
142
|
+
# Plot the mean absolute error (MAE) scores for the training and validation data for each model
|
|
143
|
+
|
|
144
|
+
plt.plot(model_names, model_scores_t, '*', label='training')
|
|
145
|
+
plt.plot(model_names, model_scores_v, '*', label='validation')
|
|
146
|
+
plt.ylabel('MAE')
|
|
147
|
+
plt.xticks(rotation=45, ha='right')
|
|
148
|
+
plt.legend()
|
|
149
|
+
plt.show()
|
|
150
|
+
plt.close()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def plot_rmse(model_names, model_scores_t, model_scores_v):
|
|
154
|
+
"""
|
|
155
|
+
Args:
|
|
156
|
+
model_names: the names of the models being compared
|
|
157
|
+
model_scores_t: the training scores for each model
|
|
158
|
+
model_scores_v: the validation scores for each model
|
|
159
|
+
|
|
160
|
+
Returns: The function does not have a Returns section, so it does not return anything
|
|
161
|
+
"""
|
|
162
|
+
# Plot the root mean squared error (RMSE) scores for the training and validation data for each model
|
|
163
|
+
|
|
164
|
+
plt.plot(model_names, model_scores_t, '*', label='training')
|
|
165
|
+
plt.plot(model_names, model_scores_v, '*', label='validation')
|
|
166
|
+
plt.ylabel('RMSE')
|
|
167
|
+
plt.xticks(rotation=45, ha='right')
|
|
168
|
+
plt.legend()
|
|
169
|
+
plt.show()
|
|
170
|
+
plt.close()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_model_class(model, model_name, x_train, y_train, x_test, y_test):
|
|
174
|
+
"""
|
|
175
|
+
Args:
|
|
176
|
+
model: the machine learning model to be tested
|
|
177
|
+
model_name: a string indicating the name of the model
|
|
178
|
+
x_train: the training data features
|
|
179
|
+
y_train: the training data target variable
|
|
180
|
+
x_test: the test data features
|
|
181
|
+
y_test: the test data target variable
|
|
182
|
+
|
|
183
|
+
Returns: The function returns a dictionary containing various scores of the model, such as its accuracy, F1 score, precision, recall, and average precision score. If the model has a predict_proba method, the function also calculates the precision-recall curve.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
model.fit(x_train, y_train)
|
|
187
|
+
|
|
188
|
+
# Use the trained model to make predictions on the training and validation data
|
|
189
|
+
y_test_pred = model.predict(x_test)
|
|
190
|
+
y_train_pred = model.predict(x_train)
|
|
191
|
+
|
|
192
|
+
# Calculate the score of the model on the training and validation data
|
|
193
|
+
score_train = model.score(x_train, y_train)
|
|
194
|
+
score_test = model.score(x_test, y_test)
|
|
195
|
+
|
|
196
|
+
# Calculate the F1 score of the model on the training and validation data
|
|
197
|
+
f1_test = f1_score(y_test, y_test_pred) # , average='micro')
|
|
198
|
+
f1_train = f1_score(y_train, y_train_pred) # , average='micro')
|
|
199
|
+
|
|
200
|
+
# Calculate the precision of the model on the training and validation data
|
|
201
|
+
precision_test = precision_score(y_test, y_test_pred, zero_division=0) # , average='micro')
|
|
202
|
+
precision_train = precision_score(y_train, y_train_pred, zero_division=0) # , average='micro')
|
|
203
|
+
|
|
204
|
+
# Calculate the recall of the model on the training and validation data
|
|
205
|
+
recall_test = recall_score(y_test, y_test_pred) # , average='micro')
|
|
206
|
+
recall_train = recall_score(y_train, y_train_pred) # , average='micro')
|
|
207
|
+
|
|
208
|
+
# Create a dictionary to store the scores of the model
|
|
209
|
+
scores = {
|
|
210
|
+
'Score': {'train': score_train, 'val': score_test},
|
|
211
|
+
'F1': {'train': f1_train, 'val': f1_test},
|
|
212
|
+
'Precision': {'train': precision_train, 'val': precision_test},
|
|
213
|
+
'Recall': {'train': recall_train, 'val': recall_test},
|
|
214
|
+
'AP': None,
|
|
215
|
+
'PR_curve': None,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
|
|
220
|
+
# If the model has a predict_proba method, use it to make predictions and calculate the average precision score
|
|
221
|
+
y_test_pred_proba = model.predict_proba(x_test)
|
|
222
|
+
y_train_pred_proba = model.predict_proba(x_train)
|
|
223
|
+
|
|
224
|
+
# Convert the target values to one-hot encoding format
|
|
225
|
+
ohe = OneHotEncoder(sparse_output=False)
|
|
226
|
+
y_train_oh = ohe.fit_transform(y_train.reshape((-1, 1)))
|
|
227
|
+
y_test_oh = ohe.fit_transform(y_test.reshape((-1, 1)))
|
|
228
|
+
|
|
229
|
+
# Calculate the average precision score of the model on the training and validation data
|
|
230
|
+
ap_test = average_precision_score(y_test_oh, y_test_pred_proba)
|
|
231
|
+
ap_train = average_precision_score(y_train_oh, y_train_pred_proba)
|
|
232
|
+
|
|
233
|
+
# Calculate the precision-recall curve of the model on the training and validation data
|
|
234
|
+
pr_curve_test = precision_recall_curve(y_test, y_test_pred_proba[:, -1])
|
|
235
|
+
pr_curve_train = precision_recall_curve(y_train, y_train_pred_proba[:, -1])
|
|
236
|
+
|
|
237
|
+
# Add the average precision score and the precision-recall curve to the scores dictionary
|
|
238
|
+
scores['AP'] = {'train': ap_train, 'val': ap_test}
|
|
239
|
+
scores['PR_curve'] = {'train': pr_curve_train, 'val': pr_curve_test}
|
|
240
|
+
except AttributeError as e:
|
|
241
|
+
# If the model does not have a predict_proba method, print an error message
|
|
242
|
+
print(e)
|
|
243
|
+
|
|
244
|
+
# Display a confusion matrix for the model's predictions on the validation data
|
|
245
|
+
ConfusionMatrixDisplay.from_predictions(y_test, y_test_pred, normalize='true')
|
|
246
|
+
plt.title(model_name)
|
|
247
|
+
plt.show()
|
|
248
|
+
plt.close()
|
|
249
|
+
|
|
250
|
+
return scores
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def plot_score_generic(model_names, model_scores_t, model_scores_v, score_name):
|
|
254
|
+
"""
|
|
255
|
+
Args:
|
|
256
|
+
model_names: the names of the models being compared
|
|
257
|
+
model_scores_t: the training scores for each model
|
|
258
|
+
model_scores_v: the validation scores for each model
|
|
259
|
+
|
|
260
|
+
Returns: The function does not have a Returns section, so it does not return anything
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
plt.plot(model_names, model_scores_t, '*', label='training')
|
|
264
|
+
|
|
265
|
+
# Plot the validation scores
|
|
266
|
+
plt.plot(model_names, model_scores_v, '*', label='validation')
|
|
267
|
+
|
|
268
|
+
plt.ylabel(score_name)
|
|
269
|
+
plt.xticks(rotation=45, ha='right')
|
|
270
|
+
plt.legend()
|
|
271
|
+
plt.show()
|
|
272
|
+
plt.close()
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# A convenience function to plot scores with the default name "score"
|
|
276
|
+
def plot_score(model_names, model_scores_t, model_scores_v):
|
|
277
|
+
"""
|
|
278
|
+
Args:
|
|
279
|
+
model_names: the names of the models being compared
|
|
280
|
+
model_scores_t: the training scores for each model
|
|
281
|
+
model_scores_v: the validation scores for each model
|
|
282
|
+
|
|
283
|
+
Returns: The function does not have a Returns section, so it does not return anything
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
plot_score_generic(model_names, model_scores_t, model_scores_v, score_name='score')
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def plot_f1_score(model_names, model_scores_t, model_scores_v):
|
|
290
|
+
"""
|
|
291
|
+
Args:
|
|
292
|
+
model_names: the names of the models being compared
|
|
293
|
+
model_scores_t: the training scores for each model
|
|
294
|
+
model_scores_v: the validation scores for each model
|
|
295
|
+
|
|
296
|
+
Returns: The function does not have a Returns section, so it does not return anything
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
plot_score_generic(model_names, model_scores_t, model_scores_v, score_name='f1')
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
# A convenience function to plot precision scores
|
|
303
|
+
def plot_score_precision(model_names, model_scores_t, model_scores_v):
|
|
304
|
+
plot_score_generic(model_names, model_scores_t, model_scores_v, score_name='precision')
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# A convenience function to plot recall scores
|
|
308
|
+
def plot_score_recall(model_names, model_scores_t, model_scores_v):
|
|
309
|
+
"""
|
|
310
|
+
Args:
|
|
311
|
+
model_names: the names of the models being compared
|
|
312
|
+
model_scores_t: the training scores for each model
|
|
313
|
+
model_scores_v: the validation scores for each model
|
|
314
|
+
|
|
315
|
+
Returns: The function does not have a Returns section, so it does not return anything
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
plot_score_generic(model_names, model_scores_t, model_scores_v, score_name='recall')
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
# A convenience function to plot average precision scores
|
|
322
|
+
def plot_score_ap(model_names, model_scores_t, model_scores_v):
|
|
323
|
+
"""
|
|
324
|
+
Plots the recall scores for different models on both training and validation sets.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
model_names (list): The names of the models being compared (e.g., list of strings).
|
|
328
|
+
model_scores_t (list): The recall scores for each model on the training set (e.g., list of floats).
|
|
329
|
+
model_scores_v (list): The recall scores for each model on the validation set (e.g., list of floats).
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
None
|
|
333
|
+
"""
|
|
334
|
+
|
|
335
|
+
plot_score_generic(model_names, model_scores_t, model_scores_v, score_name='AP')
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
# A generic function to plot precision-recall curves for different models
|
|
339
|
+
def plot_curve_generic(model_names, model_scores_t, model_scores_v, x_score_name, y_score_name):
|
|
340
|
+
"""
|
|
341
|
+
Args:
|
|
342
|
+
model_names: the names of the models being compared
|
|
343
|
+
model_scores_t: the precision, recall, and thresholds for each model on the training dataset
|
|
344
|
+
model_scores_v: the precision, recall, and thresholds for each model on the validation dataset
|
|
345
|
+
x_score_name: a string that represents the name of the score on the x-axis of the plot
|
|
346
|
+
y_score_name: string that represents the name of the score on the y-axis of the plo
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
"""
|
|
350
|
+
# Plot the precision-recall curve for each model on the training set
|
|
351
|
+
|
|
352
|
+
for mn, (precision, recall, thresholds) in zip(model_names, model_scores_t):
|
|
353
|
+
plt.plot(recall, precision, '--', label=f'{mn} training')
|
|
354
|
+
# Plot the precision-recall curve for each model on the validation set
|
|
355
|
+
for mn, (precision, recall, thresholds) in zip(model_names, model_scores_v):
|
|
356
|
+
plt.plot(recall, precision, '-', label=f'{mn} validation')
|
|
357
|
+
|
|
358
|
+
plt.ylabel(y_score_name)
|
|
359
|
+
plt.xlabel(x_score_name)
|
|
360
|
+
|
|
361
|
+
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0)
|
|
362
|
+
plt.show()
|
|
363
|
+
plt.close()
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def grid_reg(model, params, features, target):
|
|
369
|
+
"""
|
|
370
|
+
Performs grid search for hyperparameter tuning of the model.
|
|
371
|
+
|
|
372
|
+
Args:
|
|
373
|
+
model (object): The model object you want to tune.
|
|
374
|
+
params (list or dict): A dictionary or list of dictionaries containing hyperparameters to be tested.
|
|
375
|
+
If params is a list, grid search is performed for each dictionary in the list.
|
|
376
|
+
features (array-like): The input features used for training the model.
|
|
377
|
+
target (array-like): The target variable used for training the model.
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
model_grid (object): The tuned model object after performing grid search.
|
|
381
|
+
grid_results (DataFrame): A pandas DataFrame containing the results of grid search.
|
|
382
|
+
pairs (dict): A dictionary containing the mapping of parameter names to their string representations.
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
# Define scoring metrics
|
|
386
|
+
global model_grid
|
|
387
|
+
scoring = {'r2': make_scorer(r2_score),
|
|
388
|
+
'rmse': make_scorer(mean_squared_error),
|
|
389
|
+
'mae': make_scorer(mean_absolute_error)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
# If the parameters are a dictionary, we can perform a single grid search
|
|
393
|
+
model_grid = GridSearchCV(model, params, scoring=scoring, refit='r2')
|
|
394
|
+
model_grid.fit(features, target)
|
|
395
|
+
grid_results = pd.DataFrame(model_grid.cv_results_)
|
|
396
|
+
|
|
397
|
+
# Add a column for the root mean squared error
|
|
398
|
+
grid_results['root_mean_test_mse'] = list(map(lambda n: sqrt(n), grid_results['mean_test_rmse']))
|
|
399
|
+
|
|
400
|
+
# Convert the parameter dictionaries to strings and create a list of parameter names
|
|
401
|
+
params_list = grid_results['params']
|
|
402
|
+
our_list = []
|
|
403
|
+
features_list = list(set().union(*(d.keys() for d in params_list)))
|
|
404
|
+
pairs = {}
|
|
405
|
+
|
|
406
|
+
# Loop through each parameter dictionary and create a string representation
|
|
407
|
+
for params_dict in params_list:
|
|
408
|
+
our_str = ""
|
|
409
|
+
for i, k in enumerate(features_list):
|
|
410
|
+
if params_dict.get(k) is not None:
|
|
411
|
+
our_str += chr(i + 65) + ':' + str(params_dict[k]) + " "
|
|
412
|
+
pairs[k] = chr(i + 65)
|
|
413
|
+
|
|
414
|
+
our_list.append(our_str.strip())
|
|
415
|
+
|
|
416
|
+
# Replace the parameter dictionaries with their string representation
|
|
417
|
+
grid_results['params'] = our_list
|
|
418
|
+
our_list = grid_results['params']
|
|
419
|
+
|
|
420
|
+
return model_grid, grid_results, pairs
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def plot_cv_r2(model_name, model_params, model_r2, pairs):
|
|
424
|
+
"""
|
|
425
|
+
Plots the cross-validation R2 scores of a model across different hyperparameters.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
model_name (str): Name of the model for the plot title.
|
|
429
|
+
model_params (list): List of parameter names used as x-axis ticks.
|
|
430
|
+
model_r2 (array-like): Array-like object containing R2 scores for each combination of hyperparameters.
|
|
431
|
+
pairs (dict): A dictionary containing the mapping of parameter names to their string representations.
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
None
|
|
435
|
+
"""
|
|
436
|
+
# Plots the cross-validation R2 scores of a model across different hyperparameters
|
|
437
|
+
fig, ax = plt.subplots()
|
|
438
|
+
fig.set_size_inches(30, 10)
|
|
439
|
+
plt.plot(model_params, model_r2, label='r2', color='blue')
|
|
440
|
+
plt.xticks(rotation=90, ha='right')
|
|
441
|
+
plt.title(model_name)
|
|
442
|
+
|
|
443
|
+
# Create list of parameter labels from pairs dictionary
|
|
444
|
+
parameter_labels = []
|
|
445
|
+
for k, v in pairs.items():
|
|
446
|
+
parameter_labels.append(f'{k}={v}')
|
|
447
|
+
|
|
448
|
+
# Create text from parameter labels and add it to plot
|
|
449
|
+
text = '\n'.join(parameter_labels)
|
|
450
|
+
plt.legend()
|
|
451
|
+
ax.text(1.05, 0.95, text, transform=ax.transAxes, fontsize=12,
|
|
452
|
+
verticalalignment='top')
|
|
453
|
+
plt.show()
|
|
454
|
+
plt.close()
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def plot_mae_mse(model_name, model_params, model_mse, model_mae, pairs):
|
|
458
|
+
"""
|
|
459
|
+
Plots the Mean Absolute Error (MAE) and Root Mean Squared Error (RMSE) of a model across different hyperparameters.
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
model_name (str): Name of the model for the plot title.
|
|
463
|
+
model_params (list): List of parameter names used as x-axis ticks.
|
|
464
|
+
model_mse (array-like): Array-like object containing MSE values for each combination of hyperparameters.
|
|
465
|
+
model_mae (array-like): Array-like object containing MAE values for each combination of hyperparameters.
|
|
466
|
+
pairs (dict): A dictionary containing the mapping of parameter names to their string representations.
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
None
|
|
470
|
+
"""
|
|
471
|
+
|
|
472
|
+
# Create a figure and axis object
|
|
473
|
+
fig, ax = plt.subplots()
|
|
474
|
+
|
|
475
|
+
# Set the figure size
|
|
476
|
+
fig.set_size_inches(30, 10)
|
|
477
|
+
|
|
478
|
+
# Plot the mean absolute error and root mean squared error
|
|
479
|
+
#plt.plot(model_params, model_mae, label='mae')
|
|
480
|
+
plt.plot(model_params, model_mse, label='rmse')
|
|
481
|
+
|
|
482
|
+
# Rotate the x-axis tick labels by 90 degrees and align them to the right
|
|
483
|
+
plt.xticks(rotation=90, ha='right')
|
|
484
|
+
|
|
485
|
+
# Set the title of the plot to the model name
|
|
486
|
+
plt.title(model_name)
|
|
487
|
+
|
|
488
|
+
# Create a list of parameter labels based on the key-value pairs in the 'pairs' dictionary
|
|
489
|
+
parameter_labels = []
|
|
490
|
+
for k, v in pairs.items():
|
|
491
|
+
parameter_labels.append(f'{k}={v}')
|
|
492
|
+
|
|
493
|
+
# Join the parameter labels with newline characters and assign to the 'text' variable
|
|
494
|
+
text = '\n'.join(parameter_labels)
|
|
495
|
+
|
|
496
|
+
# Add a legend to the plot
|
|
497
|
+
plt.legend()
|
|
498
|
+
|
|
499
|
+
# Add the parameter labels to the plot as text in the upper-right corner
|
|
500
|
+
ax.text(1.05, 0.95, text, transform=ax.transAxes, fontsize=12,
|
|
501
|
+
verticalalignment='top')
|
|
502
|
+
|
|
503
|
+
plt.show()
|
|
504
|
+
plt.close()
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def grid_class(model, params, features, target):
|
|
508
|
+
"""
|
|
509
|
+
Performs grid search for hyperparameter tuning of a classification model.
|
|
510
|
+
|
|
511
|
+
Args:
|
|
512
|
+
model (object): The classification model object you want to tune.
|
|
513
|
+
params (list or dict): A dictionary or list of dictionaries containing hyperparameters to be tested.
|
|
514
|
+
If params is a list, grid search is performed for each dictionary in the list.
|
|
515
|
+
features (array-like): The input features used for training the classification model.
|
|
516
|
+
target (array-like): The target variable used for training the classification model.
|
|
517
|
+
|
|
518
|
+
Returns:
|
|
519
|
+
model_grid (object): The tuned classification model object after performing grid search.
|
|
520
|
+
df_grid (DataFrame): A pandas DataFrame containing the results of the grid search.
|
|
521
|
+
pairs (dict): A dictionary containing the mapping of feature names to their string representations.
|
|
522
|
+
"""
|
|
523
|
+
|
|
524
|
+
# Define the scoring metrics for the grid search
|
|
525
|
+
scoring = {'accuracy': make_scorer(accuracy_score),
|
|
526
|
+
'precision': make_scorer(precision_score, zero_division=0, average='macro'),
|
|
527
|
+
'recall': make_scorer(recall_score, average='macro'),
|
|
528
|
+
'f1': make_scorer(f1_score, average='macro')}
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
model_grid: GridSearchCV = GridSearchCV(model, params, scoring=scoring, refit='f1')
|
|
532
|
+
model_grid.fit(features, target)
|
|
533
|
+
df_grid = pd.DataFrame(model_grid.cv_results_)
|
|
534
|
+
|
|
535
|
+
# Convert the parameter dictionaries to strings and replace them in the dataframe
|
|
536
|
+
params_list = df_grid['params']
|
|
537
|
+
our_list = []
|
|
538
|
+
features_list = list(set().union(*(d.keys() for d in params_list)))
|
|
539
|
+
pairs = {}
|
|
540
|
+
|
|
541
|
+
for params_dict in params_list:
|
|
542
|
+
|
|
543
|
+
our_str = ""
|
|
544
|
+
for i, k in enumerate(features_list):
|
|
545
|
+
if params_dict.get(k) is not None:
|
|
546
|
+
our_str += chr(i + 65) + ':' + str(
|
|
547
|
+
params_dict[k]) + " " # Convert the parameter dictionaries to strings
|
|
548
|
+
pairs[k] = chr(i + 65) # Store the corresponding character label for each feature
|
|
549
|
+
|
|
550
|
+
our_list.append(our_str.strip())
|
|
551
|
+
|
|
552
|
+
df_grid['params'] = our_list # Replace the parameter dictionaries with the strings in the dataframe
|
|
553
|
+
our_list = df_grid['params']
|
|
554
|
+
|
|
555
|
+
return model_grid, df_grid, pairs
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
def plot_cv_metrics(model_name, model_params, model_f1, model_recall, model_precision, model_accuracy, pairs):
|
|
559
|
+
"""
|
|
560
|
+
Plots various evaluation metrics for a model across different hyperparameters.
|
|
561
|
+
|
|
562
|
+
Args:
|
|
563
|
+
model_name (str): The name of the model for the plot title.
|
|
564
|
+
model_params (list): List of parameter names used as x-axis ticks.
|
|
565
|
+
model_f1 (array-like): Array-like object containing F1 scores for each combination of hyperparameters.
|
|
566
|
+
model_recall (array-like): Array-like object containing recall scores for each combination of hyperparameters.
|
|
567
|
+
model_precision (array-like): Array-like object containing precision scores for each combination of hyperparameters.
|
|
568
|
+
model_accuracy (array-like): Array-like object containing accuracy scores for each combination of hyperparameters.
|
|
569
|
+
pairs (dict): A dictionary containing the mapping of parameter names to their string representations.
|
|
570
|
+
|
|
571
|
+
Returns:
|
|
572
|
+
None
|
|
573
|
+
"""
|
|
574
|
+
fig, ax = plt.subplots()
|
|
575
|
+
fig.set_size_inches(30, 10)
|
|
576
|
+
|
|
577
|
+
# Plot the F1 score, recall, precision, and accuracy
|
|
578
|
+
plt.plot(model_params, model_f1, label='f1')
|
|
579
|
+
plt.plot(model_params, model_recall, label='recall')
|
|
580
|
+
plt.plot(model_params, model_precision, label='precision')
|
|
581
|
+
plt.plot(model_params, model_accuracy, label='accuracy')
|
|
582
|
+
|
|
583
|
+
# Rotate the x-axis tick labels by 90 degrees and align them to the right
|
|
584
|
+
plt.xticks(rotation=90, ha='right')
|
|
585
|
+
|
|
586
|
+
# Set the title of the plot to the model name
|
|
587
|
+
plt.title(model_name)
|
|
588
|
+
|
|
589
|
+
# Create a list of parameter labels based on the key-value pairs in the 'pairs' dictionary
|
|
590
|
+
parameter_labels = []
|
|
591
|
+
for k, v in pairs.items():
|
|
592
|
+
parameter_labels.append(f'{k}={v}')
|
|
593
|
+
|
|
594
|
+
# Join the parameter labels with newline characters and assign to the 'text' variable
|
|
595
|
+
text = '\n'.join(parameter_labels)
|
|
596
|
+
|
|
597
|
+
# Add a legend to the plot
|
|
598
|
+
plt.legend()
|
|
599
|
+
|
|
600
|
+
# Add the parameter labels to the plot as text in the upper-right corner
|
|
601
|
+
ax.text(1.05, 0.95, text, transform=ax.transAxes, fontsize=12,
|
|
602
|
+
verticalalignment='top')
|
|
603
|
+
|
|
604
|
+
plt.show()
|
|
605
|
+
plt.close()
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def corr_reg(x, y, **kws):
|
|
609
|
+
"""
|
|
610
|
+
Calculate the Pearson correlation coefficient between x and y and add an annotation to the plot.
|
|
611
|
+
|
|
612
|
+
Args:
|
|
613
|
+
x (array-like): The x-axis data.
|
|
614
|
+
y (array-like): The y-axis data.
|
|
615
|
+
**kws: Additional keyword arguments.
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
None
|
|
619
|
+
"""
|
|
620
|
+
# Calculate the Pearson correlation coefficient between x and y
|
|
621
|
+
r, _ = stats.pearsonr(x, y)
|
|
622
|
+
|
|
623
|
+
# Get the current axes instance
|
|
624
|
+
ax = plt.gca()
|
|
625
|
+
|
|
626
|
+
# Add an annotation to the plot showing the correlation coefficient
|
|
627
|
+
ax.annotate("r = {:.1f}".format(r),
|
|
628
|
+
xy=(0.2, 0.95),
|
|
629
|
+
xycoords=ax.transAxes, size=35)
|
|
630
|
+
|
|
631
|
+
def scatterplot(x, y, **kws):
|
|
632
|
+
ax = plt.gca()
|
|
633
|
+
sns.scatterplot(x=x, y=y, ax=ax, **kws)
|
|
634
|
+
|
|
635
|
+
def corrfunc(x, y, **kws):
|
|
636
|
+
"""
|
|
637
|
+
Calculate the Pearson correlation coefficient between x and y and add an annotation to the plot.
|
|
638
|
+
|
|
639
|
+
Args:
|
|
640
|
+
x (array-like): The x-axis data.
|
|
641
|
+
y (array-like): The y-axis data.
|
|
642
|
+
**kws: Additional keyword arguments.
|
|
643
|
+
|
|
644
|
+
Returns:
|
|
645
|
+
None
|
|
646
|
+
"""
|
|
647
|
+
|
|
648
|
+
# Calculate the Pearson correlation coefficient between x and y
|
|
649
|
+
|
|
650
|
+
r, _ = stats.pearsonr(x, y)
|
|
651
|
+
# Get the current axes instance
|
|
652
|
+
ax = plt.gca()
|
|
653
|
+
# Determine the number of annotations already on the plot
|
|
654
|
+
n = len([c for c in ax.get_children() if
|
|
655
|
+
isinstance(c, matplotlib.text.Annotation)])
|
|
656
|
+
# Determine the position and color of the new annotation based on the hue category
|
|
657
|
+
pos = (.1, .9 - .1 * n)
|
|
658
|
+
color = sns.color_palette()[sns.color_palette().index(kws['color'])]
|
|
659
|
+
|
|
660
|
+
# Add an annotation to the plot showing the correlation coefficient and hue category
|
|
661
|
+
ax.annotate("{}: r = {:.2f}".format(kws['label'], r), xy=pos, xycoords=ax.transAxes, color=color,size = 30)
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def plot_pair_grid_ref(df, hue):
|
|
665
|
+
"""
|
|
666
|
+
Create a PairGrid with regression plots in the upper triangle,
|
|
667
|
+
and kernel density estimates in the lower triangle.
|
|
668
|
+
|
|
669
|
+
Args:
|
|
670
|
+
df (DataFrame): The data to plot.
|
|
671
|
+
hue (str): The column name in 'df' representing the hue category.
|
|
672
|
+
|
|
673
|
+
Returns:
|
|
674
|
+
None
|
|
675
|
+
"""
|
|
676
|
+
|
|
677
|
+
# Create a PairGrid with regression plots in the upper triangle
|
|
678
|
+
# and kernel density estimates in the lower triangle
|
|
679
|
+
g = sns.PairGrid(data=df, hue=hue, height=4, aspect=1.5)
|
|
680
|
+
g.map_upper(corr_reg)
|
|
681
|
+
g.map_lower(scatterplot)
|
|
682
|
+
g.map_diag(sns.histplot)
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def plot_pair_grid_class(df, hue):
|
|
687
|
+
"""
|
|
688
|
+
Create a PairGrid with regression plots in the upper triangle,
|
|
689
|
+
and annotations of correlation coefficients in the lower triangle.
|
|
690
|
+
|
|
691
|
+
Args:
|
|
692
|
+
df (DataFrame): The data to plot.
|
|
693
|
+
hue (str): The column name in 'df' representing the hue category.
|
|
694
|
+
|
|
695
|
+
Returns:
|
|
696
|
+
None
|
|
697
|
+
"""
|
|
698
|
+
# Create a PairGrid with regression plots in the upper triangle
|
|
699
|
+
# and annotations of correlation coefficients in the lower triangle
|
|
700
|
+
g = sns.PairGrid(data=df, hue=hue, height=4, aspect=1.5)
|
|
701
|
+
g.map_upper(corrfunc)
|
|
702
|
+
g.map_lower(scatterplot)
|
|
703
|
+
g.map_diag(sns.histplot)
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def plot_corr_heatmap(df, figsize=(12, 10)):
|
|
707
|
+
"""
|
|
708
|
+
Plot a correlation heatmap for all numeric columns of df.
|
|
709
|
+
|
|
710
|
+
A compact alternative to a full PairGrid — instead of one subplot per
|
|
711
|
+
pair of columns (which grows to n_columns^2 and becomes unreadable for
|
|
712
|
+
wide dataframes), this shows every pairwise correlation in a single
|
|
713
|
+
annotated heatmap.
|
|
714
|
+
|
|
715
|
+
Args:
|
|
716
|
+
df (DataFrame): The data to compute correlations for.
|
|
717
|
+
figsize (tuple): Size of the matplotlib figure.
|
|
718
|
+
|
|
719
|
+
Returns:
|
|
720
|
+
None
|
|
721
|
+
"""
|
|
722
|
+
corr = df.corr(numeric_only=True)
|
|
723
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
724
|
+
sns.heatmap(corr, annot=True, fmt=".2f", cmap="coolwarm", center=0, ax=ax)
|
|
725
|
+
ax.set_title("Feature correlation heatmap")
|
|
726
|
+
plt.tight_layout()
|
|
727
|
+
plt.show()
|
|
728
|
+
plt.close()
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def plot_pair_grid_top_corr(df, target, top_n=5, hue=None):
|
|
732
|
+
"""
|
|
733
|
+
Build a PairGrid using only the top_n features most correlated (by
|
|
734
|
+
absolute Pearson correlation) with the target column, plus the target
|
|
735
|
+
itself. Falls back to the full PairGrid if the target column cannot be
|
|
736
|
+
identified inside df.
|
|
737
|
+
|
|
738
|
+
Args:
|
|
739
|
+
df (DataFrame): The data to plot. Must contain the target column.
|
|
740
|
+
target: Either the target column name (str), or the target Series
|
|
741
|
+
(e.g. y = df["yield"]) — its `.name` attribute is used to find
|
|
742
|
+
the column in df.
|
|
743
|
+
top_n (int): How many of the most-correlated features to include.
|
|
744
|
+
hue (str or None): Column name in df to use as hue (for
|
|
745
|
+
classification-style coloring). None for a plain regression
|
|
746
|
+
style PairGrid.
|
|
747
|
+
|
|
748
|
+
Returns:
|
|
749
|
+
None
|
|
750
|
+
"""
|
|
751
|
+
target_name = target if isinstance(target, str) else getattr(target, "name", None)
|
|
752
|
+
|
|
753
|
+
if target_name is None or target_name not in df.columns:
|
|
754
|
+
# Can't determine which column is the target — fall back to the
|
|
755
|
+
# full pairplot rather than silently plotting nothing useful.
|
|
756
|
+
if hue is not None:
|
|
757
|
+
plot_pair_grid_class(df, hue)
|
|
758
|
+
else:
|
|
759
|
+
plot_pair_grid_ref(df, hue=hue)
|
|
760
|
+
return
|
|
761
|
+
|
|
762
|
+
corr = df.corr(numeric_only=True)[target_name].drop(labels=[target_name])
|
|
763
|
+
top_features = list(corr.abs().sort_values(ascending=False).index[:top_n])
|
|
764
|
+
cols = top_features + [target_name]
|
|
765
|
+
if hue is not None and hue not in cols:
|
|
766
|
+
cols = cols + [hue]
|
|
767
|
+
|
|
768
|
+
g = sns.PairGrid(data=df[cols], hue=hue, height=4, aspect=1.5)
|
|
769
|
+
g.map_upper(corrfunc if hue is not None else corr_reg)
|
|
770
|
+
g.map_lower(scatterplot)
|
|
771
|
+
g.map_diag(sns.histplot)
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
def _show_overview_plot(df, target, pairplot_type, top_n, hue):
|
|
775
|
+
"""
|
|
776
|
+
Internal helper used by best_model to render the "overview" plot at
|
|
777
|
+
the start of a run, according to pairplot_type:
|
|
778
|
+
'heatmap' -> plot_corr_heatmap(df) (default)
|
|
779
|
+
'top_corr' -> plot_pair_grid_top_corr(df, target, top_n, hue)
|
|
780
|
+
'full' -> the original full PairGrid (all columns)
|
|
781
|
+
'none' -> skip the overview plot entirely
|
|
782
|
+
"""
|
|
783
|
+
if pairplot_type == 'none':
|
|
784
|
+
return
|
|
785
|
+
elif pairplot_type == 'heatmap':
|
|
786
|
+
plot_corr_heatmap(df)
|
|
787
|
+
elif pairplot_type == 'top_corr':
|
|
788
|
+
plot_pair_grid_top_corr(df, target, top_n=top_n, hue=hue)
|
|
789
|
+
elif pairplot_type == 'full':
|
|
790
|
+
if hue is not None:
|
|
791
|
+
plot_pair_grid_class(df, hue)
|
|
792
|
+
else:
|
|
793
|
+
plot_pair_grid_ref(df, hue=hue)
|
|
794
|
+
else:
|
|
795
|
+
raise ValueError(
|
|
796
|
+
f"Unknown pairplot_type: {pairplot_type!r}. "
|
|
797
|
+
"Expected one of 'heatmap', 'top_corr', 'full', 'none'."
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
# Function that performs a grid search with cross-validation to find the
|
|
802
|
+
# best hyperparameters for a set of regression or classification models,
|
|
803
|
+
# and returns a dictionary containing information on the models including
|
|
804
|
+
# the model object, best hyperparameters, and evaluation metrics.
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
def best_model(features, target, mode, grid, df, hue, models=None,
|
|
808
|
+
pairplot_type='heatmap', top_n=5):
|
|
809
|
+
"""
|
|
810
|
+
Perform model training and evaluation for both regression and classification problems.
|
|
811
|
+
|
|
812
|
+
Args:
|
|
813
|
+
features: Input features.
|
|
814
|
+
target: Target values.
|
|
815
|
+
mode: 'regression' or 'classification'.
|
|
816
|
+
grid: 'Yes' or 'No', whether to perform grid search.
|
|
817
|
+
df: Dataframe for plotting pair grids.
|
|
818
|
+
hue: Hue variable for pair grids.
|
|
819
|
+
models: Additional custom models with their parameters.
|
|
820
|
+
pairplot_type: What overview plot to show at the start of the run:
|
|
821
|
+
'heatmap' - a correlation heatmap of all numeric columns in df
|
|
822
|
+
(default; compact and readable for wide dataframes).
|
|
823
|
+
'top_corr' - a PairGrid limited to the top_n features most
|
|
824
|
+
correlated with the target, plus the target itself.
|
|
825
|
+
'full' - the original full PairGrid over every column in df
|
|
826
|
+
(grows to n_columns x n_columns subplots - can get
|
|
827
|
+
very large/unreadable for wide dataframes).
|
|
828
|
+
'none' - skip the overview plot entirely.
|
|
829
|
+
top_n: Number of top-correlated features to include when
|
|
830
|
+
pairplot_type='top_corr'. Ignored otherwise.
|
|
831
|
+
|
|
832
|
+
Returns:
|
|
833
|
+
models_fit_info: Dictionary containing model information, hyperparameters, and evaluation metrics.
|
|
834
|
+
"""
|
|
835
|
+
|
|
836
|
+
# Splitting the dataset into train and test sets with a ratio of 0.2
|
|
837
|
+
x_train, x_test, y_train, y_test = train_test_split(features, target, test_size=0.2)
|
|
838
|
+
|
|
839
|
+
# Initializing a variable to store the model fit information
|
|
840
|
+
models_fit_info = None
|
|
841
|
+
|
|
842
|
+
if mode == 'regression':
|
|
843
|
+
# Show the chosen overview plot (correlation heatmap by default)
|
|
844
|
+
_show_overview_plot(df, target, pairplot_type, top_n, hue=None)
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
# Initializing a dictionary to store the models and their hyperparameters
|
|
848
|
+
models_fit_info = {
|
|
849
|
+
'LinearRegression': {'model': LinearRegression(), 'param': {}},
|
|
850
|
+
'RandomForestRegressor': {
|
|
851
|
+
'model': RandomForestRegressor(n_jobs=-1),
|
|
852
|
+
'param': [
|
|
853
|
+
{'max_depth': [4, 16, 32, 42]},
|
|
854
|
+
{'n_estimators': [50, 100, 150, 200, 300, 400, 500]}
|
|
855
|
+
]
|
|
856
|
+
},
|
|
857
|
+
'DecisionTreeRegressor': {'model': DecisionTreeRegressor(),
|
|
858
|
+
'param': {'max_features': [0.1, 0.2, 0.3], 'max_depth': [1, 2, 4, 8, 16],
|
|
859
|
+
}},
|
|
860
|
+
'Lasso': {'model': linear_model.Lasso(),
|
|
861
|
+
'param': {'alpha': [0.1, 0.2, 0.3, 0.4, 0.5, 0.7, 1.0, 2.0, 2.3, 2.7, 3.0, 3.3, 3.7, 4.0]}},
|
|
862
|
+
'Ridge': {'model': Ridge(), 'param': {'alpha': [0.1, 0.2, 0.3, 0.4, 0.5, 0.7, 1.0, 2.0, 2.3, 2.7, 3.0]}},
|
|
863
|
+
'KNeighborsRegressor': {'model': KNeighborsRegressor(n_jobs=-1),
|
|
864
|
+
'param': {'n_neighbors': [2, 3, 5, 10, 15, 17, 19, 21, 23, 25],
|
|
865
|
+
'weights': ('uniform', 'distance')}},
|
|
866
|
+
'GradientBoostingRegressor': {'model': GradientBoostingRegressor(),
|
|
867
|
+
'param': {'learning_rate': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7],
|
|
868
|
+
'n_estimators': [50, 100, 150, 200, 300, 400, 500]}},
|
|
869
|
+
'AdaBoostRegressor': {'model': AdaBoostRegressor(),
|
|
870
|
+
'param': {'learning_rate': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7],
|
|
871
|
+
'n_estimators': [50, 100, 150, 200, 300, 400, 500]}},
|
|
872
|
+
'XGBRegressor': {'model': xgb.XGBRegressor(),
|
|
873
|
+
'param': {'objective': ['reg:squarederror'], 'max_depth': [1, 2, 4, 8, 16, 64, 128, 512],
|
|
874
|
+
'learning_rate': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]}}
|
|
875
|
+
}
|
|
876
|
+
if models != None:
|
|
877
|
+
# Iterate through all models and store their info
|
|
878
|
+
for model, model_params in models:
|
|
879
|
+
models_fit_info[model.__class__.__name__] = {'model': model, 'param': model_params}
|
|
880
|
+
|
|
881
|
+
# Iterate through all models, perform grid search and final training and evaluations
|
|
882
|
+
for model_name, model_info in models_fit_info.items():
|
|
883
|
+
if grid == 'Yes':
|
|
884
|
+
# Perform grid search on the model
|
|
885
|
+
model_grid, grid_results, pairs = grid_reg(model_info['model'], model_info['param'], features, target)
|
|
886
|
+
|
|
887
|
+
# Uppdate model info with best parameters
|
|
888
|
+
model_info['best_param'] = model_grid.best_params_
|
|
889
|
+
model_class = model_info['model'].__class__
|
|
890
|
+
|
|
891
|
+
# Create a new model instance with best parameters
|
|
892
|
+
new_model_with_best_params = model_class(**model_info['best_param'])
|
|
893
|
+
model_info['model'] = new_model_with_best_params
|
|
894
|
+
|
|
895
|
+
# Plot metrics (r2, mae, mse) for cv model
|
|
896
|
+
plot_cv_r2(model_name + 'CV', grid_results['params'], grid_results['mean_test_r2'], pairs)
|
|
897
|
+
plot_mae_mse(model_name + 'CV', grid_results['params'], grid_results['mean_test_mae'],
|
|
898
|
+
grid_results['root_mean_test_mse'], pairs)
|
|
899
|
+
|
|
900
|
+
# Train and evaluate the final model
|
|
901
|
+
model = model_info['model']
|
|
902
|
+
metrics_dict = test_model_reg(model, model_name, x_train, y_train, x_test, y_test)
|
|
903
|
+
model_info['metrics'] = metrics_dict
|
|
904
|
+
|
|
905
|
+
# Create a list of model names
|
|
906
|
+
model_names = list(models_fit_info.keys())
|
|
907
|
+
|
|
908
|
+
# Plot r2, rmse, and mae for train and validation sets
|
|
909
|
+
for score_name, score_plot_fn in zip(['r2', 'rmse', 'mae'], [plot_r2, plot_rmse, plot_mae]):
|
|
910
|
+
model_score_tra = [models_fit_info[mn]['metrics'][score_name]['train'] for mn in model_names]
|
|
911
|
+
model_score_val = [models_fit_info[mn]['metrics'][score_name]['val'] for mn in model_names]
|
|
912
|
+
|
|
913
|
+
score_plot_fn(model_names, model_score_tra, model_score_val)
|
|
914
|
+
# Return the models_fit_info dictionary
|
|
915
|
+
return models_fit_info
|
|
916
|
+
|
|
917
|
+
if mode == 'classification':
|
|
918
|
+
# Show the chosen overview plot (correlation heatmap by default)
|
|
919
|
+
_show_overview_plot(df, target, pairplot_type, top_n, hue=hue)
|
|
920
|
+
# Initializing a dictionary to store the models and their hyperparameters
|
|
921
|
+
models_fit_info = {
|
|
922
|
+
'DecisionTree': {'model': DecisionTreeClassifier(), 'param': {'max_depth': [1, 2, 4, 8, 16, 64, 128, 512],
|
|
923
|
+
'max_features': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6,
|
|
924
|
+
0.7, 1]}},
|
|
925
|
+
# 'RandomForest': {'model': RandomForestClassifier(n_jobs = 1),'param' : {'max_depth':[1,2,4,6,10,328,16,64,128,256,512],'criterion':('gini', 'entropy')}},
|
|
926
|
+
'RandomForest':
|
|
927
|
+
{'model': RandomForestClassifier(n_jobs=-1),
|
|
928
|
+
'param': [{'max_depth': [1, 2, 4, 6, 10, 328, 16, 64, 128, 256, 512]},
|
|
929
|
+
{'criterion': ('gini', 'entropy')}]},
|
|
930
|
+
'SVM': {'model': svm.SVC(), 'param': {'C': [0.1, 1, 10]}},
|
|
931
|
+
'LogisticRegression': {'model': LogisticRegression(n_jobs=-1),
|
|
932
|
+
'param': {'C': [0.5, 1.0, 2.0, 3.0, 10.0, 20.0]}},
|
|
933
|
+
'KNeighborsClassifier': {'model': KNeighborsClassifier(n_jobs=-1),
|
|
934
|
+
'param': {'n_neighbors': [3, 5, 10, 12, 15, 20, 25],
|
|
935
|
+
'weights': ('uniform', 'distance')}},
|
|
936
|
+
'GradientBoostingClassifier': {'model': GradientBoostingClassifier(),
|
|
937
|
+
'param': {'learning_rate': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7],
|
|
938
|
+
'n_estimators': [50, 100, 150, 200, 300, 400, 500]}},
|
|
939
|
+
'AdaBoostClassifier': {'model': AdaBoostClassifier(),
|
|
940
|
+
'param': {'learning_rate': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7],
|
|
941
|
+
'n_estimators': [50, 100, 150, 200, 300, 400, 500]}},
|
|
942
|
+
'XGBClassifier': {'model': xgb.XGBClassifier(), 'param': {'max_depth': [1, 2, 4, 8, 16, 64, 128, 512],
|
|
943
|
+
'learning_rate': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6,
|
|
944
|
+
0.7]}},
|
|
945
|
+
'GaussianNB': {'model': GaussianNB(), 'param': {}}
|
|
946
|
+
}
|
|
947
|
+
if models != None:
|
|
948
|
+
# Iterate through all models and store their info
|
|
949
|
+
for model, model_params in models:
|
|
950
|
+
models_fit_info[model.__class__.__name__] = {'model': model, 'param': model_params}
|
|
951
|
+
|
|
952
|
+
# Iterate through all models, perform grid search and final training and evaluations
|
|
953
|
+
for model_name, model_info in models_fit_info.items():
|
|
954
|
+
if grid == 'Yes':
|
|
955
|
+
# Perform grid search on the model
|
|
956
|
+
model_grid, df_grid, pairs = grid_class(model_info['model'], model_info['param'], features, target)
|
|
957
|
+
# Uppdate model info with best parameters
|
|
958
|
+
model_info['best_param'] = model_grid.best_params_
|
|
959
|
+
model_class = model_info['model'].__class__
|
|
960
|
+
new_model_with_best_params = model_class(**model_info['best_param'])
|
|
961
|
+
# Create a new model instance with best parameters
|
|
962
|
+
model_info['model'] = new_model_with_best_params
|
|
963
|
+
# Plot metrics for cv_model
|
|
964
|
+
plot_cv_metrics(model_name + 'CV', df_grid['params'], df_grid['mean_test_f1'],
|
|
965
|
+
df_grid['mean_test_recall'], df_grid['mean_test_precision'],
|
|
966
|
+
df_grid['mean_test_accuracy'], pairs)
|
|
967
|
+
# Train and evaluate the final model
|
|
968
|
+
model = model_info['model']
|
|
969
|
+
metrics_dict = test_model_class(model, model_name, x_train, y_train, x_test, y_test)
|
|
970
|
+
model_info['metrics'] = metrics_dict
|
|
971
|
+
# Create a list of model names
|
|
972
|
+
model_names = list(models_fit_info.keys())
|
|
973
|
+
|
|
974
|
+
# for score_name, score_plot_fn in zip(['score', 'f1', 'precision', 'recall'], [plot_score, plot_f1_score, plot_score_precision,plot_score_recall]):
|
|
975
|
+
# Plot model evaluation scores
|
|
976
|
+
for score_name in ['Score', 'F1', 'Precision', 'Recall', 'AP']:
|
|
977
|
+
selected_model_names = []
|
|
978
|
+
model_score_tra = []
|
|
979
|
+
model_score_val = []
|
|
980
|
+
|
|
981
|
+
# Collect metrics for each model
|
|
982
|
+
for mn in model_names:
|
|
983
|
+
model_metrics = models_fit_info[mn]['metrics'][score_name]
|
|
984
|
+
if model_metrics is None:
|
|
985
|
+
continue
|
|
986
|
+
|
|
987
|
+
selected_model_names.append(mn)
|
|
988
|
+
model_score_tra.append(model_metrics["train"])
|
|
989
|
+
model_score_val.append(model_metrics['val'])
|
|
990
|
+
|
|
991
|
+
# Plot scores using a generic function
|
|
992
|
+
plot_score_generic(selected_model_names, model_score_tra, model_score_val, score_name)
|
|
993
|
+
|
|
994
|
+
# Plot PR curves
|
|
995
|
+
for score_name, (x_score_name, y_score_name) in [('PR_curve', ('Recall', 'Precision'))]:
|
|
996
|
+
selected_model_names = []
|
|
997
|
+
model_score_tra = []
|
|
998
|
+
model_score_val = []
|
|
999
|
+
|
|
1000
|
+
# Collect metrics for each model
|
|
1001
|
+
for mn in model_names:
|
|
1002
|
+
model_metrics = models_fit_info[mn]['metrics'][score_name]
|
|
1003
|
+
|
|
1004
|
+
if model_metrics is None:
|
|
1005
|
+
continue
|
|
1006
|
+
|
|
1007
|
+
selected_model_names.append(mn)
|
|
1008
|
+
model_score_tra.append(model_metrics['train'])
|
|
1009
|
+
model_score_val.append(model_metrics['val'])
|
|
1010
|
+
|
|
1011
|
+
# Plot PR curves using a generic function
|
|
1012
|
+
plot_curve_generic(selected_model_names, model_score_tra, model_score_val, x_score_name=x_score_name,
|
|
1013
|
+
y_score_name=y_score_name)
|
|
1014
|
+
|
|
1015
|
+
# Return the model information dictionary
|
|
1016
|
+
return models_fit_info
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: best-model-toolkit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Train, tune, and compare sklearn/XGBoost models with built-in metric plots
|
|
5
|
+
Author: Your Name
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/YOUR_USERNAME/best-model-toolkit
|
|
8
|
+
Project-URL: Issues, https://github.com/YOUR_USERNAME/best-model-toolkit/issues
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: numpy
|
|
12
|
+
Requires-Dist: pandas
|
|
13
|
+
Requires-Dist: scipy
|
|
14
|
+
Requires-Dist: matplotlib
|
|
15
|
+
Requires-Dist: seaborn
|
|
16
|
+
Requires-Dist: scikit-learn>=1.2
|
|
17
|
+
Requires-Dist: xgboost
|
|
18
|
+
|
|
19
|
+
# best-model-toolkit
|
|
20
|
+
|
|
21
|
+
A collection of functions for quickly training, tuning (`GridSearchCV`) and
|
|
22
|
+
comparing several `scikit-learn` / `XGBoost` models for **regression** and
|
|
23
|
+
**classification** tasks, with automatic plots of metrics (R², MAE, RMSE,
|
|
24
|
+
F1, precision, recall, PR curves, etc).
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
### Locally (before the package is on PyPI)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone https://github.com/YOUR_USERNAME/best-model-toolkit.git
|
|
32
|
+
cd best-model-toolkit
|
|
33
|
+
pip install -e .
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Directly from GitHub
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install git+https://github.com/YOUR_USERNAME/best-model-toolkit.git
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### From PyPI (once published, see below)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install best-model-toolkit
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from best_model_toolkit import best_model
|
|
52
|
+
import pandas as pd
|
|
53
|
+
|
|
54
|
+
df = pd.read_csv("your_data.csv")
|
|
55
|
+
X = df.drop(columns=["target"])
|
|
56
|
+
y = df["target"]
|
|
57
|
+
|
|
58
|
+
results = best_model(
|
|
59
|
+
features=X,
|
|
60
|
+
target=y,
|
|
61
|
+
mode="regression", # or "classification"
|
|
62
|
+
grid="Yes", # "Yes"/"No" — whether to run GridSearchCV
|
|
63
|
+
df=df,
|
|
64
|
+
hue=None, # column for pairplot hue (classification)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# results — dict {"model_name": {"model": ..., "param": ..., "metrics": {...}}}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Package structure
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
best_model_toolkit/
|
|
74
|
+
├── pyproject.toml
|
|
75
|
+
├── README.md
|
|
76
|
+
└── src/
|
|
77
|
+
└── best_model_toolkit/
|
|
78
|
+
├── __init__.py
|
|
79
|
+
└── core.py
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Publishing to PyPI
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install build twine
|
|
86
|
+
python -m build
|
|
87
|
+
twine upload dist/*
|
|
88
|
+
```
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
best_model_toolkit/__init__.py,sha256=nDDYHelIidK0ZRb2c1Y_RrZdJctaERKTe9XWvI2uawc,1660
|
|
2
|
+
best_model_toolkit/core.py,sha256=XUVkHo7bTikdwBhd1vGCpbFzUud6Qzdphi0k0RFf2zo,43081
|
|
3
|
+
best_model_toolkit-0.1.0.dist-info/METADATA,sha256=uKAanWpnDsVhP-dzXwx_q8ZYCsnE5RZAON7sAtWUpH8,2133
|
|
4
|
+
best_model_toolkit-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
5
|
+
best_model_toolkit-0.1.0.dist-info/top_level.txt,sha256=F02XXKWctO8zXz0crjf_scsKbEr21jdv0w6guhj4ldc,19
|
|
6
|
+
best_model_toolkit-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
best_model_toolkit
|