La libreria Python per l'analisi e manipolazione dei dati: Series, DataFrame, lettura CSV/JSON, pulizia dati, GroupBy, merge, correlazioni e grafici. Con esempi pratici ed esercizi.
Pandas — Guida completa in italiano
Pandas è la libreria Python di riferimento per l'analisi e manipolazione dei dati. Costruita sopra NumPy, fornisce due strutture dati potentissime: la Series (1D) e il DataFrame (2D, come un foglio Excel o una tabella SQL).
Il nome deriva da "Panel Data", un termine econometrico per dataset multidimensionali. È lo strumento numero uno per data scientist, analisti e chiunque lavori con dati strutturati in Python.
Il DataFrame è la struttura più usata: una tabella 2D con righe e colonne etichettate. Puoi pensarla come un dizionario di Series che condividono lo stesso indice.
Creare DataFrame in vari modi
import pandas as pd
# Da dizionario di liste
df = pd.DataFrame({
"prodotto": ["Mela", "Pane", "Latte"],
"prezzo": [0.5, 1.2, 1.0],
"quantita": [100, 50, 80]
})
# Da lista di dizionari
df2 = pd.DataFrame([
{"nome": "Anna", "eta": 28},
{"nome": "Luca", "eta": 34}
])
print(df)
Ispezionare un DataFrame
import pandas as pd
df = pd.DataFrame({
"prodotto": ["Mela", "Pane", "Latte", "Uova"],
"prezzo": [0.5, 1.2, 1.0, 2.8],
"quantita": [100, 50, 80, 30]
})
print(df.head(2)) # prime 2 righe
print(df.tail(2)) # ultime 2 righe
print(df.info()) # tipi e memoria
print(df.describe()) # statistiche numeriche
print(df.shape) # (4, 3)
print(df.dtypes) # tipo di ogni colonna
Accedere a righe e colonne
import pandas as pd
df = pd.DataFrame({
"nome": ["Anna", "Luca", "Marco"],
"eta": [28, 34, 45]
}, index=["a", "b", "c"])
# Una colonna → Series
print(df["nome"])
# Più colonne → DataFrame
print(df[["nome", "eta"]])
# loc: per etichetta
print(df.loc["b"]) # riga con indice "b"
print(df.loc["b", "eta"]) # 34
# iloc: per posizione
print(df.iloc[0]) # prima riga
print(df.iloc[0, 1]) # 28
💡 loc vs iloc: loc usa le etichette dell'indice, iloc usa le posizioni numeriche (come negli array). Ricordare questa differenza evita molti errori.
Leggere file CSV
Il formato CSV (Comma-Separated Values) è il più comune per i dataset. Pandas lo legge con una sola riga.
read_csv e to_csv
import pandas as pd
# Leggere un CSV
df = pd.read_csv("dati.csv")
# Opzioni comuni
df = pd.read_csv(
"dati.csv",
sep=";", # separatore (default ",")
encoding="utf-8",
decimal=",", # virgola decimale (formato IT)
nrows=1000, # leggi solo le prime 1000 righe
usecols=["nome", "prezzo"] # solo alcune colonne
)
# Salvare un DataFrame come CSV
df.to_csv("output.csv", index=False)
Esplorare un dataset appena letto
import pandas as pd
df = pd.read_csv("vendite.csv")
print(df.head()) # prime 5 righe
print(df.info()) # struttura
print(df.describe()) # statistiche
print(df.columns.tolist())# lista colonne
print(len(df)) # numero righe
print(df["categoria"].unique()) # valori distinti
print(df["categoria"].value_counts()) # conteggi
🎯 pd.read_csv() può leggere anche direttamente da un URL: pd.read_csv("https://.../dati.csv")
Leggere file JSON
Pandas legge anche JSON, formato comune nelle API web. La struttura viene convertita automaticamente in DataFrame.
read_json e to_json
import pandas as pd
# Da file JSON
df = pd.read_json("dati.json")
# Da una stringa JSON
import io
json_str = '''[
{"nome": "Anna", "eta": 28},
{"nome": "Luca", "eta": 34}
]'''
df = pd.read_json(io.StringIO(json_str))
print(df)
# Salvare come JSON
df.to_json("output.json", orient="records", indent=2)
JSON annidato con json_normalize
import pandas as pd
dati = [
{"nome": "Anna", "indirizzo": {"citta": "Roma", "cap": "00100"}},
{"nome": "Luca", "indirizzo": {"citta": "Milano", "cap": "20100"}}
]
# Appiattisce le strutture annidate
df = pd.json_normalize(dati)
print(df)
# nome indirizzo.citta indirizzo.cap
# 0 Anna Roma 00100
# 1 Luca Milano 20100
Analizzare i dati
Pandas offre metodi rapidi per ottenere una panoramica statistica del tuo dataset.
import pandas as pd
df = pd.DataFrame({
"nome": ["anna", "luca", "marco"],
"stipendio": [25000, 32000, 45000]
})
# Applica una funzione a una colonna
df["nome"] = df["nome"].apply(str.capitalize)
# Crea una nuova colonna calcolata
df["stipendio_netto"] = df["stipendio"] * 0.73
# Categorizza con una funzione
def fascia(s):
if s < 30000: return "Junior"
elif s < 40000: return "Mid"
return "Senior"
df["fascia"] = df["stipendio"].apply(fascia)
print(df)
Pulizia dei dati
I dati reali sono sporchi: contengono celle vuote, formati incoerenti, valori errati e duplicati. La pulizia dei dati (data cleaning) è spesso l'80% del lavoro di un data scientist.
Problema
Soluzione Pandas
Celle vuote (NaN)
dropna(), fillna()
Formato sbagliato
to_datetime(), astype()
Dati errati
filtro booleano, replace()
Duplicati
drop_duplicates()
💡 Prima di pulire, esplora sempre con df.info() e df.isnull().sum() per capire dove sono i problemi.
Celle vuote (valori mancanti)
I valori mancanti appaiono come NaN (Not a Number). Hai due strategie: rimuoverli o sostituirli.
Trovare i valori mancanti
import pandas as pd
import numpy as np
df = pd.DataFrame({
"nome": ["Anna", "Luca", None, "Marco"],
"eta": [28, np.nan, 45, 33],
"citta": ["Roma", "Milano", "Napoli", None]
})
print(df.isnull()) # True dove manca
print(df.isnull().sum()) # conteggio per colonna
# nome 1
# eta 1
# citta 1
print(df.isnull().sum().sum()) # 3 totali
Rimuovere vs Riempire
dropna() — rimuove
import pandas as pd
import numpy as np
df = pd.DataFrame({
"a": [1, 2, np.nan, 4],
"b": [5, np.nan, 7, 8]
})
# Rimuove righe con almeno un NaN
print(df.dropna())
# a b
# 0 1.0 5.0
# 3 4.0 8.0
# Rimuove colonne con NaN
print(df.dropna(axis=1))
fillna() — riempie
import pandas as pd
import numpy as np
df = pd.DataFrame({
"a": [1, 2, np.nan, 4],
"b": [5, np.nan, 7, 8]
})
# Riempi con un valore fisso
print(df.fillna(0))
# Riempi con la media della colonna
df["a"] = df["a"].fillna(df["a"].mean())
# Propaga l'ultimo valore valido
print(df.ffill())
🎯 Sostituire i NaN con la media o la mediana è una tecnica comune (imputation). Per dati ordinati nel tempo, ffill()/bfill() sono spesso più sensati.
Formato dei dati sbagliato
Spesso le date sono stringhe e i numeri sono testo. Convertirli al tipo corretto è essenziale per l'analisi.
import pandas as pd
df = pd.DataFrame({
"citta": ["Roma", "MILANO", "roma", "Napli", "Milano"]
})
# Uniforma maiuscole/minuscole
df["citta"] = df["citta"].str.capitalize()
# Correggi refusi specifici
df["citta"] = df["citta"].replace({"Napli": "Napoli"})
print(df["citta"].value_counts())
# Roma 2
# Milano 2
# Napoli 1
Rimuovere i duplicati
Le righe duplicate falsano le statistiche. Pandas le individua e rimuove facilmente.
Trovare e rimuovere duplicati
import pandas as pd
df = pd.DataFrame({
"nome": ["Anna", "Luca", "Anna", "Marco", "Luca"],
"eta": [28, 34, 28, 45, 34]
})
# Quali righe sono duplicate?
print(df.duplicated())
# 0 False
# 1 False
# 2 True ← duplicato di riga 0
# 3 False
# 4 True ← duplicato di riga 1
print(df.duplicated().sum()) # 2 duplicati
# Rimuovi i duplicati (tiene la prima occorrenza)
df_pulito = df.drop_duplicates()
print(df_pulito)
Duplicati su colonne specifiche
import pandas as pd
df = pd.DataFrame({
"email": ["a@x.it", "b@x.it", "a@x.it"],
"nome": ["Anna", "Bruno", "Anna R."],
"iscrizione": ["2024-01", "2024-02", "2024-03"]
})
# Duplicati basati solo sull'email
# keep="last" tiene l'ultima occorrenza
df_pulito = df.drop_duplicates(subset="email", keep="last")
print(df_pulito)
# Reset dell'indice dopo la rimozione
df_pulito = df_pulito.reset_index(drop=True)
print(df_pulito)
Selezione e filtro
Selezionare sottoinsiemi di dati è l'operazione più frequente in Pandas. Funziona in modo simile all'indicizzazione booleana di NumPy.
Filtro con condizioni
import pandas as pd
df = pd.DataFrame({
"nome": ["Anna", "Luca", "Marco", "Sara", "Elena"],
"eta": [28, 34, 45, 22, 39],
"citta": ["Roma", "Milano", "Roma", "Napoli", "Milano"],
"stipendio": [25000, 38000, 52000, 21000, 41000]
})
# Una condizione
print(df[df["eta"] > 30])
# Più condizioni: & (and), | (or), ~ (not)
print(df[(df["eta"] > 30) & (df["stipendio"] > 40000)])
# isin: appartenenza a una lista
print(df[df["citta"].isin(["Roma", "Napoli"])])
# query: sintassi più leggibile
print(df.query("eta > 30 and stipendio > 40000"))
Ordinare i dati
import pandas as pd
df = pd.DataFrame({
"nome": ["Anna", "Luca", "Marco", "Sara"],
"eta": [28, 34, 45, 22],
"stipendio": [25000, 38000, 52000, 21000]
})
# Ordina per una colonna
print(df.sort_values("eta"))
# Decrescente
print(df.sort_values("stipendio", ascending=False))
# Ordina per più colonne
print(df.sort_values(["citta", "eta"]) if "citta" in df else df)
# Top 3 stipendi
print(df.nlargest(3, "stipendio"))
⚠️ Usa & e | (non and/or) per combinare condizioni, e racchiudi ogni condizione tra parentesi: (df["a"] > 1) & (df["b"] < 5).
GroupBy — raggruppare e aggregare
groupby() applica lo schema split-apply-combine: divide i dati in gruppi, applica una funzione a ciascun gruppo, ricombina i risultati. È l'equivalente del GROUP BY di SQL.
GroupBy di base
import pandas as pd
df = pd.DataFrame({
"regione": ["Nord", "Sud", "Nord", "Sud", "Centro"],
"venditore": ["Anna", "Luca", "Marco", "Sara", "Elena"],
"vendite": [250, 180, 320, 150, 290]
})
# Somma vendite per regione
print(df.groupby("regione")["vendite"].sum())
# regione
# Centro 290
# Nord 570
# Sud 330
# Media
print(df.groupby("regione")["vendite"].mean())
# Conteggio
print(df.groupby("regione").size())
Aggregazioni multiple con agg
import pandas as pd
df = pd.DataFrame({
"categoria": ["A", "B", "A", "B", "A"],
"vendite": [100, 200, 150, 250, 120],
"profitto": [20, 50, 30, 60, 25]
})
# Più statistiche in una volta
print(df.groupby("categoria").agg(
vendite_tot=("vendite", "sum"),
vendite_media=("vendite", "mean"),
profitto_max=("profitto", "max"),
n=("vendite", "count")
))
# vendite_tot vendite_media profitto_max n
# categoria
# A 370 123.333333 30 3
# B 450 225.000000 60 2
💡 GroupBy è uno degli strumenti più potenti di Pandas. Padroneggiarlo ti permette di rispondere a quasi ogni domanda analitica sui tuoi dati.
Merge e Join
Spesso i dati sono divisi in più tabelle. merge() le unisce in base a una colonna comune, esattamente come i JOIN di SQL.
merge tra due DataFrame
import pandas as pd
clienti = pd.DataFrame({
"id_cliente": [1, 2, 3],
"nome": ["Anna", "Luca", "Marco"]
})
ordini = pd.DataFrame({
"id_ordine": [101, 102, 103, 104],
"id_cliente": [1, 2, 1, 3],
"totale": [50, 80, 30, 120]
})
# Inner join sulla colonna comune
df = pd.merge(clienti, ordini, on="id_cliente")
print(df)
# id_cliente nome id_ordine totale
# 0 1 Anna 101 50
# 1 1 Anna 103 30
# 2 2 Luca 102 80
# 3 3 Marco 104 120
Tipi di join e concat
import pandas as pd
a = pd.DataFrame({"k": [1, 2, 3], "x": ["a", "b", "c"]})
b = pd.DataFrame({"k": [2, 3, 4], "y": ["B", "C", "D"]})
# inner (default): solo chiavi comuni
print(pd.merge(a, b, on="k", how="inner"))
# left: tutte le righe di a
print(pd.merge(a, b, on="k", how="left"))
# outer: tutte le righe di entrambi
print(pd.merge(a, b, on="k", how="outer"))
# concat: impila DataFrame
df1 = pd.DataFrame({"x": [1, 2]})
df2 = pd.DataFrame({"x": [3, 4]})
print(pd.concat([df1, df2], ignore_index=True))
how
Risultato
inner
Solo le chiavi presenti in entrambi
left
Tutte le righe di sinistra + match
right
Tutte le righe di destra + match
outer
Tutte le righe di entrambi
Correlazioni
Il metodo corr() calcola la correlazione tra le colonne numeriche: un valore tra -1 e 1 che misura quanto due variabili si muovono insieme.
import pandas as pd
import matplotlib.pyplot as plt
df = pd.DataFrame({
"categoria": ["A", "B", "C", "D"],
"valore": [25, 40, 30, 55]
}).set_index("categoria")
df.plot(kind="bar") # barre verticali
df.plot(kind="barh") # barre orizzontali
df.plot(kind="pie", y="valore") # torta
# Istogramma e scatter (su dati numerici)
serie = pd.Series([1,2,2,3,3,3,4,4,5])
serie.plot(kind="hist", bins=5)
plt.show()
kind
Tipo di grafico
line
Linee (serie temporali)
bar / barh
Barre verticali/orizzontali
hist
Istogramma (distribuzioni)
scatter
Dispersione (relazioni)
pie
Torta (proporzioni)
box
Box plot (quartili)
Esercizi pratici
🏋️ Esercizio 1 — Analisi vendite
Crea un DataFrame con le vendite di 5 venditori in 3 regioni. Trova: il totale vendite per regione, il venditore con più vendite, e la media vendite generale.
Soluzione Esercizio 1
import pandas as pd
df = pd.DataFrame({
"venditore": ["Anna","Luca","Marco","Sara","Elena"],
"regione": ["Nord","Sud","Nord","Centro","Sud"],
"vendite": [250, 180, 320, 150, 290]
})
# Totale per regione
print(df.groupby("regione")["vendite"].sum())
# Venditore top
top = df.loc[df["vendite"].idxmax()]
print(f"Top: {top['venditore']} con {top['vendite']}")
# Media generale
print(f"Media: {df['vendite'].mean():.1f}")
🏋️ Esercizio 2 — Pulizia dataset
Dato un DataFrame con valori mancanti e duplicati, puliscilo: rimuovi i duplicati, riempi le età mancanti con la media e converti i nomi in formato corretto (prima lettera maiuscola).
Soluzione Esercizio 2
import pandas as pd
import numpy as np
df = pd.DataFrame({
"nome": ["anna","LUCA","anna","marco"],
"eta": [28, np.nan, 28, 45]
})
# 1. Rimuovi duplicati
df = df.drop_duplicates()
# 2. Riempi età mancanti con la media
df["eta"] = df["eta"].fillna(df["eta"].mean())
# 3. Formatta i nomi
df["nome"] = df["nome"].str.capitalize()
df = df.reset_index(drop=True)
print(df)
🏋️ Esercizio 3 — Merge e analisi
Hai due tabelle: clienti (id, nome) e ordini (id_cliente, totale). Uniscile e calcola la spesa totale di ogni cliente, ordinando dal più alto.
Complimenti! Hai completato il corso Pandas e ora sai leggere, pulire, analizzare e visualizzare dati reali in Python. Pandas è lo strumento quotidiano di ogni data analyst e data scientist.
Cosa hai imparato:
Series e DataFrame: le strutture dati fondamentali
Leggere/scrivere CSV e JSON
Pulizia dati: valori mancanti, formati, duplicati
Selezione, filtro, ordinamento e query
GroupBy, merge/join e correlazioni
Visualizzazione con .plot()
Il passo successivo? Approfondisci con il corso completo di Data Science, dove userai Pandas insieme a NumPy e Matplotlib su progetti reali.