🌳 Alberi Decisionali: Sistema di Valutazione Rischio di Credito

Costruisci alberi decisionali interpretabili per decisioni finanziarie

Introduzione

Benvenuto nel mondo degli alberi decisionali! A differenza delle “scatole nere” come le reti neurali, gli alberi decisionali sono modelli ML completamente interpretabili. In questa esercitazione costruirai un sistema di valutazione del rischio di credito che spiega esattamente come prende ogni decisione, rendendolo perfetto per settori regolamentati come la finanza.

Scenario Reale:
Lavorerai come analista del rischio per una banca che deve approvare o rifiutare richieste di prestito. Costruirai un albero decisionale che non solo predice il rischio ma spiega esattamente perché un cliente viene considerato rischioso, rispettando le normative finanziarie.

⚠️ ATTENZIONE: COPIA BLOCCATA

Il codice è protetto da sistema anti-copia. Dovrai SCRIVERE manualmente tutto il codice per imparare veramente! 🚫📋

Parte 1: Dataset Valutazione Credito

▶ Importazione Librerie e Creazione Dati

1
Crea una nuova cella su Google Colab e importa le librerie:
PYTHON
# IMPORT LIBRERIE PER ALBERI DECISIONALI AVANZATI
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.tree import DecisionTreeClassifier, plot_tree, export_text
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score
from sklearn.metrics import (accuracy_score, precision_score, recall_score, 
                           f1_score, confusion_matrix, classification_report,
                           roc_curve, auc, precision_recall_curve)
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.inspection import permutation_importance, PartialDependenceDisplay
import graphviz
from sklearn.tree import export_graphviz
import warnings
warnings.filterwarnings('ignore')

# Configurazione visualizzazione
sns.set_style("whitegrid")
plt.rcParams['figure.figsize'] = (14, 8)
plt.rcParams['font.size'] = 12

print("✅ Librerie per alberi decisionali importate correttamente!")
print("🌳 Algoritmi disponibili: Decision Tree, Random Forest, Gradient Boosting")

▶ Creazione Dataset Credito Realistico

2
Crea un dataset realistico per la valutazione del credito:
PYTHON
# 2. CREAZIONE DATASET VALUTAZIONE CREDITO
print("🏦 CREAZIONE DATASET VALUTAZIONE CREDITO")
print("="*50)

np.random.seed(42)  # Per risultati riproducibili
n_richieste = 500

# === CREAZIONE FEATURES FINANZIARIE REALISTICHE ===

# 1. Caratteristiche demografiche e finanziarie
eta = np.random.randint(20, 70, n_richieste)
reddito_annuale = np.random.normal(40000, 15000, n_richieste)
reddito_annuale = np.clip(reddito_annuale, 15000, 150000)  # Limiti realistici

# 2. Caratteristiche debito (con correlazione con reddito)
rapporto_debito_reddito = np.random.beta(2, 5, n_richieste)  # Distribuzione skew positiva
debito_totale = reddito_annuale * rapporto_debito_reddito * 2  # Moltiplica per variabilità

# 3. Storico creditizio
punteggio_credito = np.random.normal(650, 100, n_richieste)
punteggio_credito = np.clip(punteggio_credito, 300, 850)  # Range FICO score

# 4. Caratteristiche occupazionali (categoriche)
occupazioni = ['Impiegato', 'Libero Professionista', 'Operaio', 'Dirigente', 'Disoccupato', 'Pensionato']
occupazione = np.random.choice(occupazioni, n_richieste, p=[0.4, 0.2, 0.15, 0.1, 0.1, 0.05])

# 5. Durata occupazione (mesi)
mesi_occupazione = np.random.exponential(60, n_richieste)  # Distribuzione esponenziale
mesi_occupazione = np.clip(mesi_occupazione, 0, 480)  # Max 40 anni

# 6. Patrimonio (casa, investimenti)
possiede_casa = np.random.choice([0, 1], n_richieste, p=[0.4, 0.6])
valore_patrimonio = reddito_annuale * np.random.uniform(0.5, 3, n_richieste)

