Corso Python / Pulire e analizzare dati tabellari con pandas

pandas permette di leggere e analizzare dati in righe e colonne attraverso un DataFrame. Prima di aggregare, scegli i tipi, identifica gli importi non validi e rendi visibile il numero delle esclusioni: un totale ha significato solo se sai quali righe contiene.

Scegliere i tipi delle colonne e installare pandas

Un DataFrame rappresenta una tabella; una Series è una struttura monodimensionale usata, tra l’altro, per una colonna. Il tipo inferito da un CSV non coincide necessariamente con il significato dei dati: un ID come 001 deve rimanere una stringa se gli zeri iniziali sono importanti. Qui leggiamo le colonne come stringhe e convertiamo esplicitamente soltanto amount.

pandas è un pacchetto esterno, non un modulo della libreria standard. Seleziona un ambiente virtuale compatibile con il tuo Python e sistema operativo, poi installa in quell’ambiente:

python -m pip install pandas

La guida introduttiva ufficiale di pandas presenta lettura, selezione e aggregazione. Se pip segnala incompatibilità, verifica la versione dell’interprete e i requisiti del pacchetto prima di cambiare l’installazione di sistema.

Eseguire una validazione e un riepilogo completi

In una cartella di esercitazione distinta salva questo file UTF-8 come pandas_demo.py. Il CSV è incluso come stringa, perciò non serve ancora un file di dati esterno:

from io import StringIO
import pandas as pd

raw = "id,category,amount\ne1,food,3500\ne2,food,8000\ne3,transport,2500\ne4,food,bad\n"
df = pd.read_csv(StringIO(raw), dtype="string", keep_default_na=False)
amounts = pd.to_numeric(df["amount"], errors="coerce")
valid = amounts.notna() & (amounts >= 0) & (amounts % 1 == 0)
clean = df.loc[valid].copy()
clean["amount"] = amounts.loc[valid].astype("int64")
report = clean.groupby("category", as_index=False, sort=True)["amount"].sum()
print("accepted:", len(clean), "rejected:", int((~valid).sum()))
for row in report.itertuples(index=False):
    print(row.category, row.amount)

Dal terminale nella cartella del programma, con l’ambiente selezionato, esegui python pandas_demo.py. Usa python3 se è il nome effettivo dell’interprete nel tuo ambiente macOS/Ubuntu. Il risultato previsto è:

accepted: 3 rejected: 1
food 11500
transport 2500

StringIO rende leggibile la stringa come un file. keep_default_na=False evita di interpretare automaticamente i marcatori testuali di valori mancanti durante questa lettura. La conversione numerica marca poi bad come mancante; la maschera sceglie i valori presenti, non negativi e senza parte frazionaria. Le tre righe accettate producono 11.500 per food e 2.500 per transport.

groupby raggruppa per categoria, sum somma gli importi e as_index=False mantiene la categoria come colonna del risultato. sort=True ordina le chiavi dei gruppi. itertuples(index=False) permette di stampare ogni riga senza includere l’indice del DataFrame.

Interpretare valori mancanti, maschere e copie

pd.to_numeric(..., errors="coerce") contrassegna come mancanti i testi che non riesce a convertire. Non li rende uguali a zero e non autorizza a ignorarli. Controlla la maschera valid, il conteggio di ~valid e le righe escluse. La documentazione di to_numeric descrive conversione e possibili perdite di precisione.

df.loc[valid].copy() crea la tabella selezionata su cui assegnare gli importi convertiti, lasciando il testo originale disponibile in df. L’assegnazione usa gli indici corrispondenti delle righe accettate. Puoi esaminare df.loc[~valid] per conservare gli scarti e spiegare come hai ottenuto il risultato.

L’esempio presume importi piccoli rappresentabili in int64. Il controllo numerico non è una validazione completa dei record: non rifiuta automaticamente ID duplicati, categorie vuote o identificatori mancanti. Aggiungi queste regole secondo il contratto dei tuoi dati. Anche la somma dei gruppi deve restare nel campo del tipo scelto; per interi enormi o importi decimali esatti serve una politica esplicita di precisione, intervallo e aggregazione.

Non confondere un valore matematicamente intero con una grammatica composta solo da cifre: un testo come 8.0 può diventare 8 e superare questa maschera. Il progetto è centrato su conversione e valori, mentre il programma CSV della libreria standard applica un contratto testuale più rigoroso agli importi.

Per leggere un file vero sostituisci soltanto la riga che costruisce df con pd.read_csv("expenses-input.csv", dtype={"id":"string", "category":"string", "amount":"string"}), lasciando conversione e maschera successive. In questa variante non è specificato keep_default_na=False: valgono le impostazioni predefinite per i valori mancanti. La documentazione di read_csv aiuta a scegliere consapevolmente marcatori e tipi. Riempire tutti gli importi mancanti con zero cancellerebbe la distinzione tra “nessun importo” e “importo realmente nullo”.

Salvare e riconciliare il rapporto

Per esportare il risultato, aggiungi questa riga alla fine di pandas_demo.py, dopo la creazione e la stampa di report:

report.to_csv("category-report.csv", index=False, encoding="utf-8")

Usa un percorso di uscita nuovo: la modalità predefinita di to_csv sostituisce un file omonimo. index=False omette la colonna dell’indice; non elimina la colonna category. Consulta la documentazione di DataFrame.to_csv per le opzioni di scrittura.

  1. Con l’ingresso originale verifica tre righe accettate, una esclusa e un totale di 14.000 sia nei dati puliti sia nel rapporto.
  2. Cambia soltanto bad in 0: il conteggio diventa quattro accettate e zero escluse, mentre il totale rimane 14.000.
  3. Cambia quel valore in -1: tornano tre accettate e una esclusa. Confronta i conteggi, non soltanto il totale invariato.
  4. Ripristina bad e controlla che accettate più escluse corrispondano alle quattro righe di partenza.

Apri il CSV prodotto e verifica le due categorie e i loro totali, senza una colonna aggiuntiva per l’indice. Conserva ingresso e scarti quando serve una traccia delle trasformazioni: il solo file aggregato non permette di ricostruire quali righe siano state escluse.