跳至主要内容

R 中循环的教程——用法与替代方案

一篇关于 R 中循环的教程,介绍 R 提供的循环结构,并探索利用 R 的向量化特性作为替代方案。
· 15分钟 阅读

使用 AI 探索

本 R 循环教程将探讨 R 中可用的循环结构、这些结构应在何时使用,以及如何利用 R 的向量化等替代方案,更高效地完成循环任务。

本文将先给出一些循环示例,随后对其加以批判,并转而推荐在 R 丰富库集中广泛可用且更受欢迎的向量化替代方案。

总体而言,本教程对循环的建议是:先学习循环。它能让您在基本层面上清楚了解程序会发生什么,同时帮助您理解手头所操作的数据。

在您清楚理解循环之后,再尽量摆脱它们。

把精力投入到学习向量化替代方案上。在效率方面,它会带来回报。

若想交互式练习,请尝试我们Intermediate R 课程中的循环章节。

什么是循环?

“循环”“轮转”“迭代”,或只是重复指令,是早在计算机发明之前就存在的一种做法。它无非是通过组织一系列动作或“批处理”流程,并将需要重复的部分归组,从而将多步过程自动化。

所有现代编程语言都提供了允许重复执行指令或指令块的特殊结构。

笼统地说,现代编程语言中有两类这样的特殊结构或循环。一些循环在计数器或索引的控制下执行预定次数,在每次迭代周期后自增。这些属于 for 循环家族。

另一方面,还有一些循环基于逻辑条件的出现与验证。条件在循环结构的开始或结束处进行测试。这些变体分别属于 while 或 repeat 循环家族。

R 中循环的介绍

根据 R 基础手册,在控制流命令中,循环结构包括 for、while 和 repeat,以及附加的 break 与 next 子句。

请记住,控制流命令是让程序在不同备选方案之间分支,或者“做出决策”的命令。

您随时可以在 RStudio 命令行调用 ?Control 来查看这些控制流命令。

?Control

下图展示了 R 的循环结构:

flow chart r loop structures

R 中的 for 循环

接下来的部分将更仔细地查看上图中展示的每种结构。我们将从左侧的结构开始讨论,随后逐步移动到右侧的结构。

若想观看 for 循环的视频介绍并完成后续练习,请在我们的 Intermediate R 课程中尝试For Loop 章节。

for 循环详解

这种循环结构由“init”(初始化)的矩形框、菱形的决策节点以及矩形框 i1 组成,会被执行已知次数。

在流程图语境中,矩形框表示“执行不涉及决策的某些操作”。而菱形(或称菱形决策符号)则是“决策符号”,因此对应只能有两个逻辑答案的问题,即 True (T) 或 False (F)。

注意,为简化起见,图中略去了其他可能的符号。

初始化矩形中的一条或多条指令之后,会对某个变量上的条件进行求值,该变量可以在某个给定序列内取值。图中菱形表示的就是:“变量 v 的当前值是否属于序列 seq?”。

换言之,您在测试 v 的当前值是否在指定范围内。通常您会在初始化中定义这个范围,例如用 1:100 来确保循环开始。

如果条件不满足,结果为 False,则循环永不执行。这由 for 循环结构右侧的游离箭头表示。随后程序将执行循环块之后遇到的第一条指令。

若条件满足,则会执行一条(或一组)指令 i1。该指令块也可能是另一个循环。在这种情况下,就称为嵌套循环。

完成后,会再次对条件求值。这由从 i1 返回到顶部、紧接初始化框之后的连线表示。在 R(以及 Python)中,可以用直白的自然语言来表达:询问我们的变量是否属于某个取值范围。

注意,在其他语言中,例如 C,条件通常会显式使用逻辑运算符来表示,如大于、小于、等于等……

下面是一个简单的 for 循环示例:

# Create a vector filled with random normal values
u1 <- rnorm(30)
print("This loop calculates the square of the first 10 elements of vector u1")

# Initialize `usq`
usq <- 0. 

