为什么plyr这么慢?

我想我错误地使用plyr。 有人可以告诉我,如果这是“有效的”plyr代码?

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

一个小背景:我有几个大的聚合问题,我已经注意到,他们每个都花了一些时间。 为了解决这个问题,我对R中的各种聚合过程的performance感兴趣。

我testing了一些聚合方法 – 发现自己整天都在等待。

当我终于找回结果的时候,我发现了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)) 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') } } 

testing本来应该检查到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 by(d.dfp) 98.006 23.581809 11 byx(d.matrix) 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 

这是正确的吗? 为什么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 

为什么这么慢? 包裹作者@hadley指出,一个小小的研究发现,2011年8月份的一个邮件群发布信息

这是ddply总是与dataframe一起工作的缺点。 如果使用summary(而不是data.frame)(因为data.frame非常慢),会更快一点,但是我仍然在考虑如何克服ddply方法的这个基本限制。


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

summarize()在你的命令是一个简单的帮手function,纯粹和简单。 我们可以用我们自己的求和函数来replace它,因为它不能帮助任何不简单的事情,并且.data.(price)参数可以更加明确。 结果是

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

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

在我看来,优化的function仍然很臭,因为它不清楚,必须进行精神分析,与data.table(甚至有60%的增益)相比,仍然是慢得可笑的。


在上面提到的同一个线程中,关于plyr的慢度,提到了plyr2项目。 自从问题的原始答复时间以来,plyr作者已经发布dplyr作为dplyr的inheritance者。 虽然plyr和dplyr都被称为数据操作工具,并且您的主要兴趣是聚合,但您可能仍然对新软件包的基准testing结果感兴趣,以便进行比较,因为它具有改进的后端以提高性能。

 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中删除,随后从testing中除去matrixfunction版本。

这里是i=5, j=8基准testing结果:

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

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

对于data.frame结构缓慢的一点看法,这里是使用更大的testing数据集( 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 仍然留在尘土中。 不仅如此,而且还包括用testing数据填充数据结构的过去的system.time:

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

data.frame的创build和聚合都比data.table慢。

R中使用data.frame比一些替代方法慢,但是基准testing显示内置的R函数将水射出水面。 即使像dplyr那样pipe理data.frame,也会提高内置的速度,但并不能提供最佳的速度。 其中data.table在创build和聚合方面都更快 data.table在处理data.frames时也是如此。

到底…

Plyr由于处理和pipe理data.frame操作的方式变得很慢

[平底船::看到原来的问题的意见]。


 ## 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 

数据生成要点.rmd