dplyr
select()
filter()
e slice()
rename()
_join()
mutate()
%>%
group_by()
summarize()
mutate()
e summarize()
su più di una variabile alla volta: across()
Let us load the data:
dplyr
dplyr
è una libreria che ci semplifica di molto le cose quando si tratta di lavorare con database in R. dplyr
è parte di una collezione di librerie chiamata tidyverse
, che abbiamo già menzionato qualche giorno fa. Si può installare nel solito modo:
Naturalmente è possibile installare anche solo dplyr
individualmente, senza le altre librerie del tidyverse
, tuttavia useremo anche altre librerie presenti nel tidyverse
, e inoltre alcune funzionalità che si utilizzano in dplyr
provengono in ogni caso da altre librerie della collezione. Da un certo punto di vista possiamo considerare tidyverse
come un “ecosistema” di funzioni, che da il meglio di sé quando viene utilizzato in modo integrato.
Nota1: per interagire al meglio da dplyr
e con altre funzioni del tidyverse
dobbiamo utilizzare un linguaggio leggermente diverso rispetto a quello che utilizziamo con R base. Metaforicamente, pensate a un gergo tecnico che in un ambito ben specifico aiuta a esprimersi in modo molto più efficiente. Non è completamente diverso dal linguaggio di R base, ma ci sono alcune differenze importanti che vedremo.
Nota 2: tutto quello che vedremo fatto con dplyr
oggi può naturalmente essere fatto anche con R base (molte di queste cose le abbiamo viste nella scorsa sessione). Ovviamente questo vale per ogni funzione da ogni libreria di R. Il modo in cui le librerie come dplyr
ci aiutano è rendendo il processo più veloce e, in molti casi, facendoci scrivere meno codice.
PEr incominciare carichiamo il tidyverse
:
Ora guarderemo alcune delle funzioni più comuni di dplyr
.
select()
La funzione select()
serve per selezionare una o più colonne in un dataset, esattamente come le parentesi quadre [
. Per esempio, possiamo selezionare solo le variabili mip1
e mip2
dal nostro dataset cses
.
## mip1 mip2
## 1 la scuola il lavoro
## 2 <NA> <NA>
## 3 poverta,lavoro immigrazione
## 4 il lavoro immigrazione
## 5 lavoro immigrazione
## 6 il lavoro l'immigrazione
Per renderci la vita più facile, con dplyr
abbiamo a disposizione delle funzioni che ci permettono di selezionare gruppi di variabili senza dover scrivere tutti i nomi uno per uno.
starts_with()
: seleziona le variabili basate su un prefisso comuneends_with()
: seleziona le variabili basate su un suffisso comunecontains()
: più in generale, seleziona le variabili sulla base di una sequenza di caratteri presenti all’interno dei loro nomiNaturalmente possono essere combinati
Nota: gli oggetti prodotti da dplyr
sembrano data frame, ma in realtà appartengono a un formato leggermente diverso chiamato “tibble”. Questo si nota perchè se chiedete a R di mostrarvi il dataset, l’output includerà la frase A tibble: n x n
(il numero di righe e colonne) e altre informazioni sul tipo di valore (<dbl>
, <chr>
) sotto ai nomi delle variabili.
Il formato tibble è quasi identico al data frame, l’unica differenza è che è ottimizzato per essere usato con le funzioni che sono parte del tidyverse
. Per la maggior parte degli usi che se ne possono fare, non c’è alcuna differenza tra i formati tibble e data frame. Inoltre, in caso di necessità, è sempre possibile convertire un tibble in data frame con la funzione as.data.frame()
.
filter()
e slice()
La funzione filter()
serve per selezionare una o più righe in un dataset, e anche in questo caso fa quello che faremmo in R base usando le parentesi quadre [
. Per esempio, possiamo selezionare solo gli intervistati che vivono nelle regioni del nord Italia, hanno più di 35 anni e pensano che durante l’anno prima delle elezioni la situazione economica del paese sia peggiorata.
cses_pg <- filter(
cses,
area %in% c("Nord-Ovest", "Nord-Est"),
eta > 35,
eco_eval == -1
)
head(cses_pg)
## id sex eta eta_gr mip1 mip2 eco_eval area
## 1 3 Femmina 68 55+ poverta,lavoro immigrazione -1 Nord-Est
## 2 5 Femmina 54 35 54 lavoro immigrazione -1 Nord-Ovest
## 3 20 Femmina 65 55+ lavoro crisi economica -1 Nord-Ovest
## 4 27 Femmina 57 55+ burocrazia politica -1 Nord-Est
## 5 35 Femmina 45 35 54 disoccupazione immigrazione -1 Nord-Ovest
## 6 88 Maschio 51 35 54 sicurezza immigrazione -1 Nord-Est
La funzione slice()
(e tutte le sue versioni specifiche), invece è l’equivalente di utilizzare le parentesi quadre [
con i numeri di indice:
## id sex eta eta_gr mip1 mip2 eco_eval area
## 1 1 Femmina 60 55+ la scuola il lavoro -1 Sud
## 2 2 Maschio 62 55+ <NA> <NA> -1 Centro
## 3 10 Femmina 37 35 54 la disoccupazione la poverta -1 Sud
## 4 35 Femmina 45 35 54 disoccupazione immigrazione -1 Nord-Ovest
## 5 3 Femmina 68 55+ poverta,lavoro immigrazione -1 Nord-Est
slice_sample()
permette di estrarre alcune osservazioni casuali:## id sex eta eta_gr mip1 mip2 eco_eval area
## 1 185 Maschio 52 35 54 l'occupazione la sicurezza -1 Sud
## 2 1303 Maschio 62 55+ legalita lavoro 0 Sud
## 3 127 Maschio 29 18 34 Immigrazione Corruzione 0 Sud
## 4 237 Maschio 64 55+ Lavoro Corruzione 0 Sud
## 5 101 Femmina 77 55+ disoccupazione giovanile <NA> 1 Centro
slice_head()
e slice_tail()
permettono di estrarre le prime o ultime n
osservazioni:## id sex eta eta_gr mip1 mip2 eco_eval area
## 1 1 Femmina 60 55+ la scuola il lavoro -1 Sud
## 2 2 Maschio 62 55+ <NA> <NA> -1 Centro
## 3 3 Femmina 68 55+ poverta,lavoro immigrazione -1 Nord-Est
## 4 4 Maschio 62 55+ il lavoro immigrazione -1 Sud
## 5 5 Femmina 54 35 54 lavoro immigrazione -1 Nord-Ovest
## id sex eta eta_gr mip1 mip2 eco_eval area
## 1 1997 Femmina 67 55+ il lavoro farlo 1 Nord-Ovest
## 2 1998 Maschio 48 35 54 la disoccupazione la negatività nel paese 0 Sud
## 3 1999 Maschio 73 55+ lavoro educazione dei citadini -1 Sud
## 4 2000 Femmina 44 35 54 razzismo al livello degli attentati il lavoro -1 Centro
## 5 2001 Femmina 67 55+ lavoro pensioni -1 Nord-Ovest
rename()
Una funzione abbastanza pratica per cambiare i nomi alle variabili, come faremmo con Stata
## [1] "id" "sex" "eta" "eta_groups" "mip1" "mip2" "eco_eval" "area"
_join()
dplyr
offre una serie di funzioni per aggiungere variabili a un dataset (ovvero quello che viene chiamato comunemente merge o nel linguaggio di SQL, “join”) seguendo diversi criteri. Tutte queste funzioni hanno lo stesso suffisso e sono strutturate nello stesso modo: _join(x, y, by = var)
, dove x
è il dataset che stiamo utilizzando e che vogliamo usare come “base”, y
è il dataset le cui variabili vogliamo aggiungere a x
, e var
è la variabile “ponte” che viene usata per assegnare i valori corretti alle osservazioni corrette.
La lista completa delle possibili soluzioni che dplyr
offre per unire diversi dataset può essere trovata qui. Le più comuni sono:
left_join()
: mantiene tutte le osservazioni in x
. Se y
ha meno osservazioni di x
, le variabili in y
saranno NA
per le righe in x
che non sono presenti in y
. Se y
ha più osservazioni di x
, le righe in eccesso verranno eliminate. Il numero di osservazioni nel dataset risultante sarà lo stesso numero di osservazioni in x
.right_join()
: l’esatto contrario, ovvero mantiene tutte le osservazioni in y
, ecc.inner_join()
: mantiene tutte le osservazioni che sono sia in x
che in y
(intersezioni di insiemi).full_join()
: mantiene le osservazioni che sono in x
oppure in y
(quindi tutte le osservazioni in entrambi i dataset, unione di insiemi).Vediamo il solito esempio con il file cses2018edu.dta
:
## id sex eta eta_gr mip1 mip2 eco_eval area titstu
## 1 1 Femmina 60 55+ la scuola il lavoro -1 Sud 9
## 2 2 Maschio 62 55+ <NA> <NA> -1 Centro 5
## 3 3 Femmina 68 55+ poverta,lavoro immigrazione -1 Nord-Est 5
## 4 4 Maschio 62 55+ il lavoro immigrazione -1 Sud 6
## 5 5 Femmina 54 35 54 lavoro immigrazione -1 Nord-Ovest 5
## 6 6 Femmina 71 55+ il lavoro l'immigrazione 0 Nord-Ovest 4
mutate()
Questa è una delle funzioni più utilizzate in dplyr
. Può essere usata sia per operazioni di ricodifica molto semplici e anche cose più difficili. Per esempio, possiamo standardizzare la variabile eta
, come abbiamo fatto l’ultima volta:
## id sex eta eta_gr mip1 mip2 eco_eval area titstu eta_std
## 1 1 Femmina 60 55+ la scuola il lavoro -1 Sud 9 0.5124959
## 2 2 Maschio 62 55+ <NA> <NA> -1 Centro 5 0.6317059
## 3 3 Femmina 68 55+ poverta,lavoro immigrazione -1 Nord-Est 5 0.9893360
## 4 4 Maschio 62 55+ il lavoro immigrazione -1 Sud 6 0.6317059
## 5 5 Femmina 54 35 54 lavoro immigrazione -1 Nord-Ovest 5 0.1548658
## 6 6 Femmina 71 55+ il lavoro l'immigrazione 0 Nord-Ovest 4 1.1681511
Per le variabili categoriche, dplyr
offre la comoda (seppur non sempre intuitiva) funzione recode()
. Alcuni esempi:
cses_pg <- mutate(
cses,
area2 = recode(area, # Categoria singola per "Nord"
"Nord-Ovest" = "Nord",
"Nord-Est" = "Nord",
"Centro" = "Centro",
"Sud" = "Sud"),
area3 = recode(area, # Le osservazioni del "Nord-Ovest" diventano missing
"Nord-Ovest" = NA_character_,
"Nord-Est" = "Nord",
"Centro" = "Centro",
"Sud" = "Sud"),
eco_neg = recode(eco_eval, # Variabile dummy per valutazione negativa dell'economia
`-1` = 1,
.default = 0)
)
# Controllare le ricodifiche
table(cses_pg$area, cses_pg$area2)
##
## Centro Nord Sud
## Centro 446 0 0
## Nord-Est 0 349 0
## Nord-Ovest 0 553 0
## Sud 0 0 653
##
## Centro Nord Sud
## Centro 446 0 0
## Nord-Est 0 349 0
## Nord-Ovest 0 0 0
## Sud 0 0 653
##
## 0 1
## -1 0 646
## 0 823 0
## 1 509 0
Naturalmente si può sempre utilizzare la cara vecchia funzione ifelse()
:
%>%
L’operatore pipe è quello che rende dplyr
speciale. È una funzione che serve a concatenare altre funzioni. Tuttavia questo cambia completamente la logica del linguaggio di R.
Per darvi un’idea della logica, Andrew Heiss fa un ottimo esempio in questo tweet. Immaginate che dovete scrivere un programma con le istruzioni per svegliarsi la mattina e uscire di casa per andare al lavoro. In R, utilizzando gli oggetti, questo verrebbe scritto nel seguente modo (in Inglese, perchè in Italiano viene male):
me
me_awake <- wake_up(me)
me_out_of_bed <- get_out_of_bed(me_awake)
me_dressed <- get_dressed(me_out_of_bed)
me_out <- leave_house(me_dressed)
Quindi, prima usiamo la funzione wake_up()
con l’oggetto me
, poi usiamo la funzione get_out_of_bed()
con l’oggetto me_awake
, risultante dall’operazione precedente, e così via.
Tutta questa sequenza può anche essere scritta inserendo le funzioni una dentro l’altra:
In tal caso iniziamo a scrivere partendo dall’ultima funzione, e man mano che scriviamo procediamo al contrario, da fuori a dentro, fino ad arrivare all’oggetto di partenza.
Ora, l’operatore pipe ci permette di concatenare tutte queste funzioni in modo che (1) non dobbiamo creare troppi oggetti–come faremmo se volessimo programmare in sequenza, e (2) non dobbiamo scrivere la sequenza dalla fine verso l’inizio–come faremmo se volessimo mettere le funzioni una dentro l’altra. Utilizzando il pipe, la nostra sequenza sarebbe:
In altre parole, l’operatore pipe permette di svolgere le nostre operazioni di data management in modo sequenziale. Per esempio, possiamo fare tutte le operazioni che abbiamo visto partendo dal dataset originale e creando un oggetto alla fine:
cses_pg <- cses %>% # Partiamo dal dataset "cses"
select(eta, area) %>% # Selezionare variabili
filter(eta > 40) %>% # Selezionare osservazioni
mutate(
area2 = recode(area, # Creare una nuova variabile
"Nord-Ovest" = "Nord",
"Nord-Est" = "Nord",
"Centro" = "Centro",
"Sud" = "Sud")
)
head(cses_pg)
## eta area area2
## 1 60 Sud Sud
## 2 62 Centro Centro
## 3 68 Nord-Est Nord
## 4 62 Sud Sud
## 5 54 Nord-Ovest Nord
## 6 71 Nord-Ovest Nord
group_by()
Ora che sappiamo come funziona l’operatore pipe, possiamo tirare fuori il meglio da dplyr
. Una delle caratteristiche più utili di dplyr
(e probabilmente la ragione per cui molte persone iniziano ad usarlo) è la possibilità di eseguire operazioni per diversi gruppi all’interno dei dati. Questo può sembrare banale (SPSS e Stata lo rendono piuttosto facile da fare), tuttavia farlo con R base non è affatto intuitivo.
Per esempio, possiamo centrare la variabile eta
intorno alla media come abbiamo fatto prima, ma invece di prendere la media dell’intero campione, possiamo centrare i valori degli intervistati in ogni area geografica sulla media della loro area.
cses_pg <- cses %>%
group_by(area) %>%
mutate(
eta_std = scale(eta)
)
plot(cses_pg$eta, cses_pg$eta_std)
Notare che in questo caso non abbiamo una sola linea (e quindi un solo gruppo di osservazioni) ma più di una. Questo perchè ora il valore \(0\), la media, è diversa in ogni gruppo, dato che l’età media cambia tra aree geografiche.
Nota: ricordate che ogni volta che utilizzate la funzione group_by()
, il dataset rimarrà diviso in gruppi! Questa è una caratteristica che distingue il tibble dal data frame: il fatto che mantenga in memoria informazione riguardo l’eventuale divisione in gruppi di un dataset. Se volete fare altre operazioni su tutti i dati (per esempio calcolare la media di una variabile in tutto il dataset) dovete “annullare” il raggruppamento utilizzando la funzione ungroup()
.
summarize()
summarize()
è una delle più importanti funzionalità di dplyr
, che ci permette di “riassumere” il contenuto di una o più variabili utilizzando statistiche di vario tipo e possibilmente salvando queste informazioni in un oggetto (notare che la stessa funzione può anche essere chiamata summarise()
, in British English).
Per fare un esempio, possiamo creare una tabella che include media e deviazione standard della variabile eta
in ogni area geografica:
cses_gr <- cses %>%
group_by(area) %>%
summarize(
eta_m = mean(eta, na.rm = T),
eta_sd = sd(eta, na.rm = T)
)
cses_gr
## # A tibble: 4 x 3
## area eta_m eta_sd
## <chr> <dbl> <dbl>
## 1 Centro 51.8 16.5
## 2 Nord-Est 53.6 16.6
## 3 Nord-Ovest 52.8 16.9
## 4 Sud 48.8 16.7
mutate()
e summarize()
su più di una variabile alla volta: across()
Un’ulteriore caratteristica che rende dplyr
molto conveniente è la possibilità di generalizzare le funzioni mutate()
e summarize()
a più di una variabile. In questo modo non occorrerà ripetere lo stesso comando più volte nel caso si debba applicare la stessa funzione a più variabili (naturalmente la funzione deve essere la stessa).
A partire dalla versione più recente di dplyr
, è possibile ottenere questo con la funzione across()
, che può essere utilizzata sia con mutate
che con summarize
.
Per esempio, proviamo a usare grepl()
per creare due variabili dummy che identificano le persone che hanno risposto “immigrazione” alle due domande sul “problema più importante in Italia”, mip1
e mip2
:
cses_pg <- cses %>%
mutate(
across(
starts_with("mip"),
~ifelse(grepl("migr", .), 1, 0),
.names = "{.col}_r"
)
)
# Per quante persone l'immigrazione è il problema più importante?
mean(cses_pg$mip1_r)
## [1] 0.05847076
## [1] 0.1009495
Nelle versioni precedenti di dplyr
questa operazione veniva svolta dalle funzioni mutate_at()
e summarize_at()
, che funzionano ancora.
È ancora possibile utilizzare mutate_all()
e summarize_all()
per applicare la stessa funzione su tutte le variabili, con una logica molto simile ad applicare la funzione apply()
per colonne. Per esempio, se vogliamo sapere quante osservazioni mancanti abbiamo in ogni variabile, possiamo usare summarize_all()
:
## id sex eta eta_gr mip1 mip2 eco_eval area titstu
## 1 0 0 0 0 62 195 23 0 0
Notare che il modo più recente per fare questo è utilizzare summarize()
e across()
:
## id sex eta eta_gr mip1 mip2 eco_eval area titstu
## 1 0 0 0 0 62 195 23 0 0