for (i in 1:10) {
    # i-th element of `u1` squared into `i`-th position of `usq`
    usq[i] <- u1[i] * u1[i]
    print(usq[i])
}

print(i)

for 代码块包含在花括号中。花括号可以紧跟在测试条件之后,也可以另起一行,后面最好配合缩进。二者都非强制,但花括号确实能增强代码可读性,并便于定位循环块及其中可能的错误。

注意,平方向量 usq 在此处被初始化。在纯 RStudio 代码中这并非必要,但在标记化版本中,knitr 会因为在循环中使用前未找到该向量的引用而无法编译,从而在 RStudio 内抛出错误。关于 knitr 的更多信息,请访问R Markdown 页面。

嵌套 for 循环

既然您已经知道 for 循环也可以嵌套,那么您可能会想,为什么以及何时需要在代码中这样做。

假设您希望通过将元素设置为特定值来操作一个二维数组。

那么您可能会写出如下代码:

# Insert your own integer here
my_int <- 42

nr <- as.integer(my_int)

# Create a `n` x `n` matrix with zeros
mymat <- matrix(0, nr, nr)

# For each row and for each column, assign values based on position
# These values are the product of two indexes
for (i in 1:dim(mymat)[1]) {
    for (j in 1:dim(mymat)[2]) {
        mymat[i, j] = i * j
    }
}

提示:关于 matrix() 函数的更多信息,请访问R Documentation。

上面的代码中有两个嵌套的 for 循环,因此有两组花括号,每组都有自己的代码块并由各自的索引控制。也就是说,i 遍历行,j 遍历列。
 
生成了什么?

其实,您生成的是非常熟悉的乘法表,这应该已经烂熟于心。

您也可以自行选择一个整数,然后按您的选择生成对应的表:如果是方阵,可将该整数赋给一个变量;若是矩形表,则赋给两个变量。该变量随后将作为索引 i 与 j 的上界。

# Show the first 10x10 chunk or the first `nr` x `nr` chunk
if (nr > 10) {
    mymat[1:10, 1:10]
} else
    mymat[1:nr, 1:nr]

注意,为避免用户在屏幕上打印巨大表格,您在末尾添加了一个条件:仅当用户输入的整数大于 10 时,才打印前 10 x 10 的区域。否则,将打印 n x n 的区域。

完整代码如下:

# Insert your own integer here
my_int <- 42

nr <- as.integer(my_int)

# Create a `n` x `n` matrix with zeroes
mymat <- matrix(0, nr, nr)

while 循环

上图中居中的 while 循环,与之前类似,由一个初始化块开始,随后是一个逻辑条件。该条件通常通过控制变量与某个值的比较来表达,使用大于、小于或等于等运算,但任何能返回逻辑值 True 或 False 的表达式都可行。

如果结果为 False (F),如图右侧的游离箭头所示,循环将不会执行。随后程序会执行循环块之后遇到的第一条指令。

若结果为 True (T),则会执行指令或指令块 i1。

注意,这里还添加了另一条指令或指令块 i2:它用于更新控制变量,从而可能改变循环开始处条件的结果,但这并非必需。或者,您也许想给计数器加一,以记录已执行的迭代次数。一旦条件求值为假,迭代就会停止。

其格式为 while(cond) expr,其中 cond 是要测试的条件,expr 是要执行的表达式。

例如,下面的循环通过一个用户自定义函数(UDF)要求用户输入“宇宙以及一切”的正确答案。在用户答对之前,它会一直继续:

# Your User Defined Function
readinteger <- function(){
  n <- readline(prompt="Please, enter your ANSWER: ")
}

response <- as.integer(readinteger())

while (response!=42) {   
  print("Sorry, the answer to whatever the question MUST be 42");
  response <- as.integer(readinteger());
}

一开始,使用用户自定义函数在进入循环前获取用户输入。只要答案不是预期的 42,该循环就会继续。

换句话说,您这样做是因为否则 R 会抱怨缺失用于提供 True 或 False 的表达式——实际上,在循环中使用前并不知道“response”为何物。您也这样做是因为如果您在第一次尝试中就回答正确,循环将根本不会执行。

