当前位置: 首页 > news >正文

HoRain云--R循环实战:从语法到高效向量化技巧

🎬 HoRain 云小助手:个人主页

⛺️生活的理想,就是为了理想的生活!


⛳️ 推荐

前些天发现了一个超棒的服务器购买网站,性价比超高,大内存超划算!忍不住分享一下给大家。点击跳转到网站。

目录

⛳️ 推荐

R 循环——从语法到反模式(完整实战指南)

一、R 的三种基本循环

1️⃣ for——知道(或能确定)迭代次数时用

2️⃣ while——不知道要迭代多少次,靠条件控制

3️⃣ repeat——无限循环,必须手动 break

二、循环控制:break与 next

三、嵌套循环

四、最重要的一节:R 循环的致命反模式

❌ 最经典的错误——在循环内"生长"结果向量

✅ 修正:预分配(Pre-allocate)

🏆 终极方案:向量化——直接消灭循环

五、R 的核心哲学:先问"一定要用循环吗?"

决策树

对照示例

六、apply家族——循环的结构化替代品

七、tidyverse 流派:purrr::map()系列

八、什么时候必须用循环(无法向量化)

九、速查小结


R 循环——从语法到反模式(完整实战指南)


一、R 的三种基本循环

1️⃣for——知道(或能确定)迭代次数时用

# 最常用形式:遍历一个序列 for (i in 1:5) { print(i) } # 遍历向量元素(不是下标) fruits <- c("apple", "banana", "cherry") for (fruit in fruits) { print(paste("我喜欢吃", fruit)) }

R 的for (var in sequence)本质是"遍历序列中的每个元素",不是传统 C 语言那种for(i=0; i<n; i++)下标思维——虽然你也可以这样写:

