W3docs

Distribuzione Normale dei Dati

Scopri la distribuzione normale (gaussiana) in Python: PDF, CDF, Z-score, regola empirica, test di normalità e trasformazioni logaritmiche.

La distribuzione normale (chiamata anche distribuzione gaussiana) è la distribuzione di probabilità più importante in statistica e nel machine learning. Comprenderla a fondo — non solo riconoscerne la forma a campana, ma sapere come misurarla, testarla e utilizzarla in Python — fornisce una solida base per la preparazione dei dati, la selezione dei modelli e l'interpretazione dei risultati.

Questo capitolo tratta:

  • Cos'è la distribuzione normale e perché la sua forma è importante
  • La regola empirica (regola 68–95–99.7) e come verificarla
  • La generazione di dati normalmente distribuiti con NumPy
  • La valutazione della funzione di densità di probabilità (PDF) e della funzione di distribuzione cumulativa (CDF) con SciPy
  • Il calcolo e l'interpretazione degli Z-score
  • Come verificare se i dati sono normalmente distribuiti
  • Cosa fare quando i dati non sono normali

Cos'è la Distribuzione Normale?

Una distribuzione di probabilità descrive la probabilità con cui ciascun valore in un dataset può comparire. La distribuzione normale è una distribuzione continua, simmetrica e a forma di campana, definita da due parametri:

  • Media (μ) — il centro della campana; il punto in cui si trova il picco
  • Deviazione standard (σ) — quanto è larga o stretta la campana; un σ maggiore distribuisce i valori più lontano dal centro

Poiché la curva è simmetrica attorno alla media, media, mediana e moda sono tutte uguali per una distribuzione perfettamente normale.

La formula della funzione di densità di probabilità è:

f(x) = (1 / (σ√(2π))) × e^(−(x−μ)² / (2σ²))

Non è necessario applicare questa formula a mano — stats.norm di SciPy la gestisce — ma è utile sapere che la forma dipende solo da μ e σ.

La Regola Empirica (68–95–99.7)

Una delle proprietà più pratiche della distribuzione normale è la regola empirica, chiamata anche regola 68–95–99.7:

Distanza dalla mediaPercentuale di valori
Entro ±1 deviazione standard~68%
Entro ±2 deviazioni standard~95%
Entro ±3 deviazioni standard~99.7%

Ciò significa che in un dataset normalmente distribuito, quasi tutti i valori si trovano entro tre deviazioni standard dalla media. I valori oltre ±3σ sono genuinamente rari — circa 1 su 370 osservazioni.

Il seguente esempio simula 10 000 valori da una distribuzione normale standard (μ=0, σ=1) e verifica la regola:

import numpy as np

rng = np.random.default_rng(seed=42)
mu, sigma = 0, 1
data = rng.normal(loc=mu, scale=sigma, size=10000)

w1 = np.mean(np.abs(data - mu) < 1 * sigma) * 100
w2 = np.mean(np.abs(data - mu) < 2 * sigma) * 100
w3 = np.mean(np.abs(data - mu) < 3 * sigma) * 100

print(f"Within 1 std: {w1:.1f}%  (expected ~68%)")
print(f"Within 2 std: {w2:.1f}%  (expected ~95%)")
print(f"Within 3 std: {w3:.1f}%  (expected ~99.7%)")

Output:

Within 1 std: 67.8%  (expected ~68%)
Within 2 std: 95.4%  (expected ~95%)
Within 3 std: 99.7%  (expected ~99.7%)

La regola empirica è immediatamente utile: se un punteggio di un test è superiore a due deviazioni standard rispetto alla media, rientra nel top ~2.5% di tutti i punteggi.

Generare Dati Normalmente Distribuiti con NumPy

Il generatore di numeri casuali di NumPy produce campioni che approssimano una distribuzione normale. Il metodo rng.normal() accetta la media (loc), la deviazione standard (scale) e il numero di campioni (size):

import numpy as np

rng = np.random.default_rng(seed=7)

# Simulate IQ scores: mean=100, std=15
samples = rng.normal(loc=100, scale=15, size=1000)