repeat 循环

repeat 循环位于上面流程图的最右侧。它类似 while 循环,但无论条件结果如何,指令块 i1 与 i2 至少都会执行一次。

参照其他语言,可以把它称为“repeat until”,以强调指令 i1 与 i2 会一直执行,直到条件保持为 False (F) 或等价地变为 True (T) 从而退出;但无论如何,至少执行一次。

作为对上一个示例的变体,您可以这样写:

readinteger <- function(){
  n <- readline(prompt="Please, enter your ANSWER: ") 
}

repeat {   
  response <- as.integer(readinteger());
  if (response == 42) {
    print("Well done!");
    break
  } else print("Sorry, the answer to whatever the question MUST be 42");
}

在熟悉的输入函数之后,您看到 repeat 循环的代码块至少执行一次,并且当 if 条件满足时终止。

请注意,您需要在循环中设置一个条件,并通过 break 子句在该条件下退出。该子句引出了在循环中中断或退出的概念。

在 R 中中断与退出循环

那么,如何从循环中退出?

换种说法,除去循环的“自然”结束(要么达到预定迭代次数(for),要么满足某个条件(while、repeat)),您能否提前停止或中断循环?

如果能,该如何做?

break 语句回答了第一个问题:您已在上一个示例中见过它。

用 break 中断循环

当 R 解释器遇到 break 时,它会将控制权交给循环结束之后的那条指令(若存在)。在嵌套循环的情况下,break 只会退出最内层的循环。

下面是一个示例。

这段代码定义了一个 m x n 的零矩阵,然后进入嵌套的 for 循环来填充矩阵位置,但仅在两个索引不相等时进行。其目的是创建一个下三角矩阵,即主对角线以下的元素为非零,上面的元素保持为初始化的 0。

当索引相等时,内层循环(遍历列索引 j)的条件满足,执行 break,最内层循环被中断,直接跳到内层循环之后的指令。这条指令是一个 print()。然后,控制流回到外层 for 条件(行索引 i),再进行求值。

如果索引不同,则进行赋值,并将计数器加 1。最后,程序打印计数器 ctr,其中包含被赋值的元素数量。

# Make a lower triangular matrix (zeroes in upper right corner)
m = 10
n = 10

# A counter to count the assignment
ctr = 0

# Create a 10 x 10 matrix with zeroes
mymat = matrix(0, m, n)

for (i in 1:m) {
    for (j in 1:n) {
        if (i == j) {
            break
        } else {
            # you assign the values only when i<>j
            mymat[i, j] = i * j
            ctr = ctr + 1
        }
    }
    print(i * j)
}

# Print how many matrix cells were assigned
print(ctr)

注意,即便在并非绝对必要时加上花括号也许略显谨慎,但通常这样做能确保凡是用 { 打开的,就会用 } 关闭。如果您发现 { 或 } 的数量不匹配,就知道有错误,尽管反之未必成立!

在循环中使用 next

next 会终止某次具体的迭代并跳到下一轮。实际上,它会跳转到当前循环条件的求值处。

在其他语言中,您可能会看到等价但略显混淆的叫法“continue”,含义相同:无论您身在何处,一旦条件触发,就跳到循环条件的求值。

一个更简单的示例是在条件发生时继续循环但丢弃某次迭代:

m = 20

for (k in 1:m) {
    if (!k %% 2)
        next
    print(k)
}

这段代码会打印区间 1:m(此处 m=20)内的所有奇数。换句话说,所有除以 2 余数非零的整数(因此使用取模运算符 %%)将被打印,如 if 测试所示。

余数为零的数字将不会被打印,因为程序跳至 i in 1:m 条件的求值,并忽略后续任何指令。在此例中,print(k) 被忽略。

