在R语言中有两种程序控制结构:分支结构(choices)和循环结构(loops)。
-
分支结构,比如
if()
和switch()
,可以根据输入(判断条件)的不同选择执行不同的代码块; -
循环结构,比如
for
,while
和repeat
,则可以重复执行一段代码块,但往往每次重复都有相应参数的改变。
想跳过这一期推文?如果你已经掌握以下知识点👇🏻,Go ahead
if
和ifelse()
的区别是什么?当
x
分别为True
,False
,NA
时,下面代码中y
的值为多少?
y <- if (x) 3
switch("x", x = , y = 2, z = 3)
返回值是什么?
分支结构
if
语句的基础形式如下所示:
if (condition) true_action
if (condition) true_action else false_action
如果判断语句condition
的值为TRUE
,执行true_action
代码块,相反如果判断语句condition
值为FALSE
则执行false_action
代码块。当执行的代码块包含较多语句时,往往把代码块写入{}
中:
grade <- function(x) {
if (x > 90) {
"A"
} else if (x > 80) {
"B"
} else if (x > 50) {
"C"
} else {
"F"
}
}
由于执行for
语句可以返回相应的计算值,所以可以用于赋值语句:
x1 <- if (TRUE) 1 else 2
x2 <- if (FALSE) 1 else 2
c(x1, x2)
#> [1] 1 2
当使用没有else
的单参数for
语句,且判断值为FALSE
时,此时if
语句对于程序不产生任何影响。但实际上此时for
语句会返回一个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 input)
判断语句condition
在被执行后应该是(或者本来就是)单个逻辑值:TRUE
或FALSE
。而如果是其他的值则可能会引发错误。
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
对于判断值是数字的情况,如果数字非零,会被解读为TRUE
值;相反如果判断值为零则会被解读为FALSE
;另外如果判断值是数字类型的NaN
,则会抛出错误:
x <- 1:10
if (length(x)) "not empty" else "empty"
#> [1] "not empty"
if (0.5) "not empty" else "empty"
#> [1] "not empty"
x <- numeric()
if (length(x)) "not empty" else "empty"
#> [1] "empty"
if (NaN) "not empty" else "empty"
#> Error in if (NaN) "not empty" else "empty": argument is not interpretable as logical
如果判断语句是长度大于1的逻辑向量,程序会抛出警示:
if (c(TRUE, FALSE)) 1
#> Warning in if (c(TRUE, FALSE)) 1: the condition has length > 1 and only the
#> first element will be used
#> [1] 1
在R 3.5.0之后的版本,可以通过设置全局变量使程序遇到上面这种情况时抛出错误。有时候这是非常有用的,因为这样可以显式地指出程序的错误之处。
Sys.setenv("_R_CHECK_LENGTH_1_CONDITION_" = "true")
if (c(TRUE, FALSE)) 1
#> Error in if (c(TRUE, FALSE)) 1: the condition has length > 1
向量化的if
语句
由于if
语句不支持逻辑值向量(长度大于1),所以处理逻辑值向量的情况应该使用向量化的if
语句ifelse()
:
x <- 1:10
ifelse(x %% 5 == 0, "XXX", as.character(x))
#> [1] "1" "2" "3" "4" "XXX" "6" "7" "8" "9" "XXX"
ifelse(x %% 2 == 0, "even", "odd")
#> [1] "odd" "even" "odd" "even" "odd" "even" "odd" "even" "odd" "even"
另外有一个向量化的且支持更多判断条件的判断函数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"
switch语句
switch()
也是条件判断函数,支持多个判断条件,可以非常简单地替换复杂的if
语句:
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")
}
}
##功能替换上面函数
x_option <- function(x) {
switch(x,
a = "option 1",
b = "option 2",
c = "option 3",
stop("Invalid `x` value")
)
}
上面的switch()
语句中,如果没有匹配到正确的值,就会执行最后的stop()
函数,抛出错误,如果没有stop()
函数,却没有匹配到对应的值,则会隐式地返回NULL
值:
(switch("c", a = 1, b = 2)) #使用()可以显示隐式输出的值
#> NULL
如果多个判断条件对应相同的输出或执行代码块,前几个判断条件对应的值或者代码块可以空缺,只需填写最后一个具有相同输出值的条件。因为如果某个判断条件对应的执行语句空缺,默认和下一个判断条件相同,这个规则是模仿C语言的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] 4
循环结构
for
语句能够遍历向量中的每个元素,具有以下的基本形式:
for (item in vector) perform_action
下面的代码中,for
循环中的i
变量属于全局变量,所以会覆盖已经定义的i
值。
i <- 100
for (i in 1:3) {}
i
#> [1] 3
有两种方法终结for
循环:
-
next
可以退出当前这轮循环; -
break
可以退出整个for
循环。
for (i in 1:10) {
if (i < 3)
next
print(i)
if (i >= 5)
break
}
#> [1] 3
#> [1] 4
#> [1] 5
明显的缺陷
使用for
循环有3个明显的缺陷。首先,当需要生成数据,必须提前定义好容纳输出数据的容器,否则代码执行的速度会非常慢。
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
的长度为零,则代码运行过程中可能会出现错误:
means <- c()
out <- vector("list", length(means))
for (i in 1:length(means)) { #i取两个值1和0
out[[i]] <- rnorm(10, means[[i]]) #因为means[[1]]索引离界,故报错
}
#> Error in rnorm(10, means[[i]]): invalid arguments
会出现上面的情况的原因是:
同时支持产生递增和递减的系列值:
1:length(means)
#> [1] 1 0
如果使用seq_along(x)
,则会返回和x
长度一样的序列值(向量),且返回的序列值就是x
中元素的索引值:
seq_along(means)
#> integer(0)
out <- vector("list", length(means))
for (i in seq_along(means)) {
out[[i]] <- rnorm(10, means[[i]])
}
最后,如果使用for
遍历S3类型向量(具有class属性的向量),循环会去除向量元素的属性:
xs <- as.Date(c("2020-01-01", "2010-01-01"))
for (x in xs) {
print(x)
}
#> [1] 18262 #去除了按格式显示时间的属性,显示的是从1970-01-01至今的天数
#> [1] 14610
for (i in seq_along(xs)) {
print(xs[[i]]) #[[]]索引出的时间元素变成了单个字符串,所以可以按时间格式显示
}
#> [1] "2020-01-01"
#> [1] "2010-01-01"
while
和repeat
如果提前知道需要遍历的数据对象,那么使用for
非常的方便。但是如果提前不知道,则可以使用另外两个相关的函数while
和repaet
。
-
while(condition) action
:如果condition
值为TRUE
,则执行action
; -
repeat(action)
:一直循环执行action
,除非碰到break
语句。
对于for
循环编写的语句,可以改写用while
实现,而对于while
编写的语句,则可以改写用repeat
实现,而反过来就不一定行。这就意味着while
比for
灵活,repeat
比while
灵活。但对于for
循环能解决的问题可以尽量用for
循环。对于很多分析任务,往往可以不使用for
循环,因为map
和apply
等函数可以实现类似的功能,后面我们会详细讲解这些函数。