Perché rbindlist è “migliore” di rbind?

Sto data.table documentazione di data.table e data.table anche notato da alcune delle conversazioni qui su SO che si suppone che rbindlist sia migliore di rbind .

Mi piacerebbe sapere perché rbindlist è migliore di rbind e in quali scenari rbindlist eccelle davvero su rbind ?

C’è qualche vantaggio in termini di utilizzo della memoria?

rbindlist è una versione ottimizzata di do.call(rbind, list(...)) , che è noto per essere lento quando si usa rbind.data.frame


Dove eccelle davvero

Alcune domande che mostrano dove rbindlist brilla

Unione vettoriale veloce di liste di dati. Frammenti per riga

Difficoltà nel convertire una lunga lista di data.frames (~ 1 milione) in single data.frame usando do.call e ldply

Questi hanno parametri di riferimento che mostrano quanto può essere veloce.


rbind.data.frame è lento, per una ragione

rbind.data.frame esegue molti controlli e corrisponderà per nome. (per esempio rbind.data.frame terrà conto del fatto che le colonne possono trovarsi in ordini diversi e corrispondere per nome), rbindlist non esegue questo tipo di controllo e si unirà per posizione

per esempio

 do.call(rbind, list(data.frame(a = 1:2, b = 2:3), data.frame(b = 1:2, a = 2:3))) ## ab ## 1 1 2 ## 2 2 3 ## 3 2 1 ## 4 3 2 rbindlist(list(data.frame(a = 1:5, b = 2:6), data.frame(b = 1:5, a = 2:6))) ## ab ## 1: 1 2 ## 2: 2 3 ## 3: 1 2 ## 4: 2 3 

Alcune altre limitazioni di rbindlist

Era solito lottare per gestire i factors , a causa di un bug che è stato corretto:

rbindlist due data.tables dove uno ha un fattore e l’altro ha un tipo di carattere per una colonna ( Bug # 2650 )

Ha problemi con i nomi di colonne duplicati

vedi Messaggio di avviso: in rbindlist (allargs): NA introdotte per coercizione: ansible bug in data.table? ( Bug # 2384 )


rbind.data.frame i rownames possono essere frustranti

rbindlist può gestire lists data.frames e data.tables , e restituirà un data.table senza rownames

puoi entrare in una confusione di nomadi usando do.call(rbind, list(...)) vedi

Come evitare la ridenominazione delle righe quando si utilizza rbind all’interno di do.call?


Efficienza della memoria

In termini di memoria rbindlist è implementato in C , quindi è efficiente in termini di memoria, utilizza setattr per impostare gli attributi per riferimento

rbind.data.frame è implementato in R , fa un sacco di assegnamenti, e usa attr<- (e class<- e rownames<- tutti i quali (internamente) creano copie del data.frame creato.

Con la versione rbindlist , rbindlist era evoluto un bel po ‘, implementando molte funzionalità tra cui:

  • Scegliendo il più alto SEXPTYPE di colonne durante l’associazione – implementato nella versione v1.9.2 chiudendo FR # 2456 e Bug # 4981 .
  • Gestire correttamente le colonne dei factor – implementato per la prima volta nella v1.8.10 chiudendo il bug n. 2650 ed estendendolo attentamente ai fattori ordinati vincolanti anche nella v1.9.2 , chiudendo FR # 4856 e Bug # 5019 .

Inoltre, in v1.9.2 , rbind.data.table anche ottenuto un argomento fill , che consente di associare compilando colonne mancanti, implementate in R.

Ora in v1.9.3 , ci sono ancora più miglioramenti su queste funzionalità esistenti:

  • rbindlist ottiene un argomento use.names , che di default è FALSE per retrocompatibilità.
  • rbindlist ottiene anche un argomento di fill , che di default è anche FALSE per retrocompatibilità.
  • Queste funzionalità sono tutte implementate in C e scritte attentamente per non compromettere la velocità durante l’aggiunta di funzionalità.
  • Poiché rbindlist può ora corrispondere per nome e riempire colonne mancanti, rbind.data.table chiama subito rbindlist ora. L’unica differenza è che use.names=TRUE per impostazione predefinita per rbind.data.table , per compatibilità con le versioni precedenti.

rbind.data.frame rallenta un bel po ‘per lo più a causa delle copie (che @mnel indica anche) che potrebbero essere evitate (passando a C). Penso che non sia l’unica ragione. L’implementazione per il controllo / corrispondenza dei nomi di colonna in rbind.data.frame potrebbe anche essere più lenta quando ci sono molte colonne per data.frame e ci sono molti data.frames da colbind (come mostrato nel benchmark sottostante).

Tuttavia, la mancanza di alcune funzionalità (come il controllo dei livelli dei fattori o dei nomi corrispondenti) di alcune delle funzionalità di rbind.data.frame un peso molto piccolo (o nullo) rispetto al fatto che sia più veloce di rbind.data.frame . È perché sono stati implementati con cura in C, ottimizzati per velocità e memoria.

Ecco un punto di riferimento che mette in risalto l’efficace legame durante l’abbinamento per nome di colonna e utilizza la funzione v1.9.3 di v1.9.3 dalla v1.9.3 . Il set di dati è composto da 10000 data.frames ciascuno di dimensioni 10 * 500.

NB: questo benchmark è stato aggiornato per includere un confronto con i dplyr di bind_rows

 library(data.table) # 1.11.5, 2018-06-02 00:09:06 UTC library(dplyr) # 0.7.5.9000, 2018-06-12 01:41:40 UTC set.seed(1L) names = paste0("V", 1:500) cols = 500L foo <- function() { data = as.data.frame(setDT(lapply(1:cols, function(x) sample(10)))) setnames(data, sample(names)) } n = 10e3L ll = vector("list", n) for (i in 1:n) { .Call("Csetlistelt", ll, i, foo()) } system.time(ans1 <- rbindlist(ll)) # user system elapsed # 1.226 0.070 1.296 system.time(ans2 <- rbindlist(ll, use.names=TRUE)) # user system elapsed # 2.635 0.129 2.772 system.time(ans3 <- do.call("rbind", ll)) # user system elapsed # 36.932 1.628 38.594 system.time(ans4 <- bind_rows(ll)) # user system elapsed # 48.754 0.384 49.224 identical(ans2, setDT(ans3)) # [1] TRUE identical(ans2, setDT(ans4)) # [1] TRUE 

Le colonne vincolanti come tali senza verifica dei nomi richiedevano solo 1,3, mentre il controllo dei nomi delle colonne e il binding richiedevano in modo appropriato solo 1,5 secondi in più. Rispetto alla soluzione di base, questo è 14 volte più veloce e 18 volte più veloce della dplyr di dplyr .