小结:在 R 中使用循环

  1. 尽量将循环内的代码量降到最低,能拿到循环外的指令就拿出来。请记住,循环内部的任何内容都会被重复多次,也许并非必要。
  2. 使用 repeat 时要小心:务必通过测试条件显式设置终止条件,否则可能陷入无限循环。
  3. 如果某个循环变得(过于)庞大,最好在循环内调用一个或多个函数。函数调用会让其他用户更容易理解代码。但在以矩阵为基础的语言(如 R)中,用嵌套的 for 循环来进行矩阵或数组运算,很可能说明实现方式并不理想。
  4. 不建议在每次迭代中通过赋值来“增长”变量或数据集。在一些语言(如 Matlab)中,这会触发警告:您可以继续,但建议考虑替代方案。下一节将展示一个典型示例。

何时使用 R 循环

这些内容都不错,但您究竟应该在何时使用 R 中的循环,又在何时不该使用?

每当需要重复执行某些操作时,循环都可能派上用场。

您只需指定需要执行的次数,或指定在何种条件下执行这些操作:为控制循环变量赋初值,执行循环,然后在循环结束后,通常对结果做一些处理。

但您何时应该使用循环?

难道不能直接把所需指令复制足够多次吗?

一个经验法则是,如果您需要执行某个动作(三次)或更多次,那么循环会更适合。它能让代码更紧凑、可读性更高、可维护性更好,也能省些打字:假如您发现某条指令需要比最初预期多重复一次,您无需重写整条指令,只需修改测试条件中某个变量的取值即可。

不过,R 的特殊性也提示:只要有替代方案,尽量不要使用循环(!)。

幸运的是,确实有替代方案!

R 拥有少数编程语言才具备的一个特性,称为向量化。

R 中循环的替代方案

什么是向量化?

顾名思义,向量化就是将对简单数字(“标量”)的重复操作,转换为对向量或矩阵的一次性操作。您已在上面各小节中看到过多个例子。

现在,向量是 R 中的基本数据结构,根据 R 基础手册,它是“由若干元素组成的单个实体”。

因此,一组数字就是一个数值向量。

如果将(长度相同的)向量组合起来,就能得到一个矩阵。您可以通过不同的 R 指令按列或按行进行组合。因此在 R 中,矩阵可视为按行或按列的向量集合。以此类推,您也可以将对向量的重复操作进行向量化。

许多上述循环结构都可以通过向量化而变得“隐式”。

我说“隐式”,是因为它们并未真正消失。在更低层,向量化的替代形式会被翻译为在其实现与编译所用的低层语言(Fortran、C 或 C++)中的一段包含一个或多个循环的代码。

这些对用户是隐藏的,且通常比等价的显式 R 代码更快。但除非您打算用这些语言来实现自己的 R 函数,否则这些细节对您是完全透明的。

最基本的例子,您可以将两个向量 v1 和 v2 相加得到向量 v3,既可以用 for 循环逐个元素相加:

v1 <- c(3,4,5)
v2 <- c(3,4,6)
v3 <- c(0,0,0)
n = length(v1)

for (i in 1:n) {
    v3[i] <- v1[i] + v2[i]
}
v3

也可以使用“原生”的向量化形式:

v3 = v1 + v2
v3

注意,之所以称为“原生”,是因为 R 能识别所有算术运算符对向量与矩阵的运算。

类似地,对于两个矩阵 A 和 B,与其在对应位置逐个相加 A[i,j] 与 B[i,j](这需要两个索引 i 与 j),您可以直接让 R 这样做:

A <- matrix(1:6, nrow = 3, ncol = 2)
B <- matrix(c(2,4,3,1,5,7), nrow=3, ncol=2)

C= A + B
C

确实非常简单!

R 中的向量化详解

既然看起来基本运算次数相同,为什么向量化运行更快?

最佳解释需要深入 R 的内部机制,这值得单开一文。简而言之:首先,R 是解释型语言,因此所有关于变量定义的细节都由解释器处理。例如,您无需指定数字是浮点型,或使用指针在内存中分配空间。

R 解释器会在您输入命令时从上下文“理解”这些问题,但它是逐条命令处理的。因此,即便重复同一命令,每次也都要处理这些定义。

