Corso
Questo tutorial ti guiderà attraverso alcuni concetti e passaggi di base per la preparazione dei dati. La preparazione dei dati è il primo passo dopo aver messo le mani su qualsiasi tipo di dataset. In questa fase pre-processi i dati grezzi in una forma che possa essere analizzata facilmente e con precisione. Una corretta preparazione dei dati consente un’analisi efficiente: può eliminare errori e imprecisioni che potrebbero essersi verificati durante la raccolta dei dati e aiutare a rimuovere parte dei bias dovuti a una scarsa qualità dei dati. Per questo motivo, gran parte del tempo di un analista è dedicato a questo passaggio cruciale.
Caricare i dati, ripulirli (rimuovere dati non necessari o errati), trasformarne i formati e riordinarli sono le varie fasi coinvolte nella preparazione dei dati. In questo tutorial userai la libreria Pandas di Python per la preparazione dei dati.
Assicurati di conoscere concetti come DataFrame, Series, ecc. di Pandas, perché li incontrerai spesso in questo tutorial. Puoi approfondire pandas nel tutorial su Pandas di DataCamp.
In questo tutorial:
- Per prima cosa inizierai con una breve introduzione a Pandas, la libreria utilizzata.
- Poi caricherai i dati.
- Successivamente vedrai cosa sono i dati mancanti e come lavorarci. Vedrai come gestire i dati mancanti e come compilarli.
- Imparerai poi alcune tecniche di trasformazione dei dati: sostituzione dei valori, concatenazione di series di pandas, arricchimento del dataset con la funzione map, discretizzazione dei dati continui e, infine, variabili dummy e one-hot encoding.
Lungo il percorso troverai suggerimenti, trucchi e riferimenti ad altro materiale di approfondimento, se vuoi esplorare ulteriormente un argomento.
Iniziamo con una breve introduzione a Pandas...
Pandas
Pandas è una libreria software scritta per Python. È molto famosa nella comunità data science perché offre strutture dati potenti, espressive e flessibili che rendono facile la manipolazione e l’analisi dei dati, ed è gratuita. Inoltre, se hai un caso d’uso specifico e nuovo, puoi condividerlo in una delle mailing list di Python o sul repository GitHub di pandas: in effetti, molte funzionalità di pandas sono state guidate proprio da casi d’uso reali.
Per un’ottima introduzione a pandas, dai un’occhiata al corso Pandas Foundation di DataCamp. È molto interattivo e il primo capitolo è gratuito!
Per usare la libreria pandas, devi prima importarla. Digita questo nella tua console Python:
import pandas as pd
Caricamento dei dati
Il primo passo della preparazione dei dati è… ottenere dei dati. Se hai un file .csv, puoi caricarlo facilmente nel tuo sistema usando la funzione .read_csv() di pandas. Puoi quindi digitare:
data = pd.read_csv('path_to_file.csv')
Tuttavia, per questo tutorial lavorerai con DataFrame e Series più piccoli e facili da capire, creati al volo.
Dati mancanti
Gestione dei dati mancanti
Questo è un problema molto diffuso nel mondo dell’analisi dei dati.

I dati mancanti possono comparire nel dataset per vari motivi: il dato per un determinato campo non è stato inserito dall’utente/dall’applicazione di raccolta, i dati sono andati persi durante un trasferimento manuale, un errore di programmazione, ecc. A volte è fondamentale capirne la causa, perché questo influenzerà il modo in cui li gestirai. Ma ne parleremo più avanti nel tutorial. Per ora, concentriamoci su cosa fare se hai dati mancanti…
Per i dati numerici, pandas usa il valore in virgola mobile NaN (Not a Number) per rappresentare i dati mancanti. È un valore univoco definito nella libreria Numpy, quindi dovremo importarla. NaN è il marcatore predefinito dei valori mancanti per motivi di velocità computazionale e praticità. È un valore sentinella, nel senso che è un valore fittizio o di bandiera che può essere facilmente rilevato e gestito con le funzioni di pandas.

