for 循环通过迭代一个给定向量或列表,重复执行某个表达式。for 循环的语法是
这样的:
for (var in vector) {
expr
}
var 遍历 vector 中的各个元素值,expr 被反复迭代执行。如果 vector 中有 n 个
元素,那么上述循环就等价于:
var <- vector[[1]]
expr
var <- vector[[2]]
expr
...
var <- vector[[n]]
expr
例如,我们使用迭代变量 i 来构造一个在 1:3 上迭代的循环,并且每次迭代,都把变
量 i 的取值信息输出到屏幕上:
for (i in 1:3){
cat("The value of i is",i,"\n")
}
## The value of i is 1
## The value of i is 2
## The value of i is 3
迭代可作用于所有类型的向量,而不只是数值向量。例如,我们可以用一个字符向量
替代 1:3 这一整数向量:
for (word in c("hello", "new", "world")) {
cat("The current word is", word, "\n")
}
## The current word is hello
## The current word is new
## The current word is world
也可以用一个列表来替代它:
loop_list <- list(
a = c(1, 2, 3),
b = c("a", "b", "c", "d"))
for (item in loop_list) {
cat("item:\n length:", length(item),
"\n class:", class(item), "\n")
}
## item:
## length: 3
## class: numeric
## item:
## length: 4
## class: character
除此之外,还可以用数据框来替代它:
df <- data.frame(
x = c(1, 2, 3),
y = c("A", "B", "C"),
stringsAsFactors = FALSE)
for (col in df) {
str(col)
}
## num [1:3] 1 2 3
## chr [1:3] "A" "B" "C"
之前我们提到过,数据框是一个列表,每一元素(列)的长度必须相同。因此,上述
循环的运行方式和对一个普通列表执行 for 循环是一致的,都是按列,而不是按行进行迭代。
然而,很多时候,我们想按行迭代一个数据框。这时可以使用 for 循环,在一个整数
序列(1 到数据框的行数)上迭代。
i 每次取一个行数,我们就把这一行从数据框中挑出来,然后对这一特定的行进行相
应的操作。以下代码按行迭代数据框,并使用 str( ) 输出每一行的结构:
for (i in 1:nrow(df)) {
row <- df[i,]
cat("row", i, "\n")
str(row)
cat("\n")
}
## row 1
## 'data.frame': 1 obs. of 2 variables:
## $ x: num 1
## $ y: chr "A"
##
## row 2
## 'data.frame': 1 obs. of 2 variables:
## $ x: num 2
## $ y: chr "B"
##
## row 3
## 'data.frame': 1 obs. of 2 variables:
## $ x: num 3
## $ y: chr "C"
这里要给一个小小的警告。一般来说,按行迭代数据框并不是一个好主意,因为迭代
过程可能很慢很繁琐。更好的方法是使用第 5 章中介绍的 apply 函数族,或者第 12 章中
介绍的更强大、更高级的扩展包。
在前面的例子中,for 循环的每一次迭代都是相互独立的。有时,可以利用循环外的
变量来记录迭代状态或累积过程。最简单的例子是计算从 1~100 的和:
s <- 0
for (i in 1:100){
s <- s + i
}
s
## [1] 5050
上述例子展示了使用 for 循环进行累计求和。接下来的例子中,我们使用随机数生成
器从正态分布中进行抽样,简单地模拟一下随机游走过程(random walk):
set.seed(123)
x <- numeric(1000)
for (t in 1:(length(x) -1)) {
x[[t + 1]] <- x[[t]] + rnorm(1, 0, 0.1)
}
plot(x, type = "s", main = "Random walk", xlab = "t")
以上代码的输出结果,如图 4-1 所示。
图 4-1
上述两个例子中,尽管 for 循环过程的每一步都依赖于上一步的结果,但可以利用现
有函数,如 sum( ) 和 cumsum( ) 简化循环结构:
sum100 <- sum(1:100)
random_walk <- cumsum(rnorm(1000,0,0.1))
这些函数的基本思想和 for 循环类似,但它们是基于 C 语言的向量化实现,所以运
行效率远高于 R 中的 for 循环。因此,在可能的情况下,应该优先考虑使用这些内置函数。
1.管理 for 循环的控制流
有时,有必要对 for 循环进行干预。在每一次迭代中,我们可以选择中断 for 循环,
跳过当前迭代,或者什么都不做并终止循环。
可以使用 break 终止 for 循环:
for (i in 1:5) {
if(i == 3) break
cat("message", i, "\n")
}
## message 1
## message 2
这种方法可以用来寻找某个问题的解。以下代码试图在 1000 至 1100 之间找到满
足 (i ^ 2) %% 11 等于 (i ^ 3)%%17 的数,其中 ^ 是指数运算符,%% 是除法运算中的取
余运算符:
m <- integer()
for (i in 1000:1100) {
if ((i ^ 2) %% 11 == (i ^ 3) %% 17){
m <- c(m, i)
}
}
m
## [1] 1055 1061 1082 1086 1095
如果只要该范围内满足条件的一个数,你可以将记录追踪表达式(m <-c(m,i))替
换成一个简单的 break:
for (i in 1000:1100) {
if ((i ^ 2) %% 11 == (i ^ 3) %% 17) break
}
i
## [1] 1055
一旦找到一个解,for 循环就会终止,并将 i 的最后取值保存在当前环境中,这样就
能得到满足条件的解。
有时,也可能需要跳过 for 循环中的某一次迭代。我们可以使用 next 跳过本次迭代
的剩余部分,直接进入循环的下一次迭代:
for (i in 1:5) {
if (i == 3) next
cat("message ", i, "\n")
}
## message 1
## message 2
## message 4
## message 5
2.创建循环嵌套
for 循环中的表达式可以是任何内容,包括另一个 for 循环。例如,如果我们想要穷
举向量中元素的所有排列,可以写一个嵌套两层的 for 循环来解决这个问题:
x <- c("a", "b", "c")
combx <- character()
for (c1 in x){
for (c2 in x) {
combx <- c(combx, paste(c1, c2, sep = ",", collapse = ""))
}
}
combx
## [1] "a,a" "a,b" "a,c" "b,a" "b,b" "b,c" "c,a" "c,b" "c,c"
如果只要不同元素的排列,可以在 for 循环内部加一个判定条件:
combx2 <- character()
for (c1 in x) {
for (c2 in x) {
if (c1 == c2) next
combx2 <- c(combx2, paste(c1, c2, sep = ",", collapse = ""))
}
}
combx2
## [1] "a,b" "a,c" "b,a" "b,c" "c,a" "c,b"
除此之外,也可以去掉判定条件,把 for 循环内部的表达式替换为以下代码,以得到
完全相同的结果:
if (c1 != c2) {
combx2 <- c(combx2, paste(c1, c2, sep = ",", collapse = ""))
}
上述代码展示了嵌套循环是如何工作的,但它并不是解决这个问题的最佳方法。一些
内置函数就可以生成向量元素的排列或组合。给定一个原子向量和每个组合的元素个数,
函数 combn( ) 返回由相应组合构成的一个矩阵:
combn(c("a", "b", "c"), 2)
## [,1] [,2] [,3]
## [1,] "a" "a" "b"
## [2,] "b" "c" "c"
与之类似,函数 expand.grid( ) 返回由相应排列构成的一个数据框:
expand.grid(n = c(1, 2, 3), x = c("a", "b"))
## n x
## 1 1 a
## 2 2 a
## 3 3 a
## 4 1 b
## 5 2 b
## 6 3 b
尽管 for 循环很强大,但 R 中有很多针对特定任务编写的函数。最好优先考虑使用内
置函数,而不是直接把所有内容放到 for 循环中。在下一章中,我将会介绍能够代替许
多 for 循环的 lapply( ) 函数和其他相关函数,这些函数使代码更易于编写和理解。