# 7. Storico prestiti
prestiti_pregressi = np.random.poisson(2, n_richieste)  # Distribuzione Poisson
prestiti_pregressi = np.clip(prestiti_pregressi, 0, 10)

# 8. Target: Rischio di default (0 = Basso, 1 = Alto)
# Logica complessa per simulare realtà
prob_default = (
    0.3 * ((eta < 25) | (eta > 65)) +  # Età estrema
    0.4 * (rapporto_debito_reddito > 0.5) +  # Alto debito
    0.5 * (punteggio_credito < 580) +  # Basso credit score
    0.3 * (occupazione == 'Disoccupato') +  # Disoccupazione
    0.2 * (mesi_occupazione < 12) +  # Occupazione recente
    0.1 * (possiede_casa == 0) +  # Non possiede casa
    0.25 * (prestiti_pregressi > 5)  # Troppi prestiti
) / 2.55  # Normalizza a probabilità

# Aggiungi rumore
prob_default += np.random.normal(0, 0.1, n_richieste)
prob_default = np.clip(prob_default, 0, 1)

# Genera target basato su probabilità
rischio_default = (prob_default > 0.5).astype(int)

# Crea DataFrame
df_credito = pd.DataFrame({
    'id_richiesta': range(1000, 1000 + n_richieste),
    'eta': eta,
    'reddito_annuale': reddito_annuale,
    'debito_totale': debito_totale,
    'rapporto_debito_reddito': rapporto_debito_reddito,
    'punteggio_credito': punteggio_credito,
    'occupazione': occupazione,
    'mesi_occupazione': mesi_occupazione,
    'possiede_casa': possiede_casa,
    'valore_patrimonio': valore_patrimonio,
    'prestiti_pregressi': prestiti_pregressi,
    'prob_default': prob_default,
    'rischio_default': rischio_default
})

# Aggiungi feature derivata
df_credito['patrimonio_debito_ratio'] = df_credito['valore_patrimonio'] / (df_credito['debito_totale'] + 1)

print(f"📊 DATASET CREDITO CREATO:")
print(f"  • Numero richieste: {len(df_credito)}")
print(f"  • Features: {len(df_credito.columns) - 3} (esclusi ID, prob_default, target)")
print(f"  • Target: rischio_default (0=Basso rischio, 1=Alto rischio)")

print(f"\n📋 PRIME 10 RIGHE DEL DATASET:")
print(df_credito.head(10))

print(f"\n🎯 DISTRIBUZIONE TARGET:")
distribuzione = df_credito['rischio_default'].value_counts()
for valore, count in distribuzione.items():
    percentuale = (count / len(df_credito)) * 100
    rischio = "BASSO" if valore == 0 else "ALTO"
    print(f"  • Rischio {rischio}: {count} richieste ({percentuale:.1f}%)")

print(f"\n📈 CORRELAZIONI CON TARGET:")
correlazioni = df_credito.corr()['rischio_default'].sort_values(ascending=False)
print(correlazioni.head(10))

Parte 2: Analisi Esplorativa e Preprocessing

▶ Analisi Dati e Preparazione per Alberi Decisionali

3
Analizza i dati e preparali per gli alberi decisionali:
PYTHON
# 3. ANALISI ESPLORATIVA E PREPARAZIONE DATI
print("🔍 ANALISI ESPLORATIVA DATI CREDITO")
print("="*50)

# 3.1 Selezione features per il modello
features_numeriche = ['eta', 'reddito_annuale', 'debito_totale', 'rapporto_debito_reddito',
                     'punteggio_credito', 'mesi_occupazione', 'valore_patrimonio',
                     'prestiti_pregressi', 'patrimonio_debito_ratio']

features_categoriche = ['occupazione', 'possiede_casa']

print(f"📊 FEATURES SELEZIONATE:")
print(f"  • Numeriche ({len(features_numeriche)}): {features_numeriche}")
print(f"  • Categoriche ({len(features_categoriche)}): {features_categoriche}")

