apply()很慢 - 如何让它更快或者我的选择是什么?

apl*_*vin 15 r apply r-faq

我有一个非常大的数据框,大约有1000万行.它有列xy,我想要的是计算

hypot <- function(x) {sqrt(x[1]^2 + x[2]^2)}
Run Code Online (Sandbox Code Playgroud)

对于每一行.使用apply它需要花费大量时间(大约5分钟,从较小尺寸插值)和内存.

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

  • 编译hypot功能可将时间缩短约10%
  • 使用函数plyr大大增加了运行时间.

做这件事的最快方法是什么?

Ben*_*ker 22

怎么样with(my_data,sqrt(x^2+y^2))

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

library(rbenchmark)
Run Code Online (Sandbox Code Playgroud)

两种不同的每行功能,一种利用矢量化:

hypot <- function(x) sqrt(x[1]^2+x[2]^2)
hypot2 <- function(x) sqrt(sum(x^2))
Run Code Online (Sandbox Code Playgroud)

尝试编译这些:

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)
Run Code Online (Sandbox Code Playgroud)

结果:

                       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
Run Code Online (Sandbox Code Playgroud)

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

  • @RicardoSaporta,我认为这只是噪音 - 时间差约为0.007秒...... (3认同)
  • 如果`m < - as.matrix(d)`,那么`sqrt((m*m)%*%c(1,1))`是竞争性的(可能快〜1%,这意味着什么都没有). (3认同)
  • @BenBolker.我很好奇所以我跑了100x 250reps:`with`和`$`每个都快45%的时间,```只有10%左右. (2认同)

mne*_*nel 11

虽然Ben Bolkers的回答是全面的,但我将解释apply在数据框架上避免的其他原因.

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

鉴于您有1000万行数据,我建议您查看一下data.table可以让您在内存和时间方面高效执行的程序包.


例如,使用 tracemem

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

如果您随后分配给列,则情况会更糟 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 $<- 
Run Code Online (Sandbox Code Playgroud)

4份! - 拥有1000万行,可能会在某些时候咬你.

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

y <- with(d, sqrt(x^2 + y^2))
Run Code Online (Sandbox Code Playgroud)

但是如果我们分配给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 $<- 
Run Code Online (Sandbox Code Playgroud)

现在,如果你使用data.table:=通过引用分配(没有复制)

 library(data.table)
 DT <- data.table(d)



tracemem(DT)
[1] "<0x2d67a9a0>"
DT[,y := sqrt(x^2 + y^2)]
Run Code Online (Sandbox Code Playgroud)

没有副本!


也许我会在这里纠正,但另一个内存问题要考虑的是sqrt(x^2+y^2))将创建4个临时变量(内部) x^2,y^2,x^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]
Run Code Online (Sandbox Code Playgroud)