Domanda Perché rbindlist è "migliore" di rbind?


Sto esaminando la documentazione di data.table e anche notato da alcune delle conversazioni qui su SO che rbindlist dovrebbe essere migliore di rbind.

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

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


108
2018-03-28 03:05


origine


risposte:


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 sono

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 fa un sacco di controllo e corrisponderà per nome. (ad esempio, rbind.data.frame terrà conto del fatto che le colonne potrebbero trovarsi in ordini diversi e corrispondere per nome), rbindlist non fa 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)))
##    a b
## 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)))
##     a b
##  1: 1 2
##  2: 2 3
##  3: 1 2
##  4: 2 3

Alcune altre limitazioni di rbindlist

esso abituato a lottare per affrontare 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

vedere Messaggio di avvertenza: in rbindlist (allargs): le NA introdotte per coercizione: possibile bug in data.table? (Bug # 2384)


rbind.data.frame i rownames possono essere frustranti

rbindlist in grado di gestire lists  data.frames e data.tablese restituirà un data.table senza rownames

si può ottenere in un pasticcio di rownames usando do.call(rbind, list(...)) vedere

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 molti assegnamenti e usi attr<- (e class<- e rownames<- ognuno di questi crea (internamente) copie del data.frame creato.


129
2018-03-28 03:16



Di v1.9.2, rbindlist si era evoluto un bel po ', implementando molte funzionalità tra cui:

  • Scegliere il massimo SEXPTYPE di colonne mentre vincolante - implementato in v1.9.2 chiusura FR # 2456 e Bug # 4981.
  • maneggio factor colonne correttamente - implementate per la prima volta in v1.8.10 chiusura Bug # 2650 ed esteso al binding ordinato fattori attentamente dentro v1.9.2 pure, chiudendo FR # 4856 e Bug # 5019.

Inoltre, in v1.9.2, rbind.data.table anche guadagnato a fill argomento, 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 guadagna una discussione use.names, che di default è FALSE per compatibilità all'indietro.
  • rbindlist guadagna anche una discussione fill, che di default è anche FALSE per compatibilità all'indietro.
  • Queste funzionalità sono tutte implementate in C e scritte attentamente per non compromettere la velocità durante l'aggiunta di funzionalità.
  • Da rbindlist ora può corrispondere per nome e riempire colonne mancanti, rbind.data.table solo chiamate rbindlist adesso. L'unica differenza è questa use.names=TRUE di default per rbind.data.table, per compatibilità all'indietro.

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

Comunque rbindlist la mancanza (ndr) di alcune caratteristiche (come il controllo dei livelli dei fattori o dei nomi corrispondenti) ha un peso molto piccolo (o nullo) in quanto è 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 con i nomi delle colonne e l'utilizzo rbindlist'S use.names funzione da 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 dplyr'S 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 di dplyrLa versione di


35
2018-06-01 19:32