🧹 Preprocessing Dati con Scikit-Learn: La Fondazione del ML

Impara a pulire, trasformare e preparare dati per Machine Learning

Introduzione

Benvenuto nella base fondamentale di ogni progetto di Machine Learning: il preprocessing dei dati! In questa esercitazione imparerai le tecniche essenziali per pulire, trasformare e preparare i tuoi dati prima di applicare algoritmi ML. Il preprocessing è il segreto per modelli accurati e robusti.

Scenario Reale:
Lavorerai come Data Engineer in una startup fintech che ha appena raccolto dati grezzi sui clienti. Il tuo compito è trasformare questi dati sporchi e disorganizzati in un dataset pulito pronto per i modelli ML di valutazione del credito.

⚠️ ATTENZIONE: COPIA BLOCCATA

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

Parte 1: Creazione Dataset Realistico “Sporco”

▶ Importazione Librerie e Creazione Dati Grezzi

1
Crea una nuova cella su Google Colab e importa le librerie:
PYTHON
# IMPORT LIBRERIE PER PREPROCESSING COMPLETO
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import (
    StandardScaler, MinMaxScaler, RobustScaler,
    LabelEncoder, OneHotEncoder, OrdinalEncoder,
    PolynomialFeatures, PowerTransformer,
    SimpleImputer, KBinsDiscretizer
)
from sklearn.model_selection import train_test_split
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.feature_selection import SelectKBest, f_classif
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 preprocessing importate correttamente!")
print("🧹 Strumenti disponibili: Imputer, Scaler, Encoder, Transformer, Pipeline")

▶ Creazione Dataset Fintech "Sporco" Realistico

2
Crea un dataset finanziario realistico con problemi comuni:
PYTHON
# 2. CREAZIONE DATASET FINTECH CON PROBLEMI REALISTICI
print("🏦 CREAZIONE DATASET FINTECH 'SPORCO'")
print("="*50)

np.random.seed(42)  # Per risultati riproducibili
n_clienti = 200

# === CREAZIONE FEATURES CON PROBLEMI REALISTICI ===

# 1. Feature numerica con outliers e valori mancanti (reddito annuale)
reddito_base = np.random.normal(35000, 15000, n_clienti)
# Aggiungi outliers
outlier_indices = np.random.choice(n_clienti, 10, replace=False)
reddito_base[outlier_indices] = np.random.uniform(200000, 500000, 10)
# Aggiungi valori mancanti (NaN)
nan_indices = np.random.choice(n_clienti, 15, replace=False)
reddito_base[nan_indices] = np.nan

# 2. Feature numerica con distribuzione skew (debito)
debito = np.random.exponential(5000, n_clienti)
debito = np.where(debito > 30000, 30000, debito)  # Cap outliers

# 3. Feature categorica con problemi (stato occupazionale)
stati = ['Impiegato', 'Libero Professionista', 'Disoccupato', 'Pensionato', 'Studente', 
         'impegnato', 'LIBERO PROF.', 'disoccupato', '', 'NaN', 'N/A']
occupazione = np.random.choice(stati, n_clienti, p=[0.3, 0.2, 0.15, 0.1, 0.1, 0.05, 0.03, 0.02, 0.02, 0.02, 0.01])

# 4. Feature ordinale con problemi (rating credito)
rating_valori = ['AAA', 'AA', 'A', 'BBB', 'BB', 'B', 'CCC', 'D', 'NR', 'Unknown']
rating = np.random.choice(rating_valori, n_clienti, p=[0.1, 0.15, 0.2, 0.2, 0.1, 0.08, 0.05, 0.02, 0.05, 0.05])

# 5. Feature numerica con scale diversa (età)
eta = np.random.randint(18, 75, n_clienti)
# Aggiungi alcuni valori impossibili
eta[np.random.choice(n_clienti, 5)] = [150, -5, 200, 0, 1000]

# 6. Feature binaria con problemi (possiede casa)
casa_valori = ['SI', 'NO', 'Si', 'No', 'si', 'no', '1', '0', True, False, np.nan]
casa = np.random.choice(casa_valori, n_clienti)