Giochiamo un po’ con i dati…
import numpy as np
# Creating a pandas series
data = pd.Series([0, 1, 2, 3, 4, 5, np.nan, 6, 7, 8])
# To check if and what index in the dataset contains null value
data.isnull()
0 False
1 False
2 False
3 False
4 False
5 False
6 True
7 False
8 False
9 False
dtype: bool
Sopra abbiamo usato la funzione isnull() che restituisce un valore booleano true o false. True quando il dato in quell’indice è effettivamente mancante o NaN. L’opposto è la funzione notnull().
# To check where the dataset does not contain null value - opposite of isnull()
data.notnull()
0 True
1 True
2 True
3 True
4 True
5 True
6 False
7 True
8 True
9 True
dtype: bool
Inoltre, possiamo usare la funzione dropna() per filtrare i dati mancanti, rimuovere il valore nullo (mancante) e visualizzare solo i valori non nulli. Tuttavia, il valore NaN non viene realmente eliminato e si trova ancora nel dataset originale.
# Will not show the index 6 cause it contains null (NaN) value
data.dropna()
0 0.0
1 1.0
2 2.0
3 3.0
4 4.0
5 5.0
7 6.0
8 7.0
9 8.0
dtype: float64
data
0 0.0
1 1.0
2 2.0
3 3.0
4 4.0
5 5.0
6 NaN
7 6.0
8 7.0
9 8.0
dtype: float64
Per "eliminare" davvero il valore NaN puoi o salvare il nuovo dataset (senza NaN) in modo da non toccare la Series originale, oppure applicare un drop inplace. L’argomento inplace ha valore predefinito false.
not_null_data = data.dropna()
not_null_data
0 0.0
1 1.0
2 2.0
3 3.0
4 4.0
5 5.0
7 6.0
8 7.0
9 8.0
dtype: float64
# Drop the 6th index in the original 'data' since it has a NaN place
data.dropna(inplace = True)
data
0 0.0
1 1.0
2 2.0
3 3.0
4 4.0
5 5.0
7 6.0
8 7.0
9 8.0
dtype: float64
I dataframe però possono essere più complessi e a 2 dimensioni, cioè contenere righe e colonne. Anche in questo caso, pandas ti copre…
# Creating a dataframe with 4 rows and 4 columns (4*4 matrix)
data_dim = pd.DataFrame([[1,2,3,np.nan],[4,5,np.nan,np.nan],[7,np.nan,np.nan,np.nan],[np.nan,np.nan,np.nan,np.nan]])
data_dim
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 1.0 | 2.0 | 3.0 | NaN |
| 1 | 4.0 | 5.0 | NaN | NaN |
| 2 | 7.0 | NaN | NaN | NaN |
| 3 | NaN | NaN | NaN | NaN |
Ora diciamo che vuoi eliminare solo le righe o le colonne che sono tutte nulle o solo quelle che contengono una certa quantità di valori nulli. Esamina i vari scenari: ricorda che l’eliminazione non avviene inplace, quindi il dataset reale non viene toccato. Fai attenzione agli argomenti passati alla funzione dropna() per determinare come eliminare i dati mancanti.
# Drop all rows and columns containing NaN value
data_dim.dropna()
| 0 | 1 | 2 | 3 |
|---|
# Drop all rows and columns containing entirely of NaN value
data_dim.dropna(how = 'all')
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 1.0 | 2.0 | 3.0 | NaN |
| 1 | 4.0 | 5.0 | NaN | NaN |
| 2 | 7.0 | NaN | NaN | NaN |
# Drop only columns that contain entirely NaN value
# Default is 0 - which signifies rows
data_dim.dropna(axis = 1, how = 'all')
| 0 | 1 | 2 | |
|---|---|---|---|
| 0 | 1.0 | 2.0 | 3.0 |
| 1 | 4.0 | 5.0 | NaN |
| 2 | 7.0 | NaN | NaN |
| 3 | NaN | NaN | NaN |
# Drop all columns that have more than 2 NaN values
data_dim.dropna(axis = 1, thresh = 2)
| 0 | 1 | |
|---|---|---|
| 0 | 1.0 | 2.0 |
| 1 | 4.0 | 5.0 |
| 2 | 7.0 | NaN |
| 3 | NaN | NaN |
# Drop all rows that have more than 2 NaN values
data_dim.dropna(thresh = 2)
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 1.0 | 2.0 | 3.0 | NaN |
| 1 | 4.0 | 5.0 | NaN | NaN |
Ora sai come identificare ed eliminare i valori mancanti, sia per visualizzare il dataset risultante sia per una cancellazione inplace. In molti casi, però, eliminare semplicemente i valori nulli non è un’opzione praticabile e potresti voler compilare i dati mancanti con altri valori. Vediamo come farlo…
Compilare i dati mancanti
Per sostituire o meglio "riempire" i dati nulli, puoi usare la funzione fillna(). Ad esempio, proviamo a usare lo stesso dataset di prima e riempire i valori NaN con 0.
# Check what the dataset looks like again
data_dim
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 1.0 | 2.0 | 3.0 | NaN |
| 1 | 4.0 | 5.0 | NaN | NaN |
| 2 | 7.0 | NaN | NaN | NaN |
| 3 | NaN | NaN | NaN | NaN |
# Fill the NaN values with 0
data_dim_fill = data_dim.fillna(0)
data_dim_fill
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 1.0 | 2.0 | 3.0 | 0.0 |
| 1 | 4.0 | 5.0 | 0.0 | 0.0 |
| 2 | 7.0 | 0.0 | 0.0 | 0.0 |
| 3 | 0.0 | 0.0 | 0.0 | 0.0 |
E, come con dropna(), puoi fare molte altre cose a seconda dell’argomento che passi. Ricorda anche che passando l’argomento inplace = True apporterai la modifica al dataset originale.
# Pass a dictionary to use differnt values for each column
data_dim_fill = data_dim.fillna({0: 0, 1: 8, 2: 9, 3: 10})
data_dim_fill
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 1.0 | 2.0 | 3.0 | 10.0 |
| 1 | 4.0 | 5.0 | 9.0 | 10.0 |
| 2 | 7.0 | 8.0 | 9.0 | 10.0 |
| 3 | 0.0 | 8.0 | 9.0 | 10.0 |
Puoi passare un argomento method alla funzione fillna() che propaga automaticamente i valori non nulli in avanti (ffill o pad) o all’indietro (bfill o backfill).
# Pass method to determine how to fill-up the column - forward here
data_dim_fill = data_dim.fillna(method='ffill')
data_dim_fill
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 1.0 | 2.0 | 3.0 | NaN |
| 1 | 4.0 | 5.0 | 3.0 | NaN |
| 2 | 7.0 | 5.0 | 3.0 | NaN |
| 3 | 7.0 | 5.0 | 3.0 | NaN |
Puoi anche limitare il numero di riempimenti. Per esempio, riempi solo due posizioni nelle colonne… Inoltre, se passi axis = 1 il riempimento verrà fatto sulle righe.
# Pass method to determine how to fill-up the column - forward here
data_dim_fill = data_dim.fillna(method='ffill', limit = 2)
data_dim_fill
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 1.0 | 2.0 | 3.0 | NaN |
| 1 | 4.0 | 5.0 | 3.0 | NaN |
| 2 | 7.0 | 5.0 | 3.0 | NaN |
| 3 | 7.0 | 5.0 | NaN | NaN |
# Pass method to determine how to fill-up the row - forward here
data_dim_fill = data_dim.fillna(axis = 1, method='ffill')
data_dim_fill
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 1.0 | 2.0 | 3.0 | 3.0 |
| 1 | 4.0 | 5.0 | 5.0 | 5.0 |
| 2 | 7.0 | 7.0 | 7.0 | 7.0 |
| 3 | NaN | NaN | NaN | NaN |
Con un po’ di comprensione dei dati e del tuo caso d’uso, puoi usare la funzione fillna() in molti altri modi oltre al semplice riempimento con numeri. Potresti riempire usando la media con mean() o il valore mediano con median()…
# Check the data_dim dataset
data_dim
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 1.0 | 2.0 | 3.0 | NaN |
| 1 | 4.0 | 5.0 | NaN | NaN |
| 2 | 7.0 | NaN | NaN | NaN |
| 3 | NaN | NaN | NaN | NaN |
# Fill the NaN value with mean values in the corresponding column
data_dim_fill = data_dim.fillna(data_dim.mean())
data_dim_fill
| 0 | 1 | 2 | 3 | |
|---|---|---|---|---|
| 0 | 1.0 | 2.0 | 3.0 | NaN |
| 1 | 4.0 | 5.0 | 3.0 | NaN |
| 2 | 7.0 | 3.5 | 3.0 | NaN |
| 3 | 4.0 | 3.5 | 3.0 | NaN |
Trasformazione dei dati
Sostituzione dei valori
Finora hai lavorato solo con dati mancanti (NaN), ma potrebbero esserci situazioni in cui vuoi sostituire un valore non nullo con un valore diverso. Oppure un valore nullo è registrato come un numero casuale e quindi deve essere trattato come NaN piuttosto che come numero. È qui che la funzione replace() torna utile…
Creiamo questa volta un dataset diverso.
data = pd.Series([1,2,-99,4,5,-99,7,8,-99])
data
0 1
1 2
2 -99
3 4
4 5
5 -99
6 7
7 8
8 -99
dtype: int64
# Replace the placeholder -99 as NaN
data.replace(-99, np.nan)
0 0.0
1 1.0
2 2.0
3 3.0
4 4.0
5 5.0
7 6.0
8 7.0
9 8.0
dtype: float64
Non vedrai più -99, perché è stato sostituito con NaN e quindi non viene mostrato. Allo stesso modo puoi passare più valori da sostituire. Per farlo, creeremo un’altra series e poi concateneremo la series originale con la nuova per poi applicare la sostituzione multipla.
Concatenare Series di pandas
Per farlo, possiamo usare la funzione concat() di pandas. Per far proseguire l’indicizzazione dopo la concatenazione, puoi passare l’argomento ignore_index = True.
# Create a new Series
new_data = pd.Series([-100, 11, 12, 13])
combined_series = pd.concat([data, new_data], ignore_index = True)
combined_series
0 1
1 2
2 -99
3 4
4 5
5 -99
6 7
7 8
8 -99
9 -100
10 11
11 12
12 13
dtype: int64
# Let's replace -99 and -100 as NaN in the new combined_series
data_replaced = combined_series.replace([-99, -100], np.nan)
data_replaced
0 1.0
1 2.0
2 NaN
3 4.0
4 5.0
5 NaN
6 7.0
7 8.0
8 NaN
9 NaN
10 11.0
11 12.0
12 13.0
dtype: float64
# Argument passed can also be a dictionary with separate values
data_replaced = combined_series.replace({-99: np.nan, -100: 0})
# Same as: new_data.replace([-99, -100], [np.nan, 0])
data_replaced
0 1.0
1 2.0
2 NaN
3 4.0
4 5.0
5 NaN
6 7.0
7 8.0
8 NaN
9 0.0
10 11.0
11 12.0
12 13.0
dtype: float64
Aggiungere conoscenza - funzione map
In alcune situazioni potresti voler aggiungere maggiore insight a ciò che hai già, in base a una logica. È qui che puoi usare map e una combinazione di funzioni per ottenere ciò che vuoi. La logica può diventare più complicata, ma prova a capirla con questo esempio.
Supponiamo di avere un dataframe di numeri mappati alla loro corrispondente conta in inglese.
data_number = pd.DataFrame({'english': ['zero','one','two','three','four','five'],
'digits': [0,1,2,3,4,5]})
data_number
| english | digits | |
|---|---|---|
| 0 | zero | 0 |
| 1 | one | 1 |
| 2 | two | 2 |
| 3 | three | 3 |
| 4 | four | 4 |
| 5 | five | 5 |
Ora diciamo che vuoi aggiungere un’altra colonna che indichi i multipli di due come "Yes" e il resto come "No". Puoi scrivere una mappatura di ciascun termine inglese distinto alla sua corrispondente etichetta "Yes" o "No".
english_to_multiple = {
'two': 'yes',
'four': 'yes'
}
Quindi puoi chiamare la funzione map per aggiungere la colonna quando il valore nella colonna English è un multiplo di 2. Cosa viene inserito nelle altre colonne non multiple? Scopriamolo…
data_number['multiple'] = data_number['english'].map(english_to_multiple)
data_number
| english | digits | multiple | |
|---|---|---|---|
| 0 | zero | 0 | NaN |
| 1 | one | 1 | NaN |
| 2 | two | 2 | yes |
| 3 | three | 3 | NaN |
| 4 | four | 4 | yes |
| 5 | five | 5 | NaN |
Le altre colonne sono riempite con valori NaN, e sai già come lavorare ulteriormente con i valori mancanti. Questo era un esempio semplice. Ma speriamo tu possa trarne ispirazione per usare la funzione map in casi d’uso più specifici.
Discretizzazione - funzione cut
A volte potresti voler categorizzare in base a una logica e mettere tutti i dati in insiemi o "bin" discreti per l’analisi. Puoi usare la funzione cut() per questo. Per esempio, creiamo prima un dataset contenente 30 numeri casuali tra 1 e 100.
import random
data = random.sample(range(1, 101), 30)
# data
[45,
39,
25,
83,
27,
6,
73,
43,
36,
93,
97,
17,
15,
99,
37,
5,
31,
22,
65,
30,
3,
26,
91,
12,
52,
76,
63,
84,
59,
53]
Diciamo di volerli categorizzare in base a dei bucket definiti da noi: numeri tra 1 - 25, poi 25 - 35, 40 - 60, poi 60 - 80 e infine il resto. Quindi definiamo un bucket…
Poi useremo la funzione cut.
# Defining the starting value for each bucket
bucket = [1, 25, 35, 60, 80, 100]
cut_data = pd.cut(data, bucket)
cut_data
[(35, 60], (35, 60], (1, 25], (80, 100], (25, 35], ..., (60, 80], (60, 80], (80, 100], (35, 60], (35, 60]]
Length: 30
Categories (5, interval[int64]): [(1, 25] < (25, 35] < (35, 60] < (60, 80] < (80, 100]]
cut() è una funzione molto utile, e ci puoi fare molto di più. Ti consigliamo vivamente di consultare la documentazione di Pandas per saperne di più.
Variabili dummy e One-Hot Encoding
Questo argomento è particolarmente utile quando vuoi convertire dei dati categoriali in valori numerici per poterli usare nel tuo modello di analisi. È particolarmente comodo, soprattutto nel machine learning, dove il concetto di one-hot encoding è diffuso. In termini più tecnici: il one-hot encoding è il processo di conversione di valori categoriali in un vettore numerico monodimensionale.
Un modo per farlo con pandas è usare la funzione get_dummies(). Se una colonna del tuo dataframe ha 'n' valori distinti, la funzione ricaverà una matrice con 'n' colonne contenenti solo 1 e 0. Vediamolo con un esempio per afferrare meglio il concetto…
# Creating a DataFrame consiting individual characters in the list
data = pd.Series(list('abcdababcdabcd'))
data
0 a
1 b
2 c
3 d
4 a
5 b
6 a
7 b
8 c
9 d
10 a
11 b
12 c
13 d
dtype: object
Ora diciamo che vuoi avere vettori separati che indichino la presenza di ciascun carattere per passarli a una funzione.
Qualcosa tipo: per 'a' = [1,0,0,0,1,0,1,0,0,0,1,0,0,0], dove 1 è nelle posizioni in cui 'a' è presente e 0 dove non lo è. Usare la funzione get_dummies() renderà il compito più semplice.
pd.get_dummies(data)
| a | b | c | d | |
|---|---|---|---|---|
| 0 | 1 | 0 | 0 | 0 |
| 1 | 0 | 1 | 0 | 0 |
| 2 | 0 | 0 | 1 | 0 |
| 3 | 0 | 0 | 0 | 1 |
| 4 | 1 | 0 | 0 | 0 |
| 5 | 0 | 1 | 0 | 0 |
| 6 | 1 | 0 | 0 | 0 |
| 7 | 0 | 1 | 0 | 0 |
| 8 | 0 | 0 | 1 | 0 |
| 9 | 0 | 0 | 0 | 1 |
| 10 | 1 | 0 | 0 | 0 |
| 11 | 0 | 1 | 0 | 0 |
| 12 | 0 | 0 | 1 | 0 |
| 13 | 0 | 0 | 0 | 1 |
Tuttavia, questo è un semplice esempio per iniziare. Nelle applicazioni reali, l’appartenenza di un carattere può riferirsi a più categorie allo stesso tempo, quindi dovrai imparare metodi più sofisticati. Inoltre, per dataset molto più grandi, questa funzione potrebbe non essere molto efficiente in termini di velocità. L’articolo di DataCamp Handling Categorical Data in Python discute questi casi e l’argomento in modo più approfondito.
E adesso?
Sei arrivato alla fine del tutorial e hai imparato alcuni strumenti di base per iniziare il primo passo nel tuo percorso di analisi dei dati. Altre competenze utili nella fase di preparazione dei dati includono imparare a rilevare e filtrare gli outlier nel tuo dataset e, se il dataset è molto grande, magari iniziare con un campionamento casuale. Ma sono di nuovo temi ampi e difficili da generalizzare.
I casi d’uso reali possono essere spesso più complessi e richiedere soluzioni ad hoc, ma l’idea è di partire in piccolo, imparare strada facendo, esplorare e sporcarti le mani con dataset diversi! Assicurati di dare un’occhiata al corso Cleaning Data in Python di DataCamp per imparare altri strumenti e trucchi e, alla fine, lavorerai su un vero dataset disordinato ottenuto dalla Gapminder Foundation. Inizia subito!