相反,编译器会在编译阶段对整个代码的定义与声明“逐一清算”;代码会在您执行之前被翻译为紧凑、优化的二进制。因此,当 R 函数以这些底层语言编写时,它们更高效。

实际上,如果查看底层代码,您会发现对 C 或 C++ 的调用,通常封装在所谓的“包装器”代码中实现。

其次,在支持向量化的语言(如 R 或 Matlab)中,每条使用数值数据的指令,都是对一个天生定义为向量的对象进行操作,即便它只有一个元素。例如,当您定义一个单独的数值变量时,它在 R 内部的表示始终是一个向量,尽管仅包含一个数。

循环仍然存在于底层,但位于更底层且更快的 C/C++ 编译级别。拥有向量的优势在于:解释器只需对整个向量进行一次定义解析,而不管其大小;而对标量进行循环时,定义与分配需要逐元素进行,这就更慢。

最后,采用原生向量格式,能够利用非常高效的线性代数例程(如 BLAS,Basic Linear Algebra Subprograms)。因此在执行向量化指令时,R 可以借力这些高效的数值例程。由此可得出的建议是:若可能,尽量在单次函数调用中处理完整的数据结构,以避免各种复制操作。

闲话少说,先看一个向量化的通用示例,下一小节将深入更具体、常用的 R 向量化函数,以替代循环。

R 中向量化的一个示例

回到“增长数据”的概念,这是“更新”数据框的一种典型低效方式。

首先,使用 replicate(m, rnorm(n)) 创建一个 m x n 的矩阵,其中 m=10 个列向量、每个有 n=10 个元素,由 rnorm(n) 构造(生成正态随机数)。

然后将其转换为数据框(因此是 10 个观测、10 个变量),并使用嵌套的 for 循环对每个元素执行代数运算:在每次迭代中,用一个正弦函数为由两个索引指向的每个元素递增。

以下示例略显“人为”,但它可以代表在随机噪声上叠加一个信号:

# This is a bad loop with 'growing' data
set.seed(42)
m = 10
n = 10

# Create matrix of normal random numbers
mymat <- replicate(m, rnorm(n))

# Transform into data frame
mydframe <- data.frame(mymat)

for (i in 1:m) {
  for (j in 1:n) {
    mydframe[i, j] <- mydframe[i, j] + 10 * sin(0.75 * pi)
    print(mydframe)
  }
}

在这里,大部分执行时间都消耗在复制与管理循环上。

来看一下向量化解法:

set.seed(42)
m = 10
n = 10
mymat <- replicate(m, rnorm(n))
mydframe <- data.frame(mymat)
mydframe <- mydframe + 10 * sin(0.75 * pi)
mydframe

这看起来更简单:最后一行取代了嵌套的 for 循环。注意 使用 set.seed() 以确保两种实现给出完全相同的结果。

现在让我们量化两种方案的执行时间。

您可以使用 R 的系统命令(如 system.time())对一段代码进行计时,如下所示:

提示:只需将要评估的代码放在 system.time() 函数的括号中。

# Insert `system.time()` to measure loop execution
for (i in 1:m) {
  for (j in 1:n) {
    mydframe[i, j] <- mydframe[i, j] + 10 * sin(0.75 * pi)
  }
}

# Add `system.time()` to measure vectorized execution
mydframe <- mydframe + 10 * sin(0.75 * pi) 

在上述代码中,您仅在开始时完成选择 m 与 n、创建矩阵及其转换为数据框的工作,然后分别用两次 system.time() 调用,来比较 for 代码块与向量化“一行解”的耗时。

您会发现,即便在 m=n=10 的最小设置下,向量化版本也快约 7 倍,尽管对这种低规模场景而言,对用户的意义不大。

当设置为 m=n=100 时,差异在人感知层面变得明显;而增至 1000 时,for 循环会让人感觉卡顿数十秒,而向量化形式仍可在眨眼间完成。

