【问题标题】:does the by( ) function make growing listby() 函数是否使列表不断增长
【发布时间】:2012-12-04 14:48:05
【问题描述】:

by 函数是否会创建一个每次增长一个元素的列表?

我需要处理一个数据框,其中包含按因子列分组的大约 4M 观察值。情况类似于下面的例子:

> # Make 4M rows of data
> x = data.frame(col1=1:4000000, col2=10000001:14000000)
> # Make a factor
> x[,"f"] = x[,"col1"] - x[,"col1"] %% 5
>   
> head(x)
  col1     col2 f
1    1 10000001 0
2    2 10000002 0
3    3 10000003 0
4    4 10000004 0
5    5 10000005 5
6    6 10000006 5

现在,其中一列上的tapply 需要相当长的时间:

> t1 = Sys.time()
> z = tapply(x[, 1], x[, "f"], mean)
> Sys.time() - t1
Time difference of 22.14491 secs

但如果我这样做:

z = by(x[, 1], x[, "f"], mean)

这不会在同一时间完成(我在一分钟后放弃了)。

当然,在上面的例子中,可以使用tapply,但我实际上需要将多个列一起处理。有什么更好的方法来做到这一点?

【问题讨论】:

  • 您能否提供一小部分(例如 10 行 N 列)数据样本以及您希望执行的算法?很可能会使用其他一些 *apply 工具(有些允许多个参数),或者嵌套的 tapply(tapply(...)) 构造。

标签: r benchmarking tapply


【解决方案1】:

bytapply 慢,因为它正在包装 by。 让我们看一些基准测试:在这种情况下,tapply 比使用 by 快 3 倍以上

更新以包含@Roland 的精彩推荐:

library(rbenchmark)
library(data.table)
dt <- data.table(x,key="f")

using.tapply <- quote(tapply(x[, 1], x[, "f"], mean))
using.by <- quote(by(x[, 1], x[, "f"], mean))
using.dtable <- quote(dt[,mean(col1),by=key(dt)])

times <- benchmark(using.tapply, using.dtable, using.by, replications=10, order="relative")
times[,c("test", "elapsed", "relative")] 

#------------------------#
#         RESULTS        # 
#------------------------#

#       COMPARING tapply VS by     #
#-----------------------------------
#              test elapsed relative
#   1  using.tapply   2.453    1.000
#   2      using.by   8.889    3.624

#   COMPARING data.table VS tapply VS by   #
#------------------------------------------#
#             test elapsed relative
#   2  using.dtable   0.168    1.000
#   1  using.tapply   2.396   14.262
#   3      using.by   8.566   50.988

如果x$f是一个因素,tapply和by之间的效率损失就更大了!

不过,请注意,它们都相对于非因子输入有所改善,而 data.table 保持大致相同或更差

x[, "f"] <- as.factor(x[, "f"])
dt <- data.table(x,key="f")
times <- benchmark(using.tapply, using.dtable, using.by, replications=10, order="relative")
times[,c("test", "elapsed", "relative")] 

#               test elapsed relative
#   2   using.dtable   0.175    1.000
#   1   using.tapply   1.803   10.303
#   3       using.by   7.854   44.880



至于为什么,简短的答案在文档本身中。

?by

说明

Function by 是一个面向对象的包装器,用于将 tapply 应用于数据帧。

让我们看看by(或者更具体地说,by.data.frame)的来源:

by.data.frame
function (data, INDICES, FUN, ..., simplify = TRUE) 
{
    if (!is.list(INDICES)) {
        IND <- vector("list", 1L)
        IND[[1L]] <- INDICES
        names(IND) <- deparse(substitute(INDICES))[1L]
    }
    else IND <- INDICES
    FUNx <- function(x) FUN(data[x, , drop = FALSE], ...)
    nd <- nrow(data)
    ans <- eval(substitute(tapply(seq_len(nd), IND, FUNx, simplify = simplify)), 
        data)
    attr(ans, "call") <- match.call()
    class(ans) <- "by"
    ans
}

我们立即看到仍然有对tapply 的调用以及许多额外的调用(包括对deparse(substitute(.))eval(substitute(.)) 的调用,两者都相对较慢)。因此,您的tapply 将比对by 的类似调用相对更快是有道理的。

【讨论】:

  • 这些函数相对较慢,但我认为它们不是造成tapplyby 之间差异的原因——它们可能会增加毫秒,但不会增加秒。我怀疑速度慢的主要原因是 by 索引到数据帧中,这比索引到向量中要慢得多。
【解决方案2】:

关于执行此操作的更好方法:对于 4M 行,您应该使用 data.table

library(data.table)
dt <- data.table(x,key="f")
dt[,mean(col1),by=key(dt)]

dt[,list(mean1=mean(col1),mean2=mean(col2)),by=key(dt)]
dt[,lapply(.SD,mean),by=key(dt)]

【讨论】:

  • 太好了,非常感谢,还要感谢 @ricardo-saporta 提供的基准测试结果。
猜你喜欢
  • 2012-09-16
  • 1970-01-01
  • 2021-09-14
  • 2016-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-02
相关资源
最近更新 更多