59 votes

Pourquoi plyr est-il si lent ?

Je pense que j'utilise plyr incorrectement. Quelqu'un pourrait-il me dire si ce code plyr est '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 du temps. En essayant de résoudre les problèmes, je me suis intéressé aux performances de différentes procédures d'agrégation en R.

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

Quand j'ai enfin reçu les résultats, j'ai découvert un énorme écart entre la méthode plyr et les autres - ce qui me fait penser que j'ai fait quelque chose de complètement faux.

J'ai exécuté le code suivant (j'ai pensé jeter un œil au 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))
b.y <- function(dd) unlist(by(dd$volume, dd$price, sum))
b.y.x <- 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),
                         b.y(d.df), b.y(d.dfp), b.y.x(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 devait aller jusqu'à 5e8, mais cela a pris trop de temps - surtout à cause de plyr. Le tableau final à 5e5 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.matrix)   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.matrix)   22.014    5.296920
13          agg(d.dfp)   32.254    7.760828
14     agg.x(d.matrix)   32.435    7.804379
12           agg(d.df)   32.593    7.842397
10          b.y(d.dfp)   98.006   23.581809
11     b.y.x(d.matrix)   98.134   23.612608
9            b.y(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 est-il 2250x plus lent que data.table? Et pourquoi l'utilisation du nouveau package de dataframe n'a-t-elle pas fait de différence?

Les informations de la session sont:

> sessionInfo()
R version 2.15.1 (2012-06-22)
Plateforme: 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

packages de base attachés:
[1] stats     graphics  grDevices utils     datasets  methods   base     

autres packages attachés:
[1] xts_0.8-6        zoo_1.7-7        rbenchmark_0.3   dataframe_2.5    data.table_1.8.1     plyr_1.7.1      

chargé via un espace de noms (et non attaché):
[1] grid_2.15.1    lattice_0.20-6 tools_2.15.1

51voto

Thell Points 2772

Pourquoi c'est si lent ? Une petite recherche a localisé une publication de groupe de messagerie datant d'août 2011 où @hadley, l'auteur du package, déclare

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


En ce qui concerne l'efficacité du code plyr je ne le savais pas non plus. Après une série de tests de paramètres et de benchmarks, on dirait que nous pouvons faire mieux.

Le summarize() dans votre commande est juste une fonction d'aide, pure et simple. Nous pouvons le remplacer par notre propre fonction de somme puisqu'il n'apporte rien de plus à ce qui est déjà simple et les arguments .data et .(price) peuvent être rendus plus explicites. Le résultat est

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

Le summarize peut sembler bien, mais il n'est tout simplement pas plus rapide qu'un simple appel de fonction. C'est logique; regardez simplement notre petite fonction par rapport au code pour summarize. L'exécution de vos tests de performance avec la formule révisée donne un gain noticeable. Ne pensez pas avoir utilisé plyr incorrectement, vous ne l'avez pas fait, il n'est simplement pas efficace; rien de ce que vous pouvez faire avec cela ne le rendra aussi rapide que d'autres options.

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


Dans le même thread mentionné ci-dessus, concernant la lenteur de plyr, un projet plyr2 est mentionné. Depuis la réponse originale à la question, l'auteur de plyr a publié dplyr comme successeur de plyr. Alors que plyr et dplyr sont tous deux présentés comme des outils de manipulation de données et que votre intérêt principal déclaré est l'agrégation, vous pourriez être intéressé par les résultats de vos tests de performance du nouveau package pour comparaison car il a un backend 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]

<sup>Le package <code>dataframe</code> a été supprimé de CRAN et par la suite des tests, ainsi que les versions des fonctions matrix.</sup>

Voici les résultats du benchmark i=5, j=8:

$`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            b.y(d.df)  48.569  656.338
2 plyr_Optimized(d.df) 148.030 2000.405
1  plyr_Original(d.df) 401.890 5430.946

Sans aucun doute, l'optimisation a un peu aidé. Jetez un œil aux fonctions d.df; elles ne peuvent tout simplement pas rivaliser.

Pour un peu de perspective sur la lenteur de la structure de données frame, voici des micro-benchmarks des temps d'agrégation de data_table et dplyr en utilisant un jeu de données de test plus important (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 très en retard. De plus, voici le temps d'exécution system.time pour peupler les structures de données avec les données de test :

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

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

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

En fin de compte...

Plyr est lent en raison de la manière dont il fonctionne avec et gère la manipulation des data.frames.

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


## Version de R 3.0.2 (2013-09-25)
## Plateforme: x86_64-pc-linux-gnu (64-bit)
## 
## Packages de base attachés :
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## Autres packages attachés :
## [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        
## 
## Charger via un espace de noms (et non attaché) :
## [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  stringr_0.6.2  
## [9] tools_3.0.2

<sup><a href="https://gist.github.com/Thell/9265593">Gist de génération de données .rmd</a></sup>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X