Pourquoi plyr est si lent?

Je pense que j’utilise incorrectement plyr. Quelqu’un pourrait-il me dire s’il s’agit d’un code plyr «efficace»?

require(plyr) plyr <- function(dd) ddply(dd, .(price), summarise, ss=sum(volume)) 

Un peu de contexte: j’ai quelques gros problèmes d’agrégation et j’ai remarqué qu’ils prenaient chacun un certain temps. En essayant de résoudre les problèmes, je me suis intéressé à la performance de diverses procédures d’agrégation dans R.

J’ai testé quelques méthodes d’agrégation et je me suis retrouvé à attendre toute la journée.

Quand j’ai finalement obtenu des résultats, j’ai découvert un énorme fossé entre la méthode plyr et les autres – ce qui me fait penser que j’ai fait quelque chose de mal.

J’ai exécuté le code suivant (je pensais vérifier le nouveau package dataframe pendant que j’y étais):

 require(plyr) require(data.table) require(dataframe) require(rbenchmark) require(xts) plyr <- function(dd) ddply(dd, .(price), summarise, ss=sum(volume)) t.apply <- function(dd) unlist(tapply(dd$volume, dd$price, sum)) t.apply.x <- function(dd) unlist(tapply(dd[,2], dd[,1], sum)) l.apply <- function(dd) unlist(lapply(split(dd$volume, dd$price), sum)) l.apply.x <- function(dd) unlist(lapply(split(dd[,2], dd[,1]), sum)) by <- function(dd) unlist(by(dd$volume, dd$price, sum)) byx <- function(dd) unlist(by(dd[,2], dd[,1], sum)) agg <- function(dd) aggregate(dd$volume, list(dd$price), sum) agg.x <- function(dd) aggregate(dd[,2], list(dd[,1]), sum) dtd <- function(dd) dd[, sum(volume), by=(price)] obs <- c(5e1, 5e2, 5e3, 5e4, 5e5, 5e6, 5e6, 5e7, 5e8) timS <- timeBasedSeq('20110101 083000/20120101 083000') bmkRL <- list(NULL) for (i in 1:5){ tt <- timS[1:obs[i]] for (j in 1:8){ pxl <- seq(0.9, 1.1, by= (1.1 - 0.9)/floor(obs[i]/(11-j))) px <- sample(pxl, length(tt), replace=TRUE) vol <- rnorm(length(tt), 1000, 100) d.df <- base::data.frame(time=tt, price=px, volume=vol) d.dfp <- dataframe::data.frame(time=tt, price=px, volume=vol) d.matrix <- as.matrix(d.df[,-1]) d.dt <- data.table(d.df) listLabel <- paste('i=',i, 'j=',j) bmkRL[[listLabel]] <- benchmark(plyr(d.df), plyr(d.dfp), t.apply(d.df), t.apply(d.dfp), t.apply.x(d.matrix), l.apply(d.df), l.apply(d.dfp), l.apply.x(d.matrix), by(d.df), by(d.dfp), byx(d.matrix), agg(d.df), agg(d.dfp), agg.x(d.matrix), dtd(d.dt), columns =c('test', 'elapsed', 'relative'), replications = 10, order = 'elapsed') } } 

Le test était censé vérifier jusqu’à 5e8, mais cela a pris trop de temps, principalement à cause de plyr. Le 5e5 le tableau final montre le problème:

 $`i= 5 j= 8` test elapsed relative 15 dtd(d.dt) 4.156 1.000000 6 l.apply(d.df) 15.687 3.774543 7 l.apply(d.dfp) 16.066 3.865736 8 l.apply.x(d.masortingx) 16.659 4.008422 4 t.apply(d.dfp) 21.387 5.146054 3 t.apply(d.df) 21.488 5.170356 5 t.apply.x(d.masortingx) 22.014 5.296920 13 agg(d.dfp) 32.254 7.760828 14 agg.x(d.masortingx) 32.435 7.804379 12 agg(d.df) 32.593 7.842397 10 by(d.dfp) 98.006 23.581809 11 byx(d.masortingx) 98.134 23.612608 9 by(d.df) 98.337 23.661453 1 plyr(d.df) 9384.135 2257.972810 2 plyr(d.dfp) 9384.448 2258.048123 

Est-ce correct? Pourquoi plyr 2250x est-il plus lent que data.table ? Et pourquoi l’utilisation du nouveau package de trames de données n’a-t-elle pas fait la différence?

