R:应用与 do.call

R: apply vs do.call

我刚刚阅读了@David Arenburg 的个人资料,发现了一堆关于如何开发良好的 R 编程的有用技巧 skills/habits,其中一个特别打动了我。我一直认为 R 中的应用函数是使用数据帧的基石,但他写道:

If you are working with data.frames, forget there is a function called apply- whatever you do - don't use it. Especially with a margin of 1 (the only good usecase for this function is to operate over matrix columns- margin of 2).

Some good alternatives: ?do.call, ?pmax/pmin, ?max.col, ?rowSums/rowMeans/etc, the awesome matrixStats packages (for matrices), ?rowsum and many more

有人能给我解释一下吗?为什么应用函数不受欢迎?

我认为作者的意思是你应该使用 pre-built/vectorized 函数(因为它更容易),如果可以并且避免应用(因为原则上它是一个 for 循环并且需要更长的时间):

library(microbenchmark)

d <- data.frame(a = rnorm(10, 10, 1),
                b = rnorm(10, 200, 1))

# bad - loop
microbenchmark(apply(d, 1, function(x) if (x[1] < x[2]) x[1] else x[2]))

# good - vectorized but same result
microbenchmark(pmin(d[[1]], d[[2]])) # use double brackets!

# edited:
# -------
# bad: lapply
microbenchmark(data.frame(lapply(d, round, 1)))

# good: do.call faster than lapply
microbenchmark(do.call("round", list(d, digits = 1)))

# --------------
# Unit: microseconds
#                                  expr     min    lq     mean  median      uq     max neval
# do.call("round", list(d, digits = 1)) 104.422 107.1 148.3419 134.767 184.524 332.009   100
#                            expr     min       lq     mean  median      uq      max neval
# data.frame(lapply(d, round, 1)) 235.619 243.2055 298.5042 252.353 276.004 1550.265   100
#
#                                  expr    min      lq    mean median       uq     max neval
# do.call("round", list(d, digits = 1)) 96.389 97.5055 113.075 98.175 105.5375 730.954   100
#                            expr     min       lq     mean  median      uq      max neval
# data.frame(lapply(d, round, 1)) 235.619 243.2055 298.5042 252.353 276.004 1550.265   100
  • apply(DF, 1, f)DF 的每一行转换为向量,然后将该向量传递给 f。如果 DF 是字符串和数字的混合,那么在将行传递给 f 之前,该行将被转换为字符向量,因此,例如,即使行iris[i, -5] 包含所有数字元素。该行被转换为字符串,您不能对字符串求和。另一方面 apply(iris[-5], 1, sum) 将与 rowSums(iris[-5]).

  • 相同
  • 如果f产生一个向量,结果是一个矩阵而不是另一个数据框;此外,结果是您可能期望的转置。这个

    apply(BOD, 1, identity)
    

    给出以下内容而不是返回 BOD

           [,1] [,2] [,3] [,4] [,5] [,6]
    Time    1.0  2.0    3    4  5.0  7.0
    demand  8.3 10.3   19   16 15.6 19.8
    

    很多年前,Hadley Wickham 做了 post iapply,它在 iapply(mat, 1, identity) returns mat 的意义上是幂等的,而不是 t(mat),其中 mat 是一个矩阵。最近用他的 plyr 包可以写:

    library(plyr)
    ddplyr(BOD, 1, identity)
    

    并获取 BOD 作为数据框。

另一方面,apply(BOD, 1, sum) 将给出与 rowSums(BOD) 相同的结果,并且 apply(BOD, 1, f) 可能对函数 f 有用,而 f 会产生一个标量并且没有对应物,例如 sum / rowSums 的情况。此外,如果 f 生成一个向量并且您不介意矩阵结果,您可以自己转置 apply 的输出,虽然它很丑陋但它会起作用。

这与R如何存储矩阵和数据帧有关*。你可能知道,一个data.frame是一个list的向量,也就是说,data.frame中的每一列都是一个向量。作为一种矢量化语言,最好在矢量上进行操作,这就是不赞成 margin 为 2 的 apply 的原因:这样做你不会在矢量上工作,相反,你将跨越不同的矢量在每次迭代中。

据我所知,使用带边距 1 的 apply 与使用 do.call 没有太大区别。尽管后者可能允许更多的使用灵活性。

*此信息应位于 manuals.

中的某处