if (condition) true_action
if (condition) true_action else false_action
if (condition1) {
true_action1else if (condition2) {
}
true_action2else {
}
false_action }
5 Control flow
Introduction
R 中有两类主要的控制流类型工具:选择(if)和循环(for)。选择包含if
、switch()
等声明;循环包含for
、while
等声明。这里假定你已经学会了它们的基础用法,本章主要介绍一些技术细节和鲜为人知的高级用法。
Outline
- 5.2节:介绍
if
、ifelse()
、switch()
函数。 - 5.3节:介绍
for
、while
、repeat
等声明。
Choices
下面是if-else语句的基本使用格式:
if
语句也可以进行赋值操作;在实际书写代码时,建议只有在if-else语句可以写为一行时,才使用赋值操作。
<- if (TRUE) 1 else 2
x1 <- if (FALSE) 1 else 2
x2
c(x1, x2)
#> [1] 1 2
当if-else语句只有if
声明时,如果条件不满足,则返回NULL
。函数c()
、paste()
会自动去除返回值中的NULL
值。
<- function(name, birthday = FALSE) {
greet paste0(
"Hi ", name,
if (birthday) " and HAPPY BIRTHDAY"
)
}greet("Maria", FALSE)
#> [1] "Hi Maria"
greet("Jaime", TRUE)
#> [1] "Hi Jaime and HAPPY BIRTHDAY"
Invalid inputs
需要注意的是,if
声明中的条件返回值只能是长度为1的布尔值。如果长度大于1,在R 4.0版本前会选择第一个值,但在R 4.0版本后会报错。其他类型的输入也会报错。
if ("x") 1
#> Error in if ("x") 1: argument is not interpretable as logical
if (logical()) 1
#> Error in if (logical()) 1: argument is of length zero
if (NA) 1
#> Error in if (NA) 1: missing value where TRUE/FALSE needed
if (c(TRUE, FALSE)) 1
#> Error in if (c(TRUE, FALSE)) 1: the condition has length > 1
Vectorised if
if-else 语句只能判断一次,假如你想要判断很多次,可以使用ifelse()
函数。该函数接受三个参数:条件,返回值,其他值。如果条件为TRUE
,返回值作为结果,否则返回其他值。条件处可以是向量,返回的也是向量。(可以理解for循环if-else语句)
<- c(1:10, NA, 12)
x ifelse(x %% 5 == 0, "XXX", as.character(x))
#> [1] "1" "2" "3" "4" "XXX" "6" "7" "8" "9" "XXX" NA "12"
ifelse(x %% 2 == 0, "even", "odd")
#> [1] "odd" "even" "odd" "even" "odd" "even" "odd" "even" "odd" "even"
#> [11] NA "even"
建议只有在yes
和no
条件的返回值类型一致时,再使用ifelse()
函数。如果不同,因为c()
是atomic向量,会强制进行类型转换。函数要求得条件如果不是布尔值,则会进行类型转换as.logical()
,如果转换结果仍不是布尔值,则会返回转换后的值。
dplyr包提供了等价函数case_when()
,使用方法如下:
::case_when(
dplyr%% 35 == 0 ~ "fizz buzz",
x %% 5 == 0 ~ "fizz",
x %% 7 == 0 ~ "buzz",
x is.na(x) ~ "???",
TRUE ~ as.character(x)
)#> [1] "1" "2" "3" "4" "fizz" "6" "buzz" "8" "9" "fizz"
#> [11] "???" "12"
switch()
statement
switch()
语句是对if-else语句的压缩,例如你可以将下面的if-else语句:
<- function(x) {
x_option if (x == "a") {
"option 1"
else if (x == "b") {
} "option 2"
else if (x == "c") {
} "option 3"
else {
} stop("Invalid `x` value")
} }
简化为switch()
语句:
<- function(x) {
x_option switch(x,
a = "option 1",
b = "option 2",
c = "option 3",
stop("Invalid `x` value")
) }
再判断条件的末尾添加错误信息,可以提高代码的可读性,因为当不满足匹配条件时,switch()
语句返回NULL
。
switch("c",
(a = 1,
b = 2
))#> NULL
如果不同的输入条件返回值相同,可以省略返回值,switch()
会自动向下匹配,例如:
<- function(x) {
legs switch(x,
cow = ,
horse = ,
dog = 4,
human = ,
chicken = 2,
plant = 0,
stop("Unknown input")
)
}legs("cow")
#> [1] 4
legs("dog")
#> [1] 4
switch()
的输入可以是数值、字符串,但建议只使用字符串。
Exercises
…
Loops
for 循环的基本格式如下:
for (item in vector) perform_action
有两种中断循环的方法:break
和next
。break
用于跳出整个循环,next
用于跳出当前循环,继续下一个循环。
for (i in 1:10) {
if (i < 3) {
next
}
print(i)
if (i >= 5) {
break
}
}#> [1] 3
#> [1] 4
#> [1] 5
要注意在环境变量中不要有与item名重复的变量。for循环会赋值给item变量,这样会导致item变量的值变化。
<- 100
i for (i in 1:3) {}
i#> [1] 3
Common pitfalls
在使用for循环时,有三个常见的易错陷阱:
- 进行赋值操作前,没有定义容纳结果的变量。
- 使用
1:length(x)
作为索引,而不是seq_along(x)
。 - 直接索引S3对象。
如果没有事先定义容器,会导致for循环十分缓慢。可以使用vector()
函数,定义容器类型:
<- c(1, 50, 20)
means <- vector("list", length(means))
out for (i in 1:length(means)) {
<- rnorm(10, means[[i]])
out[[i]] }
1:length(x)
在x的长度为0时,会报错。因为:
对升序和降序都兼容,使用seq_along()
函数可以变相的解决该问题。seq_along()
函数返回一个长度与x相同的等差向量。
<- c(1, 2, 3, 1,2,3)
x <- numeric(0)
y
1:length(x)
#> [1] 1 2 3 4 5 6
seq_along(x)
#> [1] 1 2 3 4 5 6
1:length(y) # 在for循环中报错
#> [1] 1 0
seq_along(y)
#> integer(0)
<- c()
means <- vector("list", length(means))
out for (i in 1:length(means)) {
<- rnorm(10, means[[i]])
out[[i]]
}#> Error in rnorm(10, means[[i]]): invalid arguments
<- vector("list", length(means))
out for (i in seq_along(means)) {
<- rnorm(10, means[[i]])
out[[i]] }
直接迭代S3对象时,for循环会丢掉S3对象的属性:
<- as.Date(c("2020-01-01", "2010-01-01"))
xs for (x in xs) {
print(x)
}#> [1] 18262
#> [1] 14610
for (i in seq_along(xs)) {
print(xs[[i]])
}#> [1] "2020-01-01"
#> [1] "2010-01-01"
Exercises
- 一定要避免使用
1:length(x)
,下面的例子,不会报错,但是返回结果不对。
<- numeric()
x <- vector("list", length(x))
out for (i in 1:length(x)) {
<- x[i]^2
out[i]
}
out#> [[1]]
#> [1] NA
<- numeric()
x <- vector("list", length(x))
out for (i in seq_along(x)) {
<- x[i]^2
out[i]
}
out#> list()
- R的for循环只评估一次输入,即使for循环中对评估进行了更新,也不会改变,避免了无限循环的可能。
<- c(1, 2, 3)
xs for (x in xs) {
<- c(xs, x * 2)
xs
}
xs#> [1] 1 2 3 2 4 6
- R的for循环对于item的更新发生在每次迭代开始前,for循环中对item进行的更新无效。
for (i in 1:3) {
<- i * 2
i print(i)
}#> [1] 2
#> [1] 4
#> [1] 6