Perché rbindlist è "migliore" di rbind?


135

Sto esaminando la documentazione data.tablee ho anche notato alcune delle conversazioni qui su SO che rbindlistdovrebbero essere migliori di rbind.

Vorrei sapere perché è rbindlistmeglio di rbinde in quali scenari rbindlisteccelle davvero rbind?

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

Risposte:


155

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


Dove eccellono davvero

Alcune domande che mostrano dove rbindlistsono i brillanti

Unione rapida vettorizzata dell'elenco di data.frames per riga

Difficoltà nel convertire un lungo elenco di data.frames (~ 1 milione) in single data.frame usando do.call e ldply

Questi hanno parametri che mostrano quanto può essere veloce.


rbind.data.frame è lento, per un motivo

rbind.data.framefa molti controlli e corrisponderà per nome. (ad es. rbind.data.frame terrà conto del fatto che le colonne possono essere in ordini diversi e corrispondere per nome), rbindlistnon esegue questo tipo di controllo e si uniranno 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

E ' usato per lottare per affrontare factors, a causa di un bug che da allora è stato fissato:

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

Ha problemi con nomi di colonne duplicati

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


I rownames di rbind.data.frame possono essere frustranti

rbindlistpuò gestire lists data.framese data.tables, e restituirà un file data.table senza rownames

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

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


Efficienza di memoria

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

rbind.data.frameè implementato in R, fa molte assegnazioni e usa attr<-(e class<-e rownames<-tutto ciò creerà (internamente) copie dei data.frame creati.


1
Cordiali saluti attr<-, class<-e (penso) rownames<-tutti modificano sul posto.
Hadley,

5
@hadley Sei sicuro? Prova DF = data.frame(a=1:3); .Internal(inspect(DF)); tracemem(DF); attr(DF,"test") <- "hello"; .Internal(inspect(DF)).
Matt Dowle,

4
rbind.data.frameha una speciale logica di "dirottamento" - quando il suo primo argomento è a data.table, chiama .rbind.data.tableinvece, che fa un po 'di controllo e poi chiama rbindlistinternamente. Quindi, se hai già degli data.tableoggetti da associare, probabilmente c'è poca differenza di prestazioni tra rbinde rbindlist.
Ken Williams,

6
mnel, questo post forse ha bisogno di essere modificato, ora che rbindlistè in grado di abbinare per nome ( use.names=TRUE) e riempire anche le colonne mancanti ( fill=TRUE). Ho aggiornato questo , questo e questo post. Ti dispiace modificare questo o va bene se lo faccio? Ad ogni modo va bene per me.
Arun,

1
dplyr::rbind_listè anche abbastanza simile
Hadley il

48

Da v1.9.2, si rbindlistera evoluto un po ', implementando molte funzionalità tra cui:

  • Scelta SEXPTYPEdella colonna più alta durante l'associazione - implementata nella v1.9.2chiusura di FR # 2456 e Bug # 4981 .
  • Gestire factorcorrettamente le colonne - implementato per la prima volta nella v1.8.10chiusura del Bug # 2650 e esteso anche ai vincoli dei fattori ordinativ1.9.2 , chiudendo anche FR # 4856 e Bug # 5019 .

Inoltre, in v1.9.2, ha rbind.data.tableanche ottenuto un fillargomento, che consente di legare riempiendo le colonne mancanti, implementato in R.

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

  • rbindlistottiene un argomento use.names, che per impostazione predefinita è FALSEper compatibilità con le versioni precedenti.
  • rbindlistottiene anche un argomento fill, che per impostazione predefinita è anche FALSEper compatibilità con le versioni precedenti.
  • Queste funzionalità sono tutte implementate in C e scritte attentamente per non scendere a compromessi in termini di velocità durante l'aggiunta di funzionalità.
  • Dal momento che rbindlistora possono abbinare i nomi e riempire le colonne mancanti, rbind.data.tablechiama solo rbindlistora. L'unica differenza è quella use.names=TRUEdi default per rbind.data.table, per compatibilità con le versioni precedenti.

rbind.data.framerallenta un po 'per lo più a causa delle copie (che sottolinea anche @mnel) che potrebbero essere evitate (spostandosi su C). Penso che non sia l'unica ragione. L'implementazione per il controllo / corrispondenza dei nomi delle colonne rbind.data.framepotrebbe anche rallentare quando ci sono molte colonne per data.frame e ci sono molti di tali data.frames da associare (come mostrato nel benchmark di seguito).

Tuttavia, tale rbindlistmancanza (ed) alcune funzionalità (come il controllo dei livelli dei fattori o la corrispondenza dei nomi) ha un peso molto piccolo (o nullo) perché è più veloce di rbind.data.frame. È perché sono stati accuratamente implementati in C, ottimizzati per velocità e memoria.

Ecco un punto di riferimento che evidenzia l'associazione efficiente durante la corrispondenza per nomi di colonna e utilizzando rbindlistla use.namesfunzione di 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 a dplyr'sbind_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

Il collegamento delle colonne in quanto tale senza il controllo dei nomi ha richiesto solo 1,3, mentre il controllo dei nomi delle colonne e l'associazione in modo appropriato hanno richiesto solo 1,5 secondi in più. Rispetto alla soluzione base, è 14 volte più veloce e 18 volte più veloce della dplyrversione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.