`if`比ifelse更快?

时间:2015-11-30 18:26:15

标签: r performance if-statement benchmarking

当我最近重新阅读Hadley的Advanced R时,我注意到他在第6章中说`if`可以用作函数 `if`(i == 1, print("yes"), print("no")) (如果你手头有实体书,那就在第80页)

我们知道ifelse很慢(Does ifelse really calculate both of its vectors every time? Is it slow?),因为它会评估所有参数。由于`if`似乎只评估if个参数(这只是我的假设),TRUE会是一个很好的替代方案吗?

更新:根据@Benjamin和@Roman的答案以及@Gregor和其他许多人的评论,ifelse似乎是矢量化计算的更好解决方案。我在这里接受@ Benjamin的回答,因为它提供了更全面的比较和社区健康。但是,这两个答案(以及评论)都值得一读。

3 个答案:

答案 0 :(得分:16)

这更多是基于罗马答案的扩展评论,但我需要代码实用程序来解释:

罗马是正确的ififelse更快,但我认为if的速度提升并不特别有趣,因为它不是{&1;}。通过矢量化可以很容易地利用的东西。也就是说,当if / ifelse参数的长度为1时,cond仅优于test

考虑以下函数,这是一个无可否认的向量化if的弱尝试,而不会像yes一样评估noifelse条件的副作用。< / p>

ifelse2 <- function(test, yes, no){
 result <- rep(NA, length(test))
 for (i in seq_along(test)){
   result[i] <- `if`(test[i], yes[i], no[i])
 }
 result
}

ifelse2a <- function(test, yes, no){
  sapply(seq_along(test),
         function(i) `if`(test[i], yes[i], no[i]))
}

ifelse3 <- function(test, yes, no){
  result <- rep(NA, length(test))
  logic <- test
  result[logic] <- yes[logic]
  result[!logic] <- no[!logic]
  result
}


set.seed(pi)
x <- rnorm(1000)

library(microbenchmark)
microbenchmark(
  standard = ifelse(x < 0, x^2, x),
  modified = ifelse2(x < 0, x^2, x),
  modified_apply = ifelse2a(x < 0, x^2, x),
  third = ifelse3(x < 0, x^2, x),
  fourth = c(x, x^2)[1L + ( x < 0 )],
  fourth_modified = c(x, x^2)[seq_along(x) + length(x) * (x < 0)]
)

Unit: microseconds
            expr     min      lq      mean  median       uq      max neval cld
        standard  52.198  56.011  97.54633  58.357  68.7675 1707.291   100 ab 
        modified  91.787  93.254 131.34023  94.133  98.3850 3601.967   100  b 
  modified_apply 645.146 653.797 718.20309 661.568 676.0840 3703.138   100   c
           third  20.528  22.873  76.29753  25.513  27.4190 3294.350   100 ab 
          fourth  15.249  16.129  19.10237  16.715  20.9675   43.695   100 a  
 fourth_modified  19.061  19.941  22.66834  20.528  22.4335   40.468   100 a 

一些编辑:感谢Frank和Richard Scriven注意到我的缺点。

正如您所看到的,将向量分解为适合传递给if的过程是一个耗时的过程,最终会比仅运行ifelse更慢(这可能就是为什么没有一个人不愿意实施我的解决方案。)

如果您真的非常渴望提高速度,可以使用上面的ifelse3方法。或者更好的是,弗兰克不那么明显*但是很棒的解决方案。

  • 不太明显&#39;我的意思是,我花了两秒钟才意识到他做了什么。根据以下nicola的评论,请注意,仅当yesno的长度为1时才有效,否则您将坚持使用ifelse3

答案 1 :(得分:9)

if是通过.Primitive接口调用的原始(编译)函数,而ifelse是R字节码,因此似乎if会更快。运行一些快速基准

> microbenchmark(`if`(TRUE, "a", "b"), ifelse(TRUE, "a", "b"))
Unit: nanoseconds
                   expr  min   lq    mean median     uq   max neval cld
 if (TRUE) "a" else "b"   46   54  372.59   60.0   68.0 30007   100  a 
 ifelse(TRUE, "a", "b") 1212 1327 1581.62 1442.5 1617.5 11743   100   b

> microbenchmark(`if`(FALSE, "a", "b"), ifelse(FALSE, "a", "b"))
Unit: nanoseconds
                    expr  min   lq    mean median   uq   max neval cld
 if (FALSE) "a" else "b"   47   55   91.64   61.5   73  2550   100  a 
 ifelse(FALSE, "a", "b") 1256 1346 1688.78 1460.0 1677 17260   100   b

似乎如果不考虑实际分支中的代码,if至少比ifelse快20倍。但请注意,这并未考虑正在测试的表达式的复杂性以及可能的优化。

更新:请注意,此快速基准代表了if vs ifelse的一个非常简化且有些偏见的用例(正如评论中所指出的)。虽然它是正确的,但它代表了ifelse用例,因为本杰明的答案似乎提供了更公平的比较。

答案 2 :(得分:0)

是的。我使用ifelse()花费了90分钟,并将if()改进为25min来开发152589条记录

for(i in ...){
  # "Case 1"
  # asesorMinimo<-( dummyAsesor%>%filter(FechaAsignacion==min(FechaAsignacion)) )[1,] 
  # asesorRegla<-tail(dummyAsesor%>%filter( FechaAsignacion<=dumFinClase)%>%arrange(FechaAsignacion),1)
  # #Asigna Asesor
  # dummyRow<-dummyRow%>%mutate(asesorRetencion=ifelse(dim(asesorRegla)[1]==0,asesorMinimo$OperadorNombreApellido,asesorRegla$OperadorNombreApellido))



  # "Case 2"
  asesorRegla<-tail(dummyAsesor%>%filter( FechaAsignacion<=dumFinClase)%>%arrange(FechaAsignacion),1)
  asesorMinimo<-( dummyAsesor%>%filter(FechaAsignacion==min(FechaAsignacion)) )[1,] 
  if(dim(asesorRegla)[1]==0){
    dummyRow<-dummyRow%>%mutate(asesorRetencion=asesorMinimo[1,7])
  }else{
    dummyRow<-dummyRow%>%mutate(asesorRetencion=asesorRegla[1,7])
  }

}