控制流程结构

For 循环是用于在域上重复任务或任务集的流控制方法。for 循环的核心结构是

for ( [index] in [domain]){
  [body]
}

哪里

  1. [index] 是一个名称,在循环的每次迭代中只取一个 [domain] 的值。
  2. [domain] 是要迭代的值的向量。
  3. [body] 是应用于每次迭代的指令集。

作为一个简单的例子,考虑使用 for 循环来获得值向量的累积和。

x <- 1:4
cumulative_sum <- 0
for (i in x){
  cumulative_sum <- cumulative_sum + x[i]
}
cumulative_sum

优化循环结构

for 循环可用于概念化和执行要重复的任务。但是,如果不仔细构造,与 apply 系列功能的首选使用相比,它们的执行速度可能非常慢。尽管如此,你可以在 for 循环结构中包含一些元素来优化循环。在许多情况下,for 循环的良好构造将产生非常接近于 apply 函数的计算效率。

正确构造的for 循环构建在核心结构上,并包含一个语句,声明将捕获循环的每次迭代的对象。该对象应该声明一个类和一个长度。

[output] <- [vector_of_length]
for ([index] in [length_safe_domain]){
  [output][index] <- [body]
}

为了说明,让我们编写一个循环来对数值向量中的每个值进行平方(这只是一个简单的例子,仅用于说明。完成此任务的’正确’方法将是 x_squared <- x^2)。

x <- 1:100
x_squared <- vector("numeric", length = length(x))
for (i in seq_along(x)){
  x_squared[i] <- x[i]^2
}

再次注意,我们首先为输出 x_squared 声明了一个容器,并给它提供了与 x 相同长度的数字类。此外,我们使用 seq_along 函数声明了长度安全域seq_along 为适合用于 for 循环的对象生成索引向量。虽然使用 for (i in 1:length(x)) 似乎很直观,但如果 x 的长度为 0,则循环将尝试在 1:0 的域上进行迭代,从而导致错误(R 中的第 0 个索引未定义)。

插座对象和长度安全域由 apply 系列功能在内部处理,鼓励用户尽可能采用 apply 方法代替 for 循环。但是,如果构造正确,for 循环可能偶尔提供更高的代码清晰度,同时效率损失最小。

矢量化循环

For 循环通常可以成为概念化每次迭代中需要完成的任务的有用工具。当循环完全开发和概念化时,将循环转换为函数可能是有利的。

在这个例子中,我们将开发一个 for 循环来计算 mtcars 数据集中每列的平均值(同样,一个简单的例子,因为它可以通过 colMeans 函数完成)。

column_mean_loop <- vector("numeric", length(mtcars))
for (k in seq_along(mtcars)){
  column_mean_loop[k] <- mean(mtcars[[k]])
}

通过将循环体重写为函数,可以将 for 循环转换为 apply 函数。

col_mean_fn <- function(x) mean(x)
column_mean_apply <- vapply(mtcars, col_mean_fn, numeric(1))

并比较结果:

identical(column_mean_loop, 
          unname(column_mean_apply)) #* vapply added names to the elements
                                     #* remove them for comparison

矢量化形式的优点是我们能够消除几行代码。通过 apply 函数为我们处理确定输出对象的长度和类型以及遍历长度安全域的机制。此外,apply 函数比循环快一点。根据迭代次数和身体的复杂程度,人类的速度差异通常可以忽略不计。