# 3.2 Encoding features categoriche
print(f"\n🔤 ENCODING FEATURES CATEGORICHE...")

# Encoding per 'occupazione'
occupazione_encoder = LabelEncoder()
df_credito['occupazione_encoded'] = occupazione_encoder.fit_transform(df_credito['occupazione'])

print(f"  • Occupazione encoding:")
for i, classe in enumerate(occupazione_encoder.classes_):
    print(f"    {classe} → {i}")

# 'possiede_casa' è già binaria (0/1)

# 3.3 Preparazione dataset finale
X = df_credito[features_numeriche + ['occupazione_encoded', 'possiede_casa']].copy()
y = df_credito['rischio_default']

print(f"\n📦 DATASET FINALE PER MODELLO:")
print(f"  • X (features): {X.shape}")
print(f"  • y (target): {y.shape}")

# 3.4 Split train-test
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)

print(f"\n🎯 SPLIT TRAIN-TEST:")
print(f"  • Training set: {X_train.shape[0]} richieste ({X_train.shape[0]/len(X)*100:.1f}%)")
print(f"  • Test set: {X_test.shape[0]} richieste ({X_test.shape[0]/len(X)*100:.1f}%)")
print(f"  • Distribuzione target (train): {pd.Series(y_train).value_counts().to_dict()}")
print(f"  • Distribuzione target (test): {pd.Series(y_test).value_counts().to_dict()}")

# 3.5 Visualizzazione distribuzioni
print(f"\n🎨 VISUALIZZAZIONE DISTRIBUZIONI FEATURES...")

fig, axes = plt.subplots(3, 4, figsize=(16, 12))
fig.suptitle('Distribuzione Features per Rischio di Default', fontsize=16, fontweight='bold')

features_to_plot = features_numerique[:12]  # Prendi prime 12 features numeriche

