Montagne di dati, numeri che sembrano non avere senso finché non riesci a farli parlare.
Il segreto per trasformare quei numeri in storie e decisioni informate spesso risiede in pochi, agili strumenti. Con Pandas, una delle librerie più apprezzate in Python per l’analisi dei dati, e in particolare con le sue “Series”, hai tra le mani una bacchetta magica per scoprire schemi, tendenze e anomalie nascoste. Questa guida è pensata per accompagnarti passo dopo passo in un percorso pratico, partendo dalle basi fino ad arrivare a tecniche più sofisticate, per tirare fuori il massimo dai tuoi dati e fargli raccontare quello che davvero conta.
Preparati a trasformare la complessità in chiarezza, un esercizio alla volta.
Esercizio 1 (Facile): Calcolo di statistiche base
Testo:
Data la seguente Series di Pandas che rappresenta il numero di ore giornaliere di studio di uno studente nell’ultima settimana:
import pandas as pd
ore_studio = pd.Series([3, 5, 2, 4, 6, 3, 4])
Calcola: media, mediana, deviazione standard e varianza.
Soluzione:
# Calcolo della media
media = ore_studio.mean()
print("Media:", media) # Output: 3.857...
# La media è la somma dei valori divisa per il numero di elementi.
# Calcolo della mediana
mediana = ore_studio.median()
print("Mediana:", mediana) # Output: 4.0
# La mediana è il valore centrale nella Series ordinata (2,3,3,4,4,5,6).
# Calcolo deviazione standard
dev_std = ore_studio.std()
print("Deviazione standard:", dev_std) # Output: 1.345...
# Misura la dispersione dei dati attorno alla media.
# Calcolo varianza
varianza = ore_studio.var()
print("Varianza:", varianza)
# Output: 1.809...
# La varianza è il quadrato della deviazione standard.
Questo esercizio è il punto di partenza per ogni analisi dati. Le statistiche di base come media, mediana, deviazione standard e varianza offrono una prima, fondamentale panoramica dei dati. La media ci dà un’idea del valore centrale, la mediana è più robusta in presenza di outlier, mentre deviazione standard e varianza ci indicano quanto i dati sono dispersi rispetto alla media. Sono strumenti quotidiani per chiunque lavori con i numeri, utili per capire rapidamente il “cuore” e la “larghezza” di una distribuzione, ad esempio, per valutare la performance media di un team o la variabilità dei voti di uno studente.
Esercizio 2 (Facile-Intermedio): Frequenze e moda
Testo:
Data la Series:
voti = pd.Series([8, 7, 6, 8, 9, 7, 8, 6, 7, 8])
Trova il voto più frequente (moda).
Calcola la frequenza relativa di ogni voto.
Soluzione:
# 1. Moda
moda = voti.mode()
print("Moda:", moda) # Output: 8 (compare 4 volte)
# 2. Frequenze relative
freq_rel = voti.value_counts(normalize=True)
print("Frequenze relative:\n", freq_rel)
# Output:
# 8 0.4 # 4 occorrenze su 10
# 7 0.3
# 6 0.2
# 9 0.1
Comprendere la frequenza e la moda è cruciale quando si lavora con dati categorici o discreti. La moda ci rivela il valore o la categoria più comune in un dataset, il che è utile per identificare le preferenze dominanti, come il voto più frequente in una classe o il prodotto più venduto. Le frequenze relative trasformano questi conteggi in percentuali, rendendo più facile confrontare distribuzioni di diverse dimensioni o comprendere la proporzione di ogni categoria rispetto al totale. Questo esercizio è fondamentale per l’analisi esplorativa, permettendo di identificare rapidamente le tendenze prevalenti nei dati.
Esercizio 3 (Intermedio): Quartili e boxplot
Testo:
Data la Series dei punteggi di un test:
punteggi = pd.Series([45, 72, 68, 90, 55, 83, 62, 75, 78, 80])
Calcola i quartili (Q1, Q2, Q3).
Identifica eventuali outlier usando la regola del IQR.
Genera un boxplot (opzionale, se hai matplotlib).
Soluzione:
import pandas as pd
import matplotlib.pyplot as plt
punteggi = pd.Series([45, 72, 68, 90, 55, 83, 62, 75, 78, 80])
# Calcolo statistiche
stats = {
'Min': punteggi.min(),
'Q1': punteggi.quantile(0.25),
'Mediana': punteggi.median(),
'Q3': punteggi.quantile(0.75),
'Max': punteggi.max(),
'IQR': punteggi.quantile(0.75) - punteggi.quantile(0.25)
}
print(pd.DataFrame(stats, index=['Valore']).T)
# Boxplot avanzato
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
punteggi.plot(kind='box')
plt.title('Boxplot semplice')
plt.subplot(1, 2, 2)
plt.boxplot(punteggi, vert=False, widths=0.7, patch_artist=True,
boxprops=dict(facecolor='lightblue'),
medianprops=dict(color='red'))
plt.title('Boxplot avanzato')
plt.grid(axis='x')
plt.tight_layout()
plt.show()
Valore Min 45.0 Q1 63.5 Mediana 73.5 Q3 79.5 Max 90.0 IQR 16.0

