plyr 优于 dplyr 和 data.table - 怎么了?

plyr outperforms dplyr and data.table - What's wrong?

我必须对大型 table(约 2M 行)的每一行应用一个函数。我曾经为此使用 plyr,但是 table 不断增长,当前的解决方案开始接近 unacceptable 运行时。我以为我可以切换到 data.tabledplyr 一切都很好,但事实并非如此。

这是一个例子:

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

plyrdata.tabledplyr 快大约 8 倍。显然我在这里做错了什么,但是什么?


编辑

感谢 eddi 的回答,我可以将 data.tabledplyr 的运行时间分别减少到 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 代替(取决于你最终做什么)。