【问题标题】:apply() is slow - how to make it faster or what are my alternatives?apply() 很慢 - 如何让它更快或者我的选择是什么?
【发布时间】:2012-12-08 08:54:50
【问题描述】:

我有一个相当大的数据框,大约有 1000 万行。它有列xy,我想要的是计算

hypot <- function(x) {sqrt(x[1]^2 + x[2]^2)}

对于每一行。使用 apply 会花费大量时间(大约 5 分钟,从较小的尺寸进行插值)和内存。

但这对我来说似乎太多了,所以我尝试了不同的东西:

  • 编译hypot函数将时间减少约10%
  • 使用来自plyr 的函数大大增加了运行时间。

最快的方法是什么?

【问题讨论】:

    标签: r apply r-faq


    【解决方案1】:

    with(my_data,sqrt(x^2+y^2)) 呢?

    set.seed(101)
    d <- data.frame(x=runif(1e5),y=runif(1e5))
    
    library(rbenchmark)
    

    两个不同的每行函数,一个利用矢量化:

    hypot <- function(x) sqrt(x[1]^2+x[2]^2)
    hypot2 <- function(x) sqrt(sum(x^2))
    

    也尝试编译这些:

    library(compiler)
    chypot <- cmpfun(hypot)
    chypot2 <- cmpfun(hypot2)
    
    benchmark(sqrt(d[,1]^2+d[,2]^2),
              with(d,sqrt(x^2+y^2)),
              apply(d,1,hypot),
              apply(d,1,hypot2),
              apply(d,1,chypot),
              apply(d,1,chypot2),
              replications=50)
    

    结果:

                           test replications elapsed relative user.self sys.self
    5       apply(d, 1, chypot)           50  61.147  244.588    60.480    0.172
    6      apply(d, 1, chypot2)           50  33.971  135.884    33.658    0.172
    3        apply(d, 1, hypot)           50  63.920  255.680    63.308    0.364
    4       apply(d, 1, hypot2)           50  36.657  146.628    36.218    0.260
    1 sqrt(d[, 1]^2 + d[, 2]^2)           50   0.265    1.060     0.124    0.144
    2  with(d, sqrt(x^2 + y^2))           50   0.250    1.000     0.100    0.144
    

    正如预期的那样,with() 解决方案和 Tyler Rinker 的列索引解决方案本质上是相同的; hypot2 是原始 hypot 的两倍(但仍比矢量化解决方案慢约 150 倍)。正如 OP 已经指出的那样,编译并没有太大帮助。

    【讨论】:

    • 矢量是个好东西 :)
    • @RicardoSaporta,我认为这只是噪音——时间差约为 0.007 秒 ...
    • @BenBolker。我很好奇,所以我跑了 100 次 250 次重复:with$ 在大约 45% 的时间里都快了,[ 只有大约 10%。
    • 如果m &lt;- as.matrix(d),那么sqrt((m * m) %*% c(1, 1)) 具有竞争力(可能快~1%,这意味着~没有)。
    • @chersanya:当我看到你上面的第一条评论时,我笑了,因为在使用 R 一段时间后,我无法习惯其他语言没有向量化。每次我现在需要时,我都会对自己说“真的,我必须自己写这个循环吗?”
    【解决方案2】:

    虽然 Ben Bolkers 的回答很全面,但我将解释避免在 data.frames 上使用 apply 的其他原因。

    apply 会将您的data.frame 转换为矩阵。这将创建一个副本(浪费时间和内存),并可能导致意外的类型转换。

    鉴于您有 1000 万行数据,我建议您查看 data.table 包,它可以让您在内存和时间方面高效地做事。


    例如,使用tracemem

    x <- apply(d,1, hypot2)
    tracemem[0x2f2f4410 -> 0x2f31b8b8]: as.matrix.data.frame as.matrix apply 
    

    如果您随后分配给d 中的列,情况会更糟

    d$x <- apply(d,1, hypot2)
    tracemem[0x2f2f4410 -> 0x2ee71cb8]: as.matrix.data.frame as.matrix apply 
    tracemem[0x2f2f4410 -> 0x2fa9c878]: 
    tracemem[0x2fa9c878 -> 0x2fa9c3d8]: $<-.data.frame $<- 
    tracemem[0x2fa9c3d8 -> 0x2fa9c1b8]: $<-.data.frame $<- 
    

    4 份! -- 有 1000 万行,这可能会在某个时候来咬你。

    如果我们使用with,如果我们分配给一个向量,则不涉及copying

    y <- with(d, sqrt(x^2 + y^2))
    

    但是如果我们分配给data.frame中的一列d

    d$y <- with(d, sqrt(x^2 + y^2))
    tracemem[0x2fa9c1b8 -> 0x2faa00d8]: 
    tracemem[0x2faa00d8 -> 0x2faa0f48]: $<-.data.frame $<- 
    tracemem[0x2faa0f48 -> 0x2faa0d08]: $<-.data.frame $<- 
    

    现在,如果您使用data.table:= 通过引用分配(不得复制)

     library(data.table)
     DT <- data.table(d)
    
    
    
    tracemem(DT)
    [1] "<0x2d67a9a0>"
    DT[,y := sqrt(x^2 + y^2)]
    

    没有副本!


    也许我会在这里得到纠正,但另一个需要考虑的内存问题是sqrt(x^2+y^2)) 将创建 4 个临时变量(内部)x^2y^2x^2 + y^2,然后是 sqrt(x^2 + y^2))

    以下会比较慢,但只涉及创建两个变量。

     DT[, rowid := .I] # previous option: DT[, rowid := seq_len(nrow(DT))]
     DT[, y2 := sqrt(x^2 + y^2), by = rowid]
    

    【讨论】:

      【解决方案3】:

      R 是矢量化的,因此您可以使用以下内容,当然也可以插入您自己的矩阵

      X = t(matrix(1:4, 2, 2))^2
      >      [,1] [,2]
       [1,]    1    4
       [2,]    9   16
      
      rowSums(X)^0.5
      

      漂亮而高效:)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-08-23
        • 1970-01-01
        • 2023-02-16
        • 1970-01-01
        • 2021-09-25
        • 1970-01-01
        • 2021-08-17
        • 2020-12-27
        相关资源
        最近更新 更多