for idx, feature in enumerate(features_to_plot):
    ax = axes[idx // 4, idx % 4]
    
    # Separa per classe
    for classe, colore, etichetta in [(0, 'green', 'Basso Rischio'), (1, 'red', 'Alto Rischio')]:
        mask = y == classe
        ax.hist(X[feature][mask], bins=30, alpha=0.5, color=colore, label=etichetta, density=True)
    
    ax.set_title(feature.replace('_', ' ').title(), fontsize=11)
    ax.set_xlabel('')
    ax.set_ylabel('Densità')
    ax.legend(fontsize=9)
    ax.grid(True, alpha=0.3)
    
    # Rimuovi assi extra
    if idx >= len(features_to_plot) - (12 - len(features_to_plot)):
        ax.set_visible(False)

plt.tight_layout()
plt.show()

print("✅ Dati preparati per gli alberi decisionali!")

Parte 3: Costruzione Albero Decisionale Base

▶ Addestramento e Visualizzazione Albero

4
Crea e visualizza il tuo primo albero decisionale:
PYTHON
# 4. COSTRUZIONE ALBERO DECISIONALE BASE
print("🌳 COSTRUZIONE ALBERO DECISIONALE BASE")
print("="*50)

# 4.1 Creazione albero decisionale
print("⚡ Creazione albero decisionale...")
tree_clf = DecisionTreeClassifier(
    max_depth=4,           # Limita profondità per evitare overfitting
    min_samples_split=20,  # Minimo campioni per split
    min_samples_leaf=10,   # Minimo campioni per foglia
    random_state=42,
    class_weight='balanced'  # Bilancia classi (importante per dati sbilanciati)
)

# Addestramento
tree_clf.fit(X_train, y_train)

print(f"✅ Albero decisionale addestrato!")
print(f"\n📊 INFORMAZIONI ALBERO:")
print(f"  • Profondità massima: {tree_clf.get_depth()}")
print(f"  • Numero foglie: {tree_clf.get_n_leaves()}")
print(f"  • Numero features: {tree_clf.n_features_in_}")

# 4.2 Performance base
y_pred_train = tree_clf.predict(X_train)
y_pred_test = tree_clf.predict(X_test)

print(f"\n📈 PERFORMANCE BASE:")
print(f"  • Accuracy training: {accuracy_score(y_train, y_pred_train):.3f}")
print(f"  • Accuracy test: {accuracy_score(y_test, y_pred_test):.3f}")

# 4.3 Visualizzazione albero (versione testuale)
print(f"\n📝 ALBERO IN FORMATO TESTUALE:")
tree_rules = export_text(tree_clf, feature_names=list(X.columns))
print(tree_rules[:2000])  # Mostra solo primi 2000 caratteri

# 4.4 Visualizzazione albero grafica
print(f"\n🎨 VISUALIZZAZIONE GRAFICA ALBERO...")

plt.figure(figsize=(20, 12))
plot_tree(tree_clf, 
          feature_names=X.columns,
          class_names=['Basso Rischio', 'Alto Rischio'],
          filled=True,
          rounded=True,
          fontsize=10,
          proportion=True,
          max_depth=3)  # Mostra solo primi 3 livelli per chiarezza

plt.title("Albero Decisionale per Valutazione Rischio di Credito (Profondità 3)", 
          fontsize=16, pad=20)
plt.tight_layout()
plt.show()

# 4.5 Interpretazione regole principali
print(f"\n🔍 INTERPRETAZIONE REGOLE PRINCIPALI:")
print("Le prime decisioni dell'albero:")

# Analizza i primi split
n_splits_to_show = 5
feature_importances = tree_clf.feature_importances_
important_features_idx = np.argsort(feature_importances)[::-1][:n_splits_to_show]

print(f"\nTop {n_splits_to_show} feature più importanti:")
for i, idx in enumerate(important_features_idx):
    feature_name = X.columns[idx]
    importance = feature_importances[idx]
    print(f"  {i+1}. {feature_name}: {importance:.3f}")

# 4.6 Esempio decisioni per casi specifici
print(f"\n🔮 ESEMPI DECISIONI PER CASI SPECIFICI:")

# Seleziona alcuni casi dal test set
sample_cases = X_test.iloc[:3]

print("\nCaso 1 - Cliente a basso rischio tipico:")
cliente1 = sample_cases.iloc[0]
pred1 = tree_clf.predict([cliente1])[0]
prob1 = tree_clf.predict_proba([cliente1])[0]
print(f"  • Predizione: {'ALTO RISCHIO' if pred1 == 1 else 'BASSO RISCHIO'}")
print(f"  • Probabilità: Basso rischio={prob1[0]:.2f}, Alto rischio={prob1[1]:.2f}")

print("\nCaso 2 - Cliente borderline:")
cliente2 = sample_cases.iloc[1]
pred2 = tree_clf.predict([cliente2])[0]
prob2 = tree_clf.predict_proba([cliente2])[0]
print(f"  • Predizione: {'ALTO RISCHIO' if pred2 == 1 else 'BASSO RISCHIO'}")
print(f"  • Probabilità: Basso rischio={prob2[0]:.2f}, Alto rischio={prob2[1]:.2f}")

print("\n✅ Albero decisionale base creato e interpretato!")

Parte 4: Ottimizzazione e Analisi Avanzata

▶ Grid Search e Analisi Feature Importance

5
Ottimizza l'albero e analizza l'importanza delle features:
PYTHON
# 5. OTTIMIZZAZIONE E ANALISI AVANZATA
print("🎯 OTTIMIZZAZIONE PARAMETRI E ANALISI")
print("="*50)

# 5.1 Grid Search per trovare parametri ottimali
print("\n🔬 GRID SEARCH PER PARAMETRI OTTIMALI...")

# Definisci griglia di parametri
param_grid = {
    'max_depth': [3, 4, 5, 6, None],
    'min_samples_split': [10, 20, 30, 40],
    'min_samples_leaf': [5, 10, 15, 20],
    'criterion': ['gini', 'entropy']
}

print("Parametri in griglia:")
for param, values in param_grid.items():
    print(f"  • {param}: {values}")

# Crea e esegui GridSearchCV
grid_search = GridSearchCV(
    DecisionTreeClassifier(random_state=42, class_weight='balanced'),
    param_grid,
    cv=5,  # 5-fold cross validation
    scoring='f1',  # Usiamo F1-score (bilancia precision e recall)
    n_jobs=-1,
    verbose=1
)

print("\n⚡ Esecuzione GridSearchCV (potrebbe richiedere qualche secondo)...")
grid_search.fit(X_train, y_train)

print(f"\n✅ GridSearch completato!")
print(f"  • Migliori parametri: {grid_search.best_params_}")
print(f"  • Miglior F1-score: {grid_search.best_score_:.3f}")

# 5.2 Albero ottimizzato
best_tree = grid_search.best_estimator_

# Performance albero ottimizzato
y_pred_best_train = best_tree.predict(X_train)
y_pred_best_test = best_tree.predict(X_test)

print(f"\n📊 PERFORMANCE ALBERO OTTIMIZZATO:")
print(f"  • Accuracy training: {accuracy_score(y_train, y_pred_best_train):.3f}")
print(f"  • Accuracy test: {accuracy_score(y_test, y_pred_best_test):.3f}")
print(f"  • Precision test: {precision_score(y_test, y_pred_best_test):.3f}")
print(f"  • Recall test: {recall_score(y_test, y_pred_best_test):.3f}")
print(f"  • F1-score test: {f1_score(y_test, y_pred_best_test):.3f}")

# 5.3 Analisi feature importance
print(f"\n🔍 ANALISI FEATURE IMPORTANCE DETTAGLIATA:")

# Feature importance dall'albero
feature_importance = best_tree.feature_importances_
feature_importance_df = pd.DataFrame({
    'Feature': X.columns,
    'Importance': feature_importance
}).sort_values('Importance', ascending=False)

print(f"\nTop 10 feature più importanti:")
print(feature_importance_df.head(10).to_string(index=False))

# 5.4 Visualizzazione feature importance
print(f"\n🎨 VISUALIZZAZIONE FEATURE IMPORTANCE...")

fig, axes = plt.subplots(1, 2, figsize=(16, 8))

# Bar plot feature importance
top_n = 15
top_features = feature_importance_df.head(top_n)

axes[0].barh(range(len(top_features)), top_features['Importance'], color='steelblue')
axes[0].set_yticks(range(len(top_features)))
axes[0].set_yticklabels(top_features['Feature'])
axes[0].set_xlabel('Importance')
axes[0].set_title(f'Top {top_n} Feature Importance (Albero Decisionale)', fontsize=14)
axes[0].invert_yaxis()  # Mostra la più importante in alto
axes[0].grid(True, alpha=0.3, axis='x')

# Aggiungi valori sulle barre
for i, (_, row) in enumerate(top_features.iterrows()):
    axes[0].text(row['Importance'] + 0.001, i, f'{row["Importance"]:.3f}', 
                va='center', fontsize=9)

# Matrice di confusione
cm = confusion_matrix(y_test, y_pred_best_test)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', ax=axes[1],
            xticklabels=['Basso Rischio', 'Alto Rischio'],
            yticklabels=['Basso Rischio', 'Alto Rischio'])
