【问题标题】:R: apply vs do.callR:应用与 do.call
【发布时间】:2018-11-15 22:35:12
【问题描述】:

我刚刚阅读了@David Arenburg 的个人资料,发现了一堆关于如何培养良好的 R 编程技能/习惯的有用技巧,其中一个让我印象深刻。我一直认为 R 中的应用函数是处理数据帧的基石,但他写道:

如果您正在使用 data.frames,请忘记有一个名为的函数 应用 - 无论你做什么 - 不要使用它。尤其是边距为 1 (这个函数唯一好的用例是对矩阵进行操作 列 - 边距为 2)。

一些不错的选择:?do.call、?pmax/pmin、?max.col、 ?rowSums/rowMeans/etc,很棒的 matrixStats 包(对于 矩阵)、?rowsum 等等

谁能给我解释一下?为什么 apply 函数不受欢迎?

【问题讨论】:

  • 我实际上是在专门谈论 apply- 而不是整个 *apply 家族。 apply 的主要问题是它将整个数据转换为一个矩阵,这会弄乱数据(因为 matrix 不能像数据框那样存储不同的类),因此会产生意想不到的结果。因此,在对列进行操作时,最好使用*apply 系列的其余部分,例如lapplysapply。另一方面,因为 R 是向量化语言 apply,边距为 1 将非常慢(不管 matrix 问题),因此我提出使用向量化替代方案。
  • 啊哈,我明白了,非常感谢您解决这个问题!
  • 另外,this is 是关于 *apply 家族的有用读物。
  • 太棒了!再次感谢:)

标签: r apply do.call


【解决方案1】:
  • apply(DF, 1, f)DF 的每一行转换为一个向量,然后将该向量传递给 f。如果DF 是字符串和数字的混合,则该行将在传递给f 之前转换为字符向量,这样,例如apply(iris, 1, function(x) sum(x[-5])) 将不起作用,即使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 做了postiapply,这是幂等的,因为iapply(mat, 1, identity) 返回mat,而不是t(mat),其中mat 是一个矩阵。最近用他的 plyr 包可以写:

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

    并将BOD 作为数据框返回。

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

【讨论】:

    【解决方案2】:

    我认为作者的意思是,您应该使用预构建/矢量化函数(因为它更容易),如果可以并且避免应用(因为原则上它是一个 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
    

    【讨论】:

    • 所以所有应用函数本质上都是循环? lapply、sapply 等?
    • 如何回答do.call 部分?
    • 已编辑。 (.. 以及关于 for 循环;根据 burns-stat.com/pages/Tutor/R_inferno.pdf 使用 apply 是循环隐藏)
    • 您可以将microbenchmark 的输出添加到您的答案中吗?
    • @Erosennin - 是的,应用家庭是循环。考虑阅读此question by @DavidArunberg
    【解决方案3】:

    这与 R 如何存储矩阵和数据帧有关*。你可能知道,一个data.frame是一个list的向量,即data.frame中的每一列都是一个向量。作为一种矢量化语言,最好对矢量进行操作,这就是不赞成使用边距为 2 的 apply 的原因:这样做您将不会处理矢量,而是会跨越每个矢量上的不同矢量迭代。

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

    *此信息应位于manuals 中的某处。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-12
      相关资源
      最近更新 更多