在 m=n=10000 时,for 循环会卡顿一分钟以上,而向量化方案仅需 2.54 秒。当然,这些测量应谨慎对待,并会受硬件与软件配置影响;尽量避免在浏览器开几十个标签页、后台运行多个程序的情况下进行测试。不过,这些数字足以说明差异。

公允地说,增大 m 与 n 也会显著影响矩阵的生成,您可以通过在复制函数外再套一层 system.time() 来轻松观察到这一点。

欢迎您通过改变 m 与 n 来观察执行时间的变化,并将执行时间绘制为 m x n 乘积的函数。这是相关指标,因为它表示所创建矩阵的尺寸,也量化了通过 nset-ed for 循环完成任务所需的迭代次数。

以上是一个向量化的示例。当然还有许多其他示例。在 R 项目通讯(R News)第 46 页中,介绍了一些对数组或矩阵特定维度计算求和与均值的高效函数,如:rowSums()、colSums()、rowMeans() 和 colMeans()。

此外,该通讯还提到:“……‘apply’ 家族的函数,命名为 [s,l,m,t]apply,用于将另一个函数应用到对象的元素/维度上。这些 ‘apply’ 函数为有时相当复杂的任务提供了简洁语法,比写得不好的循环更易读也更快。”

apply 家族:只是隐藏的循环吗?

功能丰富而强大的apply 函数家族,本质上是向量化的函数。如果乍一看它们似乎不包含任何循环,那么在仔细探究其实现后,这一点会变得显而易见。

apply 命令(或更确切地说,一组命令)属于 R base 包。它由多个函数([s,l,m,r, t,v]apply)组成,以重复方式对矩阵或数组形式的数据切片进行操作,从而横向或纵向遍历数据,避免显式使用循环结构。这些函数作用于输入的矩阵或数组,并将一个指定的函数(可带一个或多个可选参数)应用于之上。

注意,这正是它们被称为“函数式”(可参见 Hadley Wickham 的 Advanced R 页面)的原因。

被调用的函数可以是聚合函数,如简单的均值,也可以是其它变换或子集函数。

需要指出的是,使用这些函数不一定会带来更快的执行:差异并不大;它们的优势在于避免编写繁琐的循环,从而减少出错概率。

该家族中的函数包括:apply()、sapply()、lapply()、mapply()、rapply()、tapply()、vapply()。

那么,您该在何时、如何使用它们?

值得注意的是,诸如 plyr 的包涵盖了该家族的功能;尽管不查官方文档便记住它们可能有难度,但这些函数构成了更复杂且有用组合的基础。

最常用的是前三个:

  1. apply()

您希望将某个函数应用到矩阵的行(索引“1”)或列(索引“2”)上。详情参见此页面。

  1. lapply()

您希望将某个函数应用到列表的每个元素上,并得到一个列表作为结果(这也解释了函数名中的“l”)。可在R 文档中阅读更多内容。

  1. sapply()

您希望将某个函数应用到列表的每个元素上,但希望得到一个向量而不是列表。想了解更多?请访问R 文档页面。

相关函数还有 sweep()、by() 与 aggregate(),有时会与 apply() 家族中的成员配合使用。

我会将本篇博客的讨论限定在 apply() 函数(更深入的讨论可能会留给未来的文章)。

给定一个矩阵 M,调用 apply(M,1,fun) 或 apply(M, 2,fun) 会将指定的函数 fun 应用于 M 的每一行(当指定 1)或每一列(当指定 2)。这个数值参数称为“边(margin)”,对矩阵而言仅限 1 与 2;不过,若是数组,维度最多可达 8。

该函数可以是任何合法的 R 函数,也可以是用户自定义函数(UDF),甚至可以直接在 apply() 内联编写,这非常方便。

apply():一个示例

# define matrix `mymat` by replicating the sequence `1:5` for `4` times and transforming into a matrix
mymat <- matrix(rep(seq(5), 4), ncol = 5)

# `mymat` sum on rows
apply(mymat, 1, sum)

