# Esempio 022: Riduzione della dimensionalità con la PCA (Principal Component Analysis)

# ============================================================
# Importazione delle librerie e creazione del dataset
# ============================================================
import numpy as np
import pandas as pd
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

# Creiamo un dataset di esempio con 2 feature correlate tra loro,
# ad esempio "ore di studio" e "punteggio esame" ottenuto
data = np.array([
    [2.5, 2.4],
    [0.5, 0.7],
    [2.2, 2.9],
    [1.9, 2.2],
    [3.1, 3.0],
    [2.3, 2.7],
    [2.0, 1.6],
    [1.0, 1.1],
    [1.5, 1.6],
    [1.1, 0.9]
])

# Convertiamo in DataFrame per una lettura più chiara delle feature
df = pd.DataFrame(data, columns=["Ore di studio", "Punteggio esame"])
print("Dataset originale:")
print(df)

# ============================================================
# Standardizzazione dei dati
# ============================================================
# La PCA è sensibile alla scala delle feature: variabili con range
# più ampi dominerebbero artificialmente le componenti principali.
# Standardizziamo quindi i dati a media 0 e deviazione standard 1.
scaler = StandardScaler()
data_scaled = scaler.fit_transform(df)

df_scaled = pd.DataFrame(data_scaled, columns=df.columns)
print("\nDataset standardizzato:")
print(df_scaled)

# ============================================================
# Applicazione della PCA
# ============================================================
# Riduciamo le 2 feature originali a 1 sola componente principale,
# che cattura la maggior parte della varianza presente nei dati.
pca = PCA(n_components=1)
transformed_data = pca.fit_transform(data_scaled)

print("\nDati trasformati (1 componente principale):")
print(transformed_data)

# ============================================================
# Analisi della varianza spiegata
# ============================================================
# explained_variance_ratio_ indica quanta informazione (varianza)
# del dataset originale è stata conservata dalla componente principale.
varianza_spiegata = pca.explained_variance_ratio_
print(f"\nVarianza spiegata dalla prima componente: {varianza_spiegata[0]:.2%}")

# ============================================================
# Ricostruzione approssimata dei dati originali
# ============================================================
# Con inverse_transform torniamo allo spazio originale a 2 dimensioni,
# osservando quanta informazione è andata persa nella riduzione.
data_reconstructed = pca.inverse_transform(transformed_data)

df_reconstructed = pd.DataFrame(data_reconstructed, columns=df.columns)
print("\nDati ricostruiti dopo la riduzione dimensionale:")
print(df_reconstructed)

# Calcoliamo l'errore quadratico medio di ricostruzione rispetto
# ai dati standardizzati originali
errore_ricostruzione = np.mean((data_scaled - data_reconstructed) ** 2)
print(f"\nErrore quadratico medio di ricostruzione: {errore_ricostruzione:.4f}")