print(f"Sample mean:  {samples.mean():.1f}")
print(f"Sample std:   {samples.std():.1f}")
print(f"Min:          {samples.min():.1f}")
print(f"Max:          {samples.max():.1f}")
print(f"Sample size:  {len(samples)}")

Output:

Sample mean:  98.9
Sample std:   14.1
Min:          51.2
Max:          138.6
Sample size:  1000

La media e la deviazione standard del campione sono vicine — ma non esattamente uguali — a 100 e 15, perché un campione finito presenta una variazione casuale naturale. Con 10 000 campioni le stime convergono più vicino ai parametri reali.

Perché usare un seed? Passare seed=7 a default_rng rende i risultati riproducibili. Chiunque esegua lo stesso codice ottiene gli stessi numeri, il che è fondamentale per il debug e la condivisione dei risultati. L'interfaccia default_rng (introdotta in NumPy 1.17) è preferita rispetto al vecchio np.random.normal() perché evita lo stato globale condiviso.

Valutare la PDF e la CDF con SciPy

Funzione di Densità di Probabilità (PDF)

La PDF indica la probabilità relativa di un valore. Un valore PDF più alto in x significa che x ha più probabilità di comparire. Per una distribuzione normale, il picco si trova alla media:

from scipy import stats

mu, sigma = 0, 1

x_values = [-2, -1, 0, 1, 2]
for x in x_values:
    pdf = stats.norm.pdf(x, loc=mu, scale=sigma)
    print(f"  pdf({x:2d}) = {pdf:.4f}")

Output:

  pdf(-2) = 0.0540
  pdf(-1) = 0.2420
  pdf( 0) = 0.3989
  pdf( 1) = 0.2420
  pdf( 2) = 0.0540

La PDF è simmetrica: pdf(-1) è uguale a pdf(1), e pdf(0) è il massimo (il picco della curva a campana). Il valore della PDF non è di per sé una probabilità — è una densità. Per ottenere una probabilità, si integra la PDF su un intervallo, operazione che è esattamente ciò che fa la CDF.

Funzione di Distribuzione Cumulativa (CDF)

La CDF in un valore x fornisce la probabilità che un'osservazione estratta casualmente sia minore o uguale a x. Usare stats.norm.cdf() per rispondere a domande pratiche sulle probabilità:

from scipy import stats

mu, sigma = 170, 10  # adult heights in cm

# Probability that a randomly chosen person is shorter than 180 cm
p_below_180 = stats.norm.cdf(180, loc=mu, scale=sigma)
print(f"P(height < 180 cm) = {p_below_180:.4f}")

# Probability of being taller than 185 cm
p_above_185 = 1 - stats.norm.cdf(185, loc=mu, scale=sigma)
print(f"P(height > 185 cm) = {p_above_185:.4f}")

# Probability of falling between 160 cm and 180 cm
p_range = stats.norm.cdf(180, loc=mu, scale=sigma) - stats.norm.cdf(160, loc=mu, scale=sigma)
print(f"P(160 < height < 180 cm) = {p_range:.4f}")

Output:

P(height < 180 cm) = 0.8413
P(height > 185 cm) = 0.0668
P(160 < height < 180 cm) = 0.6827

Il terzo risultato conferma la regola empirica: l'intervallo μ±σ (160–180 cm) contiene circa il 68% delle osservazioni.

Z-Score: Standardizzare la Distribuzione

Uno Z-score (chiamato anche punteggio standard) misura quante deviazioni standard un valore si trova dalla media:

Z = (x − μ) / σ

Gli Z-score consentono di confrontare valori provenienti da distribuzioni con medie e deviazioni standard diverse su una scala comune. Uno Z-score di +2.0 significa che un valore è due deviazioni standard sopra la media, indipendentemente dalle unità originali.

import numpy as np
from scipy import stats

heights = np.array([171.3, 168.7, 176.4, 171.0, 164.6, 173.6, 183.0, 179.5])

