R 循环:for、while、repeat
在 R 语言中,循环是处理重复任务的核心工具。当你需要对数据集中的每一行执行相同操作,或者重复执行计算直到满足某个条件时,循环就显得尤为重要。本文将详细介绍 R 语言的三种循环结构:for 循环、while 循环和 repeat 循环,帮助你根据不同场景选择最合适的循环方式。
for 循环:已知次数的重复
for 循环是最常用的循环结构,适用于你知道需要迭代多少次的情况。它会遍历一个向量或列表中的每一个元素,对每个元素执行相同的代码块。
基本语法
for (变量 in 向量/列表) {
# 需要重复执行的代码
}
实用示例
遍历向量元素:假设你有一个成绩向量,需要计算每个成绩加 5 分后的新值。
scores <- c(85, 90, 78, 92, 88)
for (score in scores) {
new_score <- score + 5
print(new_score)
}
输出结果:
[1] 90
[1] 95
[1] 83
[1] 97
[1] 93
使用索引遍历:有时候你需要同时获取元素的值和位置,这时可以使用索引。
fruits <- c("苹果", "香蕉", "橙子", "葡萄")
for (i in 1:length(fruits)) {
# 使用 paste0 连接字符串,生成序号和水果名称
result <- paste0("第", i, "个水果是: ", fruits[i])
print(result)
}
输出结果:
[1] "第1个水果是: 苹果"
[1] "第2个水果是: 香蕉"
[1] "第3个水果是: 橙子"
[1] "第4个水果是: 葡萄"
嵌套循环:处理多维数据时常常需要嵌套循环。
# 创建一个 3x3 的矩阵
matrix_data <- matrix(1:9, nrow = 3)
for (i in 1:nrow(matrix_data)) {
for (j in 1:ncol(matrix_data)) {
# 打印每个元素的坐标和值
cat("位置 (", i, ",", j, ") 的值是: ", matrix_data[i, j], "\n", sep = "")
}
}
while 循环:条件驱动的重复
while 循环会在指定条件为 TRUE 时持续执行代码块。它适用于你不知道需要循环多少次,但知道停止条件的情况。使用时需要特别注意避免无限循环。
基本语法
while (条件) {
# 需要重复执行的代码
# 记得在循环体内修改条件涉及的变量
}
实用示例
基础计数器:计算从 1 加到 10 的总和。
sum_result <- 0
current <- 1
while (current <= 10) {
sum_result <- sum_result + current
current <- current + 1 # 必须更新计数器,否则会无限循环
}
print(sum_result) # 输出 55
猜数字游戏:让用户猜一个随机数,直到猜对为止。
target_number <- sample(1:100, 1)
guess <- 0
attempts <- 0
cat("猜一个 1 到 100 之间的数字:\n")
while (guess != target_number) {
guess <- as.integer(readline(prompt = "请输入你的猜测: "))
attempts <- attempts + 1
if (guess < target_number) {
cat("太小了!再试一次。\n")
} else if (guess > target_number) {
cat("太大了!再试一次。\n")
} else {
cat("恭喜!你猜对了!\n")
cat("你总共猜了 ", attempts, " 次。\n", sep = "")
}
}
模拟收敛过程:计算平方根的迭代近似值。
# 计算 25 的平方根,使用牛顿迭代法的简化版本
target <- 25
guess <- 20 # 初始猜测
tolerance <- 0.001
iterations <- 0
while (abs(guess^2 - target) > tolerance) {
guess <- (guess + target / guess) / 2
iterations <- iterations + 1
cat("第", iterations, "次迭代: 猜测值 =", round(guess, 6), "\n")
}
cat("\n最终结果:", round(guess, 6), "\n")
cat("实际平方根:", sqrt(target), "\n")
repeat 循环:灵活但需谨慎
repeat 循环是一种更灵活的循环结构,它会无限重复执行代码块,直到你使用 break 语句明确退出。这种循环特别适用于你不确定循环次数,但知道具体退出条件的场景。
基本语法
repeat {
# 需要重复执行的代码
# 必须在适当位置使用 break 退出循环
}
实用示例
安全输入验证:确保用户输入有效数据。
repeat {
age_input <- readline(prompt = "请输入你的年龄 (1-120): ")
age <- as.integer(age_input)
if (!is.na(age) && age >= 1 && age <= 120) {
cat("有效的年龄输入: ", age, "\n")
break # 输入有效,退出循环
} else {
cat("输入无效,请重新输入。\n")
}
}
计算阶乘直到结果超过阈值。
n <- 1
factorial_result <- 1
repeat {
factorial_result <- factorial_result * n
cat("计算 ", n, "! = ", factorial_result, "\n", sep = "")
if (factorial_result > 1000000) {
cat("阶乘结果已超过 100 万,停止计算。\n")
break
}
n <- n + 1
}
寻找满足条件的最小整数。
find_target <- 100
current <- 1
repeat {
if (current^2 > find_target) {
cat("最小的整数 n,使得 n² > ", find_target, " 是 ", current, "\n", sep = "")
break
}
current <- current + 1
}
循环控制语句:break 与 next
在所有循环结构中,你可以使用 break 和 next 来更精确地控制程序流程。
break 语句:立即退出循环
break 语句会立即终止当前循环,继续执行循环之后的代码。
# 找出第一个能被 7 整除的数
numbers <- c(1, 3, 5, 7, 9, 11, 14, 16)
for (num in numbers) {
if (num %% 7 == 0) {
cat("找到第一个能被 7 整除的数: ", num, "\n")
break # 找到后立即退出循环
}
cat("检查 ", num, ",不是 7 的倍数\n", sep = "")
}
next 语句:跳过当前迭代
next 语句会跳过当前迭代的剩余代码,直接进入下一次迭代。
# 打印 1 到 10 之间的所有偶数
for (i in 1:10) {
if (i %% 2 == 1) {
next # 跳过奇数
}
cat("偶数: ", i, "\n")
}
结合使用 break 和 next
# 处理数据,跳过无效值,遇到特定条件终止
data_values <- c(5, 10, -1, 20, 30, -5, 40, 50)
threshold <- 100
cat("开始处理数据...\n")
for (value in data_values) {
if (value < 0) {
cat("遇到无效值 ", value, ",跳过该值。\n", sep = "")
next
}
current_sum <- sum(data_values[data_values > 0 & data_values <= threshold])
if (current_sum > threshold) {
cat("累计和已超过阈值 ", threshold, ",停止处理。\n", sep = "")
break
}
cat("处理值: ", value, ",当前累计和: ", current_sum, "\n", sep = "")
}
循环对比与选择指南
下表总结了三种循环的特点,帮助你在不同场景下做出正确选择:
| 循环类型 | 适用场景 | 终止条件 | 使用建议 |
|---|---|---|---|
| for | 已知迭代次数,遍历向量、列表或数据框的每一行/列 | 自动完成所有迭代 | 优先选择,代码清晰可读 |
| while | 不知道循环次数,但知道继续执行的条件 | 条件变为 FALSE | 适合需要持续检查的场景 |
| repeat | 不知道循环次数,需要手动控制退出 | 遇到 break 语句 | 适合需要从多个角度判断退出的场景 |
选择建议
优先使用 for 循环:当你需要遍历一个已知的集合(如数据框的每一行、列表的每个元素、向量中的每个值)时,for 循环是最自然的选择,代码可读性最好。
选择 while 循环:当你需要持续执行某操作直到外部条件变化(如用户输入正确、文件读取完成、网络连接建立)时,while 循环更为合适。
选择 repeat 循环:当你需要多次尝试且退出条件比较复杂(可能在循环体多个位置判断)时,repeat 循环配合 break 使用最为灵活。
性能与最佳实践
向量化优先原则
R 语言是基于向量化的语言,对向量和矩阵的操作有高度优化的内部实现。在可能的情况下,优先使用向量化操作而非循环。
# 低效:使用循环计算向量平方
numbers <- 1:10000
result_loop <- numeric(length(numbers))
for (i in 1:length(numbers)) {
result_loop[i] <- numbers[i]^2
}
# 高效:使用向量化操作
result_vectorized <- numbers^2
# 两者结果相同,但向量化版本速度快得多
identical(result_loop, result_vectorized)
预分配空间
如果必须使用循环,尽量预先分配结果向量的空间,避免在循环中动态增长向量。
# 不推荐:动态增长向量(效率低)
result <- c()
for (i in 1:1000) {
result <- c(result, i^2) # 每次扩展都需要重新分配内存
}
# 推荐:预分配空间
result <- numeric(1000)
for (i in 1:1000) {
result[i] <- i^2 # 直接赋值,效率高
}
避免全局变量污染
在循环中创建或修改变量时,注意使用局部变量或在适当的环境中操作。
# 使用 local 环境封装循环
calculate_squares <- local({
result <- numeric(100)
for (i in 1:100) {
result[i] <- i^2
}
return(result)
})
使用 cat 而非 print 减少输出
在大型循环中,print 会生成大量输出影响性能。如果只是需要查看中间结果,使用 cat 并在最后换行更加高效。
# 对于大型循环,使用 progress bar 监控进度
library(progress)
pb <- progress_bar$new(total = 1000)
for (i in 1:1000) {
Sys.sleep(0.01)
pb$tick()
}
实际应用场景
数据清洗与转换
# 批量处理数据框中的缺失值
clean_data <- function(df) {
for (col_name in names(df)) {
if (is.numeric(df[[col_name]])) {
# 用均值替换数值列的缺失值
df[[col_name]][is.na(df[[col_name]])] <- mean(df[[col_name]], na.rm = TRUE)
} else if (is.character(df[[col_name]])) {
# 用 "Unknown" 替换字符列的缺失值
df[[col_name]][is.na(df[[col_name]])] <- "Unknown"
}
}
return(df)
}
# 示例数据
sample_df <- data.frame(
age = c(25, NA, 30, NA),
name = c("Alice", NA, "Bob", "Charlie"),
score = c(85, 90, NA, 92)
)
cleaned_df <- clean_data(sample_df)
print(cleaned_df)
批量文件处理
# 处理多个 CSV 文件
file_list <- c("data1.csv", "data2.csv", "data3.csv")
combined_data <- data.frame()
for (file_name in file_list) {
if (file.exists(file_name)) {
file_data <- read.csv(file_name)
file_data$source_file <- file_name # 标记数据来源
combined_data <- rbind(combined_data, file_data)
cat("成功处理文件:", file_name, "\n")
} else {
cat("警告: 文件", file_name, "不存在\n")
}
}
蒙特卡洛模拟
# 使用循环进行多次模拟
simulate_pi <- function(n_points) {
count <- 0
for (i in 1:n_points) {
x <- runif(1, -1, 1)
y <- runif(1, -1, 1)
if (x^2 + y^2 <= 1) {
count <- count + 1
}
}
return(4 * count / n_points)
}
# 运行多次模拟并计算平均值
n_simulations <- 10
estimates <- numeric(n_simulations)
for (i in 1:n_simulations) {
estimates[i] <- simulate_pi(10000)
cat("第", i, "次模拟结果:", estimates[i], "\n")
}
cat("\n圆周率估计值的均值:", mean(estimates), "\n")
cat("标准差:", sd(estimates), "\n")
暂无评论,快来抢沙发吧!