# 7. Feature temporale con problemi (anni presso banca)
anni_banca = np.random.uniform(0, 30, n_clienti)
# Aggiungi valori negativi
anni_banca[np.random.choice(n_clienti, 3)] = [-2, -1, -5]

# 8. Target variable (rischio credito: 0=basso, 1=alto)
rischio = np.random.choice([0, 1], n_clienti, p=[0.7, 0.3])

# Crea DataFrame
df_fintech = pd.DataFrame({
    'id_cliente': range(1001, 1001 + n_clienti),
    'reddito_annuale': reddito_base,
    'debito_totale': debito,
    'occupazione': occupazione,
    'rating_credito': rating,
    'eta': eta,
    'possiede_casa': casa,
    'anni_presso_banca': anni_banca,
    'rischio_credito': rischio
})

print(f"📊 DATASET CREATO CON PROBLEMI REALISTICI:")
print(f"  • Numero clienti: {len(df_fintech)}")
print(f"  • Numero features: {len(df_fintech.columns) - 2} (esclusi ID e target)")
print(f"  • Target: rischio_credito (0=basso rischio, 1=alto rischio)")

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

print(f"\n⚠️  PROBLEMI INTRODOTTI NEL DATASET:")
print("  1. Valori mancanti (NaN) in reddito_annuale")
print("  2. Outliers estremi in reddito_annuale")
print("  3. Distribuzione skew in debito_totale")
print("  4. Categoriche inconsistenti (maiuscole/minuscole, valori vuoti)")
print("  5. Valori impossibili in eta (negativi, troppo alti)")
print("  6. Dati duplicati in categoriche (SI/Si/si)")
print("  7. Valori negativi in anni_presso_banca")
print("  8. Categoriche con troppe categorie (occupazione)")

Parte 2: Analisi Esplorativa Dati (EDA)

▶ Identificazione Problemi e Valori Mancanti

3
Analizza i dati e identifica tutti i problemi:
PYTHON
# 3. ANALISI ESPLORATIVA DATI (EDA)
print("🔍 ANALISI ESPLORATIVA DATASET FINTECH")
print("="*50)

# 3.1 Informazioni base del dataset
print("\n📊 INFORMAZIONI DATASET:")
print(f"Dimensioni: {df_fintech.shape[0]} righe × {df_fintech.shape[1]} colonne")
print(f"\nTipi di dati:")
print(df_fintech.dtypes)

# 3.2 Valori mancanti
print(f"\n⚠️  VALORI MANCANTI (NaN):")
missing_values = df_fintech.isnull().sum()
missing_percentage = (missing_values / len(df_fintech)) * 100
missing_df = pd.DataFrame({
    'Valori Mancanti': missing_values,
    'Percentuale': missing_percentage
})
print(missing_df[missing_df['Valori Mancanti'] > 0])

# 3.3 Statistiche descrittive per features numeriche
print(f"\n📈 STATISTICHE DESCRITTIVE (FEATURES NUMERICHE):")
numeric_features = ['reddito_annuale', 'debito_totale', 'eta', 'anni_presso_banca']
print(df_fintech[numeric_features].describe())

# 3.4 Analisi distribuzioni e outliers
print(f"\n🎨 VISUALIZZAZIONE PROBLEMI DATI...")

fig, axes = plt.subplots(2, 3, figsize=(16, 10))
fig.suptitle('Analisi Problemi Dataset Fintech', fontsize=16, fontweight='bold')

# 1. Distribuzione reddito (con outliers)
axes[0, 0].boxplot(df_fintech['reddito_annuale'].dropna())
axes[0, 0].set_title('Reddito Annuale (Boxplot)')
axes[0, 0].set_ylabel('€')
axes[0, 0].grid(True, alpha=0.3)

# 2. Distribuzione debito (skew)
axes[0, 1].hist(df_fintech['debito_totale'], bins=30, edgecolor='black', alpha=0.7, color='skyblue')
axes[0, 1].set_title('Distribuzione Debito (Skewed)')
axes[0, 1].set_xlabel('€')
axes[0, 1].set_ylabel('Frequenza')
axes[0, 1].grid(True, alpha=0.3)