# Calculate Z-scores manually
mean = heights.mean()
std  = heights.std(ddof=1)   # ddof=1 for the sample standard deviation
z_manual = (heights - mean) / std
print("Z-scores (manual):", np.round(z_manual, 2))

# Or use scipy directly
z_scipy = stats.zscore(heights, ddof=1)
print("Z-scores (scipy): ", np.round(z_scipy, 2))

Output:

Z-scores (manual): [-0.37 -0.81  0.49 -0.42 -1.5   0.01  1.59  1.01]
Z-scores (scipy):  [-0.37 -0.81  0.49 -0.42 -1.5   0.01  1.59  1.01]

Una persona con un'altezza di 183.0 cm ha uno Z-score di +1.59, il che significa che si trova 1.59 deviazioni standard sopra la media. In una distribuzione normale, questo la colloca approssimativamente nel top 6% della popolazione.

Quando gli Z-score sono utili

  • Rilevamento degli outlier: i valori con |Z| > 3 sono quasi certamente outlier in una distribuzione normale.
  • Scalatura delle feature: la conversione delle feature in Z-score (media zero, varianza unitaria) è chiamata standardizzazione, ed è necessaria per gli algoritmi sensibili alla grandezza delle feature, come SVM e k-nearest neighbours. Vedi il capitolo sulla scalatura delle feature per come applicarla con StandardScaler di scikit-learn.
  • Confrontare grandezze diverse: se si vuole confrontare il punteggio di matematica di uno studente (media 70, deviazione standard 10) con il suo punteggio di lettura (media 50, deviazione standard 5), gli Z-score forniscono un confronto equo.

Verificare se i Dati Sono Normalmente Distribuiti

Prima di applicare algoritmi che assumono la normalità, è necessario verificare l'ipotesi. I due approcci più comuni sono il test di Shapiro-Wilk (per campioni fino a ~5 000) e un rapido controllo visivo.

Test di Shapiro-Wilk

Il test di Shapiro-Wilk restituisce una statistica W e un p-value. Un p-value superiore a 0.05 significa che non ci sono prove sufficienti per rifiutare la normalità. Un p-value pari o inferiore a 0.05 indica che i dati probabilmente non sono normali.

import numpy as np
from scipy import stats

rng = np.random.default_rng(seed=42)

# Normally distributed sample
normal_sample = rng.normal(loc=0, scale=1, size=50)
stat_n, p_n = stats.shapiro(normal_sample)
print(f"Normal sample  — W={stat_n:.3f}, p={p_n:.3f}")
if p_n > 0.05:
    print("  => Cannot reject normality.")
else:
    print("  => Reject normality.")

# Skewed sample (exponential distribution)
skewed_sample = rng.exponential(scale=1, size=50)
stat_s, p_s = stats.shapiro(skewed_sample)
print(f"Skewed sample  — W={stat_s:.3f}, p={p_s:.3f}")
if p_s > 0.05:
    print("  => Cannot reject normality.")
else:
    print("  => Reject normality.")

Output:

Normal sample  — W=0.984, p=0.730
  => Cannot reject normality.
Skewed sample  — W=0.808, p=0.000
  => Reject normality.

Il campione normale supera facilmente il test (p=0.730). Il campione esponenziale fallisce in modo netto (p≈0).

Quando il test di normalità è importante

Non tutti gli algoritmi richiedono feature normalmente distribuite. I modelli ad albero (alberi decisionali, random forest, gradient boosting) e le reti neurali sono completamente agnostici rispetto alla distribuzione. La normalità è più importante per:

  • Test statistici parametrici (t-test, ANOVA, correlazione di Pearson) — i p-value di questi test sono validi solo se i dati sono approssimativamente normali.
  • Analisi discriminante lineare (LDA) — assume che ogni classe sia normalmente distribuita.
  • Gaussian naive Bayes — modella esplicitamente ogni feature come una gaussiana.
  • Intervalli di confidenza e di previsione nella regressione lineare — derivati dalla normalità dei residui.

Quando i Dati Non Sono Normali: La Trasformazione Logaritmica