axes[1].set_xlabel('Predetto')
axes[1].set_ylabel('Reale')
axes[1].set_title('Matrice di Confusione (Test Set)', fontsize=14)

plt.tight_layout()
plt.show()

# 5.5 Analisi importanza con permutation
print(f"\n📊 PERMUTATION IMPORTANCE (validazione robusta):")

# Calcola permutation importance
perm_importance = permutation_importance(
    best_tree, X_test, y_test,
    n_repeats=10,
    random_state=42,
    scoring='accuracy'
)

# Crea DataFrame risultati
perm_importance_df = pd.DataFrame({
    'Feature': X.columns,
    'Importance Mean': perm_importance.importances_mean,
    'Importance Std': perm_importance.importances_std
}).sort_values('Importance Mean', ascending=False)

print(f"\nTop 10 feature per permutation importance:")
print(perm_importance_df.head(10).round(4).to_string(index=False))

# Confronto tra i due metodi
print(f"\n🔍 CONFRONTO FEATURE IMPORTANCE METODI:")
common_top = set(feature_importance_df.head(5)['Feature']).intersection(
    set(perm_importance_df.head(5)['Feature'])
)
print(f"  • Feature nelle top 5 di entrambi i metodi: {len(common_top)}")
print(f"  • Feature comuni: {', '.join(common_top) if common_top else 'Nessuna'}")