# 3. Distribuzione età (con valori anomali)
axes[0, 2].hist(df_fintech['eta'], bins=30, edgecolor='black', alpha=0.7, color='lightgreen')
axes[0, 2].set_title('Distribuzione Età (Valori Impossibili)')
axes[0, 2].set_xlabel('Anni')
axes[0, 2].set_ylabel('Frequenza')
axes[0, 2].grid(True, alpha=0.3)
# Evidenzia valori anomali
axes[0, 2].axvspan(0, 18, alpha=0.2, color='red')
axes[0, 2].axvspan(100, 1000, alpha=0.2, color='red')

# 4. Valori categorici occupazione
occupazione_counts = df_fintech['occupazione'].value_counts()
axes[1, 0].bar(range(len(occupazione_counts)), occupazione_counts.values, color='salmon')
axes[1, 0].set_title('Valori Occupazione (Inconsistenti)')
axes[1, 0].set_xlabel('Categorie')
axes[1, 0].set_ylabel('Conteggio')
axes[1, 0].set_xticks(range(len(occupazione_counts)))
axes[1, 0].set_xticklabels(occupazione_counts.index, rotation=45, ha='right')
axes[1, 0].grid(True, alpha=0.3)

# 5. Rating credito
rating_counts = df_fintech['rating_credito'].value_counts().sort_index()
axes[1, 1].bar(range(len(rating_counts)), rating_counts.values, color='gold')
axes[1, 1].set_title('Rating Credito (Ordinale)')
axes[1, 1].set_xlabel('Rating')
axes[1, 1].set_ylabel('Conteggio')
axes[1, 1].set_xticks(range(len(rating_counts)))
axes[1, 1].set_xticklabels(rating_counts.index, rotation=45, ha='right')
axes[1, 1].grid(True, alpha=0.3)

# 6. Possiede casa (inconsistenze)
casa_counts = df_fintech['possiede_casa'].value_counts()
axes[1, 2].bar(range(len(casa_counts)), casa_counts.values, color='violet')
axes[1, 2].set_title('Possiede Casa (Binaria Inconsistente)')
axes[1, 2].set_xlabel('Valore')
axes[1, 2].set_ylabel('Conteggio')
axes[1, 2].set_xticks(range(len(casa_counts)))
axes[1, 2].set_xticklabels([str(x) for x in casa_counts.index], rotation=45, ha='right')
axes[1, 2].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# 3.5 Analisi valori unici per features categoriche
print(f"\n🔍 ANALISI VALORI UNICI FEATURES CATEGORICHE:")
categorical_features = ['occupazione', 'rating_credito', 'possiede_casa']

for feature in categorical_features:
    unique_values = df_fintech[feature].unique()
    print(f"\n{feature.upper()}:")
    print(f"  • Numero valori unici: {len(unique_values)}")
    print(f"  • Valori: {sorted([str(x) for x in unique_values])[:10]}")  # Mostra primi 10
    if len(unique_values) > 10:
        print(f"    ... e altri {len(unique_values) - 10} valori")

print(f"\n✅ Analisi EDA completata! Problemi identificati.")

Parte 3: Pulizia Dati Manuale

▶ Correzione Manuale Problemi Evidenti

4
Pulisci manualmente i problemi più evidenti:
PYTHON
# 4. PULIZIA MANUALE DATI
print("🧹 PULIZIA MANUALE DATASET")
print("="*50)

# Crea una copia per la pulizia
df_clean = df_fintech.copy()

print("🔧 Applicando correzioni manuali...")

