2012-07-18 172 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