Ti hanno mai detto che «lo stipendio medio in quell’azienda è 45.000 euro»? Poi entri, e scopri che quasi tutti prendono 28.000 e il fondatore si porta a casa mezzo milione. Nessuno ti ha mentito: è la media che, da sola, racconta solo un pezzo della storia. In questa lezione impari a calcolare con NumPy i quattro numeri che riassumono un dataset — media, mediana, moda e varianza — e soprattutto impari quando fidarti di ciascuno. È il primo gesto che farai su ogni dataset per il resto della tua carriera nel machine learning, quindi vale la pena farlo bene.
L’ndarray, indicizzazione e slicing, broadcasting e vettorizzazione sono trattati nel libro.
Prerequisiti minimi: se non ricordi cos’è un array NumPy, ti basta sapere che è una sequenza di numeri dello stesso tipo, salvata in memoria contigua, su cui NumPy sa fare matematica velocissima senza cicli for. Ambiente: Python 3.12 in un virtual environment (python -m venv .venv oppure uv venv, come preferisci), poi pip install numpy (o uv pip install numpy), e il tuo editor — VS Code o PyCharm vanno benissimo.
Media e mediana: il sindaco e l’abitante di mezzo
La media è la somma di tutti i valori divisa per quanti sono. La mediana è il valore che sta esattamente a metà quando ordini i dati: metà del dataset sta sotto, metà sopra. L’analogia che uso sempre: la media è il sindaco che dichiara «il reddito del paese è X» sommando tutto e dividendo — e se in paese si trasferisce un miliardario, la dichiarazione del sindaco schizza in alto anche se la vita di tutti gli altri non è cambiata di un centesimo. La mediana invece è l’abitante che sta in mezzo alla fila ordinata per reddito: il miliardario si mette in fondo alla fila, ma la persona di mezzo resta la stessa. Per questo si dice che la mediana è robusta agli outlier.
import numpy as np
# stipendi annui in migliaia di euro: 9 persone "normali" e il fondatore
stipendi = np.array([26, 27, 28, 28, 29, 30, 31, 33, 35, 500])
media = np.mean(stipendi) # somma / quanti sono
mediana = np.median(stipendi) # il valore di mezzo (dati ordinati)
print(f"Media: {media:.1f}k euro") # 76.7k -> gonfiata dal fondatore
print(f"Mediana: {mediana:.1f}k euro") # 29.5k -> la vita vera dell'azienda
Stesso dataset, due storie diversissime: 76.700 euro contro 29.500. Quando qualcuno ti dà «il valore tipico» di qualcosa, la prima domanda da farti è sempre: media o mediana? Se la distribuzione è asimmetrica (redditi, prezzi delle case, durata delle visite su un sito), le due divergono, e la mediana di solito è più onesta.
La moda: il piatto più ordinato
La moda è il valore che compare più spesso. Pensa al menù di una pizzeria: la media dei prezzi ti dice poco, ma «la pizza più ordinata è la margherita» è un’informazione concretissima. La moda è l’unica delle tre misure che ha senso anche su dati categorici (colori, taglie, categorie), dove media e mediana non esistono proprio.
Curiosità onesta: NumPy non ha una funzione np.mode. Non è una dimenticanza — la moda su valori continui (dove ogni numero è quasi unico) è mal definita. Per i dati discreti si usa np.unique con i conteggi, oppure scipy.stats.mode se hai SciPy installato:
import numpy as np
# voti d'esame di una classe
voti = np.array([25, 27, 30, 27, 26, 27, 30, 28, 27, 24])
# np.unique con return_counts=True restituisce i valori distinti
# e quante volte compare ciascuno
valori, conteggi = np.unique(voti, return_counts=True)
moda = valori[np.argmax(conteggi)] # il valore col conteggio massimo
print(f"Valori: {valori}") # [24 25 26 27 28 30]
print(f"Conteggi: {conteggi}") # [1 1 1 4 1 2]
print(f"Moda: {moda}") # 27, compare 4 volte
Varianza e deviazione standard: quanto ballano i dati
Media, mediana e moda ti dicono dov’è il centro. La varianza ti dice quanto i dati si allontanano da quel centro: è la media dei quadrati delle distanze dalla media. Siccome elevare al quadrato cambia l’unità di misura (euro al quadrato non significa nulla), quasi sempre usiamo la sua radice: la deviazione standard.
Analogia: due pizzaioli sfornano entrambi pizze con diametro medio di 30 cm. Il primo le fa tutte tra 29 e 31; il secondo alterna pizze da 24 a pizze da 36. Stessa media, esperienza completamente diversa per il cliente. La deviazione standard è il numero che li distingue: piccola per il pizzaiolo preciso, grande per quello ballerino.
import numpy as np
pizzaiolo_preciso = np.array([29.5, 30.2, 29.8, 30.1, 30.4, 29.6, 30.0, 30.3])
pizzaiolo_ballerino = np.array([24.0, 36.0, 27.5, 33.0, 25.5, 35.0, 28.0, 31.0])
for nome, diametri in [("preciso", pizzaiolo_preciso),
("ballerino", pizzaiolo_ballerino)]:
media = np.mean(diametri)
# ddof=1: varianza CAMPIONARIA (divide per n-1, non per n).
# Usala quando i dati sono un campione, non l'intera popolazione.
var = np.var(diametri, ddof=1)
std = np.std(diametri, ddof=1)
print(f"{nome:10s} -> media {media:.1f} cm, "
f"varianza {var:.2f}, dev. standard {std:.2f} cm")
Nota il parametro ddof=1: di default NumPy divide per n (varianza di popolazione), ma quando i tuoi dati sono un campione — cioè quasi sempre, nel ML — la stima corretta divide per n-1. È un dettaglio che con dataset grandi cambia pochissimo, ma sapere che esiste ti evita brutte figure (e differenze misteriose rispetto a pandas, che usa ddof=1 di default).
La media ti dice dove sta il centro. La deviazione standard ti dice quanto puoi fidarti di quel centro.
Tutto insieme su una distribuzione vera
Vediamo i quattro numeri all’opera su una distribuzione asimmetrica, il caso in cui divergono e diventano interessanti. Generiamo tempi di risposta simulati di un sito web: quasi tutte le richieste sono veloci, poche sono lentissime (la classica «coda lunga»).
import numpy as np
rng = np.random.default_rng(seed=42) # generatore moderno, riproducibile
# distribuzione log-normale: la classica coda lunga a destra
tempi_ms = rng.lognormal(mean=4.0, sigma=0.6, size=10_000)
print(f"Media: {np.mean(tempi_ms):7.1f} ms")
print(f"Mediana: {np.median(tempi_ms):7.1f} ms")
print(f"Dev. std: {np.std(tempi_ms, ddof=1):7.1f} ms")
print(f"95° percentile: {np.percentile(tempi_ms, 95):5.1f} ms")
# la moda di dati continui si stima col bin più alto dell'istogramma
conteggi, bordi = np.histogram(tempi_ms, bins=60)
i = np.argmax(conteggi)
print(f"Moda (stimata): {(bordi[i] + bordi[i+1]) / 2:5.1f} ms")
Su una distribuzione così, l’ordine è sempre lo stesso: moda < mediana < media, perché la coda lunga a destra «tira» la media verso l’alto. Ecco lo schema da stamparti in testa:
Su una distribuzione con coda lunga a destra le tre misure di centro si separano: la moda resta sul picco, la mediana in mezzo, la media viene trascinata dagli outlier. La deviazione standard misura la larghezza della campana.
Quando queste misure NON bastano
Onestà prima di tutto: quattro numeri non sostituiscono mai il guardare i dati. Il caso famoso è il quartetto di Anscombe — quattro dataset con media, varianza e correlazione praticamente identiche, ma forme completamente diverse una volta disegnati. E se la distribuzione è bimodale (due picchi: pensa alle altezze di un gruppo misto di adulti e bambini), media e mediana cadono in mezzo ai due picchi, in una zona dove non c’è quasi nessuno: il «valore tipico» che descrivono non è tipico di niente. Regola pratica: prima calcoli le statistiche, poi sempre un istogramma. Di come farlo parliamo esattamente nella prossima lezione, con Matplotlib.
Prova tu
Genera con rng.normal(loc=30, scale=2, size=1000) mille diametri di pizza del pizzaiolo preciso. Poi «assumi» un apprendista disastroso: concatena con np.concatenate altri 50 valori da rng.normal(loc=45, scale=1, size=50). Calcola media, mediana e deviazione standard prima e dopo l’arrivo dell’apprendista: quale delle tre misure si sposta di più? Quale quasi per niente? Prova a spiegare a voce alta il perché — se ci riesci, la lezione è tua.
Per approfondire: il capitolo 1 di Practical Statistics for Data Scientists (Bruce, Bruce, Gedeck — O’Reilly) tratta le stime di posizione e variabilità proprio da questo punto di vista pratico; e la documentazione ufficiale di NumPy sulle routine statistiche è il riferimento per tutti i parametri che qui abbiamo solo sfiorato.

I commenti sono riservati agli iscritti.
Accedi per commentare