# 4.1 Pulizia feature 'occupazione'
print("\n1. 🔄 Normalizzazione 'occupazione':")
# Mappa valori inconsistenti a categorie standard
occupazione_mapping = {
    'Impiegato': 'Impiegato',
    'impegnato': 'Impiegato',  # Typo
    'Libero Professionista': 'Libero Professionista',
    'LIBERO PROF.': 'Libero Professionista',  # Maiuscole
    'Disoccupato': 'Disoccupato',
    'disoccupato': 'Disoccupato',  # Minuscole
    'Pensionato': 'Pensionato',
    'Studente': 'Studente',
    '': 'Altro',  # Valore vuoto
    'NaN': 'Altro',
    'N/A': 'Altro'
}
df_clean['occupazione'] = df_clean['occupazione'].map(occupazione_mapping).fillna('Altro')
print(f"   • Categorie ridotte da {len(df_fintech['occupazione'].unique())} a {len(df_clean['occupazione'].unique())}")

# 4.2 Pulizia feature 'possiede_casa'
print("\n2. 🔄 Normalizzazione 'possiede_casa':")
casa_mapping = {
    'SI': 'SI',
    'Si': 'SI',
    'si': 'SI',
    '1': 'SI',
    True: 'SI',
    'NO': 'NO',
    'No': 'NO',
    'no': 'NO',
    '0': 'NO',
    False: 'NO',
    np.nan: 'NO'  # Valori mancanti mappati a NO
}
df_clean['possiede_casa'] = df_clean['possiede_casa'].map(casa_mapping).fillna('NO')
print(f"   • Categorie ridotte da {len(df_fintech['possiede_casa'].unique())} a {len(df_clean['possiede_casa'].unique())}")

# 4.3 Correzione valori impossibili in 'eta'
print("\n3. 🎯 Correzione valori impossibili 'eta':")
# Identifica e correggi valori fuori range realistico (18-100)
mask_eta_invalid = (df_clean['eta'] < 18) | (df_clean['eta'] > 100)
invalid_count = mask_eta_invalid.sum()
print(f"   • Valori impossibili trovati: {invalid_count}")

# Sostituisci con la mediana degli altri valori
eta_median = df_clean.loc[~mask_eta_invalid, 'eta'].median()
df_clean.loc[mask_eta_invalid, 'eta'] = eta_median
print(f"   • Valori corretti con mediana: {eta_median:.0f} anni")

# 4.4 Correzione valori negativi in 'anni_presso_banca'
print("\n4. 🔢 Correzione valori negativi 'anni_presso_banca':")
mask_anni_negativi = df_clean['anni_presso_banca'] < 0
neg_count = mask_anni_negativi.sum()
print(f"   • Valori negativi trovati: {neg_count}")

# Sostituisci negativi con 0
df_clean.loc[mask_anni_negativi, 'anni_presso_banca'] = 0
print(f"   • Valori negativi impostati a 0")

# 4.5 Verifica risultati pulizia
print(f"\n✅ PULIZIA MANUALE COMPLETATA!")
print(f"\n📊 CONFRONTO PRIMA/DOPO PULIZIA:")

comparison_data = []
for feature in ['occupazione', 'possiede_casa', 'eta', 'anni_presso_banca']:
    before_unique = len(df_fintech[feature].dropna().unique())
    after_unique = len(df_clean[feature].dropna().unique())
    comparison_data.append([feature, before_unique, after_unique])

comparison_df = pd.DataFrame(comparison_data, columns=['Feature', 'Valori Unici (Prima)', 'Valori Unici (Dopo)'])
print(comparison_df.to_string(index=False))

print(f"\n📋 ESEMPIO DATI PULITI (prime 5 righe):")
print(df_clean.head())

print(f"\n🔍 VALORI MANCANTI DOPO PULIZIA MANUALE:")
missing_after = df_clean.isnull().sum()
print(missing_after[missing_after > 0])

Parte 4: Preprocessing Automatico con Scikit-Learn

▶ Imputazione Valori Mancanti e Scaling

5
Applica preprocessing automatico con Scikit-Learn:
PYTHON
# 5. PREPROCESSING AUTOMATICO CON SCIKIT-LEARN
print("🤖 PREPROCESSING AUTOMATICO CON SCIKIT-LEARN")
print("="*50)