print("\n✅ Ottimizzazione e analisi avanzata completata!")

Parte 5: Ensemble Methods e Sistema di Decisione

▶ Random Forest e Sistema Decisionale Completo

6
Crea ensemble methods e sistema decisionale completo:
PYTHON
# 6. ENSEMBLE METHODS E SISTEMA DECISIONALE
print("🌲🌳🌴 ENSEMBLE METHODS: RANDOM FOREST")
print("="*50)

# 6.1 Random Forest Classifier
print("\n⚡ Creazione Random Forest...")
rf_clf = RandomForestClassifier(
    n_estimators=100,      # Numero alberi nella foresta
    max_depth=6,           # Profondità massima alberi
    min_samples_split=20,
    min_samples_leaf=10,
    class_weight='balanced',
    random_state=42,
    n_jobs=-1
)

# Addestramento
rf_clf.fit(X_train, y_train)

# Performance
y_pred_rf_train = rf_clf.predict(X_train)
y_pred_rf_test = rf_clf.predict(X_test)

print(f"✅ Random Forest addestrato con {rf_clf.n_estimators} alberi!")
print(f"\n📊 PERFORMANCE RANDOM FOREST:")
print(f"  • Accuracy training: {accuracy_score(y_train, y_pred_rf_train):.3f}")
print(f"  • Accuracy test: {accuracy_score(y_test, y_pred_rf_test):.3f}")
print(f"  • Precision test: {precision_score(y_test, y_pred_rf_test):.3f}")
print(f"  • Recall test: {recall_score(y_test, y_pred_rf_test):.3f}")
print(f"  • F1-score test: {f1_score(y_test, y_pred_rf_test):.3f}")

# 6.2 Confronto modelli
print(f"\n⚖️  CONFRONTO MODELLI:")

models_comparison = pd.DataFrame({
    'Modello': ['Decision Tree Base', 'Decision Tree Ottimizzato', 'Random Forest'],
    'Accuracy Train': [
        accuracy_score(y_train, y_pred_train),
        accuracy_score(y_train, y_pred_best_train),
        accuracy_score(y_train, y_pred_rf_train)
    ],
    'Accuracy Test': [
        accuracy_score(y_test, y_pred_test),
        accuracy_score(y_test, y_pred_best_test),
        accuracy_score(y_test, y_pred_rf_test)
    ],
    'F1-Score Test': [
        f1_score(y_test, y_pred_test),
        f1_score(y_test, y_pred_best_test),
        f1_score(y_test, y_pred_rf_test)
    ]
})

print(models_comparison.to_string(index=False))

# 6.3 Feature importance Random Forest
print(f"\n🔍 FEATURE IMPORTANCE RANDOM FOREST:")

rf_feature_importance = pd.DataFrame({
    'Feature': X.columns,
    'Importance': rf_clf.feature_importances_
}).sort_values('Importance', ascending=False)

print(f"\nTop 10 feature Random Forest:")
print(rf_feature_importance.head(10).to_string(index=False))

# 6.4 Visualizzazione confronto feature importance
print(f"\n🎨 VISUALIZZAZIONE CONFRONTO FEATURE IMPORTANCE...")

fig, axes = plt.subplots(1, 3, figsize=(18, 6))

