plyr 优于 dplyr 和 data.table - 怎么了?
plyr outperforms dplyr and data.table - What's wrong?
我必须对大型 table(约 2M 行)的每一行应用一个函数。我曾经为此使用 plyr
,但是 table 不断增长,当前的解决方案开始接近 unacceptable 运行时。我以为我可以切换到 data.table
或 dplyr
一切都很好,但事实并非如此。
这是一个例子:
library(data.table)
library(plyr)
library(dplyr)
dt = data.table("ID_1" = c(1:1000), # unique ID
"ID_2" = ceiling(runif(1000, 0, 100)), # other ID, duplicates possible
"group" = sample(LETTERS[1:10], 1000, replace = T),
"value" = runif(1000),
"ballast1" = "X", # keeps unchanged in derive_dt
"ballast2" = "Y", # keeps unchanged in derive_dt
"ballast3" = "Z", # keeps unchanged in derive_dt
"value_derived" = 0)
setkey(dt, ID_1)
extra_arg = c("A", "F", "G", "H")
ID_1 保证不包含重复项。现在我定义了一个函数来应用于每个 row/ID_1:
derive = function(tmprow, extra_arg){
if(tmprow$group %in% extra_arg){return(NULL)} # exlude entries occuring in extra_arg
group_index = which(LETTERS == tmprow$group)
group_index = ((group_index + sample(1:26, 1)) %% 25) + 1
new_group = LETTERS[group_index]
if(new_group %in% unique(dt$group)){return(NULL)}
new_value = runif(1)
row_derived = tmprow
row_derived$group = new_group
row_derived$value = runif(1)
row_derived$value_derived = 1
return(row_derived)
}
这个没有做任何有用的事情(实际的有)。关键是该函数取一行并计算相同格式的新行。
现在比较:
set.seed(42)
system.time(result_dt <- dt[, derive(.SD, extra_arg), by = ID_1])
set.seed(42)
system.time(result_dplyr <- dt %>% group_by(ID_1) %>% do(derive(., extra_arg)))
set.seed(42)
system.time(results_plyr <- x <- ddply(dt, .variable = "ID_1", .fun = derive, extra_arg))
plyr
比 data.table
和 dplyr
快大约 8 倍。显然我在这里做错了什么,但是什么?
编辑
感谢 eddi 的回答,我可以将 data.table
和 dplyr
的运行时间分别减少到 plyr
版本的 ~ 0.6 和 0.8。我将 row_derived
初始化为 data.frame: row_derived = as.data.frame(tmprow)
。这很酷,但我仍然希望这些软件包能提高性能……还有什么进一步的建议吗?
问题是您使用的赋值在 data.table
中有非常高的开销,并且 plyr
在传递给您的 derive
函数之前将该行转换为 data.frame
,从而避免它:
library(microbenchmark)
df = as.data.frame(dt)
microbenchmark({dt$group = dt$group}, {df$group = df$group})
#Unit: microseconds
# expr min lq mean median uq max neval
# { dt$group = dt$group } 1895.865 2667.499 3092.38903 3080.3620 3389.049 4984.406 100
# { df$group = df$group } 26.045 45.244 64.13909 61.6045 79.635 157.266 100
我无法提出好的解决方案,因为你说你的例子不是真正的问题,所以没有必要更好地解决它。一些基本的建议是 - 向量化代码,并使用 :=
或 set
代替(取决于你最终做什么)。
我必须对大型 table(约 2M 行)的每一行应用一个函数。我曾经为此使用 plyr
,但是 table 不断增长,当前的解决方案开始接近 unacceptable 运行时。我以为我可以切换到 data.table
或 dplyr
一切都很好,但事实并非如此。
这是一个例子:
library(data.table)
library(plyr)
library(dplyr)
dt = data.table("ID_1" = c(1:1000), # unique ID
"ID_2" = ceiling(runif(1000, 0, 100)), # other ID, duplicates possible
"group" = sample(LETTERS[1:10], 1000, replace = T),
"value" = runif(1000),
"ballast1" = "X", # keeps unchanged in derive_dt
"ballast2" = "Y", # keeps unchanged in derive_dt
"ballast3" = "Z", # keeps unchanged in derive_dt
"value_derived" = 0)
setkey(dt, ID_1)
extra_arg = c("A", "F", "G", "H")
ID_1 保证不包含重复项。现在我定义了一个函数来应用于每个 row/ID_1:
derive = function(tmprow, extra_arg){
if(tmprow$group %in% extra_arg){return(NULL)} # exlude entries occuring in extra_arg
group_index = which(LETTERS == tmprow$group)
group_index = ((group_index + sample(1:26, 1)) %% 25) + 1
new_group = LETTERS[group_index]
if(new_group %in% unique(dt$group)){return(NULL)}
new_value = runif(1)
row_derived = tmprow
row_derived$group = new_group
row_derived$value = runif(1)
row_derived$value_derived = 1
return(row_derived)
}
这个没有做任何有用的事情(实际的有)。关键是该函数取一行并计算相同格式的新行。
现在比较:
set.seed(42)
system.time(result_dt <- dt[, derive(.SD, extra_arg), by = ID_1])
set.seed(42)
system.time(result_dplyr <- dt %>% group_by(ID_1) %>% do(derive(., extra_arg)))
set.seed(42)
system.time(results_plyr <- x <- ddply(dt, .variable = "ID_1", .fun = derive, extra_arg))
plyr
比 data.table
和 dplyr
快大约 8 倍。显然我在这里做错了什么,但是什么?
编辑
感谢 eddi 的回答,我可以将 data.table
和 dplyr
的运行时间分别减少到 plyr
版本的 ~ 0.6 和 0.8。我将 row_derived
初始化为 data.frame: row_derived = as.data.frame(tmprow)
。这很酷,但我仍然希望这些软件包能提高性能……还有什么进一步的建议吗?
问题是您使用的赋值在 data.table
中有非常高的开销,并且 plyr
在传递给您的 derive
函数之前将该行转换为 data.frame
,从而避免它:
library(microbenchmark)
df = as.data.frame(dt)
microbenchmark({dt$group = dt$group}, {df$group = df$group})
#Unit: microseconds
# expr min lq mean median uq max neval
# { dt$group = dt$group } 1895.865 2667.499 3092.38903 3080.3620 3389.049 4984.406 100
# { df$group = df$group } 26.045 45.244 64.13909 61.6045 79.635 157.266 100
我无法提出好的解决方案,因为你说你的例子不是真正的问题,所以没有必要更好地解决它。一些基本的建议是 - 向量化代码,并使用 :=
或 set
代替(取决于你最终做什么)。