成绩是: 85
第3讲:控制语句与函数
2026年03月20日
提示
本讲是编程思维的核心——学完这一讲,你就能写出真正"有逻辑"的程序,而不只是一行行命令的堆砌。
让程序做判断
先看一个真实场景:
重要
分支结构让程序能根据不同情况执行不同代码。这是从"计算器"到"程序"的关键一跳——计算器只能算,程序能判断。
注记
花括号 { } 里可以有多行代码,它们作为一个整体执行。如果只有一行,花括号可以省略(但建议保留,增加可读性)。
[1] "优秀"
[1] "良好"
[1] "不及格"
提示
else if 是从上到下依次判断,一旦某个条件为 TRUE 就执行对应代码块,然后跳出整个 if-else 结构,不再检查后面的条件。
成绩合格,出勤达标 → 正常通过
成绩合格,但出勤不足 → 需补出勤
出勤达标,但成绩不足 → 需补考
成绩不足且出勤不够 → 直接不及格
ifelse():向量化的条件判断前面的 if 每次只能判断一个值;ifelse() 可以处理整个向量:
[1] "通过" "通过" "不通过" "通过" "不通过" "通过" "通过"
批量重复执行
不用循环,打印 1 到 5:
第1次
第2次
第3次
第4次
第5次
重要
循环的本质:让计算机代替你重复做一件事。人手动复制粘贴代码很累;计算机循环执行毫无怨言,而且无论重复 5 次还是 500 万次,代码都一样短。
循环的一个经典用法:边循环边把结果存起来。
1到100的和: 5050
提示
预先用 numeric(n) / character(n) 等分配好向量,比每次循环都 c(结果向量, 新值) 拼接要高效得多——后者每次都复制整个向量,数据量大时会很慢。
bill_length_mm 的均值: 43.9
bill_depth_mm 的均值: 17.1
flipper_length_mm 的均值: 201
body_mass_g 的均值: 4202
注记
注意这里用 penguins[[列名]] 而不是 penguins$列名——因为 列名 是一个变量(字符串),$ 后面不能跟变量,只能跟字面量列名。[[变量]] 才能动态取列。
1 × 1 = 1 1 × 2 = 2 1 × 3 = 3
2 × 1 = 2 2 × 2 = 4 2 × 3 = 6
3 × 1 = 3 3 × 2 = 6 3 × 3 = 9
Adelie 在 Biscoe : 44 只
Adelie 在 Dream : 56 只
Adelie 在 Torgersen : 52 只
Chinstrap 在 Dream : 68 只
Gentoo 在 Biscoe : 124 只
break 和 next:控制循环流程1 2 3 4 找到了5,停止!
R 的核心哲学:能向量化就别用循环
R 是为向量运算设计的语言。很多循环可以直接用向量操作代替,代码更短、更快:
条件驱动的循环
set.seed(42) # 设置随机种子,保证结果可重现
# 问题:平均需要投多少次才能连续出现3次正面?
模拟次数 <- 1000
所需投掷次数 <- numeric(模拟次数)
for (实验 in 1:模拟次数) {
连续正面 <- 0
总投掷 <- 0
while (连续正面 < 3) {
结果 <- sample(c("正", "反"), 1)
总投掷 <- 总投掷 + 1
if (结果 == "正") {
连续正面 <- 连续正面 + 1
} else {
连续正面 <- 0 # 一旦出现反面,重置计数
}
}
所需投掷次数[实验] <- 总投掷
}
cat("平均需要投掷:", round(mean(所需投掷次数), 1), "次\n")平均需要投掷: 13.8 次
repeat 和 break:先执行再判断第 1 次: 3
第 2 次: 6
掷出6点,停止!
警告
使用 while 和 repeat 时,务必确保循环能终止!如果忘记更新条件或忘记写 break,程序会陷入死循环。遇到卡死,按 Esc 键或点 RStudio 的停止按钮强制终止。
多路分支的优雅写法
# 用 if-else if 实现"查星期几"——重复结构太啰嗦
查星期 <- function(数字) {
if (数字 == 1) return("星期一")
else if (数字 == 2) return("星期二")
else if (数字 == 3) return("星期三")
else if (数字 == 4) return("星期四")
else if (数字 == 5) return("星期五")
else if (数字 == 6) return("星期六")
else if (数字 == 7) return("星期日")
else return("无效数字")
}
查星期(3)[1] "星期三"
7 个分支已经让代码很冗长,如果有几十个分支……
[1] "星期三"
[1] "星期日"
[1] "无效数字"
注记
switch() 的第一个参数是表达式(通常是字符串),然后是一系列 "值" = 结果 的对应关系,最后可以跟一个默认值(没有名字的那一项)。
[1] 13
[1] 30
| 场景 | 推荐方式 |
|---|---|
条件是范围判断(x > 10) |
if-else if |
条件是固定值枚举(x == "A") |
switch |
| 批量处理数据列/向量 | ifelse() |
| 复杂多条件组合 |
if-else if(配合 & |) |
提示
switch 的优势在于可读性——当你有 5 个以上固定值的分支时,switch 比一大串 else if 更清晰,也更容易修改和扩展。
把代码"打包"复用
A班均值: 87.8 ,标准差: 6.87
B班均值: 78.6 ,标准差: 9.4
C班均值: 86.8 ,标准差: 6.38
重要
DRY 原则(Don't Repeat Yourself):如果你复制粘贴了同一段代码超过两次,就应该把它写成函数。
重复代码的问题:① 难以修改:改一处要改所有地方;② 容易出错:复制时可能漏改变量名;③ 可读性差:核心逻辑被淹没在重复代码里。
A班 均值: 87.8 ,标准差: 6.87
B班 均值: 78.6 ,标准差: 9.4
C班 均值: 86.8 ,标准差: 6.38
你好, 张三 !
Hello, Alice !
Bonjour, Marie !
[1] 4
[1] NA
函数内部 x = 999
函数外部 x = 100
作用域的核心规则
<<- 可以,但不推荐)return() 返回输出,不要用全局变量传递数据 a b c
3 8 13
a b c
3 8 13
$bill_length_mm
均值 标准差
43.9 5.5
$flipper_length_mm
均值 标准差
200.9 14.1
$body_mass_g
均值 标准差
4202 802
| 函数 | 输入 | 输出 | 用途 |
|---|---|---|---|
apply(矩阵, 1或2, 函数) |
矩阵 | 向量 | 对矩阵的行(1)或列(2)应用函数 |
lapply(列表, 函数) |
列表/向量 | 列表 | 返回列表,保持原结构 |
sapply(列表, 函数) |
列表/向量 | 向量/矩阵 | 尽量简化为向量 |
tapply(向量, 分组, 函数) |
向量 + 分组 | 表格 | 按组汇总 |
提示
apply 函数族是 R 里"向量化循环"的核心工具。学好 lapply/sapply/tapply,很多场景就不需要手写 for 循环了。
# 编写一个函数,自动分析任意数值列与分组变量的关系
分组分析 <- function(数据框, 数值列名, 分组列名) {
数值 <- 数据框[[数值列名]]
分组 <- 数据框[[分组列名]]
结果 <- tapply(数值, 分组, function(x) {
x <- x[!is.na(x)]
c(
n = length(x),
均值 = round(mean(x), 1),
标准差 = round(sd(x), 1),
最小值 = min(x),
最大值 = max(x)
)
})
do.call(rbind, 结果)
}
分组分析(penguins, "body_mass_g", "species") n 均值 标准差 最小值 最大值
Adelie 151 3701 459 2850 4775
Chinstrap 68 3733 384 2700 4800
Gentoo 123 5076 504 3950 6300
善用现成工具
[1] 5
[1] 4
[1] 2.72
[1] 4.61
[1] 2
[1] 3
[1] 55
[1] 5.5
[1] 5.5
[1] 9.17
[1] 3.03
[1] 1 10
[1] 1 2 3 4 5 6 7 8 9 10
[1] 10 9 8 7 6 5 4 3 2 1
[1] 5 3 8 1 6 9 2 7 4 10
25%
3.25
25% 50% 75%
3.25 5.50 7.75
[1] 8
[1] "HELLO WORLD"
[1] "hello world"
[1] "数据挖掘"
[1] FALSE FALSE TRUE FALSE TRUE FALSE
[1] 2
[1] 3 5
[1] 1 2 4 6
which、any、all:逻辑判断三件套[1] 2 6
[1] 92 95
table() 与 prop.table():频率统计
Biscoe Dream Torgersen
Adelie 44 56 52
Chinstrap 0 68 0
Gentoo 124 0 0
Biscoe Dream Torgersen
Adelie 28.9 36.8 34.2
Chinstrap 0.0 100.0 0.0
Gentoo 100.0 0.0 0.0
library(palmerpenguins)
# 自定义:计算某列的摘要,返回格式化字符串
摘要字符串 <- function(x, 列名) {
x <- x[!is.na(x)]
sprintf("%s:均值=%.1f,标准差=%.1f,范围=[%.0f, %.0f]",
列名, mean(x), sd(x), min(x), max(x))
}
# 对三种企鹅分别输出体重摘要
for (物种 in levels(penguins$species)) {
子集 <- subset(penguins, species == 物种)
cat(摘要字符串(子集$body_mass_g, 物种), "\n")
}Adelie:均值=3700.7,标准差=458.6,范围=[2850, 4775]
Chinstrap:均值=3733.1,标准差=384.3,范围=[2700, 4800]
Gentoo:均值=5076.0,标准差=504.1,范围=[3950, 6300]
提示
sprintf() 是格式化字符串的强大工具:%.1f 表示保留1位小数的浮点数,%.0f 表示整数(0位小数),%s 表示字符串。和 C/Python 的 printf/% 格式一致。
if 分支:if-else if-else 实现多路判断;ifelse() 是向量化版本,用于批量处理数据列;嵌套 if 处理多维条件
for 循环:遍历序列,逐一执行;预分配结果向量提升效率;break 退出、next 跳过;能向量化时优先向量化,避免不必要的循环
while 循环:条件驱动,适合不知道循环次数的场景;repeat + break 是先执行再判断的变体;务必确保循环能终止
switch 语句:多个固定值分支时比 else if 更简洁;第一个参数通常是字符串;多值对应同结果时可"贯穿"
自定义函数:function() 打包可复用代码;默认参数增加灵活性;列表返回多个值;DRY 原则——复制粘贴超两次就该封装
R 内置函数:数学(abs/round/sqrt)、统计(mean/sd/quantile)、字符(paste/grep/gsub)、缺失值(is.na/na.rm)——善用这些工具,事半功倍
BMI计算(体重kg, 身高m),返回 BMI 值和对应分类(偏瘦 / 正常 / 超重 / 肥胖),用 if-else 实现分类逻辑for 循环计算斐波那契数列的前20项,并筛选出其中所有的偶数项switch 编写一个"单位换算器"函数,支持:千米↔︎英里、摄氏度↔︎华氏度、千克↔︎磅批量描述(数据框),自动对数据框每一列输出摘要(数值列输出均值/标准差,字符/因子列输出频率最高的类别)while 循环模拟"蒙特卡洛法估算π":在单位正方形内随机撒点,统计落在单位圆内的比例,当估算误差小于 0.001 时停止,输出总撒点次数penguins 数据,编写函数计算每个物种在每个岛屿的"嘴峰长宽比"(bill_length_mm / bill_depth_mm)的均值和变异系数(CV = 标准差/均值),用列表存储结果第4讲:数据导入、清洗与整理(tidyverse 初步)
readr / readxl:高效数据读取tidyr:宽表变长表,pivot_longer / pivot_wider
dplyr:数据操作的"瑞士军刀"
filter()、select()、mutate()、summarise()、group_by()
|>(%>%)第3讲:控制语句与函数
"程序就是把无限大的问题,拆解成有限次的重复判断。"
——编程实践中的领悟
数据挖掘与R语言 | 第3讲:控制语句与函数