Distribuzione dei Dati nel Machine Learning
Scopri come funzionano le distribuzioni dei dati in Python: distribuzioni normale, asimmetrica e uniforme, come rilevarle e perché sono importanti.
La distribuzione dei dati descrive come i valori sono dispersi all'interno di un dataset — quali valori sono comuni, quali sono rari e quanto si estendono dal centro. Prima di addestrare qualsiasi modello di machine learning, comprendere la distribuzione dei dati ti aiuta a scegliere l'algoritmo giusto, rilevare anomalie, decidere se scalare o trasformare le feature ed evitare distorsioni nascoste nelle previsioni.
Questo capitolo tratta le forme di distribuzione più comuni, come misurarle in Python usando NumPy e SciPy, e cosa significa ciascuna distribuzione per le tue decisioni di modellazione.
Cos'è la Distribuzione dei Dati?
Una distribuzione dei dati è il pattern con cui ogni valore (o intervallo di valori) appare in un dataset. Quando si visualizza una distribuzione, si ottiene tipicamente una curva o un istogramma la cui forma rivela molte informazioni sui dati sottostanti.
Le tre proprietà che definiscono qualsiasi distribuzione sono:
- Centro — dove si concentra la maggior parte dei valori (misurato da media, mediana e moda)
- Dispersione — quanto i valori si discostano dal centro (misurata dalla deviazione standard e dall'intervallo interquartile)
- Forma — se la distribuzione è simmetrica, asimmetrica o piatta
Comprendere tutte e tre è indispensabile prima di inserire i dati in un modello.
Tipi di Distribuzione Comuni
Distribuzione Normale
La distribuzione normale (chiamata anche distribuzione gaussiana) è la distribuzione più importante in statistica e machine learning. Il suo istogramma forma una curva a campana simmetrica: i valori si concentrano attorno alla media e la frequenza diminuisce in modo uniforme su entrambi i lati.
Proprietà principali:
- Media, mediana e moda sono tutte uguali.
- Circa il 68% dei valori cade entro una deviazione standard dalla media, il 95% entro due e il 99,7% entro tre (regola empirica).
- Definita interamente da due parametri: media (μ) e deviazione standard (σ).
Molti fenomeni del mondo reale approssimano una distribuzione normale — l'altezza degli esseri umani, gli errori di misurazione e i punteggi QI sono esempi classici. Algoritmi come l'analisi discriminante lineare, il Gaussian naive Bayes e la regressione lineare assumono (o traggono vantaggio da) feature distribuite normalmente.
import numpy as np
from scipy import stats
# Simulate 1 000 adult heights (cm) drawn from a normal distribution
rng = np.random.default_rng(seed=42)
heights = rng.normal(loc=170, scale=10, size=1000)
print(f"Mean: {np.mean(heights):.1f} cm")
print(f"Std: {np.std(heights):.1f} cm")
# Verify the empirical rule
within_1_std = np.sum(np.abs(heights - 170) < 10) / 1000 * 100
within_2_std = np.sum(np.abs(heights - 170) < 20) / 1000 * 100
within_3_std = np.sum(np.abs(heights - 170) < 30) / 1000 * 100
print(f"Within 1 std: {within_1_std:.1f}% (expected ~68%)")
print(f"Within 2 std: {within_2_std:.1f}% (expected ~95%)")
print(f"Within 3 std: {within_3_std:.1f}% (expected ~99.7%)")Output:
Mean: 169.7 cm
Std: 9.9 cm
Within 1 std: 68.8% (expected ~68%)
Within 2 std: 95.7% (expected ~95%)
Within 3 std: 99.8% (expected ~99.7%)Distribuzione Asimmetrica
Una distribuzione asimmetrica è non simmetrica: una coda è più lunga dell'altra. L'asimmetria è misurata dalla statistica di skewness — valori positivi indicano un'asimmetria a destra (positiva), valori negativi indicano un'asimmetria a sinistra (negativa) e valori prossimi allo zero indicano una sostanziale simmetria.
Asimmetrica positiva (coda destra): la coda si estende verso destra. La media è trascinata sopra la mediana da un piccolo numero di valori molto elevati. I redditi e i prezzi delle case ne sono esempi tipici — una manciata di percettori molto alti o proprietà molto costose trascinano la media ben al di sopra della mediana.
Asimmetrica negativa (coda sinistra): la coda si estende verso sinistra. I punteggi degli esami in un test facile mostrano spesso questo pattern — la maggior parte degli studenti ottiene punteggi vicini al massimo, ma alcuni ottengono punteggi molto bassi.
import numpy as np
from scipy import stats
# Right-skewed: a small number of very high values pull the mean up
salaries = np.array([30000, 32000, 34000, 35000, 36000, 38000,
40000, 42000, 45000, 55000, 70000, 120000, 200000])
print("--- Salary distribution ---")
print(f"Mean: {np.mean(salaries):.0f}") # pulled up by outliers
print(f"Median: {np.median(salaries):.0f}") # more representative center
print(f"Skewness: {stats.skew(salaries):.2f}") # positive = right skew
# Left-skewed: most values are high, a few are very low
exam_scores = np.array([40, 68, 75, 80, 82, 85, 88, 90, 91, 92, 93, 95, 98])
print("\n--- Exam score distribution ---")
print(f"Mean: {np.mean(exam_scores):.1f}")
print(f"Median: {np.median(exam_scores):.1f}")
print(f"Skewness: {stats.skew(exam_scores):.2f}") # negative = left skewOutput:
--- Salary distribution ---
Mean: 59769
Median: 40000
Skewness: 2.16
--- Exam score distribution ---
Mean: 82.8
Median: 88.0
Skewness: -1.77Quando è presente un'asimmetria, la media è una misura del centro fuorviante. La mediana è generalmente una statistica riassuntiva migliore per i dati asimmetrici.
Distribuzione Uniforme
In una distribuzione uniforme, ogni valore (o intervallo di valori) è ugualmente probabile. Un dado a sei facce equo produce una distribuzione uniforme discreta; estrarre casualmente un numero in virgola mobile tra 0 e 1 produce una distribuzione uniforme continua.
import numpy as np
rng = np.random.default_rng(seed=42)
# Continuous uniform distribution between 0 and 1
uniform_data = rng.uniform(low=0, high=1, size=10000)
print(f"Mean: {np.mean(uniform_data):.3f} (expected 0.500)")
print(f"Std: {np.std(uniform_data):.3f} (expected ~0.289)")
print(f"Min: {np.min(uniform_data):.3f}")
print(f"Max: {np.max(uniform_data):.3f}")Output:
Mean: 0.497 (expected 0.500)
Std: 0.288 (expected ~0.289)
Min: 0.000
Max: 1.000Le distribuzioni uniformi compaiono nel campionamento casuale, nell'augmentation dei dati e come prior nei modelli bayesiani.
Riepilogare una Distribuzione in Python
Prima della modellazione, calcola sempre un rapido sommario di ciascuna feature. NumPy e SciPy insieme coprono le statistiche principali:
import numpy as np
from scipy import stats
data = np.array([12, 15, 14, 10, 18, 14, 13, 16, 14, 12])
print("--- Distribution Summary ---")
print(f"Mean: {np.mean(data):.1f}")
print(f"Median: {np.median(data):.1f}")
print(f"Std: {np.std(data, ddof=1):.2f}") # sample std deviation
print(f"Min: {np.min(data)}")
print(f"Max: {np.max(data)}")
print(f"Skewness: {stats.skew(data):.3f}") # near 0 = symmetricOutput:
--- Distribution Summary ---
Mean: 13.8
Median: 14.0
Std: 2.25
Min: 10
Max: 18
Skewness: 0.200Un valore di skewness prossimo a 0.200 indica che i dati sono approssimativamente simmetrici — nessuna forte asimmetria in nessuna direzione.
Test di Normalità
Alcuni algoritmi assumono esplicitamente che le feature siano distribuite normalmente. Il test di Shapiro-Wilk è il test più affidabile per campioni piccoli (n < 5 000). Restituisce una statistica W e un p-value. Un p-value maggiore di 0,05 significa che non puoi rifiutare l'ipotesi che i dati siano normali.
import numpy as np
from scipy import stats
rng = np.random.default_rng(seed=42)
# Sample from a normal distribution
normal_sample = rng.normal(loc=0, scale=1, size=50)
stat, p = stats.shapiro(normal_sample)
print(f"Shapiro-Wilk: W={stat:.3f}, p={p:.3f}")
if p > 0.05:
print("Data appears to be normally distributed.")
else:
print("Data does not appear to be normally distributed.")Output:
Shapiro-Wilk: W=0.984, p=0.730
Data appears to be normally distributed.Quando usarlo: applica il test di Shapiro-Wilk quando le assunzioni di un modello richiedono esplicitamente la normalità — ad esempio, prima di usare un t-test parametrico o l'analisi discriminante lineare. Molti algoritmi moderni (gradient boosting, random forest, reti neurali) non sono sensibili alla forma della distribuzione delle feature, quindi non è sempre necessario questo test.
Perché la Forma della Distribuzione è Importante per la Modellazione
| Situazione | Cosa significa | Cosa fare |
|---|---|---|
| Feature normali | Le assunzioni standard sono valide | Usa modelli parametrici così come sono |
| Feature con asimmetria positiva | La media è inflazionata; i valori anomali dominano | Applica la trasformazione logaritmica o della radice quadrata |
| Feature con asimmetria negativa | I valori bassi sono outlier | Applica la trasformazione quadratica o di riflessione |
| Feature uniformi | Nessuna concentrazione centrale | Spesso va bene; normalizza per i modelli basati sulla distanza |
| Feature multimodali | Più cluster | Considera di suddividere i dati o di usare un passaggio di clustering |
Gestire i Dati Asimmetrici con la Trasformazione Logaritmica
Un rimedio comune per i dati con asimmetria positiva è la trasformazione logaritmica, che comprime i valori grandi e espande quelli piccoli, producendo spesso una distribuzione più vicina alla normale.
import numpy as np
from scipy import stats
rng = np.random.default_rng(seed=7)
# Simulate log-normally distributed incomes (a common real-world pattern)
incomes = rng.lognormal(mean=10.5, sigma=0.5, size=1000)
print(f"Before transform — skewness: {stats.skew(incomes):.2f}")
log_incomes = np.log(incomes)
print(f"After log transform — skewness: {stats.skew(log_incomes):.2f}")Output:
Before transform — skewness: 1.44
After log transform — skewness: 0.01Dopo la trasformazione logaritmica, la skewness scende da 1.44 a quasi zero, rendendo i dati molto più adatti ai modelli che assumono la normalità.
Nota: la trasformazione logaritmica richiede che tutti i valori siano strettamente positivi. Aggiungi una piccola costante (es. np.log(x + 1)) se i tuoi dati contengono zeri.
Visualizzare la Distribuzione dei Dati
La visualizzazione è il modo più rapido per ispezionare una distribuzione. Un istogramma divide i valori in intervalli e mostra quante osservazioni ricadono in ciascun intervallo. Usa gli istogrammi di Matplotlib per crearli:
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.default_rng(seed=42)
data = rng.normal(loc=170, scale=10, size=500)
plt.figure(figsize=(8, 4))
plt.hist(data, bins=30, color='steelblue', edgecolor='white')
plt.title("Height Distribution (Normal)")
plt.xlabel("Height (cm)")
plt.ylabel("Frequency")
plt.tight_layout()
plt.show()Per una panoramica più rapida su più feature contemporaneamente, usa uno scatter plot per cercare relazioni tra le distribuzioni.
Distribuzione e Scaling delle Feature
Se le tue feature hanno distribuzioni o scale molto diverse, gli algoritmi basati sulla distanza (k-nearest neighbours, SVM, k-means clustering) saranno dominati dalle feature con l'intervallo più ampio. Lo scaling delle feature è la soluzione standard: StandardScaler normalizza ciascuna feature a una media di 0 e deviazione standard di 1, mentre MinMaxScaler comprime i valori in [0, 1].
Scegli lo scaler in base alla distribuzione sottostante:
- Distribuzione normale →
StandardScaler(rimuove la media, scala a varianza unitaria) - Distribuzione uniforme o limitata →
MinMaxScaler(preserva la forma) - Fortemente asimmetrica con outlier →
RobustScaler(usa mediana e IQR, ignorando i valori estremi)
Riepilogo
- Distribuzione normale: curva a campana simmetrica; media ≈ mediana; adatta ai modelli parametrici.
- Distribuzione asimmetrica: non simmetrica; la media è trascinata verso la coda; le trasformazioni logaritmiche o di potenza possono ridurre l'asimmetria.
- Distribuzione uniforme: tutti i valori ugualmente probabili; appare nel campionamento casuale e come prior non informativi.
- Usa
scipy.stats.skew()per misurare l'asimmetria escipy.stats.shapiro()per testare formalmente la normalità. - Ispeziona sempre le distribuzioni prima della modellazione — la forma influenza la scelta dell'algoritmo, il modo in cui trasformare le feature e la strategia di scaling da applicare.
Successivamente, esplora la Distribuzione Normale dei Dati per un'analisi più approfondita della distribuzione gaussiana e su come generarla e utilizzarla con SciPy.