I quartili e i boxplot sono strumenti potenti per l’analisi della distribuzione e l’identificazione degli outlier. I quartili dividono i dati in quattro parti uguali, fornendo informazioni sulla diffusione e la simmetria della distribuzione. Il range interquartile (IQR), che è la differenza tra il terzo e il primo quartile, è un indicatore robusto della dispersione e viene usato classicamente per rilevare gli outlier, quei valori che si discostano significativamente dalla maggior parte dei dati. Un boxplot visualizza in modo conciso la mediana, i quartili e gli outlier, rendendo immediatamente visibili la forma della distribuzione, la sua simmetria e la presenza di valori anomali. Questo è cruciale in settori come il controllo qualità, la finanza (per rilevare picchi o crolli insoliti) o la medicina (per identificare risposte insolite a un trattamento).
Esercizio 4 (Intermedio-Avanzato): Normalizzazione
Testo:
Normalizza la Series seguente (scala i valori tra 0 e 1):
dati = pd.Series([150, 170, 165, 185, 160])
Usa la formula:
[math]\displaystyle \text{valore normalizzato} = \frac{x – \min(\text{Series})}{\max(\text{Series}) – \min(\text{Series})}[/math]
Soluzione:
# Normalizzazione
minimo = dati.min() # 150
massimo = dati.max() # 185
dati_normalizzati = (dati - minimo) / (massimo - minimo)
print(dati_normalizzati)
# Output:
# 0 0.000
# 1 0.571
# 2 0.429
# 3 1.000
# 4 0.286
La normalizzazione è una tecnica fondamentale nella pre-elaborazione dei dati, specialmente prima di applicare algoritmi di machine learning o modelli statistici. Scalare i valori di una Series tra 0 e 1, come mostrato in questo esercizio (normalizzazione Min-Max), garantisce che tutte le variabili abbiano lo stesso “peso” e che nessuna variabile, a causa del suo range di valori, domini le altre. Ad esempio, in un dataset con età (0-100) e reddito (migliaia di euro), senza normalizzazione il reddito avrebbe un’influenza sproporzionata. Questo esercizio mostra una delle tecniche più comuni e semplici per rendere i dati comparabili e migliorare le performance di molti modelli analitici.
Esercizio 5 (Avanzato): Statistiche ponderate
Testo:
Calcola la media ponderata dei seguenti voti, dove i pesi sono i crediti associati:
voti = pd.Series([28, 30, 24], index=["Esame A", "Esame B", "Esame C"])
pesi = pd.Series([6, 8, 4], index=["Esame A", "Esame B", "Esame C"])
Soluzione:
# Media ponderata = Σ(voto * peso) / Σ(pesi)
media_ponderata = (voti * pesi).sum() / pesi.sum()
print("Media ponderata:", media_ponderata) # Output: 28.0
# Spiegazione: (28*6 + 30*8 + 24*4)/(6+8+4) = 504/18 = 28
La media ponderata è essenziale quando i singoli dati non hanno la stessa importanza. Questo esercizio illustra perfettamente la sua applicazione nel calcolo della media dei voti universitari, dove ogni esame ha un peso (crediti) diverso. A differenza della media semplice, la media ponderata riflette l’influenza relativa di ciascun valore, fornendo una rappresentazione più accurata della realtà. È ampiamente utilizzata in finanza (per indici azionari), nell’istruzione (per i voti finali) o nella produzione (per il costo medio di beni con diverse quantità). Capire come calcolarla in Pandas è un’abilità preziosa per analisi più sofisticate.
Esercizio 6 (Difficile): Analisi distribuzione con skewness e kurtosis
Testo:
Analizza la distribuzione della Series:
import numpy as np
dati = pd.Series(np.random.gamma(shape=2, scale=2, size=1000))
Calcola skewness e kurtosis.
Interpreta i risultati (distribuzione asimmetrica? code pesanti?).
Soluzione:
# 1. Calcolo skewness e kurtosis
skew = dati.skew()
kurt = dati.kurtosis()
print("Skewness:", skew) # ~0.8 (distribuzione asimmetrica positiva)
print("Kurtosis:", kurt) # ~0.5 (code leggermente più pesanti della normale)
# 2. Interpretazione:
# - Skewness > 0: coda a destra più lunga.
# - Kurtosis > 0: code più pesanti e picco più alto rispetto a una normale.
# Grafico opzionale:
dati.plot(kind='hist', bins=30, density=True)
plt.show() # Mostra l'asimmetria a destra.
Skewness: 1.1372368007816407
Kurtosis: 1.2096934606895817