# `mymat` sum on columns
apply(mymat, 2, sum)

# With user defined function within the apply that adds any number `y` to the sum of the row
# `y` is set at `4.5`
apply(mymat, 1, function(x, y)
  sum(x) + y, y = 4.5)

# Or produce a summary column wise for each column
apply(mymat, 2, function(x, y)
  summary(mymat))

您经常会使用数据框:在这种情况下,您必须确保数据具有相同类型,否则就可能发生强制类型转换,这很可能不是您想要的。例如,在包含文本与数字的混合数据框中,数值数据会被转换为字符串或字符型。

关于在 R 中使用循环及其替代方案的最后思考

至此,我们从编程中最基本的循环结构出发,介绍了(基础的)向量化概念,并展示了 apply() 家族中一个函数的用法,该家族在 R 中经常会用到。

在代码流程控制方面,我们只涉及了循环:for、while、repeat,以及如何中断和退出它们。

既然前面的小节暗示在 R 中应尽量避免使用循环,您也许会问,为何还要学习它们。

在我看来,您应该学习这些编程结构,因为:

  1. R 很可能不会是您在数据科学或其他领域中唯一使用的语言,掌握循环等通用结构,是值得装进技能包的好东西。不同语言的循环语法可能不同,但一旦在一种语言中掌握,您就能迅速将其应用到遇到的其他语言中。
  2. R 生态非常庞大,要对所有现有函数了然于胸非常困难,甚至几乎不可能。实现同一目标的方法很多,有的更高效或更优雅,您的学习曲线将是渐进的;在使用过程中,您会开始思考如何更高效地做事,并最终发现此前从未听说过的函数。当您读到本文时,也许已有一些新库被开发发布了;除非您把全部时间都投入到 R 专业研究,否则想要全盘掌握的概率很小。
  3. 最后,至少在不处理特别高维数据集时,循环解法往往容易编写和阅读。作为数据科学家,您可能会被要求做一个一次性的原型,只需能工作即可。也许您并不关心用多么复杂或优雅的方式得到结果。也许在数据分析流程中,您只需体现领域专长并专注内容。后续的生产阶段很可能由后端专家负责(而且他们/她们可能会用另一种语言来实现!)。

如果在读完本教程后,R 中的 for 循环对您来说已不再是挑战,您可以考虑学习 DataCamp 的 Intermediate R 课程。该课程会通过一系列全新有趣的练习加强您在《R 进阶》中的相关知识。如果循环对您已毫无秘密,DataCamp 的 Writing Functions in R 课程(由 Hadley 和 Charlotte Wickham 授课)也许会引起您的兴趣。此外,别忘了看看For Loops in R 教程。

在线运行和编辑此教程中的代码

运行代码

R 循环常见问题

R 中的循环是什么?为什么有用?

循环是一种多次重复执行一段代码的方式。当您希望对多个数据点或对象执行相同操作,或希望在满足某个条件之前重复执行某个动作时,它就很有用。

R 中有哪些不同类型的循环?

R 中主要有两种循环:for 循环和 while 循环。

  • For 循环会对列表或序列中的每个元素重复执行一段代码。例如,您可以用 for 循环打印出一个向量的所有元素,或将某个函数应用到列表的每个元素。
  • While 循环会在某个条件为真时重复执行一段代码。例如,您可以用 while 循环不断请求用户输入,直到其给出有效响应为止。
如何在 R 中停止一个循环?

要在 R 中停止一个循环,您可以使用 break 语句。这会立即退出循环,并继续执行后续代码。

如何在 R 中跳过循环的下一次迭代?

要在 R 中跳过循环的当前迭代并进入下一次迭代,您可以使用 next 语句。这会跳过当前迭代剩余的部分,直接进入下一次迭代。

我可以在 R 中将循环嵌套在其他循环中吗?

可以,您可以在 R 中将循环嵌套在其他循环内。这称为“嵌套”循环。例如,您可以在一个 for 循环内部再使用一个 for 循环来遍历矩阵的元素。

主题