x <- c(10, 20, 30, 40) for (i in seq_along(x)) { # seq_along 比 1:length(x) 更安全 cat(sprintf("x[%d] = %d\n", i, x[i])) }

⚠️ 坑

1:length(x)x为空时变成1:0,即c(1, 0),循环会诡异执行两次

✅ 用seq_along(x)seq_len(n)


2️⃣while——不知道要迭代多少次,靠条件控制

# 例:不断除以2直到小于10 n <- 1000 while (n >= 10) { n <- n / 2 cat(sprintf("half: %.4f\n", n)) } # 最终 n = 7.8125 # 例:猜数小游戏 secret <- sample(1:100, 1) guess <- 0 while (guess != secret) { guess <- as.numeric(readline(prompt = "猜 1~100 的数: ")) if (guess < secret) cat("太小了!\n") if (guess > secret) cat("太大了!\n") } cat("对了!答案是", secret, "\n")

死循环风险while的条件如果永远不为FALSE,R 会话会卡死。写while时心里必须确认:"条件一定会在某次迭代后变 FALSE 吗?"


3️⃣repeat——无限循环,必须手动break

counter <- 1 repeat { cat(sprintf("counter = %d\n", counter)) counter <- counter + 1 if (counter > 5) break # ← 没有这个就是死循环 } # 例:掷骰子直到出现6 repeat { roll <- sample(1:6, 1) cat(sprintf("掷出了 %d\n", roll)) if (roll == 6) { cat("🎉 出现了6,停止!\n") break } }

repeat { ... }等价于while (TRUE) { ... },区别只是语法——用哪个都行,关键是一定写break


二、循环控制:breaknext

关键字

作用

类比其他语言

break

立刻跳出整个循环

break

next

跳过本次,进入下一轮

continue

for (i in 1:10) { if (i == 5) break # 遇到5就停 if (i %% 2 == 0) next # 偶数跳过 print(i) # 输出:1 3 } # 打印1~100中第一个能被7整除且大于50的数 for (i in 1:100) { if (i <= 50 || i %% 7 != 0) next print(i) # 56 break }

break/next只对最内层循环生效。嵌套循环里想跳多层需要额外标志变量或用函数return


三、嵌套循环

# 九九乘法表 for (i in 1:9) { for (j in 1:i) { cat(sprintf("%d×%d=%d\t", j, i, i*j)) } cat("\n") }

⚠️ 嵌套越深 → 迭代次数乘积增长 → 性能雪上加霜。如果内层循环体不依赖外层索引,考虑能不能向量化或合并。


四、最重要的一节:R 循环的致命反模式

❌ 最经典的错误——在循环内"生长"结果向量

# 💀 千万别这样写(慢到指数级) result <- c() # 空向量 for (i in 1:100000) { result <- c(result, i^2) # 每次都要拷贝整个向量! }

为什么会慢?​ 因为 R 的c()每次都在创建一个更大的新对象,把旧数据拷过去再追加——时间复杂度从 O(n) 退化到 O(n²)。

✅ 修正:预分配(Pre-allocate)

n <- 100000 result <- numeric(n) # ← 先开好空间 for (i in 1:n) { result[i] <- i^2 }

🏆 终极方案:向量化——直接消灭循环

result <- (1:100000)^2 # 一步到位,C级别执行

基准实测差异: 向量化版本可比 naive 循环快数千倍。这不是夸张——是 R 设计的根本特征:向量化运算委托到底层 C/Fortran 代码执行,而for循环在 R 解释器层面一步步跑。


五、R 的核心哲学:先问"一定要用循环吗?"

决策树

要做的事对每个元素都一样? ├── 是 → 能用向量化运算吗? │ ├── 能 → 直接用向量化(最快,最 R-style) │ │ x * 2 / sqrt(x) / ifelse(test, a, b) │ └── 不能(非同质操作)→ lapply / purrr::map └── 否(迭代间有依赖 / 副作用 / 未知终止条件) └── 用 for / while(无法避免才用)

对照示例

# ❌ 没必要写循环 vec <- c(3, 7, 2, 9) squared <- c() for (i in seq_along(vec)) { squared[i] <- vec[i]^2 } # ✅ 向量化——R 的本来面目 squared <- vec^2 # ❌ 没必要写循环 for (i in seq_along(vec)) { if (vec[i] > 5) cat(vec[i], "大\n") else cat(vec[i], "小\n") } # ✅ 向量化条件 ifelse(vec > 5, paste(vec, "大"), paste(vec, "小")) # ❌ 没必要写循环算均值 means <- c() for (col in c("mpg", "hp", "wt")) { means[col] <- mean(mtcars[[col]]) } # ✅ 一行搞定 sapply(mtcars[c("mpg", "hp", "wt")], mean)

六、apply家族——循环的结构化替代品

函数

输入

输出

典型场景

lapply(X, f)

列表/向量

list

对列表中每项做变换

sapply(X, f)

列表/向量

简化(向量/矩阵)

同上,但结果尽量压扁

vapply(X, f, FUN.VALUE)

列表/向量

指定类型的向量

生产代码首选(类型安全)

apply(mat, MARGIN, f)

矩阵

按行(M=1)/列(M=2)

行列汇总

tapply(X, INDEX, f)

向量

按分组

tapply(mtcars$mpg, mtcars$cyl, mean)

mapply(f, ...)

多向量

并行迭代

mapply(paste, letters[1:3], 1:3)

# lapply → 总是返回 list nums <- list(a = 1:3, b = 4:6, c = 7:9) lapply(nums, sum) # $a [1] 6 $b [1] 15 $c [1] 24 # sapply → 尝试简化 sapply(nums, sum) # 变成具名向量: a=6, b=15, c=24 # vapply → 最安全(声明返回值类型) vapply(nums, sum, numeric(1)) # 一样的结果,但如果 sum 返回非数值会报错警告 # apply → 矩阵行列 mat <- matrix(1:12, nrow = 3) apply(mat, 1, sum) # 行和: [1] 22 26 30 apply(mat, 2, mean) # 列均: [1] 2 5 8 11

⚠️apply会把 data.frame 先转成 matrix(丢失列类型),对纯数值矩阵没问题,但对混杂类型 data.frame 要小心。


七、tidyverse 流派:purrr::map()系列

如果你用 tidyverse,推荐purrr::map_*()替代 base R 的lapply/sapply——更一致、类型更安全、错误信息更友好:

library(purrr) nums <- 1:5 map(nums, ~ .x^2) # → list map_dbl(nums, ~ .x^2) # → numeric vector map_chr(nums, ~ paste0("ID_", .x)) # → character vector # 安全包裹:不中断整个循环 safe_log <- safely(log) map(list(10, 0, -5, 100), safe_log)

八、什么时候必须用循环(无法向量化)

场景

为什么不能向量化

随机游走 / 递推:x[i]依赖x[i-1]

前向依赖

迭代收敛:while (误差 > tol)求数值解

终止条件未知

副作用序列:逐文件读写、逐图保存、API 分页拉取

每一步有 I/O

提前退出逻辑太复杂,break是唯一清晰的表达

语义驱动

# 例:牛顿迭代求根(依赖前一步,不能用向量化) newton <- function(f, df, x0, tol = 1e-8, max_iter = 100) { x <- x0 for (i in 1:max_iter) { x_new <- x - f(x) / df(x) if (abs(x_new - x) < tol) { cat(sprintf("收敛于第 %d 步\n", i)) return(x_new) } x <- x_new } warning("未收敛"); x }

这种情况下写循环不仅合理,而且是最清晰的写法


九、速查小结

你想做的事

推荐写法

对每个元素做同样运算

向量化vec^2ifelse()

对 list 逐项变换

lapply()/purrr::map()

矩阵行列汇总

apply(mat, 1 or 2, fun)

按分组聚合

tapply()/dplyr::group_by()

递推 / 条件未知 / 副作用

for+ 预分配​ 或while

一定要写for

result <- vector("类型", n)[i]赋值

一句话记住:在 R 里,循环不是"低级"也不是"高级"——它是最后手段。先向量化,再apply/map,实在不行再写for(但记得预分配)。


如果你告诉我你当前的数据形态(比如一个 data.frame 要逐行算什么 / 读一堆 CSV / 做模拟抽样),我可以帮你把具体代码写成最优版本,并指出该不该用循环。

❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄

💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍

🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙

http://www.gsyq.cn/news/1584735.html

相关文章:

  • 使用 Python 调用商品条形码查询API并解析商品信息
  • FAST-LIVO2 源码精读(九):VoxelMap 体素地图——哈希索引与八叉树平面拟合
  • 西瓜/甜瓜智能病虫害防控喷雾机上位机 Qt信创完整项目
  • 第31章:构建自定义Code Agent——打造专属的代码助手
  • Power BI 6 月重磅更新:9 大新功能全面提升数据分析效率
  • 【ComfyUI】在Windows电脑上安装 ComfyUI并通过python脚本调用API批量生成图片
  • window显示驱动开发-Direct3D 着色器代码
  • 计算机毕业设计之网络商城系统的设计与实现
  • TVA在机电产品视觉检测的创新应用(13)
  • 告别重复造轮子:C#抽象机器人控制层,兼容ABB/安川/发那科
  • Python之stubsplit包语法、参数和实际应用案例
  • 第六章—18—数据容器的通用操作
  • Kimi LeetCode 3347. 执行操作后元素的最高频率 II C语言实现
  • 【第十期】高级进阶篇:自动化与智能化 —— 如何用 Python 和 AI 辅助挖掘漏洞?
  • 2026-06-23:合并靠近字符。用go语言,现有仅含小写字母的字符串s与整数k,规则说明如下: 1. 判定标准:同一字符串里,若两个相同字母的位置索引差值不超过k,这两个字符视作相邻靠近字符。 2
  • HarmonyOS 6商城开发学习:平板竖屏下的底部“飞件“事故——用 layoutWeight 替掉 position 与 Stack 的响应式救火
  • 项目实训(十一)| 学习路线模块:个性化学习路线生成
  • 【Linux基础】Linux 必学基础指令:echo/cat/ 重定向 / 查找命令全解析
  • 阿里通义千问,8元叠加券,真的可以领到,真没有套路,真不用拉人头,实打实的,就是这么简单!
  • 信创业务技术全景解析:从项目实施到国密安全,一文读懂信创落地核心技术体系(PPT)
  • 《个人头像上传》二、Preferences用户首选项使用指南
  • TVA在机电产品视觉检测的创新应用(11)
  • 华为OD机试真题-预测新能源发电量(C/C++/Py/Java/Js/Go)
  • MacBook的实用小技巧
  • 高股息投资笔记-股票的人性2
  • 2 建立连接
  • LIVE项目解析:基于图像先验与时间一致性的AI视频编辑技术
  • 研发与业务协同工具怎么选?2026 主流团队云存储架构深度横评与避坑指南
  • [崛起]大国纪录片系列合集
  • 极小超曲面与Yau猜想:对称流形中的无限存在性定理