import os
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import seaborn as sns
import arviz as az
from pathlib import Path
from sklearn.experimental import (
enable_iterative_imputer,# Necessario per IterativeImputer
) from sklearn.impute import IterativeImputer
16 Flusso di lavoro per la pulizia dei dati
Prerequisiti
- Leggere Cleaning sample data in standardized way di Crystal Lewis.
- Leggere Getting Started Creating Data Dictionaries: How to Create a Shareable Data Set di Buchanan et al. (2021).
- Consultare il capitolo Documentation di Data Management in Large-Scale Education Research.
- Consultare How to Make a Data Dictionary.
- Consultare data dictionary template.
Concetti e competenze chiave
- Verificare che i dati importati siano completi e caricati correttamente.
- Rimuovere duplicati, gestire valori mancanti e trasformare variabili per preparare i dati all’analisi.
- Eliminare informazioni sensibili per garantire la riservatezza.
- Creare un dizionario dei dati e documentazione per facilitare l’uso e la comprensione del dataset.
- Assicurarsi che i dati rispettino i criteri attesi, come l’unicità delle righe e la validità dei valori.
- Mantenere una struttura organizzata, separando i dati grezzi da quelli puliti e includendo la documentazione.
- Adottare regole coerenti per la denominazione delle variabili e la codifica, garantendo chiarezza e uniformità.
Preparazione del Notebook
= 42
RANDOM_SEED = np.random.default_rng(RANDOM_SEED)
rng ="colorblind")
sns.set_theme(palette"arviz-darkgrid")
az.style.use(%config InlineBackend.figure_format = 'retina'
16.1 Introduzione
Nonostante la fase più interessante di un progetto di analisi dei dati sia quella in cui si riesce a rispondere alla domanda che ha dato avvio all’indagine, gran parte del tempo di un analista è in realtà dedicata a una fase preliminare: la pulizia e il preprocessing dei dati, operazioni che vengono svolte ancor prima dell’analisi esplorativa.
In questo capitolo, esamineremo un caso concreto di data cleaning e preprocessing, seguendo il tutorial di Crystal Lewis. Il problema viene presentato come segue:
I am managing data for a longitudinal randomized controlled trial (RCT) study. For this RCT, schools are randomized to either a treatment or control group. Students who are in a treatment school receive a program to boost their math self-efficacy. Data is collected on all students in two waves (wave 1 is in the fall of a school year, and wave 2 is collected in the spring). At this point in time, we have collected wave 1 of our student survey on a paper form and we set up a data entry database for staff to enter the information into. Data has been double-entered, checked for entry errors, and has been exported in a csv format (“w1_mathproj_stu_svy_raw.csv”) to a folder (called “data”) where it is waiting to be cleaned.
Crystal Lewis elenca i seguenti passaggi da seguire nel processo di data cleaning:
- Revisione dei dati.
- Regolazione del numero di casi.
- De-identificazione dei dati.
- Eliminazione delle colonne irrilevanti.
- Divisione delle colonne, se necessario.
- Ridenominazione delle variabili.
- Trasformazione/normalizzazione delle variabili.
- Standardizzazione delle variabili.
- Aggiornamento dei tipi di variabili, se necessario.
- Ricodifica delle variabili.
- Creazione di eventuali variabili necessarie.
- Gestione dei valori mancanti, se necessario.
- Aggiunta di metadati, se necessario.
- Validazione dei dati.
- Fusione e/o unione dei dati, se necessario.
- Trasformazione dei dati, se necessario.
- Salvataggio dei dati puliti.
Sebbene l’ordine di questi passaggi sia flessibile e possa essere adattato alle esigenze specifiche, c’è un passaggio che non dovrebbe mai essere saltato: il primo, ovvero la revisione dei dati. Senza una revisione preliminare, l’analista rischia di sprecare ore a pulire i dati per poi scoprire che mancano dei partecipanti, che i dati non sono organizzati come previsto o, peggio ancora, che sta lavorando con i dati sbagliati.
Esamineremo questi passaggi seguendo il tutorial di Crystal Lewis.
16.2 Importare i Dati
I dati grezzi non dovrebbero mai essere modificati direttamente. È consigliabile organizzare i dati in una struttura di cartelle all’interno di una directory chiamata data
, che contiene due sottocartelle: raw
e processed
. I dati originali, non ancora elaborati, devono essere conservati nella cartella raw
e mantenuti inalterati. I dati ripuliti e preprocessati, invece, devono essere salvati nella cartella processed
.
Per fare un esempio, importiamo i dati dal file w1_mathproj_stu_svy_raw.csv
e iniziamo il processo di pulizia. È importante notare che tutte le istruzioni sono formulate in modo relativo alla home directory del progetto. Prima di tutto, definiamo il percorso della home directory del progetto.
# Get the home directory
= os.path.expanduser("~")
home_directory # Construct the path to the Quarto project directory
= os.path.join(home_directory, "_repositories", "psicometria")
project_directory print(project_directory)
/Users/corradocaudek/_repositories/psicometria
Specifichiamo il percorso del file da importare rispetto alla home directory del progetto.
# Definire il percorso del file CSV
= os.path.join(project_directory, "data", "w1_mathproj_stu_svy_raw.csv")
file_path print(file_path)
/Users/corradocaudek/_repositories/psicometria/data/w1_mathproj_stu_svy_raw.csv
16.3 (1) Esaminare i Dati
Procediamo con l’importazione dei dati.
= pd.read_csv(file_path)
svy svy.shape
(6, 7)
È fondamentale esaminare visivamente le prime o le ultime righe del data frame per verificare che i dati siano stati importati correttamente.
svy.head()
stu_id | svy_date | grade_level | math1 | math2 | math3 | math4 | |
---|---|---|---|---|---|---|---|
0 | 1347 | 2023-02-13 | 9 | 2 | 1 | 3.0 | 3.0 |
1 | 1368 | 2023-02-13 | 10 | 3 | 2 | 2.0 | 2.0 |
2 | 1377 | 2023-02-13 | 9 | 4 | 4 | 4.0 | 4.0 |
3 | 1387 | 2023-02-13 | 11 | 3 | 3 | NaN | NaN |
4 | 1347 | 2023-02-14 | 9 | 2 | 2 | 4.0 | 2.0 |
svy.tail()
stu_id | svy_date | grade_level | math1 | math2 | math3 | math4 | |
---|---|---|---|---|---|---|---|
1 | 1368 | 2023-02-13 | 10 | 3 | 2 | 2.0 | 2.0 |
2 | 1377 | 2023-02-13 | 9 | 4 | 4 | 4.0 | 4.0 |
3 | 1387 | 2023-02-13 | 11 | 3 | 3 | NaN | NaN |
4 | 1347 | 2023-02-14 | 9 | 2 | 2 | 4.0 | 2.0 |
5 | 1399 | 2023-02-14 | 12 | 4 | 1 | 3.0 | 1.0 |
16.4 (2) Modifica i casi secondo necessità
Il secondo passo è quello in cui vengono fatte delle semplici ma necessarie modifiche al data frame. Crystal Lewis descrive così questo passo per i dati in esame:
- Verificare la presenza di duplicati - Il record 1347 è duplicato.
- Rimuovere i duplicati.
- Ordinare per
svy_date
in ordine crescente. - Esaminare i dati dopo aver rimosso i duplicati.
# Trova i duplicati basati su 'stu_id'
= svy[svy.duplicated("stu_id", keep=False)]
duplicates
# Ordina per 'svy_date' in ordine crescente e rimuovi i duplicati mantenendo il primo
= svy.sort_values("svy_date").drop_duplicates("stu_id", keep="first")
svy
# Mostra il DataFrame finale
print(svy)
stu_id svy_date grade_level math1 math2 math3 math4
0 1347 2023-02-13 9 2 1 3.0 3.0
1 1368 2023-02-13 10 3 2 2.0 2.0
2 1377 2023-02-13 9 4 4 4.0 4.0
3 1387 2023-02-13 11 3 3 NaN NaN
5 1399 2023-02-14 12 4 1 3.0 1.0
svy.shape
(5, 7)
16.5 (3) De-identificazione dei Dati
# Rimuovi la colonna 'svy_date'
= svy.drop(columns=["svy_date"])
svy
# Mostra i nomi delle colonne rimaste
svy.columns
Index(['stu_id', 'grade_level', 'math1', 'math2', 'math3', 'math4'], dtype='object')
16.6 (4) Rimuovere le Colonne non Necessarie
Nel caso presente, la rimozione di colonne non è necessaria. Tuttavia, in molti progetti di analisi dei dati, soprattutto quando i dati vengono raccolti utilizzando software di terze parti o strumenti specifici per esperimenti psicologici, è comune trovarsi con colonne che non sono pertinenti allo studio in corso.
Queste colonne possono includere dati come identificatori interni, timestamp generati automaticamente, informazioni di debug, o variabili che non sono rilevanti per l’analisi che si intende condurre. Quando tali colonne sono irrilevanti per la ricerca, possono essere rimosse per semplificare il dataset e ridurre il rischio di confusione o errori durante l’analisi. Rimuovere le colonne non necessarie non solo rende il dataset più gestibile, ma aiuta anche a focalizzare l’analisi sulle variabili che realmente importano per rispondere alle domande di ricerca.
16.7 (5) Dividere le Colonne Secondo Necessità
Nel caso presente, questa operazione non è necessaria. Tuttavia, se si lavora con un dataset che include una colonna chiamata “NomeCompleto”, contenente sia il nome che il cognome di uno studente, è buona pratica separare questa colonna in due colonne distinte, “Nome” e “Cognome”. Questa suddivisione facilita l’analisi e la manipolazione dei dati, rendendoli più organizzati e accessibili.
16.8 (6) Rinominare le Colonne
È importante assegnare nomi chiari alle colonne del dataset. Utilizzare nomi di variabili comprensibili aiuta a rendere l’analisi dei dati più intuitiva e a ridurre il rischio di errori interpretativi.
Esempi di buone pratiche:
- Evita nomi di colonne come “x” o acronimi incomprensibili. Questi possono creare confusione durante l’analisi, specialmente se il dataset viene condiviso con altri ricercatori o se viene ripreso dopo un lungo periodo di tempo.
- Invece, cerca di utilizzare nomi di variabili che descrivano chiaramente il contenuto della colonna. Ad esempio, invece di “x1” o “VAR123”, un nome come “ansia_base” o “liv_autoefficacia” è molto più comprensibile e immediato.
- Per i nomi composti, utilizza un separatore come il trattino basso
_
. Ad esempio, se stai lavorando con dati relativi a un test psicologico, potresti avere colonne chiamate “test_ansia_pre” e “test_ansia_post” per indicare i risultati del test di ansia prima e dopo un intervento.
Esempi di nomi di colonne ben scelti:
- Nome generico:
TS
,AE
- Nome migliore:
tempo_studio
,auto_efficacia
- Nome migliore:
- Nome generico:
S1
,S2
- Nome migliore:
stress_situazione1
,stress_situazione2
- Nome migliore:
- Nome generico:
Q1
,Q2
- Nome migliore:
qualità_sonno_sett1
,qualità_sonno_sett2
- Nome migliore:
16.9 (7) Trasformare le Variabili
Nel caso presente non si applica, ma è un passo importante in molte analisi dei dati.
Esempi di trasformazione delle variabili:
Logaritmo di una variabile: Immaginiamo di avere una variabile che misura i tempi di reazione dei partecipanti a un esperimento. Se i tempi di reazione hanno una distribuzione fortemente asimmetrica (con alcuni valori molto elevati), potrebbe essere utile applicare una trasformazione logaritmica per rendere la distribuzione più simmetrica e migliorare l’interpretabilità dei risultati.
Codifica delle variabili categoriche: Se è presente una variabile categorica come il “tipo di intervento” con valori come “cognitivo”, “comportamentale” e “farmacologico”, potrebbe essere necessario trasformare questa variabile in variabili dummy (ad esempio,
intervento_cognitivo
,intervento_comportamentale
,intervento_farmacologico
), dove ogni variabile assume il valore 0 o 1 a seconda della presenza o meno di quel tipo di intervento. Questo è utile quando si utilizzano tecniche di regressione.
16.10 (8) Standardizzare / Normalizzare le Variabili
Nel caso presente non si applica, ma è un passo importante in molte analisi dei dati.
Esempi di standardizzazione delle variabili:
Standardizzazione dei punteggi: Supponiamo di avere una variabile che misura il livello di ansia su una scala da 0 a 100. Se desideriamo confrontare i livelli di ansia tra diversi gruppi o includere questa variabile in un modello di regressione, potrebbe essere utile standardizzare i punteggi (cioè, sottrarre la media e dividere per la deviazione standard) per ottenere una variabile con media 0 e deviazione standard 1. Questo processo rende i punteggi comparabili e facilita l’interpretazione dei coefficienti in un modello di regressione.
Normalizzazione delle variabili: Se hai dati su diverse variabili come “ore di sonno”, “livello di stress” e “auto-efficacia”, e queste variabili hanno scale molto diverse, potrebbe essere utile normalizzarle (ad esempio, ridimensionarle tutte su una scala da 0 a 1) per garantire che abbiano lo stesso peso in un’analisi multivariata.
Trasformare e standardizzare le variabili sono passaggi cruciali in molte analisi psicologiche, specialmente quando si confrontano dati provenienti da diverse fonti o gruppi. Questi processi aiutano a garantire che le variabili siano trattate in modo appropriato e che i risultati dell’analisi siano validi e interpretabili.
16.11 (9) Aggiornare i Tipi delle Variabili
Nel caso presente non è necessario. Supponiamo invece di avere una colonna in un dataset psicologico che contiene punteggi di un questionario, ma i dati sono stati importati come stringhe (testo) invece che come numeri. Per eseguire calcoli statistici, sarà necessario convertire questa colonna da stringa a numerico.
In Python, utilizzando pandas
, potresti farlo con il seguente codice:
import pandas as pd
# Supponiamo di avere un DataFrame chiamato 'df' con una colonna 'punteggio' che è stata importata come stringa
'punteggio'] = pd.to_numeric(df['punteggio'], errors='coerce')
df[
# Ora la colonna 'punteggio' è stata convertita in un tipo numerico e puoi eseguire calcoli su di essa
In questo esempio, la funzione pd.to_numeric
viene utilizzata per convertire la colonna punteggio
in un formato numerico, permettendo di eseguire analisi quantitative sui dati. L’opzione errors='coerce'
trasforma eventuali valori non convertibili in NaN
, garantendo che i dati errati non compromettano le analisi.
Un altro caso molto comune si verifica quando si importano dati da file Excel. Spesso capita che, all’interno di una cella di una colonna che dovrebbe contenere solo valori numerici, venga inserito erroneamente uno o più caratteri alfanumerici. Di conseguenza, l’intera colonna viene interpretata come di tipo alfanumerico, anche se i valori dovrebbero essere numerici. In questi casi, è fondamentale individuare la cella problematica, correggere il valore errato, e poi riconvertire l’intera colonna da alfanumerica a numerica.
16.12 (10) Ricodificare le Variabili
Anche se in questo caso non è necessario, la ricodifica delle variabili è una pratica molto comune nelle analisi dei dati psicologici.
Per esempio, consideriamo una variabile categoriale con modalità descritte da stringhe poco comprensibili, che vengono ricodificate con nomi più chiari e comprensibili.
Supponiamo di avere un DataFrame chiamato df
con una colonna tipo_intervento
che contiene le modalità "CT"
, "BT"
, e "MT"
per rappresentare rispettivamente “Terapia Cognitiva”, “Terapia Comportamentale” e “Terapia Mista”. Queste abbreviazioni potrebbero non essere immediatamente chiare a chiunque analizzi i dati, quindi decidiamo di ricodificarle con nomi più espliciti. Ecco come farlo in Python utilizzando pandas
:
# Supponiamo di avere un DataFrame chiamato 'df' con una colonna 'tipo_intervento'
= pd.DataFrame({"tipo_intervento": ["CT", "BT", "MT", "CT", "BT"]})
df
# Ricodifica delle modalità della variabile 'tipo_intervento' in nomi più comprensibili
"tipo_intervento_ricodificato"] = df["tipo_intervento"].replace(
df["CT": "Terapia Cognitiva",
{"BT": "Terapia Comportamentale",
"MT": "Terapia Mista"
}
)
# Ora la colonna 'tipo_intervento_ricodificato' contiene i nomi ricodificati
print(df)
tipo_intervento tipo_intervento_ricodificato
0 CT Terapia Cognitiva
1 BT Terapia Comportamentale
2 MT Terapia Mista
3 CT Terapia Cognitiva
4 BT Terapia Comportamentale
16.13 (11) Aggiungere Nuove Variabili nel Data Frame
Nel caso presente non è richiesto, ma aggiungere nuove variabili a un DataFrame è un’operazione comune durante l’analisi dei dati. Un esempio è il calcolo dell’indice di massa corporea (BMI).
Supponiamo di avere un DataFrame chiamato df
che contiene le colonne peso_kg
(peso in chilogrammi) e altezza_m
(altezza in metri) per ciascun partecipante a uno studio psicologico. Per arricchire il dataset, possiamo calcolare il BMI per ogni partecipante e aggiungerlo come una nuova variabile.
Il BMI si calcola con la formula:
\[ \text{BMI} = \frac{\text{peso in kg}}{\text{altezza in metri}^2} .\]
Ecco come aggiungere la nuova colonna.
# Supponiamo di avere un DataFrame chiamato 'df' con le colonne 'peso_kg' e 'altezza_m'
= pd.DataFrame({"peso_kg": [70, 85, 60, 95], "altezza_m": [1.75, 1.80, 1.65, 1.90]})
df
# Calcola il BMI e aggiungilo come una nuova colonna 'BMI'
"BMI"] = df["peso_kg"] / (df["altezza_m"] ** 2)
df[
# Mostra il DataFrame con la nuova variabile aggiunta
print(df)
peso_kg altezza_m BMI
0 70 1.75 22.857143
1 85 1.80 26.234568
2 60 1.65 22.038567
3 95 1.90 26.315789
16.14 (12) Affrontare il Problema dei Dati Mancanti
L’imputazione è una tecnica utilizzata per gestire i dati mancanti in un dataset, un problema comune in molte analisi. Lasciare i valori mancanti nel DataFrame può compromettere la qualità dell’analisi, poiché molti algoritmi statistici non sono in grado di gestire direttamente i dati incompleti, portando a risultati distorti o poco affidabili.
I valori mancanti possono causare diversi problemi:
- Bias dei risultati: I dati mancanti possono introdurre un bias nelle stime se i valori mancanti non sono distribuiti in modo casuale.
- Riduzione della potenza statistica: Quando si eliminano le righe con dati mancanti (rimozione listwise), si riduce la dimensione del campione, diminuendo la potenza dell’analisi.
- Impossibilità di utilizzare alcuni algoritmi: Molti algoritmi di statistica richiedono che tutti i valori siano presenti per eseguire correttamente i calcoli.
Esistono vari approcci per affrontare i dati mancanti:
- Imputazione Semplice:
- Media/Mediana: Un metodo comune e semplice è sostituire i valori mancanti con la media o la mediana della colonna. Questo metodo è facile da implementare, ma può ridurre la variabilità dei dati e portare a una sottostima della varianza.
- Mode (moda): Per le variabili categoriche, è possibile sostituire i valori mancanti con la moda (il valore più frequente). Tuttavia, questo può portare a una distorsione se la distribuzione dei dati è molto eterogenea.
- Imputazione Multipla:
- Regressione Iterativa: L’imputazione multipla, come implementata con algoritmi come
IterativeImputer
, è una procedura più sofisticata che predice i valori mancanti in modo iterativo utilizzando un modello basato sulle altre variabili del dataset. Questa tecnica tiene conto delle relazioni tra le variabili, migliorando l’accuratezza delle imputazioni rispetto ai metodi semplici. - L’imputazione multipla conserva la variabilità nei dati e riduce il bias, fornendo stime più accurate rispetto ai metodi di imputazione semplice.
- Regressione Iterativa: L’imputazione multipla, come implementata con algoritmi come
L’imputazione dei dati mancanti è essenziale per garantire che l’analisi statistica sia accurata e robusta. Sebbene i metodi semplici come la sostituzione con la media possano essere utili in alcuni casi, l’imputazione multipla offre un approccio più completo e sofisticato, particolarmente utile quando si desidera preservare le relazioni tra le variabili e mantenere l’integrità statistica del dataset. Questo argomento verrà ulteriormente discusso nel Capitolo 83.
Applichiamo la procedura dell’imputazione multipla al caso presente.
= svy.copy()
d = pd.DataFrame(d)
d # Conserva l'indice originale
= d.index
original_index d
stu_id | grade_level | math1 | math2 | math3 | math4 | |
---|---|---|---|---|---|---|
0 | 1347 | 9 | 2 | 1 | 3.0 | 3.0 |
1 | 1368 | 10 | 3 | 2 | 2.0 | 2.0 |
2 | 1377 | 9 | 4 | 4 | 4.0 | 4.0 |
3 | 1387 | 11 | 3 | 3 | NaN | NaN |
5 | 1399 | 12 | 4 | 1 | 3.0 | 1.0 |
# Converti solo le colonne numeriche relative ai punteggi in float per l'imputazione
= ["math1", "math2", "math3", "math4"]
numeric_columns = d[numeric_columns].astype(float)
d[numeric_columns]
# Applica IterativeImputer per l'imputazione multipla
= IterativeImputer(max_iter=10, random_state=0)
imputer = pd.DataFrame(
df_imputed
imputer.fit_transform(d[numeric_columns]),=numeric_columns,
columns=original_index, # Mantieni l'indice originale
index
)
# Arrotonda i valori imputati ai numeri interi più vicini
= df_imputed.round()
df_imputed
# Inserisci i valori imputati e arrotondati nel DataFrame originale
= df_imputed
d[numeric_columns]
# Mostra il DataFrame dopo l'imputazione e l'arrotondamento
print("\nDataFrame dopo l'imputazione e l'arrotondamento:")
print(d)
DataFrame dopo l'imputazione e l'arrotondamento:
stu_id grade_level math1 math2 math3 math4
0 1347 9 2.0 1.0 3.0 3.0
1 1368 10 3.0 2.0 2.0 2.0
2 1377 9 4.0 4.0 4.0 4.0
3 1387 11 3.0 3.0 3.0 4.0
5 1399 12 4.0 1.0 3.0 1.0
Per eseguire l’imputazione multipla in Python, utilizziamo il pacchetto sklearn
con il modulo IterativeImputer
, che è uno degli algoritmi più avanzati disponibili per l’imputazione dei valori mancanti. Questo algoritmo utilizza la regressione iterativa, in cui ogni valore mancante viene previsto utilizzando un modello che tiene conto di tutte le altre variabili presenti nel dataset.
Abbiamo selezionato le colonne numeriche che vogliamo imputare.
Imputazione Multipla con
IterativeImputer
:IterativeImputer
è un algoritmo che prevede i valori mancanti iterativamente. Per ciascuna colonna con valori mancanti, l’algoritmo usa una regressione basata sulle altre colonne per stimare i valori mancanti.- Il processo viene ripetuto iterativamente fino a quando i valori imputati convergono a una soluzione stabile.
max_iter=10
significa che il processo verrà ripetuto fino a un massimo di 10 volte per garantire la stabilità delle imputazioni.
Applicazione dell’Imputazione: Dopo aver eseguito l’imputazione, i valori imputati vengono reinseriti nel DataFrame originale.
Il DataFrame risultante non ha più valori mancanti nelle colonne math3
e math4
, poiché questi sono stati imputati utilizzando le relazioni con le altre variabili del dataset.
In conclusione, l’imputazione multipla è una tecnica potente che consente di gestire i valori mancanti nei dati senza dover eliminare intere righe o colonne. In questo caso, abbiamo utilizzato IterativeImputer
per prevedere i valori mancanti basandoci sulle informazioni fornite dalle altre variabili. Questo approccio aumenta l’accuratezza e la validità delle analisi successive.
16.15 (13) Aggiungere i Metadati
I metadati sono informazioni che descrivono i dati stessi, come etichette di variabili, etichette di valori, informazioni sull’origine dei dati, unità di misura e altro ancora. Questi metadati sono essenziali per comprendere, documentare e condividere correttamente un dataset.
In R, i metadati sono gestiti in modo molto dettagliato e strutturato attraverso pacchetti come haven
, labelled
, e Hmisc
. Questi pacchetti consentono di associare etichette ai dati, come etichette di variabili e di valori, e persino di gestire i valori mancanti con etichette specifiche.
- Etichette di variabili: Si possono aggiungere direttamente alle colonne di un DataFrame usando funzioni come
labelled::set_variable_labels()
. - Etichette di valori: Possono essere aggiunte a variabili categoriali utilizzando
labelled::labelled()
. - Valori mancanti: In R, è possibile etichettare specifici valori come mancanti usando
labelled::na_values<-
.
Questi strumenti rendono molto facile documentare un dataset all’interno del processo di analisi, assicurando che tutte le informazioni critiche sui dati siano facilmente accessibili e ben documentate.
In Python, la gestione dei metadati non è così strutturata come in R. pandas
, che è il pacchetto principale per la manipolazione dei dati in Python, non ha un supporto nativo per l’assegnazione di metadati direttamente alle colonne di un DataFrame, come etichette di variabili o etichette di valori. Tuttavia, ci sono alcuni approcci che si possono adottare:
Etichette di variabili: Poiché
pandas
non supporta nativamente le etichette di variabili, un modo comune per gestirle è utilizzare il campoattrs
di un DataFrame.attrs
è un dizionario che può contenere metadati personalizzati, come le etichette delle variabili. Ad esempio, si possono aggiungere descrizioni per ciascuna variabile all’interno diattrs['variable_labels']
.Etichette di valori: Le variabili categoriali in
pandas
possono avere categorie ordinate o non ordinate con nomi significativi, ma queste non sono considerate come “etichette di valori” nel senso in cui R le gestisce. Tuttavia, è possibile simulare questo comportamento rinominando le categorie di una variabile categoriale.Valori mancanti:
pandas
tratta i valori mancanti utilizzandoNaN
, ma non c’è una funzionalità nativa per etichettare valori specifici come mancanti con una descrizione. Si può gestire questo manualmente, utilizzando una combinazione di sostituzioni (replace()
) e l’uso di valori speciali.
16.15.1 Confronto R vs Python
R ffre un supporto più robusto e dettagliato per i metadati, con pacchetti specializzati che permettono di etichettare variabili, valori e gestire i dati mancanti in modo intuitivo e strutturato. I metadati possono essere direttamente integrati nei DataFrame e sono parte integrante del workflow di analisi in R.
Mentre
pandas
offre alcune capacità di manipolazione e annotazione dei dati, il supporto per i metadati è meno strutturato e richiede soluzioni personalizzate. Python si basa più su convenzioni e personalizzazioni tramite campi comeattrs
per conservare i metadati. Anche se Python è estremamente flessibile, la gestione dei metadati richiede spesso soluzioni creative rispetto alla semplicità e alla coerenza offerte da R.
In sintesi, sebbene Python sia molto potente per l’elaborazione dei dati, l’ecosistema R offre strumenti più raffinati e specializzati per la gestione dei metadati all’interno di un processo di data cleaning e analisi.
# Creazione del DataFrame 'svy'
= {
data "stu_id": [1347, 1368, 1377, 1387, 1399],
"grade_level": [9, 10, 9, 11, 12],
"math1": [2, 3, 4, 3, 4],
"math2": [1, 2, 4, 3, 1],
"math3": [3.0, 2.0, 4.0, np.nan, 3.0],
"math4": [3.0, 2.0, 4.0, np.nan, 1.0],
"int": [1, 0, 1, 0, 1],
}
= pd.DataFrame(data)
svy
# Aggiungi etichette di valore alle colonne math1:math4
= {
value_labels_math 1: "strongly disagree",
2: "disagree",
3: "agree",
4: "strongly agree",
}
for col in ["math1", "math2", "math3", "math4"]:
= svy[col].astype(
svy[col] =value_labels_math.keys(), ordered=True)
pd.CategoricalDtype(categories
)= svy[col].cat.rename_categories(value_labels_math)
svy[col]
# Aggiungi etichette di valore alla colonna 'int'
= {1: "treatment", 0: "control"}
value_labels_int "int"] = svy["int"].astype(
svy[=value_labels_int.keys(), ordered=True)
pd.CategoricalDtype(categories
)"int"] = svy["int"].cat.rename_categories(value_labels_int)
svy[
# Verifica delle etichette di valore
for col in ["math1", "math2", "math3", "math4", "int"]:
print(f"Value labels for {col}:")
print(svy[col].cat.categories)
print()
# Aggiungi etichette per valori mancanti
= -99
na_value = svy.replace(np.nan, na_value)
svy
# Aggiungi etichette di variabili utilizzando un dizionario dati (esempio semplificato)
= {
var_labels "stu_id": "Student ID",
"grade_level": "Grade Level",
"math1": "Math Response 1",
"math2": "Math Response 2",
"math3": "Math Response 3",
"math4": "Math Response 4",
"int": "Intervention Group",
}
# Assegna etichette di variabili al DataFrame (non nativo in pandas, gestito come metadati)
"variable_labels"] = var_labels
svy.attrs[
# Verifica delle etichette di variabili
print("\nVariable labels:")
for var, label in svy.attrs["variable_labels"].items():
print(f"{var}: {label}")
Value labels for math1:
Index(['strongly disagree', 'disagree', 'agree', 'strongly agree'], dtype='object')
Value labels for math2:
Index(['strongly disagree', 'disagree', 'agree', 'strongly agree'], dtype='object')
Value labels for math3:
Index(['strongly disagree', 'disagree', 'agree', 'strongly agree'], dtype='object')
Value labels for math4:
Index(['strongly disagree', 'disagree', 'agree', 'strongly agree'], dtype='object')
Value labels for int:
Index(['treatment', 'control'], dtype='object')
Variable labels:
stu_id: Student ID
grade_level: Grade Level
math1: Math Response 1
math2: Math Response 2
math3: Math Response 3
math4: Math Response 4
int: Intervention Group
16.16 (14) Validazione dei Dati
L’obiettivo è creare un report che mostri se i dati soddisfano i criteri attesi. Utilizzando il dizionario dei dati come riferimento, si possono aggiungere diversi controlli:
- Le osservazioni (righe) sono tutti distinte? Ci sono ancora ID duplicati?
- Gli ID sono tutti validi (rientrano nell’intervallo previsto)?
- Le variabili
grade_level
,int
emath
contengono tutti valori che rientrano nel set di valori atteso?
# Funzione per controllare se le righe sono uniche per una specifica colonna
def check_rows_distinct(df, column):
= df[df.duplicated(column, keep=False)]
duplicates if len(duplicates) > 0:
print(f"Le righe duplicate trovate per la colonna {column}:")
print(duplicates)
else:
print(f"Tutte le righe sono uniche per la colonna {column}.")
# Funzione per controllare se i valori sono compresi in un intervallo
def check_col_vals_between(df, column, left, right):
= df[(df[column] < left) | (df[column] > right)]
outside_range if len(outside_range) > 0:
print(f"Valori fuori dall'intervallo trovati in {column}:")
print(outside_range)
else:
print(f"Tutti i valori in {column} sono compresi tra {left} e {right}.")
# Funzione per controllare se i valori appartengono a un insieme specifico
def check_col_vals_in_set(df, column, valid_set):
= df[~df[column].isin(valid_set)]
invalid_vals if len(invalid_vals) > 0:
print(f"Valori non validi trovati in {column}:")
print(invalid_vals)
else:
print(f"Tutti i valori in {column} appartengono all'insieme {valid_set}.")
# Esegui le verifiche
"stu_id")
check_rows_distinct(svy, "stu_id", 1300, 1400)
check_col_vals_between(svy, "grade_level", {9, 10, 11, 12, pd.NA})
check_col_vals_in_set(svy, "math1", {1, 2, 3, 4, pd.NA})
check_col_vals_in_set(svy, "math2", {1, 2, 3, 4, pd.NA})
check_col_vals_in_set(svy, "math3", {1, 2, 3, 4, pd.NA})
check_col_vals_in_set(svy, "math4", {1, 2, 3, 4, pd.NA})
check_col_vals_in_set(svy,
print("Validazione completata.")
Tutte le righe sono uniche per la colonna stu_id.
Tutti i valori in stu_id sono compresi tra 1300 e 1400.
Tutti i valori in grade_level appartengono all'insieme {9, 10, 11, 12, <NA>}.
Tutti i valori in math1 appartengono all'insieme {1, 2, 3, 4, <NA>}.
Tutti i valori in math2 appartengono all'insieme {1, 2, 3, 4, <NA>}.
Valori non validi trovati in math3:
stu_id grade_level math1 math2 math3 math4
3 1387 11 3 3 NaN NaN
Valori non validi trovati in math4:
stu_id grade_level math1 math2 math3 math4
3 1387 11 3 3 NaN NaN
Validazione completata.
In R, la procedura precedente può essere gestita in modo più semplice utilizzando il pacchetto pointblank, che offre strumenti dedicati per facilitare questo processo.
Il dataset ripulito soddisfa tutte le aspettative delineate da Crystal Lewis.
- Completo: Tutti i dati raccolti sono stati inseriti e/o recuperati. Non dovrebbero esserci dati estranei che non appartengono al dataset (come duplicati o partecipanti non autorizzati).
- Valido: Le variabili rispettano i vincoli definiti nel tuo dizionario dei dati. Ricorda che il dizionario dei dati specifica i nomi delle variabili, i tipi, i range, le categorie e altre informazioni attese.
- Accurato: Sebbene non sia sempre possibile determinare l’accuratezza dei valori durante il processo di pulizia dei dati (ovvero, se un valore è realmente corretto o meno), in alcuni casi è possibile valutarla sulla base della conoscenza pregressa riguardante quel partecipante o caso specifico.
- Coerente: I valori sono allineati tra le varie fonti. Ad esempio, la data di nascita raccolta attraverso un sondaggio studentesco dovrebbe avere un formato corrispondere alla data di nascita raccolta dal distretto scolastico.
- Uniforme: I dati sono standardizzati attraverso i moduli e nel tempo. Ad esempio, lo stato di partecipazione ai programmi di pranzo gratuito o a prezzo ridotto è sempre fornito come una variabile numerica con la stessa rappresentazione, oppure il nome della scuola è sempre scritto in modo coerente in tutto il dataset.
- De-identificato: Tutte le informazioni personali identificabili (PII) sono state rimosse dal dataset per proteggere la riservatezza dei partecipanti (se richiesto dal comitato etico/consenso informato).
- Interpretabile: I dati hanno nomi di variabili leggibili sia da umani che dal computer, e sono presenti etichette di variabili e valori laddove necessario per facilitare l’interpretazione.
- Analizzabile: Il dataset è in un formato rettangolare (righe e colonne), leggibile dal computer e conforme alle regole di base della struttura dei dati.
Una volta completati i 14 passaggi precedenti, è possibile esportare questo dataset ripulito nella cartella processed
per le successive analisi statistiche.
16.17 (15) Unire e/o aggiungere dati se necessario
In questo passaggio, è possibile unire o aggiungere colonne o righe presenti in file diversi. È importante eseguire nuovamente i controlli di validazione dopo l’unione/aggiunta di nuovi dati.
16.18 (16) Trasformare i dati se necessario
Esistono vari motivi per cui potrebbe essere utile memorizzare i dati in formato long
o wide
. In questo passaggio, è possibile ristrutturare i dati secondo le esigenze.
16.19 (17) Salvare il dataset pulito finale
L’ultimo passaggio del processo di pulizia consiste nell’esportare o salvare il dataset pulito. Come accennato in precedenza, può essere utile esportare/salvare il dataset in più di un formato di file (ad esempio, un file .csv e un file .parquet).
16.20 Organizzazione dei file e informazioni aggiuntive
Infine, è essenziale includere una documentazione adeguata per garantire che le informazioni siano interpretate correttamente, sia da altri utenti che da te stesso, se dovessi tornare a lavorare su questo progetto in futuro. La documentazione minima da fornire dovrebbe includere:
- Documentazione a livello di progetto: Questa sezione fornisce informazioni contestuali sul perché e come i dati sono stati raccolti. È utile per chiunque voglia comprendere lo scopo e la metodologia del progetto.
- Metadati a livello di progetto: Se condividi i dati in un repository pubblico o privato, è importante includere metadati a livello di progetto. Questi metadati forniscono informazioni dettagliate che facilitano la ricerca, la comprensione e la consultabilità dei dati. I metadati a livello di progetto possono includere descrizioni generali del progetto, parole chiave, e riferimenti bibliografici.
- Dizionario dei dati: Un documento che descrive tutte le variabili presenti nel dataset, inclusi i loro nomi, tipi, range di valori, categorie e qualsiasi altra informazione rilevante. Questo strumento è fondamentale per chiunque voglia comprendere o analizzare i dati.
- README: Un file che fornisce una panoramica rapida dei file inclusi nel progetto, spiegando cosa contengono e come sono interconnessi. Il README è spesso il primo documento consultato e serve a orientare l’utente tra i vari file e risorse del progetto.
Questa documentazione non solo aiuta a mantenere il progetto organizzato, ma è anche cruciale per facilitare la collaborazione e l’archiviazione a lungo termine.
16.21 Dizionario dei Dati
Approfondiamo qui il problema della creazione del Dizionario dei dati.
Un dizionario dei dati è un documento che descrive le caratteristiche di ciascuna variabile in un dataset. Include informazioni come il nome della variabile, il tipo di dato, il range di valori, le categorie (per le variabili categoriche), e altre informazioni rilevanti. Questo strumento è essenziale per comprendere e analizzare correttamente il dataset.
Si presti particolare attenzione alle guide di stile per la denominazione delle variabili e la codifica dei valori delle risposte.
16.21.1 Passi per Creare un Dizionario dei Dati
- Identificare le variabili: Elenca tutte le variabili nel dataset.
- Descrivere ogni variabile: Per ciascuna variabile, identifica il tipo (ad esempio,
int
,float
,datetime
,category
), il range di valori accettabili e, se applicabile, le categorie. - Salvare il dizionario dei dati: Il dizionario dei dati può essere salvato in un file
.csv
o.xlsx
per facilitarne la consultazione.
Per fare un esempio, utilizzeremo il dataset del tutorial di Crystal Lewis. Il codice seguente creerà due file:
data_dictionary.csv
: Un file CSV contenente il dizionario dei dati.data_dictionary.xlsx
: Un file Excel contenente lo stesso dizionario dei dati.
# Creazione del Dizionario dei Dati
= {
data_dict "Variable Name": [
"stu_id",
"svy_date",
"grade_level",
"math1",
"math2",
"math3",
"math4",
],"Type": ["int", "datetime", "int", "int", "int", "float", "float"],
"Description": [
"Student ID",
"Survey Date",
"Grade Level",
"Math Response 1 (1: Strongly Disagree, 4: Strongly Agree)",
"Math Response 2 (1: Strongly Disagree, 4: Strongly Agree)",
"Math Response 3 (1: Strongly Disagree, 4: Strongly Agree)",
"Math Response 4 (1: Strongly Disagree, 4: Strongly Agree)",
],"Range/Values": [
"1347-1399",
"2023-02-13 to 2023-02-14",
"9-12",
"1-4",
"1-4",
"1.0-4.0 (NaN allowed)",
"1.0-4.0 (NaN allowed)",
],
}
= pd.DataFrame(data_dict)
data_dict_df
print(data_dict_df)
Variable Name Type Description \
0 stu_id int Student ID
1 svy_date datetime Survey Date
2 grade_level int Grade Level
3 math1 int Math Response 1 (1: Strongly Disagree, 4: Stro...
4 math2 int Math Response 2 (1: Strongly Disagree, 4: Stro...
5 math3 float Math Response 3 (1: Strongly Disagree, 4: Stro...
6 math4 float Math Response 4 (1: Strongly Disagree, 4: Stro...
Range/Values
0 1347-1399
1 2023-02-13 to 2023-02-14
2 9-12
3 1-4
4 1-4
5 1.0-4.0 (NaN allowed)
6 1.0-4.0 (NaN allowed)
Una volta creato il Dizionario dei dati lo possiamo salvare in un file CSV o Excel:
# Salva il Dizionario dei Dati in un file CSV
"data_dictionary.csv", index=False)
data_dict_df.to_csv(
# Opzionalmente, salva il Dizionario dei Dati in un file Excel
"data_dictionary.xlsx", index=False) data_dict_df.to_excel(
Questi file forniscono una documentazione chiara e strutturata del dataset, utile per qualsiasi analisi successiva o per la condivisione con altri.
16.22 Guide di Stile
Le guide di stile possono essere applicate a diversi aspetti di un progetto di analisi dei dati, non soltanto al dizionario dei dati. Un’ottima introduzione alle regole di stile per un progetto di analisi dei dati è fornita in questo capitolo.
16.23 Python e R
Nella discussione precedente, abbiamo accennato alle differenze tra Python e R per quanto riguarda la fase di pulizia e pre-elaborazione dei dati. Sebbene Python, tramite la libreria Pandas, offra strumenti potenti e flessibili per la manipolazione dei dati, R si distingue per la sua capacità di semplificare e ottimizzare queste operazioni, specialmente quando si tratta di progetti complessi.
R è stato progettato specificamente per l’analisi statistica, e molte delle sue funzioni native sono state sviluppate con un focus particolare sulla semplicità d’uso e l’efficienza nei processi di cleaning e preprocessing. Un esempio emblematico di questa facilità è la creazione di un Dizionario dei Dati, un’operazione essenziale per documentare e descrivere accuratamente il dataset utilizzato in un progetto di analisi. In R, questa operazione può essere completata con una singola istruzione utilizzando pacchetti specifici. Per esempio, il pacchetto datadictionary
permette di generare un dizionario dei dati in modo rapido ed efficiente, come illustrato nel file README. Questo rende R particolarmente vantaggioso quando si lavora con dataset complessi che richiedono una documentazione dettagliata e strutturata.
D’altra parte, Python, con Pandas, è estremamente flessibile e può essere adattato a una vasta gamma di esigenze di pulizia e manipolazione dei dati. Tuttavia, la flessibilità di Pandas richiede spesso un approccio più manuale e dettagliato per compiti che in R potrebbero essere gestiti con comandi più concisi e specifici. Ad esempio, in Python, come abbiamo visto in precedenza, la creazione di un dizionario dei dati richiede una serie di passaggi personalizzati che possono includere la costruzione manuale di tabelle, la gestione delle categorie e dei metadati, e la documentazione. Questa flessibilità rende Python particolarmente adatto a progetti che richiedono un elevato grado di personalizzazione o l’integrazione di dati provenienti da diverse fonti. Tuttavia, può risultare più laborioso e meno intuitivo rispetto a R per operazioni standardizzate di cleaning e preprocessing.
In definitiva, la scelta tra Python e R per la pulizia e pre-elaborazione dei dati dipende dalle specifiche esigenze del progetto e dalle competenze dell’analista. R offre strumenti altamente ottimizzati per un’analisi statistica rapida e strutturata, rendendolo ideale per progetti che richiedono una documentazione dettagliata e una gestione intuitiva dei dati. Python, con la sua flessibilità e potenza, è preferibile per progetti che richiedono personalizzazioni complesse o integrazioni con altri strumenti di analisi dei dati e sviluppo software.
Quando si tratta di progetti di analisi dei dati, è importante considerare non solo la potenza degli strumenti disponibili, ma anche la loro capacità di semplificare e rendere efficienti le operazioni di pulizia e pre-elaborazione. Scegliere lo strumento giusto può fare una grande differenza in termini di tempo, efficienza e qualità dei risultati ottenuti.
16.24 Riflessioni Conclusive
Nel processo di analisi dei dati, la fase di pulizia e pre-elaborazione è cruciale per garantire la qualità e l’integrità dei risultati finali. Sebbene questa fase possa sembrare meno interessante rispetto all’analisi vera e propria, essa costituisce la base su cui si costruiscono tutte le successive elaborazioni e interpretazioni. Attraverso una serie di passaggi strutturati, come quelli illustrati in questo capitolo, è possibile trasformare dati grezzi e disordinati in un dataset pulito, coerente e pronto per l’analisi. La cura nella gestione dei dati, dalla rimozione di duplicati alla creazione di un dizionario dei dati, è fondamentale per ottenere risultati affidabili e riproducibili.
Informazioni sull’Ambiente di Sviluppo
%load_ext watermark
%watermark -n -u -v -iv -m
Last updated: Sun Aug 18 2024
Python implementation: CPython
Python version : 3.12.4
IPython version : 8.26.0
Compiler : Clang 16.0.6
OS : Darwin
Release : 23.6.0
Machine : arm64
Processor : arm
CPU cores : 8
Architecture: 64bit
numpy : 1.26.4
seaborn : 0.13.2
matplotlib: 3.9.1
arviz : 0.18.0
pandas : 2.2.2
sklearn : 1.5.1