Data management con data.table

data.table è una libreria che ci aiuta a fare data management in modo efficiente, come dplyr. Tuttavia, ci sono alcune importanti differenze tra i due:

Nella maggior parte delle applicazioni, la differenza tra data.table e dplyr in termini di prestazioni non è così grande. La grammatica di dplyr è sicuramente più intuitiva, anche se è piuttosto diversa da quella di R base. Tuttavia, data.table è una risorsa importante ed è utile sapere che esiste.

data.table è più flessibile di dplyr, e permette una gamma più ampia di operazioni. Qui ne passeremo in rassegna solo alcune, seguendo lo stesso ordine della scorsa sessione. Per la maggior parte delle operazioni di data management questo è tutto ciò che ci serve.

Il primo passo è sempre quello di installare la libreria. In realtà tutti voi dovreste già averla installata, dato che è una dipendenza di rio. Ovviamente chi non ha rio può installare data.table con il solito comando.

install.packages("data.table")

Carichiamo la libreria

library(data.table)

Leggere i dati

Per prima cosa, come al solito, carichiamo i dati. data.table offre una funzione per leggere i dati, chiamata fread() che è piuttosto veloce quando si tratta di caricare file di grandi dimensioni. Lo svantaggio è che non legge i dati in qualsiasi formato, come rio, ma solo alcuni formati tabellari base, il più comune dei quali probabilmente è .csv. Per questo motivo, ho fatto una copia del nostro file cses2018small in formato .csv.

setwd("/folder/where/you/keep/the/data")
cses <- fread("cses2018small.csv")

Notare che l’oggetto prodotto con fread() non è un “data frame”, ma una “data table”. Come per il “tibble” in dplyr, questo è un formato dati leggermente diverso che è ottimizzato per essere usato con data.table. Possiamo vedere il formato chiedendo a R la classe dell’oggetto tramite la funzione class().

class(cses)
## [1] "data.table" "data.frame"

Naturalmente possiamo sempre caricare i dati usando rio, tuttavia in questo caso occorre dire a rio che vogliamo che i dati siano nel formato ottimizzato per data.table. Possiamo farlo con l’opzione setclass.

library(rio)
cses_2 <- import("cses2018small.xlsx", setclass = "data.table")
class(cses_2)
## [1] "data.table" "data.frame"

Infine, possiamo sempre convertire un data frame nel formato compatibile con data.table usando la funzione as.data.table().

Selezionare variabili

Il modo per selezionare le variabili con data.table è simile a R base, con alcuni miglioramenti. La logica è la stessa della selezione delle colonne negli oggetti bidimensionali. Come abbiamo fatto l’ultima volta, creiamo un nuovo dataset che include solo le variabili mip1 e mip2 dal database cses.

