Pourquoi rbindlist est-il «meilleur» que rbind?

Je passe en data.table documentation de data.table et data.table également remarqué, dans certaines conversations ici sur SO, que rbindlist est censé être meilleur que rbind .

Je voudrais savoir pourquoi rbindlist est meilleur que rbind et dans quels scénarios rbindlist excelle vraiment sur rbind ?

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

rbindlist est une version optimisée de do.call(rbind, list(...)) , connue pour être lente lors de l’utilisation de rbind.data.frame


Où ça excelle vraiment

Certaines questions montrent où rbindlist brille sont

Fusion vectorisée rapide de la liste de data.frames par ligne

Problème lors de la conversion de la longue liste de data.frames (~ 1 million) en simple data.frame en utilisant do.call et ldply

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


rbind.data.frame est lent, pour une raison

rbind.data.frame fait beaucoup de vérifications et correspondra par nom. (c.-à-d. que rbind.data.frame tiendra compte du fait que les colonnes peuvent être dans des ordres différents et correspondent par nom), rbindlist ne fait pas ce genre de vérification et se joindra par position

par exemple

 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 

Quelques autres limitations de rbindlist

Il avait l’ habitude de lutter contre les factors , à cause d’un bug 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 n ° 2650 )

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

voir Message d’avertissement: dans rbindlist (allargs): NA introduits par coercition: bogue possible dans data.table? ( Bogue n ° 2384 )


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

rbindlist peut gérer les lists data.frames et data.tables , et renverra un data.table sans nom de domaine

vous pouvez entrer dans un mélange de noms de domaine en utilisant do.call(rbind, list(...)) see

Comment éviter de renommer des lignes lors de l’utilisation de rbind dans do.call?


Efficacité de la mémoire

En termes de mémoire, rbindlist est implémenté en C , donc efficace en mémoire, il utilise setattr pour définir les atsortingbuts par référence

rbind.data.frame est implémenté dans R , il fait beaucoup d’atsortingbutions et utilise attr<- (et class<- et rownames<- qui créeront (en interne) des copies du data.frame créé.

En v1.9.2 , rbindlist avait beaucoup évolué, mettant en œuvre de nombreuses fonctionnalités, notamment:

  • Choisir le plus haut SEXPTYPE de colonnes lors de la liaison – implémenté dans la v1.9.2 fermant FR # 2456 et le bogue # 4981 .
  • Traitement correct des colonnes de factor – implémentées pour la première fois dans la v1.8.10 fermant le bogue n ° 2650 et en étendant soigneusement les facteurs ordonnés à la v1.9.2 dans la v1.9.2 , en fermant les n ° 4856 et 5019 .

De plus, dans v1.9.2 , rbind.data.table également obtenu un argument de fill , qui permet de se lier en remplissant les colonnes manquantes, implémentées dans R.

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

  • rbindlist gagne un argument use.names , qui est par défaut FALSE pour la rétrocompatibilité.
  • rbindlist également un fill argument, qui est également par défaut FALSE pour la compatibilité ascendante.
  • Ces fonctionnalités sont toutes implémentées en C, et écrites avec soin pour ne pas compromettre la vitesse tout en ajoutant des fonctionnalités.
  • Comme rbindlist peut maintenant correspondre aux noms et remplir les colonnes manquantes, rbind.data.table appelle simplement rbindlist maintenant. La seule différence est que use.names=TRUE par défaut pour rbind.data.table , pour une compatibilité ascendante.

rbind.data.frame ralentit un peu, principalement à cause des copies (que @mnel indique également) qui pourraient être évitées (en passant à C). Je pense que ce n’est pas la seule raison. L’implémentation de la vérification / correspondance des noms de colonne dans rbind.data.frame pourrait aussi être plus lente lorsqu’il y a beaucoup de colonnes par data.frame et que beaucoup de ces data.frames sont liés (comme indiqué dans le benchmark ci-dessous).

Cependant, cette rbindlist manque (ed) de certaines fonctionnalités (comme la vérification des niveaux de facteurs ou des noms correspondants) qui pèsent très peu (ou pas) rbind.data.frame elle est 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 test qui met en évidence l’efficacité de la liaison lors de la mise en correspondance avec les noms de colonnes, en utilisant la fonctionnalité v1.9.3 de v1.9.3 de la version v1.9.3 . Le jeu de données comprend 10000 data.frames de 10 * 500 chacun.

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

Les colonnes de liaison en tant que telles sans vérification des noms ne prenaient que 1,3, car la vérification des noms de colonnes et de la liaison ne prenait que 1,5 seconde de plus. Par rapport à la solution de base, cela est 14 fois plus rapide et 18 fois plus rapide que la dplyr de dplyr .