if (condition) true_action
if (condition) true_action else false_action
if (condition1) {
true_action1
} else if (condition2) {
true_action2
} else {
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语句可以写为一行时,才使用赋值操作。
x1 <- if (TRUE) 1 else 2
x2 <- if (FALSE) 1 else 2
c(x1, x2)
#> [1] 1 2当if-else语句只有if声明时,如果条件不满足,则返回NULL。函数c()、paste()会自动去除返回值中的NULL值。
greet <- function(name, birthday = FALSE) {
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 > 1Vectorised if
if-else 语句只能判断一次,假如你想要判断很多次,可以使用ifelse()函数。该函数接受三个参数:条件,返回值,其他值。如果条件为TRUE,返回值作为结果,否则返回其他值。条件处可以是向量,返回的也是向量。(可以理解for循环if-else语句)
x <- c(1:10, NA, 12)
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(),使用方法如下:
dplyr::case_when(
x %% 35 == 0 ~ "fizz buzz",
x %% 5 == 0 ~ "fizz",
x %% 7 == 0 ~ "buzz",
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语句:
x_option <- function(x) {
if (x == "a") {
"option 1"
} else if (x == "b") {
"option 2"
} else if (x == "c") {
"option 3"
} else {
stop("Invalid `x` value")
}
}简化为switch()语句:
x_option <- function(x) {
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()会自动向下匹配,例如:
legs <- function(x) {
switch(x,
cow = ,
horse = ,
dog = 4,
human = ,
chicken = 2,
plant = 0,
stop("Unknown input")
)
}
legs("cow")
#> [1] 4
legs("dog")
#> [1] 4switch()的输入可以是数值、字符串,但建议只使用字符串。
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变量的值变化。
i <- 100
for (i in 1:3) {}
i
#> [1] 3Common pitfalls
在使用for循环时,有三个常见的易错陷阱:
- 进行赋值操作前,没有定义容纳结果的变量。
- 使用
1:length(x)作为索引,而不是seq_along(x)。 - 直接索引S3对象。
如果没有事先定义容器,会导致for循环十分缓慢。可以使用vector()函数,定义容器类型:
means <- c(1, 50, 20)
out <- vector("list", length(means))
for (i in 1:length(means)) {
out[[i]] <- rnorm(10, means[[i]])
}1:length(x)在x的长度为0时,会报错。因为:对升序和降序都兼容,使用seq_along()函数可以变相的解决该问题。seq_along()函数返回一个长度与x相同的等差向量。
x <- c(1, 2, 3, 1,2,3)
y <- numeric(0)
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)means <- c()
out <- vector("list", length(means))
for (i in 1:length(means)) {
out[[i]] <- rnorm(10, means[[i]])
}
#> Error in rnorm(10, means[[i]]): invalid arguments
out <- vector("list", length(means))
for (i in seq_along(means)) {
out[[i]] <- rnorm(10, means[[i]])
}直接迭代S3对象时,for循环会丢掉S3对象的属性:
xs <- as.Date(c("2020-01-01", "2010-01-01"))
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),下面的例子,不会报错,但是返回结果不对。
x <- numeric()
out <- vector("list", length(x))
for (i in 1:length(x)) {
out[i] <- x[i]^2
}
out
#> [[1]]
#> [1] NA
x <- numeric()
out <- vector("list", length(x))
for (i in seq_along(x)) {
out[i] <- x[i]^2
}
out
#> list()- R的for循环只评估一次输入,即使for循环中对评估进行了更新,也不会改变,避免了无限循环的可能。
xs <- c(1, 2, 3)
for (x in xs) {
xs <- c(xs, x * 2)
}
xs
#> [1] 1 2 3 2 4 6- R的for循环对于item的更新发生在每次迭代开始前,for循环中对item进行的更新无效。
for (i in 1:3) {
i <- i * 2
print(i)
}
#> [1] 2
#> [1] 4
#> [1] 6