Pourquoi rbindlist est-il «meilleur» que rbind?

135

Je suis en train de parcourir la documentation data.tableet j'ai également remarqué certaines des conversations ici sur SO qui rbindlistest censé être meilleur que rbind.

Je voudrais savoir pourquoi vaut rbindlistmieux rbindet dans quels scénarios rbindlistexcelle vraiment rbind?

Y a-t-il un avantage en termes d'utilisation de la mémoire?

Chinmay Patil
la source

Réponses:

155

rbindlistest une version optimisée de do.call(rbind, list(...)), qui est connue pour être lente lors de l'utilisationrbind.data.frame


Où excelle-t-il vraiment

Quelques questions qui montrent où se rbindlisttrouve la lumière

Fusion vectorisée rapide de la liste de données, trames par ligne

Problème lors de la conversion d'une longue liste de data.frames (~ 1 million) en data.frame unique à l'aide de do.call et ldply

Ceux-ci ont des repères qui montrent à quelle vitesse cela peut être.


rbind.data.frame est lent, pour une raison

rbind.data.framefait beaucoup de vérifications et correspondra par nom. (c'est-à-dire que rbind.data.frame tiendra compte du fait que les colonnes peuvent être dans des ordres différents, et correspondre par nom), rbindlistne fait pas ce genre de vérification et rejoindra par position

par exemple

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

Quelques autres limitations de rbindlist

Il avait du mal à gérer factors, en raison d'un bogue qui a depuis été corrigé:

rbindlist deux data.tables où l'un a un facteur et l'autre un type de caractère pour une colonne ( bogue # 2650 )

Il a des problèmes avec les noms de colonne en double

voir Message d'avertissement: dans rbindlist (allargs): AN introduits par coercition: bug possible dans data.table? ( Bogue n ° 2384 )


Les noms de domaine rbind.data.frame peuvent être frustrants

rbindlistpeut gérer lists data.frameset data.tables, et renverra une table data.table sans noms de domaine

vous pouvez vous mêler de noms de domaine en utilisant do.call(rbind, list(...)) see

Comment éviter de renommer les lignes lors de l'utilisation de rbind dans do.call?


Efficacité de la mémoire

En termes de mémoire rbindlistest implémentée dans C, la mémoire est donc efficace, il utilise setattrpour définir des attributs par référence

rbind.data.frameest implémenté dans R, il effectue de nombreuses affectations et utilise attr<-(et class<-et rownames<-tout cela créera (en interne) des copies du data.frame créé.

mnel
la source
1
FYI attr<-, class<-et (je pense) rownames<-tous modifier en place.
hadley
5
@hadley Êtes-vous sûr? Essayez DF = data.frame(a=1:3); .Internal(inspect(DF)); tracemem(DF); attr(DF,"test") <- "hello"; .Internal(inspect(DF)).
Matt Dowle
4
rbind.data.framea une logique de "détournement" spéciale - quand son premier argument est a data.table, il appelle à la .rbind.data.tableplace, ce qui fait un peu de vérification et appelle ensuite en rbindlistinterne. Donc, si vous avez déjà des data.tableobjets à lier, il y a probablement peu de différence de performances entre rbindet rbindlist.
Ken Williams
6
mnel, cet article a peut-être besoin d'être édité, maintenant qu'il rbindlistest capable de correspondre par noms ( use.names=TRUE) et de remplir également les colonnes manquantes ( fill=TRUE). J'ai mis à jour ceci , ceci et ce post. Cela vous dérange-t-il de le modifier ou est-ce que je peux le faire? Dans les deux cas, ça me va.
Arun
1
dplyr::rbind_listest également assez similaire
hadley
48

By v1.9.2, rbindlistavait beaucoup évolué, implémentant de nombreuses fonctionnalités, notamment:

  • Choix de la plus haute SEXPTYPEdes colonnes lors de la liaison - implémenté en v1.9.2fermant FR # 2456 et Bug # 4981 .
  • Gérer factorcorrectement les colonnes - d'abord implémenté lors de la v1.8.10fermeture du bogue n ° 2650 et étendu à la liaison des facteurs ordonnés avec soin v1.9.2également, en fermant FR # 4856 et le bogue n ° 5019 .

En outre, dans v1.9.2, a rbind.data.tableégalement gagné un fillargument, qui permet de lier en remplissant les colonnes manquantes, implémenté dans R.

Maintenant v1.9.3, il y a encore plus d'améliorations sur ces fonctionnalités existantes:

  • rbindlistgagne un argument use.names, qui par défaut est FALSEpour la compatibilité ascendante.
  • rbindlistgagne également un argument fill, qui par défaut est également FALSEpour la compatibilité ascendante.
  • Ces fonctionnalités sont toutes implémentées en C, et écrites avec soin pour ne pas compromettre la vitesse lors de l'ajout de fonctionnalités.
  • Puisque rbindlistpeut maintenant correspondre par noms et remplir les colonnes manquantes, il rbind.data.tablesuffit d'appeler rbindlistmaintenant. La seule différence est que use.names=TRUEpar défaut pour rbind.data.table, pour une compatibilité ascendante.

rbind.data.frameralentit un peu principalement à cause des copies (ce que @mnel souligne également) qui pourraient être évitées (en passant à C). Je pense que ce n'est pas la seule raison. La mise en œuvre de la vérification / correspondance des noms de colonnes dans rbind.data.framepourrait également être plus lente lorsqu'il y a beaucoup de colonnes par data.frame et qu'il y a beaucoup de data.frames à lier (comme indiqué dans le benchmark ci-dessous).

Cependant, ce rbindlistmanque de certaines fonctionnalités (comme la vérification des niveaux de facteur ou la correspondance des noms) n'a qu'un poids très minime (ou pas) pour qu'il soit plus rapide que rbind.data.frame. C'est parce qu'ils ont été soigneusement implémentés en C, optimisés pour la vitesse et la mémoire.

Voici un benchmark qui met en évidence la liaison efficace tout en faisant correspondre les noms de colonnes ainsi qu'en utilisant rbindlistla use.namesfonctionnalité de v1.9.3. L'ensemble de données se compose de 10000 data.frames de taille 10 * 500 chacun.

NB: ce benchmark a été mis à jour pour inclure une comparaison avec 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

La liaison des colonnes en tant que telles sans vérifier les noms n'a pris que 1,3 alors que la vérification des noms de colonnes et la liaison appropriée ne prenaient que 1,5 seconde de plus. Par rapport à la solution de base, c'est 14 fois plus rapide et 18 fois plus rapide que dplyrla version de.

Arun
la source