Previsione di opportunità di Cross Sell
Versione integrata nel sito senza iframe. Il notebook ricostruito resta disponibile come file .ipynb.
Gabriele Iocco
Previsione di opportunità di Cross Sell di assicurazioni
Costruisco un modello predittivo in grado di prevedere se gli assicurati dell'anno passato potrebbero essere interessati ad acquistare anche un'assicurazione per il proprio veicolo.
Importo le librerie necessarie
Codice
import pandas as pd
import numpy as np
import seaborn as sns
import warnings
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, classification_report, confusion_matrix
from sklearn.model_selection import GridSearchCV
from imblearn.pipeline import Pipeline as ImbPipeline
Carico il dataset e analizzo i dati
Codice
BASE_URL = "D:/Anaconda/Cross_Sell/"
Codice
df_cross_sell = pd.read_csv(BASE_URL+"insurance_cross_sell.csv")
Codice
df_cross_sell.shape
Out[5]:
(381109, 12)
Codice
df_cross_sell.count()
Out[6]:
id 381109 Gender 381109 Age 381109 Driving_License 381109 Region_Code 381109 Previously_Insured 381109 Vehicle_Age 381109 Vehicle_Damage 381109 Annual_Premium 381109 Policy_Sales_Channel 381109 Vintage 381109 Response 381109 dtype: int64
Codice
df_cross_sell.head()
Out[7]:
| id | Gender | Age | Driving_License | Region_Code | Previously_Insured | Vehicle_Age | Vehicle_Damage | Annual_Premium | Policy_Sales_Channel | Vintage | Response | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | Male | 44 | 1 | 28.0 | 0 | > 2 Years | Yes | 40454.0 | 26.0 | 217 | 1 |
| 1 | 2 | Male | 76 | 1 | 3.0 | 0 | 1-2 Year | No | 33536.0 | 26.0 | 183 | 0 |
| 2 | 3 | Male | 47 | 1 | 28.0 | 0 | > 2 Years | Yes | 38294.0 | 26.0 | 27 | 1 |
| 3 | 4 | Male | 21 | 1 | 11.0 | 1 | < 1 Year | No | 28619.0 | 152.0 | 203 | 0 |
| 4 | 5 | Female | 29 | 1 | 41.0 | 1 | < 1 Year | No | 27496.0 | 152.0 | 39 | 0 |
Codice
print("\nInformazioni sul dataset:")
print(df_cross_sell.info())
Informazioni sul dataset: <class 'pandas.core.frame.DataFrame'> RangeIndex: 381109 entries, 0 to 381108 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 381109 non-null int64 1 Gender 381109 non-null object 2 Age 381109 non-null int64 3 Driving_License 381109 non-null int64 4 Region_Code 381109 non-null float64 5 Previously_Insured 381109 non-null int64 6 Vehicle_Age 381109 non-null object 7 Vehicle_Damage 381109 non-null object 8 Annual_Premium 381109 non-null float64 9 Policy_Sales_Channel 381109 non-null float64 10 Vintage 381109 non-null int64 11 Response 381109 non-null int64 dtypes: float64(3), int64(6), object(3) memory usage: 34.9+ MB None
Codice
df_cross_sell.describe()
Out[9]:
| id | Age | Driving_License | Region_Code | Previously_Insured | Annual_Premium | Policy_Sales_Channel | Vintage | Response | |
|---|---|---|---|---|---|---|---|---|---|
| count | 381109.000000 | 381109.000000 | 381109.000000 | 381109.000000 | 381109.000000 | 381109.000000 | 381109.000000 | 381109.000000 | 381109.000000 |
| mean | 190555.000000 | 38.822584 | 0.997869 | 26.388807 | 0.458210 | 30564.389581 | 112.034295 | 154.347397 | 0.122563 |
| std | 110016.836208 | 15.511611 | 0.046110 | 13.229888 | 0.498251 | 17213.155057 | 54.203995 | 83.671304 | 0.327936 |
| min | 1.000000 | 20.000000 | 0.000000 | 0.000000 | 0.000000 | 2630.000000 | 1.000000 | 10.000000 | 0.000000 |
| 25% | 95278.000000 | 25.000000 | 1.000000 | 15.000000 | 0.000000 | 24405.000000 | 29.000000 | 82.000000 | 0.000000 |
| 50% | 190555.000000 | 36.000000 | 1.000000 | 28.000000 | 0.000000 | 31669.000000 | 133.000000 | 154.000000 | 0.000000 |
| 75% | 285832.000000 | 49.000000 | 1.000000 | 35.000000 | 1.000000 | 39400.000000 | 152.000000 | 227.000000 | 0.000000 |
| max | 381109.000000 | 85.000000 | 1.000000 | 52.000000 | 1.000000 | 540165.000000 | 163.000000 | 299.000000 | 1.000000 |
Codice
df_cross_sell["Gender"].value_counts()
Out[10]:
Gender Male 206089 Female 175020 Name: count, dtype: int64
Codice
df_cross_sell["Vehicle_Damage"].value_counts()
Out[11]:
Vehicle_Damage Yes 192413 No 188696 Name: count, dtype: int64
Codice
df_cross_sell["Vehicle_Age"].value_counts()
Out[12]:
Vehicle_Age 1-2 Year 200316 < 1 Year 164786 > 2 Years 16007 Name: count, dtype: int64
Controllo se ci sono valori mancanti o nulli
Codice
print("Valori mancanti per colonna:")
print(df_cross_sell.isna().sum())
Valori mancanti per colonna: id 0 Gender 0 Age 0 Driving_License 0 Region_Code 0 Previously_Insured 0 Vehicle_Age 0 Vehicle_Damage 0 Annual_Premium 0 Policy_Sales_Channel 0 Vintage 0 Response 0 dtype: int64
Codice
print("Valori nulli per colonna:")
print(df_cross_sell.isnull().sum())
Valori nulli per colonna: id 0 Gender 0 Age 0 Driving_License 0 Region_Code 0 Previously_Insured 0 Vehicle_Age 0 Vehicle_Damage 0 Annual_Premium 0 Policy_Sales_Channel 0 Vintage 0 Response 0 dtype: int64
Codice
counts_response = df_cross_sell["Response"].value_counts()
counts_response
Out[15]:
Response 0 334399 1 46710 Name: count, dtype: int64
Codice
print(f"Acquirenti che hanno risposto positivamente alla proposta = {counts_response[1]} ({counts_response[1]/counts_response.sum()*100:.2f}%)")
print(f"Acquirenti che non hanno risposto positivamente alla proposta = {counts_response[0]} ({counts_response[0]/counts_response.sum()*100:.2f}%)")
Acquirenti che hanno risposto positivamente alla proposta = 46710 (12.26%) Acquirenti che non hanno risposto positivamente alla proposta = 334399 (87.74%)
Codice
class_distribution=(df_cross_sell['Response'].value_counts(normalize=True))
Codice
print(class_distribution)
Response 0 0.877437 1 0.122563 Name: proportion, dtype: float64
Codice
plt.figure(figsize=(10, 6))
class_distribution.plot(kind='bar')
plt.title('Distribuzione delle classi (Response)')
plt.xlabel('Classi')
plt.ylabel('Percentuale')
plt.xticks(rotation=0)
plt.show()
Modello di regressione logistica
Faccio il drop della colonna id e separo la colonna 'Response' dal resto del dataset
X conterrà tutte le feature (variabili indipendenti) che useremo per prevedere 'Response'
y è la variabile target (dipendente) che stiamo cercando di prevedere
Codice
df_cross_sell.drop('id', axis=1, inplace=True)
Codice
X = df_cross_sell.drop('Response', axis=1) # dati in formato pandas
y = df_cross_sell['Response']
Divisione in train, val e test set, definizione delle colonne e creazione del preprocessor
Codice
from sklearn.model_selection import train_test_split
# Funzione di valutazione
def evaluate_model(y_true, y_pred, y_pred_proba, model_name):
results = {
"Accuracy": accuracy_score(y_true, y_pred),
"Precision": precision_score(y_true, y_pred),
"Recall": recall_score(y_true, y_pred),
"F1-score": f1_score(y_true, y_pred),
"AUC-ROC": roc_auc_score(y_true, y_pred_proba)
}
print(f"\nEvaluation for {model_name}:")
for metric, value in results.items():
print(f"{metric}: {value:.4f}")
print("\nConfusion Matrix:")
print(confusion_matrix(y_true, y_pred))
print("\nClassification Report:")
print(classification_report(y_true, y_pred))
return results
Codice
RANDOM_SEED = 42
X_train_lr, X_test_lr, y_train_lr, y_test_lr = train_test_split(X, y, test_size=0.15, random_state=RANDOM_SEED)
X_train_lr, X_val_lr, y_train_lr, y_val_lr = train_test_split(X_train_lr, y_train_lr, test_size=0.17, random_state=RANDOM_SEED)
Codice
X_train_lr.shape # set di addestramento
Out[29]:
(268871, 10)
Codice
X_val_lr.shape # set di validazione
# serve a valutare le performance del modello durante il processo di addestramento
# aiuta a prevenire l'overfitting, aiuta a regolare gli iperparametri
# fornisce una stima delle performance del modello su dati non visti
Out[30]:
(55071, 10)
Codice
X_test_lr.shape # set di test. Usato per la valutazione finale del modello
Out[31]:
(57167, 10)
Codice
numeric_features = ['Age', 'Annual_Premium', 'Vintage', 'Driving_License', 'Region_Code', 'Previously_Insured', 'Policy_Sales_Channel']
categorical_features = ['Gender', 'Vehicle_Age', 'Vehicle_Damage']
Preprocessor e one-hot encoding
Codice
# il preprocessor serve per standardizzare il processo di preparazione, pulizia e trasformazione dei dati
# assicura che i dati siano trattati allo stesso modo in fase di training, validazione e testing
# il one-hot encoding è una tecnica di codifica dei dati categorici
preprocessor = ColumnTransformer(
transformers=[
('num', StandardScaler(), numeric_features),
('cat', OneHotEncoder(drop='first', sparse_output=False, handle_unknown='ignore'), categorical_features)
])
Codice
# la pipeline automatizza il flusso di lavoro
# combina più step di preprocessing e il modello in un unico oggetto
# prevenzione del data leakage, codice più pulito
pipeline_lr = Pipeline([
('preprocessor', preprocessor),
('classifier', LogisticRegression(class_weight='balanced', random_state=RANDOM_SEED, max_iter=1000))
])
Addestro il modello
Codice
pipeline_lr.fit(X_train_lr, y_train_lr)
Out[36]:
Pipeline(steps=[('preprocessor',
ColumnTransformer(transformers=[('num', StandardScaler(),
['Age', 'Annual_Premium',
'Vintage', 'Driving_License',
'Region_Code',
'Previously_Insured',
'Policy_Sales_Channel']),
('cat',
OneHotEncoder(drop='first',
handle_unknown='ignore',
sparse_output=False),
['Gender', 'Vehicle_Age',
'Vehicle_Damage'])])),
('classifier',
LogisticRegression(class_weight='balanced', max_iter=1000,
random_state=42))])In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Pipeline(steps=[('preprocessor',
ColumnTransformer(transformers=[('num', StandardScaler(),
['Age', 'Annual_Premium',
'Vintage', 'Driving_License',
'Region_Code',
'Previously_Insured',
'Policy_Sales_Channel']),
('cat',
OneHotEncoder(drop='first',
handle_unknown='ignore',
sparse_output=False),
['Gender', 'Vehicle_Age',
'Vehicle_Damage'])])),
('classifier',
LogisticRegression(class_weight='balanced', max_iter=1000,
random_state=42))])ColumnTransformer(transformers=[('num', StandardScaler(),
['Age', 'Annual_Premium', 'Vintage',
'Driving_License', 'Region_Code',
'Previously_Insured',
'Policy_Sales_Channel']),
('cat',
OneHotEncoder(drop='first',
handle_unknown='ignore',
sparse_output=False),
['Gender', 'Vehicle_Age', 'Vehicle_Damage'])])['Age', 'Annual_Premium', 'Vintage', 'Driving_License', 'Region_Code', 'Previously_Insured', 'Policy_Sales_Channel']
StandardScaler()
['Gender', 'Vehicle_Age', 'Vehicle_Damage']
OneHotEncoder(drop='first', handle_unknown='ignore', sparse_output=False)
LogisticRegression(class_weight='balanced', max_iter=1000, random_state=42)
Codice
from termcolor import colored
def print_colored_title(text):
light_green = '\033[38;2;000;204;000m'
bold = '\033[1m'
reset = '\033[0m'
print(f"{light_green}{bold}{text}{reset}")
Predizioni sul set di training
Codice
y_pred_train_lr = pipeline_lr.predict(X_train_lr)
y_pred_proba_train_lr = pipeline_lr.predict_proba(X_train_lr)[:, 1]
print("Performance sul set di training:")
train_results_lr = evaluate_model(y_train_lr, y_pred_train_lr, y_pred_proba_train_lr, "Logistic Regression (Training)")
Performance sul set di training:
Evaluation for Logistic Regression (Training):
Accuracy: 0.6386
Precision: 0.2494
Recall: 0.9730
F1-score: 0.3971
AUC-ROC: 0.8354
Confusion Matrix:
[[139714 96277]
[ 887 31993]]
Classification Report:
precision recall f1-score support
0 0.99 0.59 0.74 235991
1 0.25 0.97 0.40 32880
accuracy 0.64 268871
macro avg 0.62 0.78 0.57 268871
weighted avg 0.90 0.64 0.70 268871
Con una Precision del 24,94% il modello porta a molti falsi positivi
Predizioni sul set di validazione
Codice
y_pred_val_lr = pipeline_lr.predict(X_val_lr)
y_pred_proba_val_lr = pipeline_lr.predict_proba(X_val_lr)[:, 1]
print("\nPerformance sul set di validazione:")
val_results_lr = evaluate_model(y_val_lr, y_pred_val_lr, y_pred_proba_val_lr, "Logistic Regression (Validation)")
Performance sul set di validazione:
Evaluation for Logistic Regression (Validation):
Accuracy: 0.6429
Precision: 0.2498
Recall: 0.9752
F1-score: 0.3977
AUC-ROC: 0.8387
Confusion Matrix:
[[28910 19503]
[ 165 6493]]
Classification Report:
precision recall f1-score support
0 0.99 0.60 0.75 48413
1 0.25 0.98 0.40 6658
accuracy 0.64 55071
macro avg 0.62 0.79 0.57 55071
weighted avg 0.90 0.64 0.70 55071
Il modello mostra coerenza tra training e validazione, suggerendo una buona generalizzazione
Faccio previsioni sul set di test per valutare le performance finali del modello dopo che è stato addestrato
Codice
y_pred_test_lr = pipeline_lr.predict(X_test_lr)
y_pred_proba_test_lr = pipeline_lr.predict_proba(X_test_lr)[:, 1]
print("Logistic Regression:")
test_results_lr = evaluate_model(y_test_lr, y_pred_test_lr, y_pred_proba_test_lr, "Logistic Regression")
Logistic Regression:
Evaluation for Logistic Regression:
Accuracy: 0.6413
Precision: 0.2557
Recall: 0.9727
F1-score: 0.4049
AUC-ROC: 0.8403
Confusion Matrix:
[[29686 20309]
[ 196 6976]]
Classification Report:
precision recall f1-score support
0 0.99 0.59 0.74 49995
1 0.26 0.97 0.40 7172
accuracy 0.64 57167
macro avg 0.62 0.78 0.57 57167
weighted avg 0.90 0.64 0.70 57167
Il modello è eccellente nell'identificare i falsi positivi dati dall'alto Recall ma avendo una bassa Precision include molti falsi positivi
Visualizzazione delle matrici di confusione
Codice
import matplotlib.pyplot as plt
from sklearn.metrics import ConfusionMatrixDisplay
def plot_confusion_matrix(y_true, y_pred, title):
fig, ax = plt.subplots(figsize=(10, 6))
ConfusionMatrixDisplay.from_predictions(y_true, y_pred, ax=ax, cmap='Blues', values_format='d')
precision = precision_score(y_true, y_pred)
recall = recall_score(y_true, y_pred)
plt.text(0, -0.15, f"Precision: {precision:.4f}", transform=ax.transAxes)
plt.text(1, -0.15, f"Recall: {recall:.4f}", transform=ax.transAxes, ha='right')
plt.title(title)
plt.tight_layout()
plt.show()
# Matrice di confusione per il set di training
plot_confusion_matrix(y_train_lr, y_pred_train_lr, 'Matrice di Confusione (Training)')
# Matrice di confusione per il set di validazione
plot_confusion_matrix(y_val_lr, y_pred_val_lr, 'Matrice di Confusione (Validazione)')
# Matrice di confusione per il set di test
plot_confusion_matrix(y_test_lr, y_pred_test_lr, 'Matrice di Confusione (Test)')
Cambio la soglia di decisione
Set di training
Codice
y_pred_train_lr_mod = np.where(y_pred_proba_train_lr>0.65,1,0)
y_pred_proba_train_lr_mod = pipeline_lr.predict_proba(X_train_lr)[:, 1]
print("Performance sul set di training:")
train_results_lr_mod = evaluate_model(y_train_lr, y_pred_train_lr_mod, y_pred_proba_train_lr_mod, "Logistic Regression (Training)")
Performance sul set di training:
Evaluation for Logistic Regression (Training):
Accuracy: 0.7477
Precision: 0.2967
Recall: 0.7764
F1-score: 0.4294
AUC-ROC: 0.8354
Confusion Matrix:
[[175495 60496]
[ 7353 25527]]
Classification Report:
precision recall f1-score support
0 0.96 0.74 0.84 235991
1 0.30 0.78 0.43 32880
accuracy 0.75 268871
macro avg 0.63 0.76 0.63 268871
weighted avg 0.88 0.75 0.79 268871
Set di validazione
Codice
y_pred_val_lr_mod = np.where(y_pred_proba_val_lr>0.65,1,0)
y_pred_proba_val_lr_mod = pipeline_lr.predict_proba(X_val_lr)[:, 1]
print("\nPerformance sul set di validazione:")
val_results_lr_mod = evaluate_model(y_val_lr, y_pred_val_lr_mod, y_pred_proba_val_lr_mod, "Logistic Regression (Validation)")
Performance sul set di validazione:
Evaluation for Logistic Regression (Validation):
Accuracy: 0.7500
Precision: 0.2971
Recall: 0.7816
F1-score: 0.4305
AUC-ROC: 0.8387
Confusion Matrix:
[[36101 12312]
[ 1454 5204]]
Classification Report:
precision recall f1-score support
0 0.96 0.75 0.84 48413
1 0.30 0.78 0.43 6658
accuracy 0.75 55071
macro avg 0.63 0.76 0.64 55071
weighted avg 0.88 0.75 0.79 55071
Set di test
Codice
y_pred_test_lr_mod = np.where(y_pred_proba_test_lr>0.65,1,0)
y_pred_proba_test_lr_mod = pipeline_lr.predict_proba(X_test_lr)[:, 1]
print("Logistic Regression:")
test_results_lr_mod = evaluate_model(y_test_lr, y_pred_test_lr_mod, y_pred_proba_test_lr_mod, "Logistic Regression")
Logistic Regression:
Evaluation for Logistic Regression:
Accuracy: 0.7526
Precision: 0.3083
Recall: 0.7817
F1-score: 0.4422
AUC-ROC: 0.8403
Confusion Matrix:
[[37418 12577]
[ 1566 5606]]
Classification Report:
precision recall f1-score support
0 0.96 0.75 0.84 49995
1 0.31 0.78 0.44 7172
accuracy 0.75 57167
macro avg 0.63 0.77 0.64 57167
weighted avg 0.88 0.75 0.79 57167
Visualizzazione delle matrici di confusione
Codice
def plot_confusion_matrix(y_true, y_pred, title):
fig, ax = plt.subplots(figsize=(10, 6))
ConfusionMatrixDisplay.from_predictions(y_true, y_pred, ax=ax, cmap='Blues', values_format='d')
precision = precision_score(y_true, y_pred)
recall = recall_score(y_true, y_pred)
plt.text(0, -0.15, f"Precision: {precision:.4f}", transform=ax.transAxes)
plt.text(1, -0.15, f"Recall: {recall:.4f}", transform=ax.transAxes, ha='right')
plt.title(title)
plt.tight_layout()
plt.show()
plot_confusion_matrix(y_train_lr, y_pred_train_lr_mod, 'Matrice di Confusione (Training)')
plot_confusion_matrix(y_val_lr, y_pred_val_lr_mod, 'Matrice di Confusione (Validazione)')
plot_confusion_matrix(y_test_lr, y_pred_test_lr_mod, 'Matrice di Confusione (Test)')
Confronto diretto delle metriche
Codice
results_lr = f"""
{'Set':<12}{'Accuracy':<12}{'F1-score':<12}{'AUC-ROC':<12}{'Precision':<12}{'Recall':<12}
{'-'*70}
{'Training ':<12}{train_results_lr['Accuracy']:<12.4f}{train_results_lr['F1-score']:<12.4f}{train_results_lr['AUC-ROC']:<12.4f}{train_results_lr['Precision']:<12.4f}{train_results_lr['Recall']:<12.4f}
{'Validation ':<12}{val_results_lr['Accuracy']:<12.4f}{val_results_lr['F1-score']:<12.4f}{val_results_lr['AUC-ROC']:<12.4f}{val_results_lr['Precision']:<12.4f}{val_results_lr['Recall']:<12.4f}
{'Test ':<12}{test_results_lr['Accuracy']:<12.4f}{test_results_lr['F1-score']:<12.4f}{test_results_lr['AUC-ROC']:<12.4f}{test_results_lr['Precision']:<12.4f}{test_results_lr['Recall']:<12.4f}
{'Train 0.65 ':<12}{train_results_lr_mod['Accuracy']:<12.4f}{train_results_lr_mod['F1-score']:<12.4f}{train_results_lr_mod['AUC-ROC']:<12.4f}{train_results_lr_mod['Precision']:<12.4f}{train_results_lr_mod['Recall']:<12.4f}
{'Val 0.65 ':<12}{val_results_lr_mod['Accuracy']:<12.4f}{val_results_lr_mod['F1-score']:<12.4f}{val_results_lr_mod['AUC-ROC']:<12.4f}{val_results_lr_mod['Precision']:<12.4f}{val_results_lr_mod['Recall']:<12.4f}
{'Test 0.65 ':<12}{test_results_lr_mod['Accuracy']:<12.4f}{test_results_lr_mod['F1-score']:<12.4f}{test_results_lr_mod['AUC-ROC']:<12.4f}{test_results_lr_mod['Precision']:<12.4f}{test_results_lr_mod['Recall']:<12.4f}
"""
print(results_lr)
Set Accuracy F1-score AUC-ROC Precision Recall ---------------------------------------------------------------------- Training 0.6386 0.3971 0.8354 0.2494 0.9730 Validation 0.6429 0.3977 0.8387 0.2498 0.9752 Test 0.6413 0.4049 0.8403 0.2557 0.9727 Train 0.65 0.7477 0.4294 0.8354 0.2967 0.7764 Val 0.65 0.7500 0.4305 0.8387 0.2971 0.7816 Test 0.65 0.7526 0.4422 0.8403 0.3083 0.7817
Dopo varie prove ho impostato la soglia di decisione a 0.65, facendo così aumentano l'accuracy, F1-score e Precision. La Recall scende ma è ancora buona
Confronto delle curve ROC per mostrare il trade-off tra il tasso di veri positivi e il tasso di falsi positivi
Codice
from sklearn.metrics import roc_curve, auc
# Curva ROC soglia 0.5
def plot_roc_curve(y_true, y_pred_proba, label, color):
fpr, tpr, thresholds = roc_curve(y_true, y_pred_proba)
roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, label=f'{label} (AUC = {roc_auc:.2f})', color=color)
return fpr, tpr, thresholds
plt.figure(figsize=(10, 6))
# Curve ROC per i diversi set con soglia a 0.5
fpr_train, tpr_train, thresholds_train = plot_roc_curve(y_train_lr, y_pred_proba_train_lr, 'Training', 'darkblue')
fpr_val, tpr_val, thresholds_val = plot_roc_curve(y_val_lr, y_pred_proba_val_lr, 'Validation', 'darkorange')
fpr_test, tpr_test, thresholds_test = plot_roc_curve(y_test_lr, y_pred_proba_test_lr, 'Test', 'purple')
# Linea di riferimento (random classifier)
plt.plot([0, 1], [0, 1], color='#C0C0C0', linestyle='--')
# Funzione per trovare il punto più vicino a una data soglia
def find_nearest(array, value):
return (np.abs(array - value)).argmin()
# Punti per le soglie specifiche
soglie = [0.5, 0.65] # Aggiungo le soglie che ho usato
colori = ['#A7C7E7', '#FFCC99']
for soglia, colore in zip(soglie, colori):
# Trovo il punto più vicino alla soglia per ogni set
idx_train = find_nearest(thresholds_train, soglia)
idx_val = find_nearest(thresholds_val, soglia)
idx_test = find_nearest(thresholds_test, soglia)
# Plotto i punti
plt.plot(fpr_test[idx_test], tpr_test[idx_test], 'o', color=colore, markersize=10, label=f'Soglia {soglia}')
plt.xlabel('Falsi Positivi')
plt.ylabel('Veri Positivi')
plt.title('ROC Curve con Soglie')
plt.legend(loc="lower right")
plt.show()
# Curva ROC soglia 0.65
def plot_roc_curve(y_true, y_pred_proba, label, color):
fpr, tpr, thresholds = roc_curve(y_true, y_pred_proba)
roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, label=f'{label} (AUC = {roc_auc:.2f})', color=color)
return fpr, tpr, thresholds
plt.figure(figsize=(10, 6))
# Curve ROC per i diversi set con soglia a 0.65
fpr_train, tpr_train, thresholds_train = plot_roc_curve(y_train_lr, y_pred_proba_train_lr_mod, 'Training', 'blue')
fpr_val, tpr_val, thresholds_val = plot_roc_curve(y_val_lr, y_pred_proba_val_lr_mod, 'Validation', 'orange')
fpr_test, tpr_test, thresholds_test = plot_roc_curve(y_test_lr, y_pred_proba_test_lr_mod, 'Test', 'green')
# Linea di riferimento (random classifier)
plt.plot([0, 1], [0, 1], color='#C0C0C0', linestyle='--')
# Funzione per trovare il punto più vicino a una data soglia
def find_nearest(array, value):
return (np.abs(array - value)).argmin()
# Punti per le soglie specifiche
soglie = [0.5, 0.65] # Aggiungo le soglie che ho usato
colori = ['#A7C7E7', '#FFCC99']
for soglia, colore in zip(soglie, colori):
# Trovo il punto più vicino alla soglia per ogni set
idx_train = find_nearest(thresholds_train, soglia)
idx_val = find_nearest(thresholds_val, soglia)
idx_test = find_nearest(thresholds_test, soglia)
# Plotto i punti
plt.plot(fpr_test[idx_test], tpr_test[idx_test], 'o', color=colore, markersize=10, label=f'Soglia {soglia}')
plt.xlabel('Falsi Positivi')
plt.ylabel('Veri Positivi')
plt.title('ROC Curve con Soglie')
plt.legend(loc="lower right")
plt.show()
Ho inglobato il codice delle due curve in un'unica cella in modo da poter confrontare meglio i plot
Essendo le curve molto simili, in un unico grafico si andrebbero a sovrapporre
Analisi delle differenze nelle distribuzioni di probabilità
Codice
warnings.filterwarnings('ignore')
plt.figure(figsize=(10, 6))
sns.kdeplot(y_pred_proba_train_lr_mod, fill=True, color="blue", label="Train")
sns.kdeplot(y_pred_proba_val_lr_mod, fill=True, color="orange", label="Validation")
sns.kdeplot(y_pred_proba_test_lr_mod, fill=True, color="black", label="Test")
plt.axvline(x=0.65, color='red', linestyle='--', label='Soglia 0.65')
plt.xlabel('Probabilità predette')
plt.ylabel('Density')
plt.title('Distribuzione delle probabilità predette')
plt.legend()
plt.show()
La distribuzione a tre picchi suggerisce che il modello sta discriminando tra diverse categorie di clienti, i poco propensi, i moderatamente propensi e altamente propensi.
Scatter plot
Serve per visualizzare la distribuzione di probabilità, identificare i falsi positivi e negativi, valutare l'impatto delle soglie di decisione
Codice
def plot_probability_scatter(y_true, y_pred_proba):
plt.figure(figsize=(10, 6))
# Creo un array di indici
indices = np.arange(len(y_true))
# Scatter plot per la classe negativa (0)
plt.scatter(indices[y_true == 0], y_pred_proba[y_true == 0],
color='blue', alpha=0.5, label='Classe Negativa')
# Scatter plot per la classe positiva (1)
plt.scatter(indices[y_true == 1], y_pred_proba[y_true == 1],
color='red', alpha=0.5, label='Classe Positiva')
# Linea per la soglia
plt.axhline(y=0.65, color='black', linestyle='--', label='Soglia 0.65')
plt.title('Scatter Plot delle Probabilità Predette')
plt.xlabel('Indice dell\'istanza')
plt.ylabel('Probabilità Predetta')
plt.legend()
plt.show()
plot_probability_scatter(y_test_lr, y_pred_proba_test_lr_mod)
TP = punti rossi sopra la soglia, TN = punti blu sotto la soglia, FP = punti blu sopra la soglia, FN = punti rossi sotto la soglia
Confronto delle metriche per diverse soglie di decisione
Codice
from sklearn.metrics import precision_recall_curve
def plot_precision_recall_vs_threshold(y_true, y_pred, test_name):
precisions, recalls, thresholds = precision_recall_curve(y_true, y_pred)
plt.figure(figsize=(10, 6))
plt.plot(thresholds, precisions[:-1], "blue", label="Precision")
plt.plot(thresholds, recalls[:-1], "orange", label="Recall")
plt.xlabel("Threshold")
plt.legend(loc="upper left")
plt.ylim([0, 1])
plt.title(f"Precision and Recall vs. Threshold ({test_name})")
plot_precision_recall_vs_threshold(y_train_lr, y_pred_proba_train_lr_mod, "Train")
plot_precision_recall_vs_threshold(y_val_lr, y_pred_proba_val_lr_mod, "Validation")
plot_precision_recall_vs_threshold(y_test_lr, y_pred_proba_test_lr_mod, "Test")
plt.show()
Analizzo gli errori
Codice
def analyze_errors(y_true, y_pred, test_name):
errors = y_true != y_pred
error_rate = errors.sum() / len(y_true)
print(f"{test_name} - Numero di errori: {errors.sum()} su {len(y_true)} esempi ({error_rate:.2%})")
return errors
train_errors_lr = analyze_errors(y_train_lr, y_pred_train_lr_mod, "Training")
val_errors_lr = analyze_errors(y_val_lr, y_pred_val_lr_mod, "Validation")
test_errors_lr = analyze_errors(y_test_lr, y_pred_test_lr_mod, "Test")
Training - Numero di errori: 67849 su 268871 esempi (25.23%) Validation - Numero di errori: 13766 su 55071 esempi (25.00%) Test - Numero di errori: 14143 su 57167 esempi (24.74%)
Codice
def analyze_error_types(y_true, y_pred, test_name):
cm = confusion_matrix(y_true, y_pred)
tn, fp, fn, tp = cm.ravel()
print(f"\n{test_name} Error Analysis:")
print(f"False Positives: {fp} ({fp/(fp+tn):.2%} of actual negatives)")
print(f"False Negatives: {fn} ({fn/(fn+tp):.2%} of actual positives)")
analyze_error_types(y_train_lr, y_pred_train_lr_mod, "Training")
analyze_error_types(y_val_lr, y_pred_val_lr_mod, "Validation")
analyze_error_types(y_test_lr, y_pred_test_lr_mod, "Test")
Training Error Analysis: False Positives: 60496 (25.63% of actual negatives) False Negatives: 7353 (22.36% of actual positives) Validation Error Analysis: False Positives: 12312 (25.43% of actual negatives) False Negatives: 1454 (21.84% of actual positives) Test Error Analysis: False Positives: 12577 (25.16% of actual negatives) False Negatives: 1566 (21.83% of actual positives)
I tassi di errore sono simili in tutti e tre i set, quindi il modello generalizza bene, però il numero di falsi positivi è troppo grande
Bar plot
Codice
plt.figure(figsize=(10, 6))
bars = plt.bar(['Training', 'Validation', 'Test'],
[train_errors_lr.mean(), val_errors_lr.mean(), test_errors_lr.mean()],
yerr=[train_errors_lr.std(), val_errors_lr.std(), test_errors_lr.std()],
capsize=5, color=['#A7C7E7', '#FFCC99', '#C0C0C0'])
plt.title('Confronto del Tasso di Errore tra i test')
plt.ylabel('Tasso di Errore')
# Aggiungo etichette con i valori sopra ciascuna barra
for i, bar in enumerate(bars):
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2., height,
f'{height:.4f}',
ha='center', va='bottom')
plt.tight_layout() # Assicura che tutto si adatti bene nella figura
plt.show()
Intervallo di confidenza
Codice
from scipy.stats import norm
accuracy_lr = accuracy_score(y_test_lr, y_pred_test_lr_mod)
n_lr = len(y_test_lr)
std_error_lr = np.sqrt(accuracy_lr * (1 - accuracy_lr) / n_lr)
confidence_interval_lr = norm.interval(0.95, loc=accuracy_lr, scale=std_error_lr)
print(f"Accuratezza: {accuracy_lr:.4f}")
print(f"Intervallo di confidenza al 95%: {confidence_interval_lr}")
Accuratezza: 0.7526 Intervallo di confidenza al 95%: (0.7490648538668198, 0.7561391974215109)
Codice
accuracy_lr = 0.7526
confidence_interval_lr = (0.7490648538668198, 0.7561391974215109)
plt.figure(figsize=(10, 6))
# Calcolo l'errore come la differenza tra il valore di accuratezza e i limiti dell'intervallo di confidenza
yerr = [[accuracy_lr - confidence_interval_lr[0]], [confidence_interval_lr[1] - accuracy_lr]]
plt.bar(['Accuratezza'], [accuracy_lr], yerr=yerr, capsize=5)
plt.title('Accuratezza con Intervallo di Confidenza al 95%')
plt.ylabel('Accuratezza')
plt.ylim(0, 1) # Assumendo che l'accuratezza sia tra 0 e 1
# Aggiungo l'etichetta con il valore esatto sopra la barra
plt.text(0, accuracy_lr, f'{accuracy_lr:.4f}', ha='center', va='bottom')
plt.show()
L'accuratezza del modello si trova tra il 74.90% e il 75.61%. Essendo l'intervallo stretto la stima è abbastanza precisa
Importanza delle feature per determinare il contributo di ogni feature alla preformance del modello
Codice
feature_names_lr = (numeric_features +
pipeline_lr.named_steps['preprocessor']
.named_transformers_['cat']
.get_feature_names_out(categorical_features).tolist())
feature_importance_lr = pd.DataFrame({
'feature': feature_names_lr,
'importance': abs(pipeline_lr.named_steps['classifier'].coef_[0])
}).sort_values('importance', ascending=False)
print("\nFeature Importance:")
print(feature_importance_lr)
N = 10 # Numero di top feature da visualizzare
plt.figure(figsize=(10, 6))
sns.barplot(x='importance', y='feature', data=feature_importance_lr.head(N))
plt.title(f'Top {N} Feature Importances (Logistic Regression 0.65)')
plt.tight_layout()
plt.show()
Feature Importance:
feature importance
10 Vehicle_Damage_Yes 1.996013
5 Previously_Insured 1.985682
8 Vehicle_Age_< 1 Year 1.136031
0 Age 0.390421
9 Vehicle_Age_> 2 Years 0.195323
6 Policy_Sales_Channel 0.123041
7 Gender_Male 0.108362
3 Driving_License 0.056416
1 Annual_Premium 0.034451
2 Vintage 0.004401
4 Region_Code 0.001927
Decision boundary
Codice
# Le feature che voglio visualizzare
feature1 = 'Annual_Premium'
feature2 = 'Vintage'
# Griglia di punti
x0, x1 = X[feature1], X[feature2]
xx, yy = np.meshgrid(np.linspace(x0.min()-1, x0.max()+1, 100),
np.linspace(x1.min()-1, x1.max()+1, 100))
# Preparo i dati per la predizione
X_plot = np.c_[xx.ravel(), yy.ravel()]
# Creo un DataFrame con le stesse colonne di X originale
X_plot_df_lr = pd.DataFrame(X_plot, columns=[feature1, feature2])
for col in X.columns:
if col not in X_plot_df_lr.columns:
X_plot_df_lr[col] = X[col].mode()[0] # Uso il valore più frequente per le altre feature
# Predizioni
Z = pipeline_lr.predict_proba(X_plot_df_lr)[:, 1]
Z = Z.reshape(xx.shape)
plt.figure(figsize=(10, 6))
plt.contourf(xx, yy, Z, alpha=0.4, cmap='RdYlBu')
scatter = plt.scatter(X[feature1], X[feature2], c=y, alpha=0.8, cmap='RdYlBu')
plt.xlabel(feature1)
plt.ylabel(feature2)
plt.title(f'Decision Boundary - {feature1} vs {feature2}')
plt.colorbar(scatter)
plt.show()
La maggior parte dei clienti ha un premio annuale inferiore a 100.000
Secondo modello - Random Forest
Codice
X_train_rf, X_test_rf, y_train_rf, y_test_rf = train_test_split(X, y, test_size=0.15, random_state=RANDOM_SEED)
X_train_rf, X_val_rf, y_train_rf, y_val_rf = train_test_split(X_train_rf, y_train_rf, test_size=0.17, random_state=RANDOM_SEED)
Codice
pipeline_rf = Pipeline([
('preprocessor', preprocessor), # Usa il preprocessor esistente
('classifier', RandomForestClassifier(class_weight='balanced', random_state=RANDOM_SEED, n_jobs=-1))
])
Addestro il modello
Codice
pipeline_rf.fit(X_train_rf, y_train_rf)
Out[85]:
Pipeline(steps=[('preprocessor',
ColumnTransformer(transformers=[('num', StandardScaler(),
['Age', 'Annual_Premium',
'Vintage', 'Driving_License',
'Region_Code',
'Previously_Insured',
'Policy_Sales_Channel']),
('cat',
OneHotEncoder(drop='first',
handle_unknown='ignore',
sparse_output=False),
['Gender', 'Vehicle_Age',
'Vehicle_Damage'])])),
('classifier',
RandomForestClassifier(class_weight='balanced', n_jobs=-1,
random_state=42))])In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Pipeline(steps=[('preprocessor',
ColumnTransformer(transformers=[('num', StandardScaler(),
['Age', 'Annual_Premium',
'Vintage', 'Driving_License',
'Region_Code',
'Previously_Insured',
'Policy_Sales_Channel']),
('cat',
OneHotEncoder(drop='first',
handle_unknown='ignore',
sparse_output=False),
['Gender', 'Vehicle_Age',
'Vehicle_Damage'])])),
('classifier',
RandomForestClassifier(class_weight='balanced', n_jobs=-1,
random_state=42))])ColumnTransformer(transformers=[('num', StandardScaler(),
['Age', 'Annual_Premium', 'Vintage',
'Driving_License', 'Region_Code',
'Previously_Insured',
'Policy_Sales_Channel']),
('cat',
OneHotEncoder(drop='first',
handle_unknown='ignore',
sparse_output=False),
['Gender', 'Vehicle_Age', 'Vehicle_Damage'])])['Age', 'Annual_Premium', 'Vintage', 'Driving_License', 'Region_Code', 'Previously_Insured', 'Policy_Sales_Channel']
StandardScaler()
['Gender', 'Vehicle_Age', 'Vehicle_Damage']
OneHotEncoder(drop='first', handle_unknown='ignore', sparse_output=False)
RandomForestClassifier(class_weight='balanced', n_jobs=-1, random_state=42)
Predizioni sul set di training
Codice
y_pred_train_rf = pipeline_rf.predict(X_train_rf)
y_pred_proba_train_rf = pipeline_rf.predict_proba(X_train_rf)[:, 1]
print("Performance sul set di training:")
train_results_rf = evaluate_model(y_train_rf, y_pred_train_rf, y_pred_proba_train_rf, "Random Forest (Training)")
Performance sul set di training:
Evaluation for Random Forest (Training):
Accuracy: 0.9998
Precision: 0.9990
Recall: 0.9997
F1-score: 0.9993
AUC-ROC: 1.0000
Confusion Matrix:
[[235959 32]
[ 11 32869]]
Classification Report:
precision recall f1-score support
0 1.00 1.00 1.00 235991
1 1.00 1.00 1.00 32880
accuracy 1.00 268871
macro avg 1.00 1.00 1.00 268871
weighted avg 1.00 1.00 1.00 268871
Predizioni sul set di validazione
Codice
y_pred_val_rf = pipeline_rf.predict(X_val_rf)
y_pred_proba_val_rf = pipeline_rf.predict_proba(X_val_rf)[:, 1]
print("\nPerformance sul set di validazione:")
val_results_rf = evaluate_model(y_val_rf, y_pred_val_rf, y_pred_proba_val_rf, "Random Forest (Validation)")
Performance sul set di validazione:
Evaluation for Random Forest (Validation):
Accuracy: 0.8704
Precision: 0.3713
Recall: 0.1042
F1-score: 0.1628
AUC-ROC: 0.8337
Confusion Matrix:
[[47238 1175]
[ 5964 694]]
Classification Report:
precision recall f1-score support
0 0.89 0.98 0.93 48413
1 0.37 0.10 0.16 6658
accuracy 0.87 55071
macro avg 0.63 0.54 0.55 55071
weighted avg 0.83 0.87 0.84 55071
Faccio previsioni sul set di test per valutare le performance finali del modello dopo che è stato addestrato
Codice
y_pred_test_rf = pipeline_rf.predict(X_test_rf)
y_pred_proba_test_rf = pipeline_rf.predict_proba(X_test_rf)[:, 1]
print("Random Forest Regression:")
test_results_rf = evaluate_model(y_test_rf, y_pred_test_rf, y_pred_proba_test_rf, "Random Forest Regression")
Random Forest Regression:
Evaluation for Random Forest Regression:
Accuracy: 0.8672
Precision: 0.3903
Recall: 0.1039
F1-score: 0.1641
AUC-ROC: 0.8377
Confusion Matrix:
[[48831 1164]
[ 6427 745]]
Classification Report:
precision recall f1-score support
0 0.88 0.98 0.93 49995
1 0.39 0.10 0.16 7172
accuracy 0.87 57167
macro avg 0.64 0.54 0.55 57167
weighted avg 0.82 0.87 0.83 57167
Confronto diretto delle metriche
Codice
results_rf = f"""
Riepilogo delle metriche principali:
{'Set':<10}{'Accuracy':<12}{'F1-score':<12}{'AUC-ROC':<12}{'Precision':<12}{'Recall':<12}
{'-'*70}
{'Training':<10}{train_results_rf['Accuracy']:<12.4f}{train_results_rf['F1-score']:<12.4f}{train_results_rf['AUC-ROC']:<12.4f}{train_results_rf['Precision']:<12.4f}{train_results_rf['Recall']:<12.4f}
{'Validation':<10}{val_results_rf['Accuracy']:<12.4f}{val_results_rf['F1-score']:<12.4f}{val_results_rf['AUC-ROC']:<12.4f}{val_results_rf['Precision']:<12.4f}{val_results_rf['Recall']:<12.4f}
{'Test':<10}{test_results_rf['Accuracy']:<12.4f}{test_results_rf['F1-score']:<12.4f}{test_results_rf['AUC-ROC']:<12.4f}{test_results_rf['Precision']:<12.4f}{test_results_rf['Recall']:<12.4f}
"""
print(results_rf)
Riepilogo delle metriche principali: Set Accuracy F1-score AUC-ROC Precision Recall ---------------------------------------------------------------------- Training 0.9998 0.9993 1.0000 0.9990 0.9997 Validation0.8704 0.1628 0.8337 0.3713 0.1042 Test 0.8672 0.1641 0.8377 0.3903 0.1039
Nel set di training abbiamo delle prestazioni quasi perfette, nel test di validazione e test invece abbiamo prestazioni peggiori, indice di overfitting
Visualizzazione delle matrici di confusione
Codice
plot_confusion_matrix(y_train_rf, y_pred_train_rf, 'Matrice di Confusione (Training)')
plot_confusion_matrix(y_val_rf, y_pred_val_rf, 'Matrice di Confusione (Validazione)')
plot_confusion_matrix(y_test_rf, y_pred_test_rf, 'Matrice di Confusione (Test)')
Confronto delle curve ROC
Codice
plt.figure(figsize=(10, 6))
# Curve ROC per i diversi set
fpr_train, tpr_train, thresholds_train = plot_roc_curve(y_train_rf, y_pred_proba_train_rf, 'Training', 'darkblue')
fpr_val, tpr_val, thresholds_val = plot_roc_curve(y_val_rf, y_pred_proba_val_rf, 'Validation', 'darkorange')
fpr_test, tpr_test, thresholds_test = plot_roc_curve(y_test_rf, y_pred_proba_test_rf, 'Test', 'purple')
# Linea di riferimento (random classifier)
plt.plot([0, 1], [0, 1], color='#C0C0C0', linestyle='--')
# Funzione per trovare il punto più vicino a una data soglia
def find_nearest(array, value):
return (np.abs(array - value)).argmin()
# Plotto i punti
plt.plot(fpr_test[idx_test], tpr_test[idx_test], 'o', color=colore, markersize=10, label=f'Soglia {soglia}')
plt.xlabel('Falsi Positivi')
plt.ylabel('Veri Positivi')
plt.title('ROC Curve con Soglie')
plt.legend(loc="lower right")
plt.show()
Analisi delle differenze nelle distribuzioni di probabilità
Codice
warnings.filterwarnings('ignore')
plt.figure(figsize=(10, 6))
sns.kdeplot(y_pred_proba_train_rf, fill=True, color="blue", label="Train")
sns.kdeplot(y_pred_proba_val_rf, fill=True, color="orange", label="Validation")
sns.kdeplot(y_pred_proba_test_rf, fill=True, color="black", label="Test")
plt.xlabel('Predicted Probability')
plt.ylabel('Density')
plt.title('Distribution of Predicted Probabilities')
plt.legend()
plt.show()
Confronto delle metriche per diverse soglie di decisione
Codice
from sklearn.metrics import precision_recall_curve
def plot_precision_recall_vs_threshold(y_true, y_scores, test_name):
precisions, recalls, thresholds = precision_recall_curve(y_true, y_scores)
plt.figure(figsize=(10, 6))
plt.plot(thresholds, precisions[:-1], "b--", label="Precision")
plt.plot(thresholds, recalls[:-1], "g-", label="Recall")
plt.xlabel("Threshold")
plt.legend(loc="upper left")
plt.ylim([0, 1])
plt.title(f"Precision and Recall vs. Threshold ({test_name})")
plot_precision_recall_vs_threshold(y_train_rf, y_pred_proba_train_rf, "Train")
plot_precision_recall_vs_threshold(y_val_rf, y_pred_proba_val_rf, "Validation")
plot_precision_recall_vs_threshold(y_test_rf, y_pred_proba_test_rf, "Test")
plt.show()
Analizzo gli errori
Codice
def analyze_errors(y_true, y_pred, test_name):
errors = y_true != y_pred
error_rate = errors.sum() / len(y_true)
print(f"{test_name} - Numero di errori: {errors.sum()} su {len(y_true)} esempi ({error_rate:.2%})")
return errors
train_errors_rf = analyze_errors(y_train_rf, y_pred_train_rf, "Training")
val_errors_rf = analyze_errors(y_val_rf, y_pred_val_rf, "Validation")
test_errors_rf = analyze_errors(y_test_rf, y_pred_test_rf, "Test")
Training - Numero di errori: 43 su 268871 esempi (0.02%) Validation - Numero di errori: 7139 su 55071 esempi (12.96%) Test - Numero di errori: 7591 su 57167 esempi (13.28%)
Codice
def analyze_error_types(y_true, y_pred, test_name):
cm = confusion_matrix(y_true, y_pred)
tn, fp, fn, tp = cm.ravel()
print(f"\n{test_name} Error Analysis:")
print(f"False Positives: {fp} ({fp/(fp+tn):.2%} of actual negatives)")
print(f"False Negatives: {fn} ({fn/(fn+tp):.2%} of actual positives)")
analyze_error_types(y_train_rf, y_pred_train_rf, "Training")
analyze_error_types(y_val_rf, y_pred_val_rf, "Validation")
analyze_error_types(y_test_rf, y_pred_test_rf, "Test")
Training Error Analysis: False Positives: 32 (0.01% of actual negatives) False Negatives: 11 (0.03% of actual positives) Validation Error Analysis: False Positives: 1175 (2.43% of actual negatives) False Negatives: 5964 (89.58% of actual positives) Test Error Analysis: False Positives: 1164 (2.33% of actual negatives) False Negatives: 6427 (89.61% of actual positives)
Codice
plt.figure(figsize=(10, 6))
bars = plt.bar(['Training', 'Validation', 'Test'],
[train_errors_rf.mean(), val_errors_rf.mean(), test_errors_rf.mean()],
yerr=[train_errors_rf.std(), val_errors_rf.std(), test_errors_rf.std()],
capsize=5, color=['#A7C7E7', '#FFCC99', '#C0C0C0'])
plt.title('Confronto del Tasso di Errore tra i test')
plt.ylabel('Tasso di Errore')
# Aggiungoe etichette con i valori sopra ciascuna barra
for i, bar in enumerate(bars):
height = bar.get_height()
plt.text(bar.get_x() + bar.get_width()/2., height,
f'{height:.4f}',
ha='center', va='bottom')
plt.tight_layout()
plt.show()
Intervallo di confidenza
Codice
accuracy_rf = accuracy_score(y_test_rf, y_pred_test_rf)
n_rf = len(y_test_rf)
std_error_rf = np.sqrt(accuracy_rf * (1 - accuracy_rf) / n_rf)
confidence_interval_rf = norm.interval(0.95, loc=accuracy_rf, scale=std_error_rf)
print(f"Accuratezza: {accuracy_rf:.4f}")
print(f"Intervallo di confidenza al 95%: {confidence_interval_rf}")
Accuratezza: 0.8672 Intervallo di confidenza al 95%: (0.8644318706448486, 0.8699953338612476)
Codice
plt.figure(figsize=(10, 6))
# Calcolo l'errore come la differenza tra il valore di accuratezza e i limiti dell'intervallo di confidenza
yerr_rf = [[accuracy_rf - confidence_interval_rf[0]], [confidence_interval_rf[1] - accuracy_rf]]
plt.bar(['Accuratezza'], [accuracy_rf], yerr=yerr, capsize=5)
plt.title('Accuratezza con Intervallo di Confidenza al 95%')
plt.ylabel('Accuratezza')
plt.ylim(0, 1) # Assumendo che l'accuratezza sia tra 0 e 1
# Aggiungo l'etichetta con il valore esatto sopra la barra
plt.text(0, accuracy_rf, f'{accuracy_rf:.4f}', ha='center', va='bottom')
plt.show()
Importanza delle feature
Codice
feature_names_rf = (numeric_features +
pipeline_rf.named_steps['preprocessor']
.named_transformers_['cat']
.get_feature_names_out(categorical_features).tolist())
feature_importance_rf = pd.DataFrame({
'feature': feature_names_rf,
'importance': pipeline_rf.named_steps['classifier'].feature_importances_
}).sort_values('importance', ascending=False)
print("\nFeature Importance:")
print(feature_importance_rf)
N = 10 # Numero di top feature da visualizzare
plt.figure(figsize=(10, 6))
sns.barplot(x='importance', y='feature', data=feature_importance_rf.head(N))
plt.title(f'Top {N} Feature Importances (Random Forest)')
plt.tight_layout()
plt.show()
Feature Importance:
feature importance
10 Vehicle_Damage_Yes 0.199428
2 Vintage 0.190459
1 Annual_Premium 0.166639
5 Previously_Insured 0.136071
0 Age 0.116371
4 Region_Code 0.085140
6 Policy_Sales_Channel 0.066108
8 Vehicle_Age_< 1 Year 0.026329
7 Gender_Male 0.007918
9 Vehicle_Age_> 2 Years 0.004882
3 Driving_License 0.000655
Modelli SMOTE e Undersampling
Codice
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.15, random_state=RANDOM_SEED)
X_train, X_val, y_train, y_val = train_test_split(X_train, y_train, test_size=0.17, random_state=RANDOM_SEED)
def evaluate_model_due(y_true, y_pred, y_pred_proba):
return {
'Accuracy': accuracy_score(y_true, y_pred),
'Precision': precision_score(y_true, y_pred),
'Recall': recall_score(y_true, y_pred),
'F1-score': f1_score(y_true, y_pred),
'AUC-ROC': roc_auc_score(y_true, y_pred_proba)
}
def create_and_evaluate_models(X_train, y_train, X_val, y_val, X_test, y_test, model_class, model_name):
base_model = model_class(random_state=RANDOM_SEED) # Istanza del modello base
# base_model è il parametro che verrà passato
# alle funzioni LogisticRegression e RandomForestClassifier
# durante la creazione e valutazione dei modelli successivamente
# Creo i modelli
smote = ImbPipeline([
('preprocessor', preprocessor),
('smote', SMOTE(random_state=RANDOM_SEED)), # Bilanciamento
('classifier', base_model) # Classificatore
])
under = ImbPipeline([
('preprocessor', preprocessor),
('undersampler', RandomUnderSampler(random_state=RANDOM_SEED)),
('classifier', base_model)
])
# Dizionario
models = {
f'{model_name} SMOTE': smote,
f'{model_name} Undersampling': under
}
# Inizializzazione del dizionario vuoto
results = {}
# Iterazione su ciascun modello del dizionario
for name, model in models.items():
model.fit(X_train, y_train) # Addestramento
y_train_pred = model.predict(X_train) # Previsioni e calcolo delle probabilità
y_train_pred_proba = model.predict_proba(X_train)[:, 1]
train_results = evaluate_model_due(y_train, y_train_pred, y_train_pred_proba) # Valutazione delle prestazioni
y_val_pred = model.predict(X_val)
y_val_pred_proba = model.predict_proba(X_val)[:, 1]
val_results = evaluate_model_due(y_val, y_val_pred, y_val_pred_proba)
y_test_pred = model.predict(X_test)
y_test_pred_proba = model.predict_proba(X_test)[:, 1]
test_results = evaluate_model_due(y_test, y_test_pred, y_test_pred_proba)
# Memorizzazione dei risultati nel dizionario
results[name] = {
'model': model,
'training': train_results,
'validation': val_results,
'test': test_results
}
return results
Codice
# Creo un dizionario con i risultati per Logistic Regression 0.65 e Random Forest Standard che poi verranno
# usati per fare le comparazioni
results_lr_065 = {
'Accuracy': test_results_lr_mod['Accuracy'],
'F1-score': test_results_lr_mod['F1-score'],
'AUC-ROC': test_results_lr_mod['AUC-ROC'],
'Precision': test_results_lr_mod['Precision'],
'Recall': test_results_lr_mod['Recall']
}
results_rf = {
'Accuracy': test_results_rf['Accuracy'],
'F1-score': test_results_rf['F1-score'],
'AUC-ROC': test_results_rf['AUC-ROC'],
'Precision': test_results_rf['Precision'],
'Recall': test_results_rf['Recall']
}
Codice
# Creazione e valutazione dei modelli di Regressione Logistica e Random Forest
lr_results = create_and_evaluate_models(X_train, y_train, X_val, y_val, X_test, y_test,
LogisticRegression, "Logistic Regression")
rf_results = create_and_evaluate_models(X_train, y_train, X_val, y_val, X_test, y_test,
RandomForestClassifier, "Random Forest")
Codice
# Combino i risultati
all_results = {**lr_results, **rf_results}
comparison_train = pd.DataFrame({name: results['training'] for name, results in all_results.items()}).transpose()
# Creo il confronto per il set di validazione
comparison_val = pd.DataFrame({name: results['validation'] for name, results in all_results.items()}).transpose()
# Creo il DataFrame per i risultati del test
comparison_test = pd.DataFrame({name: results['test'] for name, results in all_results.items()}).transpose()
# Aggiungo le righe di summary per Random Forest e Logistic Regression 0.65
comparison_test.loc['Logistic Regression 0.65'] = pd.Series(results_lr_065)
comparison_test.loc['Random Forest Standard'] = pd.Series(results_rf)
Codice
from termcolor import colored
train_text = "Risultati sul set di training:"
val_text = "Risultati sul set di validazione:"
test_text = "Risultati sul set di test di tutti i modelli esaminati:"
def print_colored_title(text):
light_green = '\033[38;2;000;204;000m'
bold = '\033[1m'
reset = '\033[0m'
print(f"{light_green}{bold}{text}{reset}")
print_colored_title(train_text)
print(comparison_train)
print("\n")
print_colored_title(val_text)
print(comparison_val)
print("\n")
print_colored_title(test_text)
print(comparison_test)
Risultati sul set di training: Accuracy Precision Recall F1-score \ Logistic Regression SMOTE 0.638968 0.249483 0.972111 0.397063 Logistic Regression Undersampling 0.638503 0.249404 0.973388 0.397070 Random Forest SMOTE 0.999874 0.999483 0.999483 0.999483 Random Forest Undersampling 0.763303 0.340641 0.999909 0.508165 AUC-ROC Logistic Regression SMOTE 0.835329 Logistic Regression Undersampling 0.835420 Random Forest SMOTE 1.000000 Random Forest Undersampling 0.967654 Risultati sul set di validazione: Accuracy Precision Recall F1-score \ Logistic Regression SMOTE 0.643188 0.249807 0.974166 0.397646 Logistic Regression Undersampling 0.642643 0.249712 0.975668 0.397649 Random Forest SMOTE 0.836847 0.327040 0.330430 0.328726 Random Forest Undersampling 0.712789 0.280117 0.876239 0.424522 AUC-ROC Logistic Regression SMOTE 0.838584 Logistic Regression Undersampling 0.838833 Random Forest SMOTE 0.833170 Random Forest Undersampling 0.838147 Risultati sul set di test di tutti i modelli esaminati: Accuracy Precision Recall F1-score \ Logistic Regression SMOTE 0.641646 0.255724 0.971695 0.404892 Logistic Regression Undersampling 0.641227 0.255661 0.972950 0.404921 Random Forest SMOTE 0.835902 0.343843 0.339096 0.341453 Random Forest Undersampling 0.714573 0.290127 0.881344 0.436548 Logistic Regression 0.65 0.752602 0.308310 0.781651 0.442201 Random Forest Standard 0.867214 0.390257 0.103876 0.164079 AUC-ROC Logistic Regression SMOTE 0.840149 Logistic Regression Undersampling 0.840037 Random Forest SMOTE 0.835023 Random Forest Undersampling 0.841565 Logistic Regression 0.65 0.840257 Random Forest Standard 0.837689
Logistic Regression con SMOTE e Undersampling hanno la Recall alta, identificano un numero elevato di casi positivi ma inglobano anche i falsi positivi.
Random Forest Undersampling ha una buona Recall ma una bassa Precision inglobando falsi positivi.
Random Forest SMOTE ha una bassa Recall, perde molti veri positivi.
Random Forest Standard ha una Recall bassissima, perde molti veri positivi.
Logistic Regression 0.65 ha una buona Recall, una buona Accuracy e F1-score tra le più alte
ROC Curve
Codice
def plot_roc_curve(y_true, y_pred_proba, label, color):
fpr, tpr, thresholds = roc_curve(y_true, y_pred_proba)
roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, label=f'{label} (AUC = {roc_auc:.2f})', color=color)
return fpr, tpr, thresholds
plt.figure(figsize=(10, 6))
# Curve ROC per i modelli SMOTE e Undersampling
for name, results in all_results.items():
try:
model = results['model']
y_pred_proba = model.predict_proba(X_test)[:, 1]
plot_roc_curve(y_test, y_pred_proba, name, None) # None per colore casuale
except Exception as e:
pass # Silenziosamente ignora eventuali errori
# Curve ROC per RF standard e LR standard (solo test)
fpr_rf, tpr_rf, _ = plot_roc_curve(y_test_rf, y_pred_proba_test_rf, 'Random Forest Standard (Test)', 'purple')
fpr_lr, tpr_lr, _ = plot_roc_curve(y_test_lr, y_pred_proba_test_lr, 'Logistic Regression 0.65 (Test)', 'darkgreen')
# Linea di riferimento (random classifier)
plt.plot([0, 1], [0, 1], color='#C0C0C0', linestyle='--', label='Random Chance')
# Funzione per trovare il punto più vicino a una data soglia
def find_nearest(array, value):
return (np.abs(array - value)).argmin()
# Aggiungo il punto per la soglia specifica (solo per LR standard)
soglie = [0.65]
colori = ['#A7C7E7', '#FFCC99']
for soglia, colore in zip(soglie, colori):
idx_lr = find_nearest(_, soglia) # _ contiene i thresholds dell'ultima chiamata a plot_roc_curve (LR)
plt.plot(fpr_lr[idx_lr], tpr_lr[idx_lr], 'o', color=colore, markersize=10, label=f'LR Soglia {soglia}')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curves - All Models')
plt.legend(loc="lower right")
plt.grid(True)
plt.show()
print("\nValori AUC-ROC:")
for name, results in all_results.items():
model = results['model']
y_pred_proba = model.predict_proba(X_test)[:, 1]
roc_auc = auc(roc_curve(y_test, y_pred_proba)[0], roc_curve(y_test, y_pred_proba)[1])
print(f"{name}: {roc_auc:.4f}")
print(f"Random Forest Standard: {auc(fpr_rf, tpr_rf):.4f}")
print(f"Logistic Regression 0.65: {auc(fpr_lr, tpr_lr):.4f}")
Valori AUC-ROC: Logistic Regression SMOTE: 0.8400 Logistic Regression Undersampling: 0.8400 Random Forest SMOTE: 0.8416 Random Forest Undersampling: 0.8416 Random Forest Standard: 0.8377 Logistic Regression 0.65: 0.8403