6 Dati
Una tabella è un insieme di vettori affiancati; con cinque verbi la interroghiamo senza cicli.
- data frame
- read.csv
- fattori
- valori mancanti
- dplyr
- pipe
- group_by
Lezione
La tabella: vettori affiancati
I vettori del modulo scorso avevano un difetto: clienti ed eta erano due variabili separate, e solo noi sapevamo che il secondo elemento dell’una corrispondeva al secondo dell’altra. Il data frame mette i vettori uno accanto all’altro come colonne di una tabella, con una riga per osservazione. È l’oggetto con cui si lavora in R il 90 % del tempo, ed è esattamente ciò che avete sempre chiamato «foglio Excel».
I dati arrivano quasi sempre da un file CSV, testo con le colonne separate da virgole:
library(dplyr)
ecommerce <- read.csv("ecommerce.csv")
str(ecommerce)
summary(ecommerce)
Il file degli ordini ha una riga per ordine e queste colonne: CustomerID, OrderID, OrderDate, Age, Gender, Membership, ProductCategory, ProductName, UnitPrice, Quantity, TotalAmount, PaymentMethod, City, ReturnStatus. str() mostra la struttura: quante righe, quante colonne e il tipo di ogni colonna (int, num, chr). summary() mostra per ogni colonna numerica minimo, quartili, media, massimo e, se ci sono, quanti NA.
Una colonna si estrae con il dollaro e si comporta come un vettore normale:
ecommerce$Age
mean(ecommerce$Age)
mean(ecommerce$Age, na.rm = TRUE)
La prima media è NA: basta un valore mancante e la media non esiste. na.rm = TRUE dice «rimuovi gli NA e calcola sul resto». È la prima decisione che i dati vi impongono e va presa consapevolmente, non lasciata al caso.
Le colonne di testo che rappresentano categorie (sesso, città, tipo di abbonamento) vanno convertite in fattori, così summary() le conta invece di ignorarle:
ecommerce$Gender <- as.factor(ecommerce$Gender)
ecommerce$ProductCategory <- as.factor(ecommerce$ProductCategory)
summary(ecommerce)
Selezionare righe e colonne alla vecchia maniera
Con le parentesi quadre del modulo scorso, su un data frame si indica [righe, colonne]:
ecommerce[, c("CustomerID", "Age")]
ecommerce[ecommerce$Age < 35 & !is.na(ecommerce$Age), ]
ecommerce[ecommerce$Age < 35 & !is.na(ecommerce$Age), c("CustomerID", "Age")]
Funziona, ma la terza riga è già illeggibile. is.na() chiede «è mancante?» e ! lo nega: senza quel pezzo le righe con età mancante entrerebbero come righe di NA. Il pacchetto dplyr esiste per rendere leggibili queste operazioni.
I cinque verbi di dplyr
dplyr offre pochi verbi, ognuno con un compito solo. Tutti ricevono un data frame come primo argomento e restituiscono un data frame.
select(ecommerce, CustomerID, Age)
filter(ecommerce, Age < 35)
mutate(ecommerce, gruppo_eta = ifelse(Age < 35, "Giovane", "Adulto"))
summarise(ecommerce, eta_media = mean(Age, na.rm = TRUE), n = n())
selectsceglie le colonne;filtersceglie le righe che soddisfano una condizione, comeclienti[eta < 35]del modulo scorso;mutatecrea una colonna nuova calcolata dalle altre;ifelse(condizione, se_vero, se_falso)è l’ifdel modulo 2 applicato a tutta una colonna in un colpo;summariseriduce la tabella a una riga di statistiche;n()conta le righe.
Il quinto verbo, group_by, non fa nulla da solo: divide la tabella in gruppi, così che il summarise successivo produca una riga per gruppo invece che una sola.
La pipe: un verbo per riga
Per applicare più verbi in sequenza si potrebbero annidare: filter(select(ecommerce, CustomerID, Age), Age < 35). Si legge dall’interno verso l’esterno, e con quattro verbi diventa un rebus. L’operatore pipe %>% risolve: prende ciò che sta a sinistra e lo passa come primo argomento a ciò che sta a destra.
ecommerce %>%
select(CustomerID, Age) %>%
filter(!is.na(Age)) %>%
mutate(gruppo_eta = ifelse(Age < 35, "Giovane", "Adulto")) %>%
group_by(gruppo_eta) %>%
summarise(n = n())
Si legge dall’alto in basso come una ricetta: prendi gli ordini, tieni due colonne, scarta le età mancanti, aggiungi il gruppo, raggruppa, conta. Nel corso scriviamo sempre così: un verbo per riga, la pipe in fondo alla riga.
Un uso tipico sull’ecommerce: fatturato e ordini per categoria, e la quota di resi per metodo di pagamento.
ecommerce %>%
group_by(ProductCategory) %>%
summarise(ordini = n(), fatturato = sum(TotalAmount)) %>%
arrange(desc(fatturato))
ecommerce %>%
group_by(PaymentMethod) %>%
summarise(quota_resi = mean(ReturnStatus))
ReturnStatus vale 0 o 1: la media di una colonna 0/1 è la proporzione di 1. È un trucco che useremo continuamente. arrange(desc(...)) ordina dal più grande.
Titanic: quando il caso limite è nei dati
Il secondo dataset è l’elenco dei passeggeri del Titanic: PassengerId, Survived (0/1), Pclass, Name, Sex, Age, SibSp (fratelli e coniugi a bordo), Parch (genitori e figli), Ticket, Fare, Cabin, Embarked (porto: C, Q, S). Prima domanda: quante persone oltre i 50 anni erano a bordo?
titanic <- read.csv("titanic.csv")
titanic$Sex <- as.factor(titanic$Sex)
titanic$Pclass <- as.factor(titanic$Pclass)
titanic %>%
filter(Age > 50) %>%
summarise(n = n())
Attenzione
filter(Age > 50)scarta in silenzio le 177 righe con età mancante, perchéNA > 50non è né vero né falso efiltertiene solo iTRUE. Il conteggio è giusto per «over 50 tra chi ha l’età nota», ma se poi calcolate «percentuale di over 50 sul totale» dividendo per 891 passeggeri, il denominatore è sbagliato. Scrivete sempre esplicitamentefilter(!is.na(Age))e decidete voi il denominatore.
Seconda domanda: tra gli over 50, che percentuale sono uomini e donne?
titanic %>%
filter(!is.na(Age), Age > 50) %>%
group_by(Sex) %>%
summarise(n = n()) %>%
mutate(percentuale = n / sum(n))
Il mutate dopo il summarise calcola la quota di ogni gruppo sul totale dei gruppi: sum(n) è la somma delle righe della tabella riassunta, cioè tutti gli over 50. È il paradosso di Simpson del modulo 1 visto dal lato del codice: cambiare cosa sta al denominatore cambia la risposta.
Terza domanda, la più tipica: è sopravvissuta una quota maggiore in prima o in terza classe?
titanic %>%
group_by(Pclass) %>%
summarise(quota_sopravvissuti = mean(Survived))
titanic %>%
group_by(Pclass, Sex) %>%
summarise(quota_sopravvissuti = mean(Survived), n = n())
Il secondo comando raggruppa per due variabili e produce una riga per ogni combinazione classe × sesso. Guardando la seconda tabella si scopre che dentro ogni classe le donne sopravvivono molto più degli uomini: l’aggregazione per sola classe nascondeva la variabile che conta di più.
Attenzione Dopo un
group_bycon due variabili,summarisetoglie solo l’ultimo raggruppamento: la tabella risultante è ancora raggruppata perPclass. Unmutate(percentuale = n / sum(n))a quel punto calcola le quote dentro ogni classe, non sul totale. Se volete il totale generale, aggiungeteungroup()prima delmutate, oppure scrivetesummarise(..., .groups = "drop"). È l’errore più comune di tutto il modulo: nessun errore in console, solo un numero diverso.
In aula il modulo occupa due lezioni: nella lezione 7 i verbi di dplyr sull’ecommerce, nella lezione 8 il ripasso (
lezione_8.R) e il lab sul Titanic. Le domande su biglietti, famiglie e porto d’imbarco usano esattamente gli stessi cinque verbi e sono nella seconda parte dititanic.R. Guardate il minimo diFaree i valori diEmbarked: anche lì c’è un caso limite nei dati.
Per approfondire
L’indicizzazione con vettori logici è nel modulo 3; ifelse è la versione vettoriale dell’if del modulo 2. Nel modulo 7 le statistiche dentro summarise (media, mediana, deviazione standard) diventano il centro, e impareremo a disegnarle con ggplot2.