Selezione “classica” con le parentesi quadre [ come in R base:

cses_pg <- cses[, c("mip1", "mip2")]
head(cses_pg)
##              mip1           mip2
## 1:      la scuola      il lavoro
## 2:                              
## 3: poverta,lavoro   immigrazione
## 4:      il lavoro   immigrazione
## 5:         lavoro   immigrazione
## 6:      il lavoro l'immigrazione

Se non vogliamo usare le virgolette possiamo anche mettere i nomi delle variabili nella funzione list():

cses_pg <- cses[, list(mip1, mip2)]
head(cses_pg)
##              mip1           mip2
## 1:      la scuola      il lavoro
## 2:                              
## 3: poverta,lavoro   immigrazione
## 4:      il lavoro   immigrazione
## 5:         lavoro   immigrazione
## 6:      il lavoro l'immigrazione

Che può essere scritta in modo più rapido sostituendo list() con .():

cses_pg <- cses[, .(mip1, mip2)]
head(cses_pg)

Se la lista di variabili da selezionare è fa parte di un oggetto possiamo selezionare in modo più sofisticato, tuttavia dobbiamo usare due punti ..:

vars <- grep("mip", names(cses))
cses_pg <- cses[, ..vars]
head(cses_pg)

Perché i due punti? La logica è simile alla sintassi per spostarsi in su e in giù tra le directory in un sistema di tipo Unix. In questo caso, i due punti significano che stiamo cambiando ambiente dall’interno della tabella dati all’ambiente globale (dove si trova l’oggetto vars).

Selezionare osservazioni

Anche selezionare osservazioni con data.table è una versione semplificata di quella di R base. Per esempio, non abbiamo bisogno di mettere la virgola , dopo l’espressione che usiamo per selezionare le osservazioni.

Oltre ai soliti operatori logici data.table offre alcune risorse utili. Un esempio è %chin%, che è come %in% ma ottimizzato per lavorare con valori alfanumerici.

Come abbiamo fatto la volta scorsa, selezioniamo solo gli intervistati che vivono nelle regioni del nord, hanno più di 35 anni, e pensano che durante l’anno prima delle elezioni la situazione economica del paese sia peggiorata.

cses_pg <- cses[area %chin% 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

Possiamo anche selezionare in base a pattern corrispondenti nelle variabili stringa, come abbiamo fatto usando la funzione grep(). Per esempio, se vogliamo trovare tutti coloro che hanno risposto immigrazione alla domanda sul problema più importante in Italia, possiamo cercare il pattern “migr” nella variabile mip1 e filtrare solo i casi in cui il pattern corrisponde usando l’espressione %like% – la logica è identica a grep().

cses_pg <- cses[mip1 %like% "migr"]
cses_pg
##        id     sex eta eta_gr         mip1                 mip2 eco_eval       area
##   1:    9 Femmina  53  35 54 immigrazione costi della politica        1        Sud
##   2:   40 Femmina  39  35 54 Immigrazione         Criminalita'        1 Nord-Ovest
##   3:   49 Femmina  69    55+ immigrazione          occupazione        1   Nord-Est
##   4:   77 Maschio  67    55+ immigrazione               lavoro       -1     Centro
##   5:   89 Femmina  50  35 54 Immigrazione      Crisi economica        0        Sud
##  ---                                                                              
## 113: 1861 Femmina  39  35 54 immigrazione           educazione        0 Nord-Ovest
## 114: 1889 Femmina  49  35 54 immigrazione recessione economica        0     Centro
## 115: 1925 Maschio  42  35 54    immigrati                tasse        1   Nord-Est
## 116: 1934 Maschio  67    55+ immigrazione rispettare le regole        0 Nord-Ovest
## 117: 1953 Maschio  69    55+ immigrazione     reddito - lavoro       -1 Nord-Ovest

Se vogliamo selezionare tutte le osservazioni entro un certo intervallo di valori di una variabile, possiamo usare le espressioni %between% o %inrange%. Per esempio, selezioniamo tutti gli intervistati di età compresa tra 30 e 45 anni:

cses_pg <- cses[eta %between% c(30, 45)]
# OR
cses_pg <- cses[eta %inrange% c(30, 45)]
cses_pg
##        id     sex eta eta_gr                                mip1               mip2 eco_eval       area
##   1:   10 Femmina  37  35 54                   la disoccupazione         la poverta       -1        Sud
##   2:   16 Femmina  33  18 34                           sicurezza             lavoro       -1 Nord-Ovest
##   3:   25 Maschio  35  35 54                              lavoro              tasse        0   Nord-Est
##   4:   35 Femmina  45  35 54                      disoccupazione       immigrazione       -1 Nord-Ovest
##   5:   36 Femmina  43  35 54            disoccupazione giovanile servizio sanitario       -1     Centro
##  ---                                                                                                   
## 426: 1978 Maschio  35  35 54                              lavoro     pensioni basse        1        Sud
## 427: 1985 Femmina  42  35 54                   la dissocupazione       la sicurezza        0 Nord-Ovest
## 428: 1987 Femmina  38  35 54                        meritocrazia            poverta        0     Centro
## 429: 1990 Femmina  39  35 54                            le tasse                          -1   Nord-Est
## 430: 2000 Femmina  44  35 54 razzismo al livello degli attentati          il lavoro       -1     Centro

Una caratteristica comoda di data.table è che possiamo eliminare in modo agevole le osservazioni mancanti solo in alcune variabili. Qualche giorno fa abbiamo visto la funzione na.omit() di R base, che cancella tutte le osservazioni che hanno almeno un valore mancante in una variabile. Con questa funzione in R base non c’è modo di specificare per quali variabili vogliamo eliminare i valori mancanti, le prende in considerazione necessariamente tutte. data.table però ha la sua versione di na.omit(), che permette di specificare per quali variabili vogliamo eliminare i valori mancanti.

# Eliminare tutte le osservazioni mancanti nella variabile "eco_eval"
vars <- grep("eco_eval", names(cses))
cses_pg <- na.omit(cses, cols = vars)

Creare e trasformare variabili

Il linguaggio con cui comunicare con data.table per ricodificare le variabili, sia cambiando i valori delle variabili esistenti che creandone di nuove, implica l’uso del simbolo :=.

cses_pg <- cses[, eta_std := scale(eta)]
head(cses_pg)
##    id     sex eta eta_gr           mip1           mip2 eco_eval       area   eta_std
## 1:  1 Femmina  60    55+      la scuola      il lavoro       -1        Sud 0.5124959
## 2:  2 Maschio  62    55+                                     -1     Centro 0.6317059
## 3:  3 Femmina  68    55+ poverta,lavoro   immigrazione       -1   Nord-Est 0.9893360
## 4:  4 Maschio  62    55+      il lavoro   immigrazione       -1        Sud 0.6317059
## 5:  5 Femmina  54  35 54         lavoro   immigrazione       -1 Nord-Ovest 0.1548658
## 6:  6 Femmina  71    55+      il lavoro l'immigrazione        0 Nord-Ovest 1.1681511

Possiamo anche ricodificare più valori di una variabile categorica, come con la funzione recode() di dplyr. Come al solito, mettiamo assieme Nord-Est e Nord-Ovest in una sola categoria:

cses_pg <- cses[, area2 := c("Nord-Ovest" = "Nord", 
                             "Nord-Est" = "Nord",
                             "Centro" = "Centro",
                             "Sud" = "Sud") [area] ]
head(cses_pg)
##    id     sex eta eta_gr           mip1           mip2 eco_eval       area   eta_std  area2
## 1:  1 Femmina  60    55+      la scuola      il lavoro       -1        Sud 0.5124959    Sud
## 2:  2 Maschio  62    55+                                     -1     Centro 0.6317059 Centro
## 3:  3 Femmina  68    55+ poverta,lavoro   immigrazione       -1   Nord-Est 0.9893360   Nord
## 4:  4 Maschio  62    55+      il lavoro   immigrazione       -1        Sud 0.6317059    Sud
## 5:  5 Femmina  54  35 54         lavoro   immigrazione       -1 Nord-Ovest 0.1548658   Nord
## 6:  6 Femmina  71    55+      il lavoro l'immigrazione        0 Nord-Ovest 1.1681511   Nord

Naturalmente possiamo accorpare le operazioni di ricodifica all’interno della stessa operazione, mettendo l’operatore := prima di scrivere tutte le espressioni di ricodifica:

cses_pg <- cses[, ":="(area2 = c("Nord-Ovest" = "Nord", 
                                 "Nord-Est" = "Nord",
                                 "Centro" = "Centro",
                                 "Sud" = "Sud") [area],
                eta_std = scale(eta))]
head(cses_pg)
##    id     sex eta eta_gr           mip1           mip2 eco_eval       area   eta_std  area2
## 1:  1 Femmina  60    55+      la scuola      il lavoro       -1        Sud 0.5124959    Sud
## 2:  2 Maschio  62    55+                                     -1     Centro 0.6317059 Centro
## 3:  3 Femmina  68    55+ poverta,lavoro   immigrazione       -1   Nord-Est 0.9893360   Nord
## 4:  4 Maschio  62    55+      il lavoro   immigrazione       -1        Sud 0.6317059    Sud
## 5:  5 Femmina  54  35 54         lavoro   immigrazione       -1 Nord-Ovest 0.1548658   Nord
## 6:  6 Femmina  71    55+      il lavoro l'immigrazione        0 Nord-Ovest 1.1681511   Nord

Excursus: modificare un oggetto “data table” esistente vs. creare un nuovo oggetto

Una cosa importante: ogni volta che utilizziamo il simbolo := noi stiamo modificando l’oggetto “data table” esistente, anche se pensiamo di creare un oggetto nuovo. Ad esempio, nel nostro caso le nuove variabili create (area2 e eta_std) sono presenti anche nel dataset originale!

names(cses_pg)
##  [1] "id"       "sex"      "eta"      "eta_gr"   "mip1"     "mip2"     "eco_eval" "area"     "eta_std"  "area2"
names(cses)
##  [1] "id"       "sex"      "eta"      "eta_gr"   "mip1"     "mip2"     "eco_eval" "area"     "eta_std"  "area2"

Perché questo? Ricordiamo che data.table è stato pensato per essere usato con dataset molto grandi, nell’ordine di Gigabyte, che in R vengono caricati nella RAM. In questi casi, creare un nuovo oggetto contenente gli stessi dati porterebbe via ulteriore memoria. Per evitare questo, data.table non crea copie dei dati.

Questa caratteristica fa sì che non ci sia neanche bisogno di usare la freccia <- quando vogliamo aggiungere variabili a un dataset. Per esempio, creiamo una variabile chiamata neg_eco con valore \(1\) per chi valuta l’economia dell’anno precedente in modo negativo e \(0\) per tutti gli altri:

cses[, neg_eco := ifelse(eco_eval == -1, 1, 0)]
names(cses)
##  [1] "id"       "sex"      "eta"      "eta_gr"   "mip1"     "mip2"     "eco_eval" "area"     "eta_std"  "area2"    "neg_eco"

Questo comportamento è onnipresente in data.table, quindi se creiamo un nuovo oggetto contenente il dataset usando la freccia <-, le modifiche che facciamo a quell’oggetto appariranno anche nel vecchio oggetto!

cses_2 <- cses
cses_2[, teen := ifelse(eta %in% 1:10, 1, 0)]
names(cses_2) # Fin qui tutto bene
##  [1] "id"       "sex"      "eta"      "eta_gr"   "mip1"     "mip2"     "eco_eval" "area"     "eta_std"  "area2"    "neg_eco"  "teen"
names(cses)   # WTF?
##  [1] "id"       "sex"      "eta"      "eta_gr"   "mip1"     "mip2"     "eco_eval" "area"     "eta_std"  "area2"    "neg_eco"  "teen"

Questo succede ogni volta che usiamo l’operatore :=, che è l’operatore che usiamo per creare nuove variabili nel dataset.

Come facciamo se non vogliamo modificare l’oggetto iniziale? Se vogliamo fare una copia del dataset, come nel caso dell’oggetto cses_pg, dobbiamo utilizzare la funzione copy():

cses_pg <- copy(cses)

Ora possiamo fare delle modifiche all’oggetto cses_pg, evitando che tali modifiche si propaghino all’oggetto originale cses:

cses_pg[, elder := ifelse(eta >= 65, 1, 0)]
names(cses_pg) # Qui sì
##  [1] "id"       "sex"      "eta"      "eta_gr"   "mip1"     "mip2"     "eco_eval" "area"     "eta_std"  "area2"    "neg_eco"  "teen"     "elder"
names(cses)    # Qui no
##  [1] "id"       "sex"      "eta"      "eta_gr"   "mip1"     "mip2"     "eco_eval" "area"     "eta_std"  "area2"    "neg_eco"  "teen"

In questo esercizio vogliamo mantenere il dataset originale identico a come l’abbiamo caricato, quindi eliminiamo le nuove variabili che abbiamo appena creato nel nostro oggetto cses_pg credendo di non modificare anche cses.

cses[, ":="(eta_std = NULL,
            area2 = NULL,
            teen = NULL,
            neg_eco = NULL)]
names(cses)
## [1] "id"       "sex"      "eta"      "eta_gr"   "mip1"     "mip2"     "eco_eval" "area"

Queste variabili ora rimangono in cses_pg

names(cses_pg)
##  [1] "id"       "sex"      "eta"      "eta_gr"   "mip1"     "mip2"     "eco_eval" "area"     "eta_std"  "area2"    "neg_eco"  "teen"     "elder"

Fare operazioni per gruppi

Applicare funzioni per gruppo (come le variabili di centratura del gruppo che abbiamo visto la volta scorsa) è molto facile in data.table. Per farlo è sufficiente aggiungere l’opzione by alla fine della ricodifica. Si può usare un singolo fattore di raggruppamento o più di uno:

# Raggruppando per area
cses_pg[, eta_std := scale(eta), by = "area"]
with(cses_pg, plot(eta, eta_std))

# Raggruppando per area e genere
cses_pg[, eta_std := scale(eta), by = c("area", "sex")]
with(cses_pg, plot(eta, eta_std))

Lo stesso risultato si ottiene con la funzione keyby:

cses_pg[, keyby = c("area", "sex"),
        eta_std := scale(eta)]

Estrarre informazioni dai dati

Con data.table è relativamente facile creare oggetti contenenti aggregazioni dei dati come con summarize() in dplyr. Anche in questo caso si utilizzano le funzioni list() o .() (notare che in questo caso non usiamo l’operatore :=).

# Questo produce un oggetto "data.table"
cses[, .(eta_m = mean(eta, na.rm = T))]
##      eta_m
## 1: 51.4018
# Questo produce un vettore
cses[, mean(eta, na.rm = T)]
## [1] 51.4018

Ovviamente è altrettanto facile ottenere dati aggregati per gruppo:

cses_gr <- cses[, .(eta_m = mean(eta, na.rm = T)), by = "area"]
cses_gr
##          area    eta_m
## 1:        Sud 48.75038
## 2:     Centro 51.79372
## 3:   Nord-Est 53.57307
## 4: Nord-Ovest 52.84629

Concatenare le espressioni

La volta scorsa abbiamo visto come in dplyr sia possibile concatenare le diverse operazioni che eseguiamo sui dati usando l’operatore pipe %>%. Con data.table possiamo scrivere espressioni complesse (cioè espressioni che includono tutte le operazioni che dobbiamo eseguire, come ricodifiche, selezione di osservazioni e variabili, ecc.) all’interno della stessa operazione, definita dalle parentesi quadre [ ... ]. Tuttavia, possiamo anche concatenare espressioni come in dplyr, semplicemente scrivendo diverse operazioni in sequenza: DT[ ... ][ ... ][ ... ][ ... ].

cses_gr <- copy(cses)
cses_gr <- cses_gr[, ":="(area2 = c("Nord-Ovest" = "Nord", 
                                 "Nord-Est" = "Nord",
                                 "Centro" = "Centro",
                                 "Sud" = "Sud") [area])][, # Qui iniziamo la seconda operazione
                  keyby = c("area2", "eta_gr"),
                  .(m_eco = mean(eco_eval, na.rm = TRUE))]
cses_gr
##     area2 eta_gr       m_eco
## 1: Centro  18 34 -0.07407407
## 2: Centro  35 54 -0.19078947
## 3: Centro    55+ -0.01941748
## 4:   Nord  18 34  0.13636364
## 5:   Nord  35 54 -0.08013937
## 6:   Nord    55+  0.05111111
## 7:    Sud  18 34 -0.13375796
## 8:    Sud  35 54 -0.23529412
## 9:    Sud    55+ -0.16600791

Il meglio di due mondi: dtplyr

Nella scorsa sessione abbiamo visto le funzioni più importanti di dplyr, una libreria che sta diventando molto popolare tra gli utenti di R. Oggi abbiamo visto alcune funzioni di data.table, un’altra libreria per fare data management che funziona molto bene (cioè in modo rapido) con dataset di grandi dimensioni. Tra i due, dplyr vince chiaramente in termini di chiarezza e ordine della sintassi. D’altra parte, data.table vince in termini di prestazioni (anche se la differenza tra i due diventa apparente solo quando si utilizzano dataset molto grandi).

Partendo da una considerazione di questo tipo, qualcuno ha pensato che sarebbe bello avere un pacchetto che combini il front end di dplyr con il back end di data.table, traducendo le funzioni di dplyr nel linguaggio di data.table e usando quest’ultimo per eseguire le operazioni in background. Ecco quindi dtplyr, una libreria sviluppata dal team di sviluppo del tidyverse.

dtplyr funziona in modo relativamente semplice: le funzioni sono le stesse di dplyr, l’unica differenza è che dobbiamo convertire il nostro oggetto dati in un “lazy data table”. In questo modo, dtplyr saprà che le funzioni di dplyr che stiamo scrivendo dovranno essere tradotte in data.table ed eseguite sull’oggetto “lazy data table”.

Iniziamo installando e caricando la libreria. Dobbiamo anche caricare dplyr, dato che la maggior parte delle funzioni provengono da lì.

install.packages("dtplyr")
library(dtplyr)
library(dplyr, warn.conflicts = F)

Ora dobbiamo fare solo 2 cose. Prima di tutto, trasformiamo il nostro oggetto in un “lazy data table” con la funzione lazy_dt():

cses2 <- lazy_dt(cses)

Notare che l’oggetto cses2 è considerato una “lista”, non un data frame (o tibble, o data table). Questo perchè l’oggetto non contiene solo i dati, ma anche diverse altre informazioni che dtplyr usa per convertire le funzioni di dplyr nel linguaggio di data.table.

Notare anche che se chiediamo a R di mostrarci l’oggetto, riusciremo a vedere sia i dati che il comando di data.table usato per mostrare i dati:

head(cses2)
## Source: local data table [?? x 8]
## Call:   head(`_DT1`, n = 6L)
## 
##      id sex       eta eta_gr mip1             mip2             eco_eval area      
##   <int> <chr>   <int> <chr>  <chr>            <chr>               <int> <chr>     
## 1     1 Femmina    60 55+    "la scuola"      "il lavoro"            -1 Sud       
## 2     2 Maschio    62 55+    ""               ""                     -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
## 6     6 Femmina    71 55+    "il lavoro"      "l'immigrazione"        0 Nord-Ovest
## 
## # Use as.data.table()/as.data.frame()/as_tibble() to access results

dtplyr ci mostra anche un messaggio che ci spiega come possiamo accedere ai dati: dobbiamo convertire l’oggetto lazy_dt in un oggetto dati, che sia un “data frame”, “tibble” o “data table”:

cses2 %>% as_tibble() %>% head()
## # A tibble: 6 x 8
##      id sex       eta eta_gr mip1             mip2             eco_eval area      
##   <int> <chr>   <int> <chr>  <chr>            <chr>               <int> <chr>     
## 1     1 Femmina    60 55+    "la scuola"      "il lavoro"            -1 Sud       
## 2     2 Maschio    62 55+    ""               ""                     -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
## 6     6 Femmina    71 55+    "il lavoro"      "l'immigrazione"        0 Nord-Ovest

Questa è la seconda cosa che dobbiamo fare con dtplyr: dopo aver svolto tutte le operazioni di data management, dobbiamo ricordarci di estrarre i dati dall’oggetto, aggiungendo alla fine della catena di operazioni una funzione per convertirlo in un dataset. Possiamo quindi usare le funzioni as_tibble() o as.data.frame() o as.data.table() per ottenere i dati nel formato che preferiamo.

cses_gr <- cses2 %>%
  select(eta_gr, area, eco_eval) %>%
  filter(eta_gr %in% c("18 34", "55+")) %>% 
  mutate(
    area2 = recode(area,
                 "Nord-Ovest" = "Nord",
                 "Nord-Est" = "Nord",
                 "Centro" = "Centro",
                 "Sud" = "Sud")
    ) %>%
  group_by(area2, eta_gr) %>%
  summarize(
    m_eco = mean(eco_eval, na.rm = TRUE)
  ) %>%
  as.data.frame()

cses_gr
##    area2 eta_gr       m_eco
## 1 Centro  18 34 -0.07407407
## 2 Centro    55+ -0.01941748
## 3   Nord  18 34  0.13636364
## 4   Nord    55+  0.05111111
## 5    Sud  18 34 -0.13375796
## 6    Sud    55+ -0.16600791

Ovviamente se chiediamo a R di mostrarci l’oggetto “lazy data table” senza convertirlo in un oggetto dati prima, dtplyr mostrerà anche la sintassi che data.table usa per eseguire le operazioni chieste in linguaggio dplyr. Questo può anche tornare utile per vedere le equivalenze tra i due linguaggi, e impararli entrambi.

cses2 %>%
  select(eta_gr, area, eco_eval) %>%
  filter(eta_gr %in% c("18 34", "55+")) %>% 
  mutate(
    area2 = recode(area,
                 "Nord-Ovest" = "Nord",
                 "Nord-Est" = "Nord",
                 "Centro" = "Centro",
                 "Sud" = "Sud")
    ) %>%
  group_by(area2, eta_gr) %>%
  summarize(
    m_eco = mean(eco_eval, na.rm = TRUE)
  )
## Source: local data table [?? x 3]
## Call:   `_DT1`[, .(eta_gr, area, eco_eval)][eta_gr %in% c("18 34", "55+")][, 
##     `:=`(area2 = recode(area, `Nord-Ovest` = "Nord", `Nord-Est` = "Nord", 
##         Centro = "Centro", Sud = "Sud"))][, .(m_eco = mean(eco_eval, 
##     na.rm = TRUE)), keyby = .(area2, eta_gr)]
## 
##   area2  eta_gr   m_eco
##   <chr>  <chr>    <dbl>
## 1 Centro 18 34  -0.0741
## 2 Centro 55+    -0.0194
## 3 Nord   18 34   0.136 
## 4 Nord   55+     0.0511
## 5 Sud    18 34  -0.134 
## 6 Sud    55+    -0.166 
## 
## # Use as.data.table()/as.data.frame()/as_tibble() to access results

Valutare la performance di dtplyr

Quanto guadagnamo in termini di velocità usando dtplyr invece che dplyr? Come si è detto sopra, data.table da il meglio di sé quando ha a che fare con dataset molto grandi. Con dataset “normali” (diciamo qualche migliaia di osservazioni e centinaia di variabili) il valore aggiunto di avere un back end come quello di data.table è relativamente limitato.

Per fare un esempio, proviamo a paragonare la performance di dplyr e dtplyr in una serie di semplici operazioni fatte sui dati cses, e chiedendo un data frame come risultato in entrambi i casi.

Possiamo misurare quanto tempo R impiega a svolgere una determinata operazione usando la funzione system.time():

# Creiamo un lazy data table per `dtplyr`
cses2 <- lazy_dt(cses)
# Creiamo un tibble `dplyr`
cses3 <- as_tibble(cses)

# Performance di `dtplyr`
system.time(
  cses2 %>%
  filter(eta_gr %in% c("18 34", "55+")) %>% 
  mutate(
    area2 = recode(area,
                 "Nord-Ovest" = "Nord",
                 "Nord-Est" = "Nord",
                 "Centro" = "Centro",
                 "Sud" = "Sud")
    ) %>%
  group_by(area2, eta_gr) %>%
  summarize(
    m_eco = mean(eco_eval, na.rm = TRUE)
  ) %>%
  as.data.frame()
)
##    user  system elapsed 
##   0.004   0.000   0.004
# Performance di `dplyr`
system.time(
  cses3 %>%
  filter(eta_gr %in% c("18 34", "55+")) %>% 
  mutate(
    area2 = recode(area,
                 "Nord-Ovest" = "Nord",
                 "Nord-Est" = "Nord",
                 "Centro" = "Centro",
                 "Sud" = "Sud")
    ) %>%
  group_by(area2, eta_gr) %>%
  summarize(
    m_eco = mean(eco_eval, na.rm = TRUE)
  ) %>%
  as.data.frame()
)
## `summarise()` has grouped output by 'area2'. You can override using the `.groups` argument.
##    user  system elapsed 
##   0.020   0.001   0.021