Un rimedio comune per i dati con asimmetria positiva (ad es. reddito, prezzi delle case, importi delle transazioni) è la trasformazione logaritmica, che comprime i valori grandi e dilata quelli piccoli. Il risultato è spesso vicino alla normalità:

import numpy as np
from scipy import stats

rng = np.random.default_rng(seed=7)

# Simulate log-normally distributed incomes (a realistic pattern)
incomes = rng.lognormal(mean=10.5, sigma=0.5, size=1000)
log_incomes = np.log(incomes)

sk_before = stats.skew(incomes)
sk_after  = stats.skew(log_incomes)

stat_before, p_before = stats.shapiro(incomes[:50])
stat_after,  p_after  = stats.shapiro(log_incomes[:50])

print(f"Before transform — skewness: {sk_before:.2f},  Shapiro p={p_before:.3f}")
print(f"After log transform — skewness: {sk_after:.2f}, Shapiro p={p_after:.3f}")

Output:

Before transform — skewness: 1.44,  Shapiro p=0.000
After log transform — skewness: 0.01, Shapiro p=0.940

Dopo la trasformazione logaritmica, l'asimmetria scende da 1.44 a quasi zero, e il p-value di Shapiro-Wilk sale da 0.000 a 0.940 — i dati trasformati superano facilmente il test di normalità.

Attenzione:

  • La trasformazione logaritmica richiede che tutti i valori siano strettamente positivi. Se i dati contengono zeri, usare np.log(x + 1) (trasformazione log-più-uno).
  • Se i dati hanno un'asimmetria negativa (coda lunga a sinistra), provare invece una trasformazione quadratica o a riflessione.
  • Applicare sempre la stessa trasformazione sia al set di addestramento che a qualsiasi nuovo dato in fase di inferenza.

Perché la Distribuzione Normale è Importante nel Machine Learning

La distribuzione normale compare ovunque nel ML, spesso in modo implicito:

  • Teorema del Limite Centrale: la media di un ampio campione casuale è approssimativamente normalmente distribuita, indipendentemente dalla distribuzione originale. Questo è alla base degli intervalli di confidenza e dei test di ipotesi sulle metriche dei modelli.
  • Analisi dei residui: i residui di una regressione lineare ben adattata dovrebbero essere approssimativamente normali. Le deviazioni segnalano una specificazione errata del modello.
  • Inizializzazione dei pesi nelle reti neurali: schemi come l'inizializzazione Xavier e He estraggono i pesi iniziali da distribuzioni normali per prevenire la scomparsa o l'esplosione dei gradienti.
  • Processi gaussiani: una famiglia di modelli probabilistici che pongono una distribuzione normale sulle funzioni, utilizzata nell'ottimizzazione bayesiana e nella quantificazione dell'incertezza.
  • Rilevamento degli outlier: in molti domini, i dati oltre ±3σ dalla media vengono segnalati come anomalie.

Capire dove si assume la distribuzione normale — e quando tale assunzione non regge — aiuta a evitare errori di modellazione silenziosi.

  • La distribuzione normale è definita dalla sua media (μ) e dalla deviazione standard (σ); la sua curva a forma di campana è simmetrica attorno alla media.
  • La regola empirica afferma che il 68%, il 95% e il 99.7% dei valori si trovano entro 1, 2 e 3 deviazioni standard dalla media.
  • Usare numpy.random.default_rng().normal() per generare campioni normalmente distribuiti e scipy.stats.norm.pdf() / .cdf() per valutare le probabilità.
  • Gli Z-score standardizzano i valori su una scala comune; sono essenziali per il rilevamento degli outlier e la scalatura delle feature.
  • Usare scipy.stats.shapiro() per testare formalmente la normalità — ma ricordare che molti moderni algoritmi ML non la richiedono.
  • Quando i dati sono asimmetrici positivamente, una trasformazione logaritmica li rende spesso approssimativamente normali.

Per un confronto più ampio dei tipi di distribuzione (uniforme, asimmetrica, multimodale), vedere il capitolo Distribuzione dei Dati. Per come misurare la dispersione e il centro, vedere Deviazione Standard e Media, Mediana e Moda.

Was this page helpful?