# Decision Tree base
tree_top10 = feature_importance_df.head(10)
axes[0].barh(range(len(tree_top10)), tree_top10['Importance'], color='skyblue')
axes[0].set_yticks(range(len(tree_top10)))
axes[0].set_yticklabels(tree_top10['Feature'])
axes[0].set_title('Decision Tree Base', fontsize=12)
axes[0].invert_yaxis()

# Decision Tree ottimizzato
best_tree_top10 = feature_importance_df.head(10)
axes[1].barh(range(len(best_tree_top10)), best_tree_top10['Importance'], color='lightgreen')
axes[1].set_yticks(range(len(best_tree_top10)))
axes[1].set_yticklabels(best_tree_top10['Feature'])
axes[1].set_title('Decision Tree Ottimizzato', fontsize=12)
axes[1].invert_yaxis()

# Random Forest
rf_top10 = rf_feature_importance.head(10)
axes[2].barh(range(len(rf_top10)), rf_top10['Importance'], color='salmon')
axes[2].set_yticks(range(len(rf_top10)))
axes[2].set_yticklabels(rf_top10['Feature'])
axes[2].set_title('Random Forest', fontsize=12)
axes[2].invert_yaxis()

plt.suptitle('Confronto Feature Importance tra Modelli', fontsize=14, y=1.02)
plt.tight_layout()
plt.show()

# 6.5 Sistema decisionale interpretabile
print(f"\n💡 SISTEMA DECISIONALE INTERPRETABILE:")
print("Creazione regole di business dall'albero decisionale...")

# Estrai regole dall'albero ottimizzato
def extract_decision_rules(tree, feature_names, class_names):
    """Estrae regole di decisione dall'albero"""
    n_nodes = tree.tree_.node_count
    children_left = tree.tree_.children_left
    children_right = tree.tree_.children_right
    feature = tree.tree_.feature
    threshold = tree.tree_.threshold
    
    rules = []
    
    def traverse(node, depth, rule):
        if children_left[node] != children_right[node]:  # Nodo interno
            name = feature_names[feature[node]]
            rules.append(f"{'  ' * depth}SE {name} <= {threshold[node]:.2f}")
            traverse(children_left[node], depth + 1, rule + f" AND {name} <= {threshold[node]:.2f}")
            
            rules.append(f"{'  ' * depth}ALTRIMENTI (SE {name} > {threshold[node]:.2f})")
            traverse(children_right[node], depth + 1, rule + f" AND {name} > {threshold[node]:.2f}")
        else:  # Foglia
            class_id = np.argmax(tree.tree_.value[node])
            class_name = class_names[class_id]
            probability = tree.tree_.value[node][0][class_id] / np.sum(tree.tree_.value[node][0])
            rules.append(f"{'  ' * depth}→ DECISIONE: {class_name} (prob: {probability:.2f})")
    
    traverse(0, 0, "")
    return rules

# Estrai e mostra prime 20 regole
rules = extract_decision_rules(best_tree, X.columns, ['Basso Rischio', 'Alto Rischio'])
print("\nPrime 20 regole di decisione:")
for i, rule in enumerate(rules[:20]):
    print(rule)

# 6.6 Esempio decisioni per nuovi clienti
print(f"\n🔮 SISTEMA DI DECISIONE PER NUOVI CLIENTI:")

# Crea nuovi clienti "sconosciuti"
nuovi_clienti = pd.DataFrame({
    'eta': [25, 45, 60, 35, 55],
    'reddito_annuale': [30000, 60000, 80000, 45000, 120000],
    'debito_totale': [15000, 30000, 10000, 50000, 20000],
    'rapporto_debito_reddito': [0.5, 0.5, 0.125, 1.11, 0.17],
    'punteggio_credito': [580, 720, 800, 550, 750],
    'mesi_occupazione': [6, 120, 240, 24, 180],
    'valore_patrimonio': [50000, 200000, 500000, 80000, 600000],
    'prestiti_pregressi': [1, 3, 0, 5, 2],
    'patrimonio_debito_ratio': [3.33, 6.67, 50.0, 1.6, 30.0],
    'occupazione_encoded': [0, 1, 3, 0, 2],  # 0=Impiegato, 1=Lib.Prof., 2=Operaio, 3=Dirigente
    'possiede_casa': [0, 1, 1, 0, 1]
})

