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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ best_model_toolkit