# Separa features e target
X = df_clean.drop(['id_cliente', 'rischio_credito'], axis=1)
y = df_clean['rischio_credito']

print(f"📊 DATI PER PREPROCESSING:")
print(f"  • Features (X): {X.shape}")
print(f"  • Target (y): {y.shape}")
print(f"  • Distribuzione target: {y.value_counts().to_dict()}")

# 5.1 Identifica tipi di features
numeric_features = X.select_dtypes(include=['int64', 'float64']).columns.tolist()
categorical_features = X.select_dtypes(include=['object']).columns.tolist()

print(f"\n🔍 IDENTIFICAZIONE TIPI FEATURES:")
print(f"  • Features numeriche ({len(numeric_features)}): {numeric_features}")
print(f"  • Features categoriche ({len(categorical_features)}): {categorical_features}")

# 5.2 Definizione transformer per ogni tipo di feature
print(f"\n⚙️  CREAZIONE PIPELINE DI PREPROCESSING...")

# Pipeline per features numeriche
numeric_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='median')),  # Imputa con mediana
    ('scaler', RobustScaler())  # RobustScaler per gestire outliers
])

# Pipeline per features categoriche
categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='most_frequent')),  # Imputa con moda
    ('onehot', OneHotEncoder(handle_unknown='ignore', sparse_output=False))  # One-hot encoding
])

# 5.3 Combinazione transformer con ColumnTransformer
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
        ('cat', categorical_transformer, categorical_features)
    ],
    remainder='passthrough'  # Mantieni altre colonne
)

print(f"✅ Pipeline creata con successo!")
print(f"  • Numeriche: Imputazione (mediana) → RobustScaler")
print(f"  • Categoriche: Imputazione (moda) → OneHotEncoding")

# 5.4 Applica preprocessing
print(f"\n⚡ Applicando preprocessing ai dati...")
X_preprocessed = preprocessor.fit_transform(X)

# Ottieni nomi features dopo preprocessing
# Per one-hot encoding, otteniamo i nomi delle colonne
numeric_feature_names = numeric_features
categorical_feature_names = []

# Ottieni nomi delle colonne one-hot
if len(categorical_features) > 0:
    onehot_encoder = preprocessor.named_transformers_['cat'].named_steps['onehot']
    categorical_feature_names = onehot_encoder.get_feature_names_out(categorical_features).tolist()

# Nomi finali delle features
all_feature_names = numeric_feature_names + categorical_feature_names

print(f"\n📊 DATI DOPO PREPROCESSING:")
print(f"  • Shape originale: {X.shape}")
print(f"  • Shape dopo preprocessing: {X_preprocessed.shape}")
print(f"  • Numero features dopo one-hot: {len(all_feature_names)}")

# 5.5 Converti in DataFrame per visualizzazione
X_preprocessed_df = pd.DataFrame(X_preprocessed, columns=all_feature_names)

print(f"\n📋 ESEMPIO DATI PREPROCESSATI (prime 5 righe, prime 10 colonne):")
print(X_preprocessed_df.iloc[:5, :10])

print(f"\n📈 STATISTICHE DATI PREPROCESSATI (features numeriche):")
print(X_preprocessed_df[numeric_feature_names].describe().round(3))

Parte 5: Tecniche Avanzate e Confronto

▶ Tecniche Avanzate e Confronto Metodi

6
Esplora tecniche avanzate e confronta diversi metodi:
PYTHON
# 6. TECNICHE AVANZATE DI PREPROCESSING
print("🚀 TECNICHE AVANZATE E CONFRONTO METODI")
print("="*50)

# 6.1 Confronto diversi scaler per features numeriche
print("\n📊 CONFRONTO DIVERSI SCALER:")

# Prendiamo solo le features numeriche originali
X_numeric = df_clean[numeric_features].copy()

# Applica diversi scaler
scalers = {
    'StandardScaler (media=0, var=1)': StandardScaler(),
    'MinMaxScaler (range 0-1)': MinMaxScaler(),
    'RobustScaler (resistente outliers)': RobustScaler(),
    'PowerTransformer (Yeo-Johnson)': PowerTransformer(method='yeo-johnson')
}