print("\n💼 NUOVI CLIENTI DA VALUTARE:")
for i, cliente in nuovi_clienti.iterrows():
    print(f"\nCliente #{i+1}:")
    print(f"  • Età: {cliente['eta']} anni")
    print(f"  • Reddito: €{cliente['reddito_annuale']:,.0f}")
    print(f"  • Debito: €{cliente['debito_totale']:,.0f}")
    print(f"  • Punteggio credito: {cliente['punteggio_credito']}")
    print(f"  • Occupazione: {occupazione_encoder.inverse_transform([int(cliente['occupazione_encoded'])])[0]}")
    print(f"  • Possiede casa: {'SI' if cliente['possiede_casa'] == 1 else 'NO'}")

# Predizioni
predizioni_rf = rf_clf.predict(nuovi_clienti)
probabilita_rf = rf_clf.predict_proba(nuovi_clienti)

print(f"\n🎯 DECISIONI DEL SISTEMA:")
print("="*60)
print(f"{'Cliente':^10} | {'Decisione':^15} | {'Prob Basso':^12} | {'Prob Alto':^12} | {'Raccomandazione':^20}")
print("-" * 60)

for i, (pred, prob) in enumerate(zip(predizioni_rf, probabilita_rf)):
    decisione = "APPROVATO" if pred == 0 else "RIFIUTATO"
    prob_basso = prob[0]
    prob_alto = prob[1]
    
    # Raccomandazione basata su probabilità
    if prob_alto > 0.7:
        raccomandazione = "ALTO RISCHIO - RIFIUTA"
    elif prob_alto > 0.4:
        raccomandazione = "MEDIO RISCHIO - VALUTA"
    else:
        raccomandazione = "BASSO RISCHIO - APPROVA"
    
    print(f"{i+1:^10} | {decisione:^15} | {prob_basso:^12.2f} | {prob_alto:^12.2f} | {raccomandazione:^20}")

print(f"\n✅ SISTEMA DI VALUTAZIONE CREDITO COMPLETATO!")
print(f"🎯 Il Random Forest raggiunge un'accuratezza del {accuracy_score(y_test, y_pred_rf_test)*100:.1f}%")
print(f"💡 Il sistema spiega ogni decisione con regole chiare e interpretabili!")
print(f"🏦 Pronto per l'uso in produzione per valutare richieste di prestito.")

Concetti di Alberi Decisionali Appresi

Congratulazioni! Ora conosci:

  • Alberi Decisionali: Modelli ML interpretabili per classificazione
  • Split Decisionale: Criteri Gini e Entropy per divisioni ottimali
  • Controllo Overfitting: max_depth, min_samples_split, min_samples_leaf
  • GridSearchCV: Ricerca automatica parametri ottimali
  • Feature Importance: Importanza delle variabili nelle decisioni
  • Permutation Importance: Metodo robusto per valutare importanza
  • Random Forest: Ensemble di alberi per migliore performance
  • Interpretabilità: Estrazione regole di business dagli alberi
  • Applicazione Reale: Sistema di valutazione rischio credito
  • Metriche Avanzate: Precision, Recall, F1-score per dati sbilanciati

Prossimi Passi negli Alberi Decisionali

Nella prossima esercitazione imparerai:

  • 🚀 Gradient Boosting Machines (XGBoost, LightGBM)
  • 🌳 Alberi decisionali per regressione
  • 🔍 SHAP values per interpretabilità avanzata
  • 📊 Alberi decisionali per time series
  • 🎯 Ottimizzazione bayesiana degli iperparametri

Hai costruito un sistema professionale di valutazione del credito con alberi decisionali!

Torna in alto