Skewness e Kurtosis sono due metriche cruciali per capire la “forma” di una distribuzione oltre la semplice media e deviazione standard. La skewness (asimmetria) ci dice se la distribuzione è sbilanciata a sinistra o a destra, indicando una maggiore concentrazione di dati su un lato della media. Una skewness positiva, come quella ottenuta con la distribuzione gamma, significa una “coda” più lunga verso valori più alti. La kurtosis (curtosi) descrive la “pesantezza delle code” e l’acume del picco della distribuzione rispetto a una distribuzione normale. Una kurtosis positiva indica code più pesanti (più outlier) e un picco più pronunciato. Queste misure sono vitali in campi come la gestione del rischio finanziario, dove le code pesanti indicano eventi estremi più probabili, o nel controllo qualità, per monitorare la regolarità dei processi produttivi.
Perché Usare le Series in Pandas? Vantaggi e Motivazioni
Quando lavori con i dati in Python, ti imbatti spesso in Pandas, una libreria che rende l’analisi un gioco da ragazzi. Al suo cuore ci sono due strutture dati principali: i DataFrame e le Series. Qui ci concentriamo sulle Series, l’equivalente di un singolo vettore o colonna di dati, ma con superpoteri che vanno ben oltre un semplice array.
Ecco i motivi principali per cui le Series sono uno strumento così potente e indispensabile per l’analisi statistica:
1. Etichette (Indici Personalizzati)
A differenza delle normali liste o degli array di NumPy, ogni elemento in una Series ha un’etichetta associata, chiamata indice. Questo indice non deve essere per forza numerico; può essere una data, un nome, un ID, o qualsiasi altra cosa abbia senso per i tuoi dati.
- Accesso Intuitivo: Puoi accedere ai dati usando queste etichette, rendendo il tuo codice più leggibile e meno propenso a errori. Immagina di voler il valore delle vendite di “Gennaio”: con una Series, scrivi semplicemente
vendite["Gennaio"]invece di dover ricordare la sua posizione numericavendite[0].
2. Metodi Statistici Integrati
Le Series sono cariche di funzioni predefinite che ti permettono di calcolare statistiche in un attimo, senza scrivere codice da zero.
- Analisi Descrittiva Facile: Che tu voglia la media, la mediana, la deviazione standard, i quartili, la moda, o anche metriche più complesse come skewness e kurtosis, le Series hanno un metodo dedicato. Questo ti fa risparmiare tempo e ti assicura calcoli precisi.
3. Gestione dei Dati Mancanti
I dati reali sono quasi sempre “sporchi” e pieni di valori mancanti. Le Series sono progettate per gestire queste imperfezioni.
- Robustezza ai NaN: Supportano nativamente i valori NaN (Not a Number) per indicare dati mancanti. Inoltre, offrono metodi pratici come
dropna()per rimuoverli ofillna()per sostituirli, rendendo la pulizia dei dati molto più semplice.
4. Allineamento Automatico
Una delle funzionalità più apprezzate di Pandas è l’allineamento automatico dei dati basato sull’indice quando esegui operazioni tra Series.
- Operazioni Sicure: Se sommi due Series con indici diversi, Pandas allineerà i dati in base agli indici corrispondenti, inserendo NaN dove non c’è corrispondenza. Questo previene errori comuni e rende le tue operazioni più affidabili.
5. Velocità e Ottimizzazione
Sotto il cofano, le Series si basano su NumPy, la libreria di calcolo numerico ad alte prestazioni di Python.
- Efficienza: Questo le rende incredibilmente veloci e ottimizzate per operazioni vettoriali (cioè, operazioni che agiscono su intere colonne di dati in una volta sola), il che è fondamentale quando lavori con grandi dataset.
6. Integrazione con i DataFrame
Le Series e i DataFrame sono due facce della stessa medaglia. Un DataFrame è, in sostanza, una collezione di Series che condividono lo stesso indice.
- Fondamenta dei DataFrame: Ogni colonna di un DataFrame è di fatto una Series. Questo significa che padroneggiare le Series è un passo cruciale per lavorare efficacemente con i DataFrame, che sono la struttura dati più usata per dataset multidimensionali.
In sintesi, le Series di Pandas non sono solo un contenitore per i tuoi dati, ma uno strumento intelligente e ottimizzato che semplifica enormemente l’analisi statistica, dalla preparazione alla visualizzazione, rendendole un pilastro per chiunque lavori con Python e i dati.
Ed ora passiamo ad alcuni esercizi Hero:
Esercizio 7 (Avanzato): Rolling Statistics per Serie Temporali
Testo:
Hai una Series che rappresenta il prezzo giornaliero di un’azione (in EUR):
prezzi = pd.Series(
[100, 102, 105, 103, 107, 110, 108, 112, 115, 118],
index=pd.date_range("2024-01-01", periods=10)
)
Calcola la media mobile a 3 giorni.
Trova il massimo rolling a 5 giorni.
Identifica i giorni in cui il prezzo supera la media mobile.
Soluzione:
# 1. Media mobile a 3 giorni
media_mobile = prezzi.rolling(window=3).mean()
print("Media mobile:\n", media_mobile)
# Output (dall'indice 2024-01-03 in poi):
# 2024-01-03 102.333333 # (100+102+105)/3
# 2024-01-04 103.333333 # (102+105+103)/3
# ...
# 2. Massimo rolling a 5 giorni
max_rolling = prezzi.rolling(window=5).max()
print("Massimo rolling:\n", max_rolling)
# Output (dall'indice 2024-01-05):
# 2024-01-05 107 # max tra [100,102,105,103,107]
# 2024-01-06 110 # max tra [102,105,103,107,110]
# ...
# 3. Giorni con prezzo > media mobile
giorni_sopra_media = prezzi[prezzi > media_mobile]
print("Giorni sopra la media:\n", giorni_sopra_media)
# Output:
# 2024-01-03 105 # 105 > 102.33
# 2024-01-05 107 # 107 > 105.66
# ...
Media mobile:
2024-01-01 NaN
2024-01-02 NaN
2024-01-03 102.333333
2024-01-04 103.333333
2024-01-05 105.000000
2024-01-06 106.666667
2024-01-07 108.333333
2024-01-08 110.000000
2024-01-09 111.666667
2024-01-10 115.000000
Freq: D, dtype: float64
Massimo rolling:
2024-01-01 NaN
2024-01-02 NaN
2024-01-03 NaN
2024-01-04 NaN
2024-01-05 107.0
2024-01-06 110.0
2024-01-07 110.0
2024-01-08 112.0
2024-01-09 115.0
2024-01-10 118.0
Freq: D, dtype: float64
Giorni sopra la media:
2024-01-03 105
2024-01-05 107
2024-01-06 110
2024-01-08 112
2024-01-09 115
2024-01-10 118
dtype: int64
Le rolling statistics (statistiche mobili o “finestra scorrevole”) sono fondamentali nell’analisi delle serie temporali, come i prezzi delle azioni, i dati meteorologici o le vendite nel tempo. Calcolare una media mobile (rolling().mean()) permette di levigare le fluttuazioni a breve termine e identificare le tendenze sottostanti. Il massimo rolling (rolling().max()) può aiutare a individuare i picchi in un determinato periodo. L’identificazione dei giorni in cui il prezzo supera la media mobile è un classico segnale usato nell’analisi tecnica dei mercati finanziari, suggerendo potenziali opportunità di acquisto o vendita. Questo esercizio offre un’introduzione pratica a concetti indispensabili per chiunque analizzi dati che cambiano nel tempo.
Esercizio 8 (Difficile): Analisi di Distribuzione con Cut e Qcut
Testo:
Data una Series di punteggi di studenti:
punteggi = pd.Series([78, 92, 65, 85, 88, 72, 95, 60, 81, 53])
Suddividi i punteggi in 3 categorie con cut:
- “Basso” (50-70), “Medio” (70-85), “Alto” (85-100).
Usa qcut per creare 3 gruppi con uguale frequenza (terzili).
Conta gli studenti in ogni gruppo per entrambi i metodi.
Soluzione:
# 1. Suddivisione con cut (bins fissi)
categorie = pd.cut(
punteggi,
bins=[50, 70, 85, 100],
labels=["Basso", "Medio", "Alto"]
)
print("Categorie (cut):\n", categorie.value_counts())
# Output:
# Basso 3 # 53, 60, 65
# Medio 4 # 72, 78, 81, 85
# Alto 3 # 88, 92, 95
# 2. Suddivisione con qcut (quantili)
terzili = pd.qcut(
punteggi,
q=3,
labels=["Q1", "Q2", "Q3"]
)
print("Terzili (qcut):\n", terzili.value_counts())
# Output (circa uguale conteggio):
# Q1 4 # Valori più bassi
# Q2 3 # Valori mediani
# Q3 3 # Valori alti
# 3. Confronto visivo
pd.DataFrame({
"Punteggio": punteggi,
"Categoria (cut)": categorie,
"Terzile (qcut)": terzili
})
Categorie (cut):
Medio 4
Basso 3
Alto 3
Name: count, dtype: int64
Terzili (qcut):
Q1 4
Q2 3
Q3 3
Name: count, dtype: int64
| Punteggio | Categoria (cut) | Terzile (qcut) | |
|---|---|---|---|
| 0 | 78 | Medio | Q2 |
| 1 | 92 | Alto | Q3 |
| 2 | 65 | Basso | Q1 |
| 3 | 85 | Medio | Q2 |
| 4 | 88 | Alto | Q3 |
| 5 | 72 | Medio | Q1 |
| 6 | 95 | Alto | Q3 |
| 7 | 60 | Basso | Q1 |
| 8 | 81 | Medio | Q2 |
| 9 | 53 | Basso | Q1 |
Gli strumenti pd.cut e pd.qcut sono essenziali per la discretizzazione (o binning) di variabili continue, ovvero trasformare dati numerici in categorie. pd.cut è utile quando si vogliono definire intervalli specifici (es. fasce di età, livelli di reddito fissi) e controllare manualmente i limiti dei “bin”. pd.qcut, invece, crea bin con un numero approssimativamente uguale di osservazioni, dividendo i dati in quantili (terzili, quartili, ecc.). Questo è particolarmente utile quando la distribuzione dei dati è molto asimmetrica e si vuole assicurare che ogni categoria abbia una rappresentazione simile. L’esercizio mostra come queste funzioni siano usate per classificare i punteggi degli studenti, un’applicazione diretta in ambito educativo o nel marketing per segmentare i clienti.
Esercizio 9 (Hero Mode): Simulazione Monte Carlo con Series
Testo:
Simula 10.000 lanci di due dadi e analizza la distribuzione della somma:
- Genera due Series casuali (
dado1edado2, valori 1-6). - Calcola la Series
somma_dadi.
Trova:
- La frequenza relativa di ogni somma possibile (2-12).
- La probabilità di ottenere un valore [math]\displaystyle \ge 10[/math].
- La skewness della distribuzione.
Soluzione:
import numpy as np
# 1. Generazione dei dadi
np.random.seed(42) # Per riproducibilità
dado1 = pd.Series(np.random.randint(1, 7, size=10_000))
dado2 = pd.Series(np.random.randint(1, 7, size=10_000))
# 2. Somma dei dadi
somma_dadi = dado1 + dado2
# 3. Analisi
freq_rel = somma_dadi.value_counts(normalize=True).sort_index()
print("Frequenze relative:\n", freq_rel)
# Probabilità somma >= 10
prob_10_plus = (somma_dadi >= 10).mean()
print("P(somma >= 10):", prob_10_plus) # ~0.167 (16.7%)
# Skewness
print("Skewness:", somma_dadi.skew()) # ~0 (distribuzione simmetrica)
# Grafico della distribuzione
somma_dadi.plot(kind='hist', bins=11, density=True, alpha=0.7)
plt.title("Distribuzione della somma di due dadi")
plt.show()
Risultati attesi:
Frequenze relative: 2 0.0273 3 0.0608 4 0.0811 5 0.1101 6 0.1383 7 0.1616 8 0.1414 9 0.1105 10 0.0858 11 0.0556 12 0.0275 Name: proportion, dtype: float64 P(somma >= 10): 0.1689 Skewness: -0.009971594128595534

