矢量化是您需要在 R 中习惯的最基本(也是不寻常)的事情之一。许多(大多数?)R 操作都是矢量化的。但有些事情并非如此——而且if(){}else{}
是非矢量化的事物之一。它用于控制流(无论是否运行代码块),而不用于向量运算。ifelse()
是一个用于向量的单独函数,其中第一个参数是“测试”,第二个和第三个参数是“如果是”和“如果否”结果。测试是一个向量,返回的值是测试中每个项目的适当的是/否结果。结果将与测试的长度相同.
所以我们会写你的IsPretty
像这样的函数:
IsPretty <- function(PetalWidth){
return(ifelse(PetalWidth > 0.3, "Y", "N"))
}
df <- iris
df$Pretty = IsPretty(df$Petal.Width)
对比一个if(){...}else{...}
测试条件长度为 1 的块,并且可以在其中运行任意代码...
- 可能返回比测试更大的结果,或者更小的结果,或者没有结果 - 可能会修改其他对象...你可以在里面做任何事情if(){}else()
,但测试条件的长度必须为 1。
你可以用你的IsPretty
一次运行一行 - 它对于任何一行都可以正常工作。所以我们可以将它放入一个循环中,如下所示,一次检查一行,给出if()
一次进行一项测试,一次分配一个结果。但是 R 针对矢量化进行了优化,这会明显变慢,并且是一个坏习惯。
IsPrettyIf <-function(PetalWidth){
if (PetalWidth >0.3) return("Y")
return("N")
}
for(i in 1:nrow(df)) {
df$PrettyLoop[i] = IsPrettyIf(df$Petal.Width[i])
}
下面的基准测试显示矢量化版本的速度提高了 50 倍。这是一个如此简单的案例和如此小的数据,因此并不重要,但对于更大的数据,或更复杂的操作,矢量化和非矢量化代码之间的差异可能是几分钟与几天。
microbenchmark::microbenchmark(
loop = {
for(i in 1:nrow(df)) {
df$PrettyLoop[i] = IsPrettyIf(df$Petal.Width[i])
}
},
vectorized = {
df$Pretty = IsPretty(df$Petal.Width)
}
)
Unit: microseconds
expr min lq mean median uq max neval
loop 3898.9 4365.6 5880.623 5442.3 7041.10 11344.6 100
vectorized 47.7 59.6 112.288 67.4 83.85 1819.4 100
对于 R 学习者来说,这是一个常见的问题 - 您可以在 Stack Overflow 上找到人们使用的许多问题if(){}else{}
当他们需要时ifelse()
或相反亦然。为什么不能ifelse返回向量?是来自问题另一面的常见问题解答。
你的尝试发生了什么?
df <- iris
## The condition has length equal to the number of rows in the data frame
df$Petal.Width > 0.3
#> [1] FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE
#> [13] FALSE FALSE FALSE TRUE TRUE FALSE FALSE FALSE FALSE TRUE FALSE TRUE
## ... truncated
## R warns us that only the first value (which happens to be FALSE) is used
result = if(df$Petal.Width > 0.3) {"Y"} else {"N"}
#> Warning in if (df$Petal.Width > 0.3) {: the condition has length > 1 and only
#> the first element will be used
## So the result is a single "N"
result
#> [1] "N"
length(result)
#> [1] 1
## R "recycles" inputs that are of insufficient length
## so we get a full column of "N"
df$Pretty = result
head(df)
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species Pretty
#> 1 5.1 3.5 1.4 0.2 setosa N
#> 2 4.9 3.0 1.4 0.2 setosa N
#> 3 4.7 3.2 1.3 0.2 setosa N
#> 4 4.6 3.1 1.5 0.2 setosa N
#> 5 5.0 3.6 1.4 0.2 setosa N
#> 6 5.4 3.9 1.7 0.4 setosa N
Created on 2020-11-08 by the reprex package (v0.3.0)