# Visualizzazione confronto
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
fig.suptitle('Confronto Diversi Scaler per Reddito Annuale', fontsize=16, fontweight='bold')

for idx, (scaler_name, scaler) in enumerate(scalers.items()):
    ax = axes[idx // 2, idx % 2]
    
    # Applica scaler
    X_scaled = scaler.fit_transform(X_numeric[['reddito_annuale']].values)
    
    # Istogramma
    ax.hist(X_scaled, bins=30, edgecolor='black', alpha=0.7, color=['skyblue', 'lightgreen', 'salmon', 'gold'][idx])
    
    # Statistiche
    ax.axvline(X_scaled.mean(), color='red', linestyle='--', linewidth=2, label=f'Media: {X_scaled.mean():.2f}')
    ax.axvline(np.median(X_scaled), color='green', linestyle='--', linewidth=2, label=f'Mediana: {np.median(X_scaled):.2f}')
    
    ax.set_title(scaler_name, fontsize=12)
    ax.set_xlabel('Valore Scalato')
    ax.set_ylabel('Frequenza')
    ax.legend(fontsize=9)
    ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# 6.2 Encoding avanzato per features categoriche
print("\n🔤 CONFRONTO METODI ENCODING CATEGORICHE:")

# Esempio con la feature 'occupazione'
occupazione_data = df_clean[['occupazione']].copy()

encoding_methods = {
    'Label Encoding': LabelEncoder(),
    'One-Hot Encoding': OneHotEncoder(sparse_output=False),
    'Ordinal Encoding': OrdinalEncoder(categories=[['Studente', 'Disoccupato', 'Impiegato', 'Libero Professionista', 'Pensionato', 'Altro']])
}

encoding_results = {}
for method_name, encoder in encoding_methods.items():
    try:
        if method_name == 'One-Hot Encoding':
            encoded = encoder.fit_transform(occupazione_data)
            encoding_results[method_name] = {
                'shape': encoded.shape,
                'example': encoded[0] if len(encoded) > 0 else []
            }
        else:
            encoded = encoder.fit_transform(occupazione_data.values.ravel())
            encoding_results[method_name] = {
                'shape': encoded.shape,
                'example': encoded[:5] if len(encoded) > 0 else []
            }
    except Exception as e:
        encoding_results[method_name] = {'error': str(e)}

print("Risultati encoding:")
for method, result in encoding_results.items():
    print(f"\n  • {method}:")
    if 'error' in result:
        print(f"    Errore: {result['error']}")
    else:
        print(f"    Shape: {result['shape']}")
        print(f"    Esempio primi valori: {result['example']}")

# 6.3 Discretizzazione (binning) di features numeriche
print("\n📦 DISCRETIZZAZIONE (BINNING) FEATURES NUMERICHE:")

# Esempio con 'eta'
print("\nEsempio discretizzazione 'eta':")
kbins = KBinsDiscretizer(n_bins=5, encode='ordinal', strategy='uniform')
eta_binned = kbins.fit_transform(df_clean[['eta']])

# Mostra bins
bin_edges = kbins.bin_edges_[0]
print(f"  • Numero bins: {kbins.n_bins_[0]}")
print(f"  • Bin edges: {bin_edges.round(1)}")
print(f"  • Esempio valori originali: {df_clean['eta'].iloc[:5].values}")
print(f"  • Esempio valori binned: {eta_binned[:5].flatten()}")

# 6.4 Creazione nuove features (feature engineering)
print("\n🎯 CREAZIONE NUOVE FEATURES (FEATURE ENGINEERING):")

# Crea alcune nuove features derivate
df_engineered = df_clean.copy()

# 1. Ratio debito/reddito
df_engineered['debito_reddito_ratio'] = df_engineered['debito_totale'] / (df_engineered['reddito_annuale'] + 1)  # +1 per evitare divisione per 0

# 2. Età categorizzata
df_engineered['eta_categoria'] = pd.cut(df_engineered['eta'], 
                                       bins=[0, 30, 45, 60, 100], 
                                       labels=['Giovane', 'Adulto', 'Maturo', 'Senior'])

# 3. Interazione tra features
df_engineered['eta_anni_banca'] = df_engineered['eta'] * df_engineered['anni_presso_banca']

print("Nuove features create:")
print(f"  1. debito_reddito_ratio: rapporto debito/reddito")
print(f"  2. eta_categoria: età in categorie")
print(f"  3. eta_anni_banca: interazione età × anni banca")

print(f"\n📋 ESEMPIO NUOVE FEATURES (prime 5 righe):")
print(df_engineered[['eta', 'reddito_annuale', 'debito_totale', 'anni_presso_banca', 
                    'debito_reddito_ratio', 'eta_categoria', 'eta_anni_banca']].head())

# 6.5 Pipeline completa per ML
print("\n⚡ PIPELINE COMPLETA PER MACHINE LEARNING:")

# Definisci pipeline completa
from sklearn.ensemble import RandomForestClassifier

ml_pipeline = Pipeline(steps=[
    ('preprocessor', preprocessor),  # Il nostro preprocessor di prima
    ('classifier', RandomForestClassifier(n_estimators=100, random_state=42))
])

print("✅ Pipeline ML creata con successo!")
print("  Passaggi: Preprocessing → Classificatore (Random Forest)")

# 6.6 Split dati e valutazione rapida
print("\n🎯 SPLIT DATI E VALUTAZIONE RAPIDA:")

# Split train-test
X_final = df_clean.drop(['id_cliente', 'rischio_credito'], axis=1)
y_final = df_clean['rischio_credito']

X_train, X_test, y_train, y_test = train_test_split(
    X_final, y_final, test_size=0.2, random_state=42, stratify=y_final
)

print(f"  • Training set: {X_train.shape[0]} campioni")
print(f"  • Test set: {X_test.shape[0]} campioni")
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()}")

