12 Basic: control flow
到目前为止,本课程中写过的所有代码都是线性执行的,即按从头到尾的顺序执行所有语句。只使用这种单向控制流,可以编写一些非常简单的程序,这种写法可以视作是不对代码的执行顺序做任何人为的控制。
简单来讲,控制流(control flow)就是控制代码的流程,通过一定方式控制代码执行的顺序。Control flow 能够极大地增加代码的功能性,是现代编程语言的经典成分,绝大多数编程语言中都有控制流的模块。
Control flow 可以分为 conditional flow (条件流)和 loop (循环)两大类。
(本节内所有的流程图都来自GeeksforGeeks)
12.1 Conditional flow
12.1.1 if else
基本语法结构
if (cond) {
cons.expr
} else {
alt.expr
}
如果cond
的结果是TRUE
,执行cons.expr
,反之则执行alt.expr
。cond
通常都是 relational operator 的结果,或者是is.xxx()
类 function 的结果。cond
的执行结果要求是长度为 1 的 logical vector,如果长度超过 1,执行时会得到一个 error,详见12.1.1.3。
age <- 12
# age <- c(11, 12)
if (age < 12) {
"elementary school"
} else {
"middle school"
}
#> [1] "middle school"
在 Rstudio 中可以通过 Snippets 的功能快速输入if else
结构:
if else
有以下 4 种情况:
12.1.1.1 Single cond
without else
如果不需要else
部分,可以简化为:
if (cond) {
expr
}
当expr
和cond
都很简短时,可以进一步简化为:
例如:
input <- 1
if (input == 1) "yes"
#> [1] "yes"
if (input == 2) "yes" else "no"
#> [1] "no"
12.1.1.2 Multiple cond
s with else if
if (cond1) {
cond1.expr
} else if (cond2) {
cond2.expr
} else if (cond3) {
cond3.expr
}
例如:
input <- 1
if (input == 1) {
"a"
} else if (input == 2) {
"b"
} else if (input == 3) {
"c"
}
#> [1] "a"
但要注意,上面这段代码,如果input
不是1,2,3
这 3 个互斥条件中的任意一个,执行时不会有任何的提示,这种情况属于没有考虑到所有的可能,存在漏洞,容易出问题,建议改成以下写法:
input <- 4
if (input == 1) {
"a"
} else if (input == 2) {
"b"
} else if (input == 3) {
"c"
} else {
stop(paste("Exception encountered,", input, "does not satisfy any condition!"))
}
#> Error: Exception encountered, 4 does not satisfy any condition!
同样,也可以通过 Snippets 快速输入if else if
结构:
12.1.1.3 Complex cond
if
结构中的cond
可以是比较复杂的关系 + 逻辑运算的结果,具体又分为两种情况:
- 结果是长度为 1 的 logical vector
a <- 0
if (a > -3 & a < -1 || a > 1 & a < 3) {
"yes"
}
- 结果是长度大于 1 的 logical vector
当cond
的结果是长度大于 1 的 logical vector,在 4.2.0 以下版本的 R 中会得到一个 warning,例如
rnd <- runif(3)
rnd
if (rnd > 0.95) {
"you are awfully lucky"
}
运行结果为
[1] 0.5021010 0.8256499 0.1971661
Warning message:
In if (rnd > 0.95) { :
the condition has length > 1 and only the first element will be used
相同的代码在 4.2.0 及以上版本的 R 中,会得到一个 error,例如
因此,如果是需要依据expr
中所有元素的对比结果来作出最终判断,那么正确的做法是运用all(x)
和any(x)
,其中x
为一个 logical vector。如果x
中所有的值都是TRUE
,那么all(x)
的结果为TRUE
,反之则是FALSE
。如果x
中所有的值都是FALSE
,any(x)
的结果才是FALSE
,反之都是TRUE
。
12.1.1.4 Vecterized if else
: ifelse()
当需要针对一个 vector、matrix、array 或 data.frame 中的所有元素按照是否符合某种条件为依据统一处理时,建议使用ifelse()
,是if else
的向量化版本,其基本语法结构为:
ifelse(test, yes, no)
其中,test
表示对某个输入 object 的所有元素的检验,检验的结果是和该 object 同样大小的一个 logical object ;yes
是 object 的元素中检测为TRUE
时对应的返回值;no
是检测结果为FALSE
时的返回值。yes
和no
可以是一行语句,其执行结果必须和输入的 object 同样大小;如果不是,则会触发 recycling rule,(详见7.2.2),直至满足该限制。
例如:
m1 <- matrix(sample(1:100, 16), nrow = 4, ncol = 4)
m1
#> [,1] [,2] [,3] [,4]
#> [1,] 5 51 29 89
#> [2,] 71 58 98 67
#> [3,] 70 18 84 13
#> [4,] 86 20 69 92
print("检查每个元素是奇数还是偶数")
#> [1] "检查每个元素是奇数还是偶数"
ifelse(m1 %% 2 == 0, "even", "odd")
#> [,1] [,2] [,3] [,4]
#> [1,] "odd" "odd" "odd" "odd"
#> [2,] "odd" "even" "even" "odd"
#> [3,] "even" "even" "even" "odd"
#> [4,] "even" "even" "odd" "even"
# 等价于
# ifelse(
# m1 %% 2 == 0,
# matrix("even", nrow = 4, ncol = 4),
# matrix("odd", nrow = 4, ncol = 4)
# )
print("将所有奇数变成偶数,偶数维持不变")
#> [1] "将所有奇数变成偶数,偶数维持不变"
ifelse(m1 %% 2 == 0, m1, m1 + 1)
#> [,1] [,2] [,3] [,4]
#> [1,] 6 52 30 90
#> [2,] 72 58 98 68
#> [3,] 70 18 84 14
#> [4,] 86 20 70 92
12.1.2 switch
(optional)
switch
结构可以视作是多个cond
的if
结构的等价写法,其基本语法结构如下:
switch (object,
case1 = expr1,
case2 = expr2
)
其中,object
的不同值,对应不同的case
,例如:
input <- 1
switch(input,
"1" = print("a"),
"2" = print("b"),
"3" = print("c"),
stop("No pre-set output for current `input`")
)
#> [1] "a"
switch
结构也可以通过 Snippets 快速键入:
12.2 Loop
Loop 结构包括三种,for
,while
和repeat
。
12.2.1 for
基本语法结构:
for(var in seq) {
expr
}
例如:
其中seq
为1:3
,表示一个长度为 3 的 vector,所有 elements 会根据位置上的先后顺序被 loop,第 1 次 loop 就把seq
的第 1 个 element 取出来赋值给var
,即i
,然后执行expr
。依次 loop 下去,直到seq
的所有 elements 都被 loop 完毕。
for
结构也可以通过 Snippets 快速键入:
for
结构有以下使用技巧\注意事项:
12.2.1.1 seq
can be any object
# character
df <- data.frame(
score_last = c(100, 88, 93),
score_current = c(99, 96, 77)
)
for (i in names(df)) {
print(i)
print(mean(df[, i]))
}
#> [1] "score_last"
#> [1] 93.66667
#> [1] "score_current"
#> [1] 90.66667
# numeric
df <- data.frame(
score_last = c(100, 88, 93),
score_current = c(99, 96, 77)
)
for (i in 1:length(df)) {
print(i)
print(mean(df[, i]))
}
#> [1] 1
#> [1] 93.66667
#> [1] 2
#> [1] 90.66667
# numeric
df <- data.frame(
score_last = c(100, 88, 93),
score_current = c(99, 96, 77)
)
for (i in df) {
print(i)
print(mean(i))
}
#> [1] 100 88 93
#> [1] 93.66667
#> [1] 99 96 77
#> [1] 90.66667
12.2.1.2 seq
is a fixed anonymous object within for
seq
的本质是一个短暂存在的anonymous object
,随着for
结构的开始而出现,随着for
结构的结束而消亡。这也就意味着,如果使用一个已经存在的 object 作为seq
,一旦for
结构开始执行,所使用的seq
就固定了,在for
结构的内部改动该 object 不会改变seq
,因为二者是两个完全不同的 objects。
vec_num <- 1:5
for (i in vec_num) {
vec_num <- 100
cat("i =", i, "vec_num =", vec_num, "\n")
}
#> i = 1 vec_num = 100
#> i = 2 vec_num = 100
#> i = 3 vec_num = 100
#> i = 4 vec_num = 100
#> i = 5 vec_num = 100
12.2.1.3 Use seq_along()
使用seq_along()
而非:
生成for
结构中的seq
。
seq_along(x)
产生一个和x
等长的自然数序列,起点为 1,终点为x
的长度。
for
结构的常见用途是重复某个操作一定次数,并且将这些重复操作所得结果储存,例如
means <- c(80, 90, 100)
out_colon <- vector("list", length(means))
for (i in 1:length(means)) {
out_colon[[i]] <- rnorm(10, means[[i]])
}
out_colon
#> [[1]]
#> [1] 79.76980 77.61132 79.90355 79.77655 80.94621 79.02797
#> [7] 80.35437 79.52474 80.77968 80.44533
#>
#> [[2]]
#> [1] 90.72544 91.42940 91.37350 89.07315 89.44835 88.24212
#> [7] 90.43240 88.94611 89.92620 90.58424
#>
#> [[3]]
#> [1] 98.83501 101.51207 99.76628 101.71629 100.53997
#> [6] 100.81183 98.51398 100.10704 101.15548 99.79305
当length(x)
中的x
长度大于 0 时,使用1:length(x)
和使用seq_along()
效果一样,
means <- c(80, 90, 100)
out_seqalong <- vector("list", length(means))
for (i in seq_along(means)) {
out_seqalong[[i]] <- rnorm(10, means[[i]])
}
out_seqalong
#> [[1]]
#> [1] 81.05216 81.41232 79.41046 80.22320 78.22984 79.72294
#> [7] 82.85554 80.54777 79.61770 81.08065
#>
#> [[2]]
#> [1] 92.41319 90.33130 89.74081 90.68757 89.33327 90.71700
#> [7] 90.29722 90.47815 90.86888 90.71255
#>
#> [[3]]
#> [1] 98.06717 100.32467 97.89801 98.65786 100.50766
#> [6] 99.52305 99.67196 101.20876 100.53723 100.99713
但当length(x)
中的x
长度为 0 时,使用1:length(x)
会有问题,
means <- c()
out_seqalong <- vector("list", length(means))
for (i in 1:length(means)) {
out_colon[[i]] <- rnorm(10, means[[i]])
}
#> Error in rnorm(10, means[[i]]): invalid arguments
out_colon
#> [[1]]
#> [1] 79.76980 77.61132 79.90355 79.77655 80.94621 79.02797
#> [7] 80.35437 79.52474 80.77968 80.44533
#>
#> [[2]]
#> [1] 90.72544 91.42940 91.37350 89.07315 89.44835 88.24212
#> [7] 90.43240 88.94611 89.92620 90.58424
#>
#> [[3]]
#> [1] 98.83501 101.51207 99.76628 101.71629 100.53997
#> [6] 100.81183 98.51398 100.10704 101.15548 99.79305
原因是在于:
既可以生成升序自然数序列,又可以生成降序自然数序列,
但显然,当想要循环的对象长度为 0 时,应该是不循环才更符合逻辑,所以使用seq_along()
会更符合预期,
12.2.1.4 var
is a named object
和seq
不同,var
不是一个 anonymous object,它随着for
结构的开始而出现,但不会随着for
结构的结束而消亡,所以有两个特点:
-
var
在当次 loop 内可以更改,但是不会影响下次 loop。
for (i in 1:5) {
cat("the var used in the current loop is", i, "\n")
i <- i + 5
cat("the var now has been changed to", i, "\n")
}
#> the var used in the current loop is 1
#> the var now has been changed to 6
#> the var used in the current loop is 2
#> the var now has been changed to 7
#> the var used in the current loop is 3
#> the var now has been changed to 8
#> the var used in the current loop is 4
#> the var now has been changed to 9
#> the var used in the current loop is 5
#> the var now has been changed to 10
-
var
在for
结构执行完毕后会储存在该for
结构所在的 environment 里,其 value 为seq
的最后一个 element。
for (i in 1:5) {
}
i
#> [1] 5
12.2.1.5 Use var
as subscript
使用var
作为位置下标(subscript),用作 subsetting 的 index,可以实现遍历的效果。
n_ob <- 1000
medians_col <- vector(mode = "integer", length = 3)
df <- data.frame(
c1 = sample(10000, size = n_ob),
c2 = sample(10000, size = n_ob),
c3 = sample(10000, size = n_ob)
)
for (r in 1:3) {
medians_col[r] <- median(df[, r])
}
medians_col
#> [1] 5283 5370 4929
但凡是采用这个使用技巧时,如果for
结构里发现有语句是通过subcript
取子集,但又没使用该for
结构中的var
作为下标,很有可能就是出错了。
例 1:
rm(list=ls())
set.seed(1)
J <- 1000
I <- 30
K <- 30
D <- 1.7
X <- matrix(NA, J, I)
P <- matrix(NA, J, I)
theta<-rnorm(J, 0, 1)
b<-rnorm(I, 0, 1)
theta[theta>3] <- 3
theta[theta<-3] <- -3
b[b > 3] <- 3
b[b < -3] <- -3
for(j in 1:J){
for(i in 1:I){
P[j, i] <- 1/(1 + exp(-D*(theta[j] - b[i])))
r <- runif(1, 0, 1)
if(P[j,i] < r){
X[j, i] <- 0
}else{
X[j, i] <- 1
}
}
}
theta_k<-seq(-3, 3, length.out = K)
theta_end<-matrix(NA, J, 1)
L_k<-matrix(NA, K, 1)
for(j in 1:J){
for(k in 1:K){
p_k <- 1/(1 + exp(-D*(theta_k[k] - b)))
for(i in 1:I){
p_k[i] <- ifelse(
X[j,i] == 0,
1-p_k[i],
p_k[i]
)
L_k[k] <- prod(p_k)
}
fenzi <- sum(theta_k*L_k*((1/sqrt(2*pi))*exp(-(theta_k)^2/2)))
fenmu <- sum(L_k*((1/sqrt(2*pi))*exp(-(theta_k)^2/2)))
}
theta_end[j] <- fenzi/fenmu
}
print(mean(abs(theta_end - theta)))
例 2:
# Hello everyone : )
#
#
#
# I was trying to write a function for evaluating the p-values of the t-test of a lm model, I know is a little bit silly and probably useless but I want to practice.
#
# The issue here is that it only evaluates the first variable.
#
# Here is the code:
#Data
library(ISLR2)
Auto = tibble(Auto)
#Model
lm.fit = lm(mpg ~ horsepower, data = Auto)
#Function for evaluate p-values
tStest = function(x) {
x = as.numeric(x)
a = rep(0,length(x))
for(i in seq_along(x)) {
if (x[i] > 0.025) {
a[i] = 'Accept Ho'
} else {
a = 'Reject Ho'
}
}
print(a)
}
pv = summary(lm.fit)$coefficients[, 4] #p-values
tStest(pv) #only returns one value
#But it works with a simple vector
v = c(1,2,3)
tStest(v)
# Does anyone know where is the problem? Also I'm interested in other approaches to achieve the same objective
#
# Sorry about my broken english, and thank you in advance
12.2.1.6 Initialize output object
输出结果(for
结构中每次 loop 改动的结果变量)需提前初始化。
在前面的例子中,medians_col
就是for
结构的输出结果,medians_col <- vector(mode = "integer", length = 3)
就是初始化,即在for
结构开始前给medians_col
赋值,并且大小也根据预期提前设定好,这样for
结构可以顺利执行,并且执行效率最高。反之,如果不提前初始化结果变量,那么在for
结构执行时,global environment 里是没有medians_col
的,报错并终止:
12.2.1.7 Alter the process of loop structure
通常在 loop 结构中,会使用if
结构来实现满足条件时改动 loop 结构的执行过程。有 2 种改动,next
和break
。
-
next
:跳过当前 loop
for (i in 1:3) {
if (i == 2) next
print(i)
}
#> [1] 1
#> [1] 3
-
break
:跳出当前 loop 结构
for (i in 1:3) {
if (i == 3) break
print(i)
}
#> [1] 1
#> [1] 2
12.2.1.8 The recommended way of using for
for
适合于多次重复相同的操作,这也就意味着写for
结构之前必须想清楚,需要重复的操作究竟有哪些,即for
结构中expr
到底要写什么,否则就会出现额外多写代码、整个代码结构不简洁的情况,
# column-wise centering
n_stu <- 30
df <- data.frame(
math = sample(1:150, n_stu, replace = TRUE),
chinese = sample(1:150, n_stu, replace = TRUE),
english = sample(1:150, n_stu, replace = TRUE),
history = sample(1:100, n_stu, replace = TRUE),
geography = sample(1:100, n_stu, replace = TRUE),
politics = sample(1:100, n_stu, replace = TRUE)
)
# calculate mean then subtract mean from raw data
n_col <- ncol(df)
mean_col <- vector(mode = "numeric", length = n_col)
for (r in 1:n_col) {
mean_col[r] <- mean(df[[r]])
}
df_centered_verbose <- df
for (r in 1:n_col) {
df_centered_verbose[[r]] <- df[[r]] - mean_col[r]
}
# more compact
df_centered_compact <- df
for (r in 1:n_col) {
mean_col <- mean(df[[r]])
df_centered_compact[[r]] <- df[[r]] - mean_col
}
identical(df_centered_verbose, df_centered_compact)
#> [1] TRUE
12.2.1.9 The best scenario to use for
for
结构最适合的任务情境应当是前一次 loop 和后一次 loop 有依赖关系,这种情况只有按顺序执行的 loop 结构才能够处理。不同次 loop 之间没有任何联系彼此独立时,都可以有替代for
结构的写法。
Loops 间存在联系的示例:
num_ite <- 20
a <- rep(0, num_ite)
for (i in 1:num_ite) {
if (i != 1) {
a[i] <- a[i - 1] + i
}
}
a
#> [1] 0 2 5 9 14 20 27 35 44 54 65 77 90 104
#> [15] 119 135 152 170 189 209
12.2.2 while
while
结构可以视作是for
+if
。
基本语法结构:
while(cond) {
expr
}
while
结构执行前,当cond
的执行结果为TRUE
时,才会进入while
结构,否则会直接跳过。进入while
结构后,执行expr
,然后会检查cond
的执行结果是否为TRUE
,是则继续下一次 loop,否则跳出while
结构。
在使用while
结构的时候要注意,一定要确保在expr
中,cond
会被更改,并酌情设定退出机制,否则非常容易陷入 forever loop。
# forever loop
i <- 1
while (i <= 5) {
print(i)
}
# make sure:
# 1. cond is modified within each loop
# 2. loop can always be jumped out of
set.seed(123)
rnd_unif <- runif(1, -1, 1)
cum_sum_rnd_unif <- 0
count <- 1
while (cum_sum_rnd_unif <= 1) {
cum_sum_rnd_unif <- cum_sum_rnd_unif + rnd_unif
print(cum_sum_rnd_unif)
count <- count + 1
if (count > 20) break
}
#> [1] -0.424845
#> [1] -0.8496899
#> [1] -1.274535
#> [1] -1.69938
#> [1] -2.124225
#> [1] -2.54907
#> [1] -2.973915
#> [1] -3.39876
#> [1] -3.823605
#> [1] -4.24845
#> [1] -4.673295
#> [1] -5.09814
#> [1] -5.522984
#> [1] -5.947829
#> [1] -6.372674
#> [1] -6.797519
#> [1] -7.222364
#> [1] -7.647209
#> [1] -8.072054
#> [1] -8.496899
print(count)
#> [1] 21
while
结构也可以通过 Snippets 快速键入:
12.2.3 repeat
repeat
结构同样也可以视作是for
+if
。
基本语法结构:
repeat {
expr
}
在使用repeat
结构的时候要注意,一定要确保expr
中有带break
的if
结构,保证不会陷入 forever loop:
i <- 1
repeat {
print(i)
i <- i + 1
if (i > 5) {
break
}
}
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
考虑到while
和repeat
结构在不够谨慎的情况下都有陷入 forever loop 的风险,而二者的本质作用都是执行多次 loop,和for
结构并无差异,因此,在绝大多数情况下都建议使用for
结构。
例如前文展示在使用while
结构时避免 forever loop 的代码完全可以用如下for
结构替代:
set.seed(123)
rnd_unif <- runif(1, min = -1, max = 1)
cum_sum_rnd_unif <- 0
n_loop <- 20
for (i in 1:n_loop) {
cum_sum_rnd_unif <- cum_sum_rnd_unif + rnd_unif
print(cum_sum_rnd_unif)
}
#> [1] -0.424845
#> [1] -0.8496899
#> [1] -1.274535
#> [1] -1.69938
#> [1] -2.124225
#> [1] -2.54907
#> [1] -2.973915
#> [1] -3.39876
#> [1] -3.823605
#> [1] -4.24845
#> [1] -4.673295
#> [1] -5.09814
#> [1] -5.522984
#> [1] -5.947829
#> [1] -6.372674
#> [1] -6.797519
#> [1] -7.222364
#> [1] -7.647209
#> [1] -8.072054
#> [1] -8.496899