# 📘 Pandas – Capire e manipolare i dati in modo semplice > *Una guida spiegata, con esempi utili e un tono amichevole.* --- ## 1. Perché esiste Pandas? Quando iniziamo a lavorare seriamente con i dati (analisi, grafici, machine learning…) scopriamo una cosa: > In Python la lista da sola non basta. Le liste sono ottime, i dizionari anche… ma quando i dati diventano tabellari (come un Excel), iniziamo a voler fare operazioni del tipo: * “Mostrami solo le righe che rispettano una certa condizione” * “Calcolami la media dei voti per ogni classe” * “Riordina i dati in base a una colonna” * “Gestisci i valori mancanti” * “Convertimi automaticamente le categorie in numeri” Con le strutture base di Python tutto questo **si può fare**, certo… ma richiede *molte righe*, molta pazienza e parecchi cicli `for`. Pandas fa tutto questo in **una riga sola** e lo fa in modo leggibile. --- ### 2. Cos’è un DataFrame e perché è così importante? Il **DataFrame** è il cuore di Pandas. È una “super tabella”, pensata per essere: * **facile da leggere** (somiglia a un Excel) * **facile da manipolare** (usa metodi chiari) * **veloce internamente** (è ottimizzato in C) * **flessibile** (colonne di tipi diversi, filtri rapidi, raggruppamenti…) Un DataFrame è come un foglio di calcolo: ha **righe** (con un indice) e **colonne** (ognuna con un nome). Per rendere tutto molto concreto, iniziamo subito con una tabella di esempio. --- ## 3. Un esempio concreto: tabella studenti ```python import pandas as pd data = { "Nome": ["Alice", "Bob", "Carlo", "Diana", "Elena"], "Età": [17, 18, 17, 18, 17], "Corso": ["Informatica", "Informatica", "Scientifico", "Scientifico", "Informatica"], "Voto": [88, 92, 75, 85, 90] } df = pd.DataFrame(data) df ``` **Output:** | | Nome | Età | Corso | Voto | | - | ----- | --- | ----------- | ---- | | 0 | Alice | 17 | Informatica | 88 | | 1 | Bob | 18 | Informatica | 92 | | 2 | Carlo | 17 | Scientifico | 75 | | 3 | Diana | 18 | Scientifico | 85 | | 4 | Elena | 17 | Informatica | 90 | Vedi come appare subito leggibile? * ogni colonna ha un nome * ogni riga ha un indice * ogni cella è raggiungibile facilmente --- ## 4. Come leggere e capire un DataFrame Prima di manipolare qualsiasi tabella, serve capirla. Pandas offre alcune funzioni “diagnostiche”, molto utili. --- ### 4.1. `df.head()` – Guarda le prime righe Serve per una *sbirciatina iniziale*, come quando apri un file Excel. ```python df.head() ``` Utile con dataset grandi (1000+ righe), perché evita di stampare tutto. --- ### 4.2. `df.info()` – Tipo di dati e struttura ```python df.info() ``` Questo comando risponde a domande del tipo: * quante righe ci sono? * quante colonne? * di che tipo sono i valori? (numeri, stringhe…) * ci sono valori mancanti? È fondamentale prima di fare machine learning: devi sapere com’è formato il dataset. --- ### 4.3. `df.describe()` – Statistiche numeriche veloci ```python df.describe() ``` Ti fa vedere: * media * deviazione standard * minimo e massimo * quartili È uno dei modi più rapidi per capire “come si distribuiscono” i numeri del dataset. --- ## 5. Selezionare e filtrare i dati (ovvero: come estrarre solo ciò che ci interessa) Un DataFrame può essere filtrato in vari modi. Vediamoli con esempi reali sulla tabella degli studenti. --- ### 5.1. Selezionare una colonna ```python df_nome = df["Nome"] ``` Questo restituisce una **Serie**, cioè una colonna singola. --- ### 5.2. Selezionare più colonne ```python df_nv = df[["Nome", "Voto"]] ``` Restituisce un DataFrame più piccolo. --- ### 5.3. Filtrare righe in base a una condizione Esempio: studenti con voto ≥ 90 ```python bravissimi = df[df["Voto"] >= 90] ``` **Perché è comodo?** Perché questo tipo di operazione con liste richiederebbe un `for`, un `if` e una lista nuova. Con Pandas basta una riga. --- ### 5.4. Filtri multipli Studentesse/i di Informatica con voto ≥ 85: ```python bravi_informatica = df[(df["Voto"] >= 85) & (df["Corso"] == "Informatica")] ``` Il bello è che la sintassi sembra quasi “linguaggio naturale”. --- ## 6. Aggiungere, modificare e rimuovere colonne --- ### 6.1. Creare una nuova colonna Esempio: normalizzare il voto su scala 0–1. ```python df["Voto_norm"] = df["Voto"] / 100 ``` **Perché è importante?** Molti algoritmi di machine learning funzionano meglio con valori normalizzati (0,1). --- ### 6.2. Modificare una colonna esistente Se voglio aggiungere 2 voti a **tutti** gli studenti posso semplicemente *aggiungere 2 a tutta la colonna `voto`*, faclie no? ```python df["Voto"] = df["Voto"] + 2 ``` --- ### 6.3. Eliminare colonne utilizzo la funzione `drop` offerta dall'istanza di `DataFrame` specificando quali colonne eliminare (lista) ```python df = df.drop(columns=["Voto_norm"]) ``` --- ## 7. Ordinare e raggruppare --- ### 7.1. Ordinare i dati utilizzo la funzione `sort_values` offerta dall'istanza di `DataFrame` specificando in base a quele colonna ordinare e se in modo crescente (`ascending=True`) o decrescente (`ascending=False`) ```python df.sort_values("Voto", ascending=False) ``` Per capire “chi ha preso il voto più alto”. --- ### 7.2. Raggruppamenti – `groupby()` Il metodo **`groupby()`** è uno degli strumenti più potenti di Pandas perché permette di **riorganizzare** i dati in gruppi e applicare **funzioni di aggregazione** per ottenere informazioni sintetiche (proprio come in SQL 😉). L’idea è semplice: > *“Raggruppa le righe che hanno lo stesso valore in una (o più) colonne, e poi calcola qualcosa su ogni gruppo.”* È un po’ come dire: “fammi la media per ogni classe”, “quanti studenti ci sono per ogni età”, “voto massimo in ogni combinazione corso–età”, ecc. --- #### Raggruppare per una singola colonna Esempio: media dei voti per corso. ```python df.groupby("Corso")["Voto"].mean() ``` **Output:** | Corso | Voto medio | | ----------- | ---------- | | Informatica | 90 | | Scientifico | 80 | --- #### Funzioni di aggregazione più comuni (e cosa significano) Una volta creato il gruppo, puoi applicare molte funzioni utili: | Funzione | Significato | | ----------- | ------------------------------------------------------- | | `mean()` | media dei valori | | `sum()` | somma del gruppo | | `max()` | valore massimo | | `min()` | valore minimo | | `count()` | numero di elementi (escludendo NaN) | | `size()` | numero di righe (inclusi NaN) | | `median()` | mediana | | `std()` | deviazione standard | | `var()` | varianza | | `nunique()` | quanti valori diversi compaiono (anche su stringhe) | La sintassi è sempre: ```python df.groupby("Colonna")["ColonnaNumerica"].funzione() ``` Esempi veloci: ```python df.groupby("Corso")["Voto"].max() df.groupby("Età")["Voto"].count() df.groupby("Corso")["Voto"].std() ``` --- #### Raggruppare più colonne insieme `groupby()` permette anche di raggruppare per **due o più colonne**, creando gruppi multidimensionali. Esempio: media dei voti per coppia **(Corso, Età)**. ```python df.groupby(["Corso", "Età"])["Voto"].mean() ``` **Output:** | Corso | Età | Voto medio | | ----------- | --- | ---------- | | Informatica | 17 | 89 | | Informatica | 18 | 92 | | Scientifico | 17 | 75 | | Scientifico | 18 | 85 | Questo è molto utile quando vuoi analizzare differenze tra categorie incrociate: corso × età, classe × sezione, genere × fascia d’età, ecc. --- #### Applicare più aggregazioni in una volta Pandas permette di applicare **più funzioni insieme** usando `agg()`. Esempio: ```python df.groupby("Corso")["Voto"].agg(["mean", "max", "min", "count"]) ``` **Output:** | Corso | mean | max | min | count | | ----------- | ---- | --- | --- | ----- | | Informatica | 90 | 92 | 88 | 3 | | Scientifico | 80 | 85 | 75 | 2 | --- ##### Applicare funzioni diverse a colonne diverse Puoi anche aggregare più colonne in modo indipendente: ```python df.groupby("Corso").agg({ "Voto": ["mean", "max"], "Età": "count" }) ``` * **`groupby()` crea gruppi** di righe che condividono lo stesso valore in una o più colonne. * **su ogni gruppo applichi una funzione**, che “riassume” numericamente il gruppo. * è perfetto per ottenere statistiche, confronti e analisi descrittive. * è uno degli strumenti più usati per preparare dati al machine learning o alla visualizzazione. --- ## 8. Pulizia dei dati – una fase cruciale nel ML Molti dataset reali sono **sporchi**: * valori mancanti * duplicati * tipi sbagliati * categorie inconsistenti La pulizia dei dati è uno dei passaggi più importanti nel machine learning. --- ### 8.1. Trovare valori mancanti ```python df.isnull().sum() ``` Ti dice quante celle vuote ci sono in ogni colonna. --- ### 8.2. Sostituire i valori mancanti ```python df.fillna(0) ``` Sostituisce i valori nulli con il valore `0` (ma potrebbe essere qualsiasi cosa... anche la media) --- ### 8.3. Eliminare righe incomplete In alternativa possiamo semplicemente eliminare (droppare) le righe con almeno un valore nullo ```python df.dropna() ``` --- ### 8.4. Trovare duplicati ```python df.duplicated().sum() ``` --- ### 8.5. Rimuovere duplicati ```python df.drop_duplicates() ``` --- ## 9. Preparare i dati per il Machine Learning Quando prepariamo un dataset per un modello di ML, dobbiamo: 1. **trasformare categorie in numeri** 2. **normalizzare** i valori 3. separare **features** e **target** 4. dividere in **train/test** --- ### 9.1. Convertire colonne categoriche in numeri Pandas lo fa automaticamente: ```python df_encoded = pd.get_dummies(df, columns=["Corso"]) ``` Trasforma: | Nome | Corso | | ---- | ----------- | | Bob | Informatica | in: | Nome | Corso_Informatica | Corso_Scientifico | | ---- | ----------------- | ----------------- | Questo è ciò che serve alla maggior parte dei modelli ML. #### Altro esempio Ho questa tabella con due colonne categoriche (colore e dimensione dove ognuna ha tre valori possibili): | Index | Color | Size | | ----- | ----- | ------ | | 0 | Red | Small | | 1 | Blue | Large | | 2 | Green | Medium | | 3 | Blue | Small | | 4 | Red | Large | **applico** `df_encoded = pd.get_dummies(df, columns=["Color","Size"])` **e ottengo:** | Index | Color_Blue | Color_Green | Color_Red | Size_Large | Size_Medium | Size_Small | | ----- | ---------- | ----------- | --------- | ---------- | ----------- | ---------- | | 0 | False | False | True | False | False | True | | 1 | True | False | False | True | False | False | | 2 | False | True | False | False | True | False | | 3 | True | False | False | False | False | True | | 4 | False | False | True | True | False | False | --- ### 9.2. Normalizzare valori numerici ```python df["Voto_norm"] = (df["Voto"] - df["Voto"].min()) / (df["Voto"].max() - df["Voto"].min()) ``` In questo esempio trasporto tutti i voti in un range che parte da 0 (sottraggo a tutti i voti il voto minimo) e poi divido ogni voto per l'ampiezza dell'intervallo (max-min). In questo otterrò valori compresi in `[0,1]` --- ### 9.3. Separare features e target ```python X = df[["Età", "Voto"]] y = df["Corso"] ``` In questo modo otterrò in `X` un dataframe con le sole colonne "Età" e "Voto" e in `Y` un dataframe con la sola colonna "Corso". Questo risulterà **estremamente** utile quando dovremo addestrare un modello di machine learning perché dovremo esplicitare al modello quali sono i valori di partenza (dati in ingresso (`X`)) e quali sono quelli da predire (output (`Y`)) --- ## 10. Ricettario di esempi utili, spiegati --- ### 🔹 Studenti di Informatica ```python df[df["Corso"] == "Informatica"] ``` > Filtriamo la tabella mantenendo solo le righe dove la colonna "Corso" vale "Informatica". --- ### 🔹 Chi ha il voto massimo? ```python df["Voto"].max() ``` > Selezioniamo la colonna “Voto” e chiediamo il suo valore massimo. --- ### 🔹 Media voti per età ```python df.groupby("Età")["Voto"].mean() ``` > Raggruppiamo per età → per ogni gruppo calcoliamo la media. --- ### 🔹 Aggiungere una colonna "Promosso" ```python df["Promosso"] = df["Voto"] >= 85 ``` Assegno alla nuova colonna "Promosso" l'**esito** della condizione *voto >= 85*. Ricordiamoci che questa condizione viene fatta per **tutta la tabella** contemporaneamente. > Creare colonne target binarie (due valori) è spesso molto utile per "dire" al mostro modello di ML che una specifica feature sappiamo essere utile. #### **Perchè è così utile?** Noi umani sappiamo che i voti 5, 6 o 7 sono differenti, lo capisce anche il computer. Il computer sa che `5!=6` e `5!=7` e `6!=7`. Noi umani sappiamo anche che eseguire il salto da 5 a 6 è **molto** più **pesante** che eseguire il salto tra 6 e 7. In maniera assoluta (`6-5` e `7-6`) sono lo stesso salto (`1`) ma contestualizzato nel registro scolastico sappiamo che i voti >= 6 sono positivi ("verdi") mentre quelli inferiori al 6 sono negativi ("rossi"). Di conseguenza, se vogliamo che anche i nostri modelli di ML siano al corrente di questa relazione presente nei dati ma "nascosta" o implicita dobbiamo renderla esplicita. --- ### 🔹 Ordinare per corso e poi voto ```python df.sort_values(["Corso", "Voto"], ascending=[True, False]) ``` > Prima ordina alfabeticamente per corso, poi decrescente per voto.