2012-07-18 138 views
59

我想我错误地使用了plyr。有人可以告诉我这是否是“高效”的plyr代码?plyr为什么这么慢?

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

有点上下文:我有一些大的聚合问题,我已经注意到他们每个人都需要一些时间。在试图解决这些问题时,我开始对R中的各种聚合过程的性能感兴趣。我测试了几种聚合方法 - 发现我自己一整天都在等待。

当我终于找回结果时,我发现plyr方法和其他方法之间存在巨大差距 - 这让我认为我做了一件错误的事情。

我跑了下面的代码(我想我会看看新的数据帧封装,而我是在它):

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') 
    } 
} 

测试应该检查可达5E8,但时间太长 - 主要由于plyr。 5e5决赛桌上显示问题:

$`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 

是这样吗?为什么plyr 2250x比data.table慢?为什么没有使用新的数据框包有所作为?

会话信息是:

> 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 
+3

对于相对简单的数据操作/汇聚问题,我发现数据表是非常快的。如果它能做到,我完全不会感到惊讶,这是明显的赢家。我对“plyr”对此评论不够熟悉。 – Joshua 2012-07-18 02:30:03

+1

你看过'plyr'和'data.table'的文档吗?如果我没有记错的话,'plyr'可以与''''''data'frame's一起使用。 'data.table'使用完全不同的表示形式,使用键控列和高效的基数排序。这样更像数据库。 – 2012-07-18 02:33:08

+0

我曾看过 - 但无法弄清楚。 plyr不仅仅是慢一点...适用于家庭,agg,并且非常快 - 它们是基础。这就是为什么我认为我必须与plyr做出一些新秀的错误。 – ricardo 2012-07-18 02:44:48

回答

51

为什么它是如此之慢?一个小小的研究位于邮件组记录从八月2011,其中@hadley,包的作者,states

这是一份ddply总是与数据 框架工作方式的缺点。如果使用汇总而不是 data.frame(因为data.frame非常慢),它会快一点,但我仍在考虑如何解决ddply 方法的基本限制 。


至于是高效 plyr代码我不知道。经过一系列参数测试和基准测试后,看起来我们可以做得更好。

summarize()在你的命令中是一个简单的帮助函数,纯粹而简单。我们可以用我们自己的求和函数来替换它,因为它不能帮助任何不简单的事情,并且可以使得参数变得更加明确。其结果是

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

summarize可能看起来不错,但它仅仅是不超过一个简单的函数调用更快。这说得通;只需看看我们的小函数与codesummarize。用修改后的公式运行您的基准测试会产生显着的收益。不要认为你错误地使用了plyr,你没有,它只是没有效率;没有什么可以做到的,它会使其与其他选项一样快。

在我看来,优化功能仍然很臭,因为它不明确,必须进行思维分析,与data.table相比仍然很慢(甚至有60%的增益)。


在上述同样的thread,关于plyr的缓慢,一个plyr2项目被提及。自plyr作者已发布dplyr作为plyr的继承者的问题的原始答案时间以来。虽然plyr和dplyr都被称为数据操作工具,并且您的主要兴趣是聚合,但您可能仍然对新软件包的基准测试结果感兴趣,以便进行比较,因为它具有改进的后端以提高性能。

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] 

dataframe的包已经被从CRAN和随后除去从测试中,与基体功能的版本一起。

这里的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 

毫无疑问的优化帮助一点。看看d.df函数;他们只是无法竞争。

对于data.frame结构缓慢的一点透视,这里是使用更大的测试数据集(i=8,j=8)的data_table和dplyr的聚集时间的微基准。

$`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 

的data.frame是仍然留在灰尘。不仅如此,但这里的经过system.time来用测试数据的数据结构:

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

兼具创造与data.frame聚集比data.table慢。

在 [R 与data.frame 工作比一些替代品速度较慢,但​​作为基准测试显示,内置的R的功能和吹plyr出来的水。即使像dplyr那样管理data.frame,它会改进内置插件,但不会提供最佳速度;其中data.table 更快都在创建和聚合 data.table在处理data.frames时会执行它的操作。

到底...

Plyr是因为它与工程和管理data.frame操作方式缓慢。

[punt ::查看原始问题的评论]。


## 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 stringr_0.6.2 
## [9] tools_3.0.2 

Data-Generating gist .rmd

+0

+1。好的建议。谢了哥们。我今天正在使用你的'plyr'和'dc'代码重新运行测试。当他们完成后,我会发布一个答案。我决定放下矩阵位,稍微加快速度(因为将df移动到矩阵中似乎没有增加太多)。 – ricardo 2012-08-18 00:06:14

+0

我接受了这个答案,因为看起来就像我们将要得到的那样 - 除非哈德利想检查和解释'plyr'的内部运作。 – ricardo 2012-08-27 20:39:10

+3

@Thell既然你已经提到易用性,我已经添加了'dtd()'实际上是一起的,iiuc。任何人都可以说这不容易打败我。但是,使用data.table后端的dplyr比直接使用data.table慢,然后呢?怎么来的? – 2013-09-28 19:56:45