- Le somme 7 e 6 sono le più frequenti (rispettivamente [math]\displaystyle \approx 16.7\%[/math] e [math]\displaystyle \approx 13.9\%[/math]).
- La distribuzione è simmetrica (skewness [math]\displaystyle \approx 0[/math]), come atteso per la somma di dadi equi.
Questo esercizio di simulazione Monte Carlo è un esempio avanzato di come Pandas possa essere usato per modellare scenari probabilistici. Simulare 10.000 lanci di dadi non solo dimostra i concetti di frequenza relativa e probabilità su larga scala, ma illustra anche come la legge dei grandi numeri porti la distribuzione simulata ad assomigliare alla distribuzione teorica attesa. L’analisi della skewness (che si avvicina a zero) conferma la simmetria attesa della somma di due dadi. Le simulazioni Monte Carlo sono strumenti potenti in molti campi, dalla finanza (per modellare il rischio) all’ingegneria (per valutare la probabilità di fallimento di un sistema) o alla ricerca scientifica, permettendo di esplorare scenari complessi impossibili da analizzare analiticamente.
Perché questi esercizi sono utili?
- Rolling statistics: Fondamentali per finanza e dati temporali.
- Cut/qcut: Usati per discretizzare variabili continue (es: fasce di reddito).
- Monte Carlo: Applicabile in probabilità, fisica e machine learning.