L’information de la session est:

 > sessionInfo() R version 2.15.1 (2012-06-22) Platform: x86_64-apple-darwin9.8.0/x86_64 (64-bit) locale: [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8 attached base packages: [1] stats graphics grDevices utils datasets methods base other attached packages: [1] xts_0.8-6 zoo_1.7-7 rbenchmark_0.3 dataframe_2.5 data.table_1.8.1 plyr_1.7.1 loaded via a namespace (and not attached): [1] grid_2.15.1 lattice_0.20-6 tools_2.15.1 

Pourquoi c’est si lent? Une petite recherche a permis de localiser un groupe de courrier datant d’août 2011, où @hadley, l’auteur du paquet, déclare

Ceci est un inconvénient de la manière dont ddply fonctionne toujours avec les blocs de données. Ce sera un peu plus rapide si vous utilisez résumé au lieu de data.frame (car data.frame est très lent), mais je réfléchis encore à la manière de surmonter cette limitation fondamentale de l’approche ddply.


Quant à être un code plyr efficace, je ne le savais pas non plus. Après plusieurs tests de paramétrage et de benchmark, il semble que nous pouvons faire mieux.

Le summarize() dans votre commande est une fonction d’assistance, pure et simple. Nous pouvons le remplacer par notre propre fonction de sum, car cela n’aide pas pour tout ce qui n’est pas déjà simple et les .data et .(price) .data .(price) peuvent être rendus plus explicites. Le résultat est

 ddply( dd[, 2:3], ~price, function(x) sum( x$volume ) ) 

Le summarize peut sembler intéressant, mais ce n’est pas plus rapide qu’un simple appel de fonction. Ca a du sens; Il suffit de regarder notre petite fonction par rapport au code pour summarize . L’exécution de vos benchmarks avec la formule révisée génère un gain notable. Ne prenez pas cela pour signifier que vous avez utilisé plyr incorrectement, vous ne l’avez pas fait, ce n’est tout simplement pas efficace; rien de ce que vous pouvez en faire ne le rendra aussi vite que d’autres options.

A mon avis, la fonction optimisée pue encore car elle n’est pas claire et doit être analysée mentalement tout en étant ridiculement lente par rapport à data.table (même avec un gain de 60%).


Dans le même fil mentionné ci-dessus, en ce qui concerne la lenteur de plyr, un projet plyr2 est mentionné. Depuis l’époque de la réponse originale à la question, l’auteur plyr a publié dplyr comme successeur de plyr. Alors que plyr et dplyr sont tous deux facturés comme des outils de manipulation de données et que votre intérêt principal est l’agrégation, vos résultats de référence du nouveau package peuvent vous intéresser, car son backend a été retravaillé pour améliorer les performances.

 plyr_Original <- function(dd) ddply( dd, .(price), summarise, ss=sum(volume)) plyr_Optimized <- function(dd) ddply( dd[, 2:3], ~price, function(x) sum( x$volume ) ) dplyr <- function(dd) dd %.% group_by(price) %.% summarize( sum(volume) ) data_table <- function(dd) dd[, sum(volume), keyby=price] 

Le package dataframe a été supprimé de CRAN, puis des tests, avec les versions des fonctions de masortingce.

Voici le i=5, j=8 résultats de référence:

 $`obs= 500,000 unique prices= 158,286 reps= 5` test elapsed relative 9 data_table(d.dt) 0.074 1.000 4 dplyr(d.dt) 0.133 1.797 3 dplyr(d.df) 1.832 24.757 6 l.apply(d.df) 5.049 68.230 5 t.apply(d.df) 8.078 109.162 8 agg(d.df) 11.822 159.757 7 by(d.df) 48.569 656.338 2 plyr_Optimized(d.df) 148.030 2000.405 1 plyr_Original(d.df) 401.890 5430.946 

Nul doute que l'optimisation a un peu aidé. Jetez un oeil sur les fonctions d.df ; ils ne peuvent tout simplement pas rivaliser.

Pour une petite perspective sur la lenteur de la structure data.frame, voici des micro-tests des temps d'agrégation de data_table et dplyr en utilisant un dataset de test plus grand ( i=8,j=8 ).

 $`obs= 50,000,000 unique prices= 15,836,476 reps= 5` Unit: seconds expr min lq median uq max neval data_table(d.dt) 1.190 1.193 1.198 1.460 1.574 10 dplyr(d.dt) 2.346 2.434 2.542 2.942 9.856 10 dplyr(d.df) 66.238 66.688 67.436 69.226 86.641 10 

Le data.frame est toujours dans la poussière. Non seulement cela, mais voici le system.time écoulé pour remplir les structures de données avec les données de test:

 `d.df` (data.frame) 3.181 seconds. `d.dt` (data.table) 0.418 seconds. 

La création et l'agrégation de data.frame sont toutes deux plus lentes que celles de data.table.

Travailler avec le data.frame dans R est plus lent que certaines alternatives mais comme les tests montrent que les fonctions R intégrées sortent de l'eau. Même la gestion des données comme le fait dplyr, ce qui améliore les fonctions intégrées, ne donne pas une vitesse optimale; où data.table est plus rapide à la fois dans la création et l'agrégation et data.table fait ce qu'il fait en travaillant avec / sur data.frames.

À la fin...

Plyr est lent à cause de la façon dont il fonctionne et gère la manipulation de data.frame .

[punt :: voir les commentaires à la question originale].


 ## R version 3.0.2 (2013-09-25) ## Platform: x86_64-pc-linux-gnu (64-bit) ## ## attached base packages: ## [1] stats graphics grDevices utils datasets methods base ## ## other attached packages: ## [1] microbenchmark_1.3-0 rbenchmark_1.0.0 xts_0.9-7 ## [4] zoo_1.7-11 data.table_1.9.2 dplyr_0.1.2 ## [7] plyr_1.8.1 knitr_1.5.22 ## ## loaded via a namespace (and not attached): ## [1] assertthat_0.1 evaluate_0.5.2 formatR_0.10.4 grid_3.0.2 ## [5] lattice_0.20-27 Rcpp_0.11.0 reshape2_1.2.2 ssortingngr_0.6.2 ## [9] tools_3.0.2 

GIST Générateur de données