# Addestra pipeline
print("\n🤖 Addestramento pipeline ML...")
ml_pipeline.fit(X_train, y_train)

# Valutazione
train_score = ml_pipeline.score(X_train, y_train)
test_score = ml_pipeline.score(X_test, y_test)

print(f"\n📊 PERFORMANCE MODELLO:")
print(f"  • Accuracy training: {train_score:.3f}")
print(f"  • Accuracy test: {test_score:.3f}")

if abs(train_score - test_score) > 0.1:
    print("  ⚠️  Attenzione: possibile overfitting!")
else:
    print("  ✅ Modello ben bilanciato tra training e test")

print(f"\n🎉 PREPROCESSING COMPLETATO CON SUCCESSO!")
print("   I tuoi dati sono ora pronti per qualsiasi algoritmo di Machine Learning!")

Concetti di Preprocessing Appresi

Congratulazioni! Ora conosci:

  • EDA Completa: Analisi Esplorativa Dati per identificare problemi
  • Pulizia Manuale: Correzione valori inconsistenti e impossibili
  • Imputazione Valori Mancanti: SimpleImputer con strategie median/moda
  • Scaling Features: StandardScaler, MinMaxScaler, RobustScaler
  • Encoding Categoriche: LabelEncoder, OneHotEncoder, OrdinalEncoder
  • ColumnTransformer: Applica transformer diversi a colonne diverse
  • Pipeline: Sequenza automatizzata di passaggi di preprocessing
  • Feature Engineering: Creazione nuove features da quelle esistenti
  • Discretizzazione: KBinsDiscretizer per trasformazione in categorie
  • Power Transformer: Trasformazioni per normalizzare distribuzioni

Prossimi Passi nel Preprocessing

Nella prossima esercitazione imparerai:

  • 📊 Feature selection automatica
  • 🔍 Dimensionality reduction (PCA, t-SNE)
  • 🤖 AutoML per preprocessing automatizzato
  • 📈 Time series preprocessing
  • 🖼️ Image data preprocessing

Hai padroneggiato le tecniche fondamentali di preprocessing per Machine Learning!

Torna in alto