`if` 比 ifelse 快吗?
Is `if` faster than ifelse?
最近在重读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 的回答,因为它提供了更全面的比较和社区健康。但是,这两个答案(和评论)都值得一读。
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 倍。但是,请注意,这并没有考虑被测试表达式的复杂性和可能的优化。
Update:请注意,这个快速基准代表了 if
与 ifelse
的一个非常简化且有些偏颇的用例(正如在注释)。虽然它是正确的,但它没有充分代表 ifelse
用例,因为本杰明的回答似乎提供了更公平的比较。
这更多是基于 Roman 的回答的扩展评论,但我需要代码实用程序来说明:
Roman 是正确的,if
比 ifelse
快,但我的印象是 if
的速度提升并不是特别有趣,因为它不是什么可以通过矢量化轻松利用。也就是说,仅当 cond
/test
参数的长度为 1 时,if
才优于 ifelse
。
考虑以下函数,它是一个公认的对 if
向量化的弱尝试,没有像 ifelse
那样评估 yes
和 no
条件的副作用.
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
方法。或者更好的是,Frank 的不太明显*但绝妙的解决方案。
- by 'less obvious' 我的意思是,我花了两秒钟才意识到他做了什么。根据下面尼古拉的评论,请注意这仅在
yes
和 no
的长度为 1 时有效,否则你会想要坚持使用 ifelse3
是的。我使用 ifelse() 为 152589 条记录开发了 90 分钟,使用 if() 改进为 25 分钟
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])
}
}
最近在重读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 的回答,因为它提供了更全面的比较和社区健康。但是,这两个答案(和评论)都值得一读。
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 倍。但是,请注意,这并没有考虑被测试表达式的复杂性和可能的优化。
Update:请注意,这个快速基准代表了 if
与 ifelse
的一个非常简化且有些偏颇的用例(正如在注释)。虽然它是正确的,但它没有充分代表 ifelse
用例,因为本杰明的回答似乎提供了更公平的比较。
这更多是基于 Roman 的回答的扩展评论,但我需要代码实用程序来说明:
Roman 是正确的,if
比 ifelse
快,但我的印象是 if
的速度提升并不是特别有趣,因为它不是什么可以通过矢量化轻松利用。也就是说,仅当 cond
/test
参数的长度为 1 时,if
才优于 ifelse
。
考虑以下函数,它是一个公认的对 if
向量化的弱尝试,没有像 ifelse
那样评估 yes
和 no
条件的副作用.
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
方法。或者更好的是,Frank 的不太明显*但绝妙的解决方案。
- by 'less obvious' 我的意思是,我花了两秒钟才意识到他做了什么。根据下面尼古拉的评论,请注意这仅在
yes
和no
的长度为 1 时有效,否则你会想要坚持使用ifelse3
是的。我使用 ifelse() 为 152589 条记录开发了 90 分钟,使用 if() 改进为 25 分钟
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])
}
}