数据挖掘与R语言

第3讲:控制语句与函数

2026年03月20日

上讲回顾

  • 数据类型:数值、字符、逻辑三大常用类型;class() 查看,as.*() 转换
  • 向量:R 的基础单元;c() 创建,[ ] 索引,逻辑索引是筛选利器
  • 矩阵:二维同类型容器;apply() 按行/列汇总
  • 数据框:日常分析主力;$ 取列,subset() 筛行,merge() 关联
  • 列表与因子:列表装任意结构;因子处理分类变量,levels 控制顺序

本讲内容

  • Part 1:if 分支 ——让程序做判断(约15分钟)
  • Part 2:for 循环 ——批量重复执行(约20分钟)
  • Part 3:while 循环 ——条件驱动的循环(约10分钟)
  • Part 4:switch 语句 ——多路分支的优雅写法(约10分钟)
  • Part 5:自定义函数 ——把代码"打包"复用(约20分钟)
  • Part 6:R 内置函数 ——善用现成工具(约15分钟)

提示

本讲是编程思维的核心——学完这一讲,你就能写出真正"有逻辑"的程序,而不只是一行行命令的堆砌。

Part 1 if 分支

让程序做判断

1.1 为什么需要分支?

先看一个真实场景:

▶️ 查看代码
成绩 <- 85

# 没有分支:只能输出固定内容
cat("成绩是:", 成绩, "\n")
成绩是: 85 
▶️ 查看代码
# 有了 if 分支:程序能"思考"了
if (成绩 >= 60) {
  cat("恭喜,通过了!\n")
} else {
  cat("很遗憾,需要补考。\n")
}
恭喜,通过了!

重要

分支结构让程序能根据不同情况执行不同代码。这是从"计算器"到"程序"的关键一跳——计算器只能算,程序能判断。

1.2 if 的基本语法

▶️ 查看代码
# 最基本的形式
if (条件) {
  # 条件为 TRUE 时执行这里
}

# 有 else 的形式
if (条件) {
  # TRUE 时
} else {
  # FALSE 时
}

# 多路分支:else if
if (条件1) {
  # 条件1 为 TRUE
} else if (条件2) {
  # 条件2 为 TRUE
} else {
  # 以上都不满足
}

注记

花括号 { } 里可以有多行代码,它们作为一个整体执行。如果只有一行,花括号可以省略(但建议保留,增加可读性)。

1.3 多路分支:成绩等级判断

▶️ 查看代码
判断等级 <- function(分数) {
  if (分数 >= 90) {
    等级 <- "优秀"
  } else if (分数 >= 80) {
    等级 <- "良好"
  } else if (分数 >= 70) {
    等级 <- "中等"
  } else if (分数 >= 60) {
    等级 <- "及格"
  } else {
    等级 <- "不及格"
  }
  return(等级)
}

判断等级(95)
[1] "优秀"
▶️ 查看代码
判断等级(82)
[1] "良好"
▶️ 查看代码
判断等级(55)
[1] "不及格"

提示

else if从上到下依次判断,一旦某个条件为 TRUE 就执行对应代码块,然后跳出整个 if-else 结构,不再检查后面的条件。

1.4 嵌套 if:条件中的条件

▶️ 查看代码
评估学生 <- function(成绩, 出勤率) {
  if (成绩 >= 60) {
    if (出勤率 >= 0.8) {
      cat("成绩合格,出勤达标 → 正常通过\n")
    } else {
      cat("成绩合格,但出勤不足 → 需补出勤\n")
    }
  } else {
    if (出勤率 >= 0.8) {
      cat("出勤达标,但成绩不足 → 需补考\n")
    } else {
      cat("成绩不足且出勤不够 → 直接不及格\n")
    }
  }
}

评估学生(75, 0.9)
成绩合格,出勤达标 → 正常通过
▶️ 查看代码
评估学生(75, 0.6)
成绩合格,但出勤不足 → 需补出勤
▶️ 查看代码
评估学生(50, 0.9)
出勤达标,但成绩不足 → 需补考
▶️ 查看代码
评估学生(50, 0.6)
成绩不足且出勤不够 → 直接不及格

1.5 ifelse():向量化的条件判断

前面的 if 每次只能判断一个值ifelse() 可以处理整个向量

▶️ 查看代码
成绩 <- c(85, 92, 58, 76, 43, 95, 61)

# ifelse(条件向量, TRUE时的值, FALSE时的值)
结果 <- ifelse(成绩 >= 60, "通过", "不通过")
结果
[1] "通过"   "通过"   "不通过" "通过"   "不通过" "通过"   "通过"  
▶️ 查看代码
# 嵌套 ifelse 实现多档分级
等级 <- ifelse(成绩 >= 90, "优秀",
         ifelse(成绩 >= 80, "良好",
         ifelse(成绩 >= 70, "中等",
         ifelse(成绩 >= 60, "及格", "不及格"))))

data.frame(成绩 = 成绩, 等级 = 等级)
  成绩   等级
1   85   良好
2   92   优秀
3   58 不及格
4   76   中等
5   43 不及格
6   95   优秀
7   61   及格

注记

if vs ifelse()if 是控制流语句,用于程序逻辑分支;ifelse() 是向量化函数,用于数据列的批量转换。日常处理数据列时,优先用 ifelse()

1.6 小测验:预测输出

▶️ 查看代码
x <- 15

if (x > 10) {
  cat("大于10\n")
  if (x > 20) {
    cat("而且大于20\n")
  } else {
    cat("但不大于20\n")
  }
} else {
  cat("不大于10\n")
}
▶️ 查看代码
x <- 15

if (x > 10) {
  cat("大于10\n")
  if (x > 20) {
    cat("而且大于20\n")
  } else {
    cat("但不大于20\n")
  }
} else {
  cat("不大于10\n")
}
大于10
但不大于20

Part 2 for 循环

批量重复执行

2.1 循环的直觉

不用循环,打印 1 到 5:

▶️ 查看代码
cat("第1次\n")
第1次
▶️ 查看代码
cat("第2次\n")
第2次
▶️ 查看代码
cat("第3次\n")
第3次
▶️ 查看代码
cat("第4次\n")
第4次
▶️ 查看代码
cat("第5次\n")
第5次

用循环,打印 1 到 100,代码一样简洁:

▶️ 查看代码
for (i in 1:5) {
  cat("第", i, "次\n")
}
第 1 次
第 2 次
第 3 次
第 4 次
第 5 次

重要

循环的本质:让计算机代替你重复做一件事。人手动复制粘贴代码很累;计算机循环执行毫无怨言,而且无论重复 5 次还是 500 万次,代码都一样短。

2.2 for 循环的基本语法

▶️ 查看代码
for (变量 in 序列) {
  # 对序列中的每个元素,执行这里的代码
  # 变量 会依次取序列中每个值
}
▶️ 查看代码
# 遍历数字向量
for (i in c(2, 4, 6, 8)) {
  cat(i, "的平方是", i^2, "\n")
}
2 的平方是 4 
4 的平方是 16 
6 的平方是 36 
8 的平方是 64 
▶️ 查看代码
# 遍历字符向量
水果 <- c("苹果", "香蕉", "橙子")
for (f in 水果) {
  cat("我喜欢吃", f, "\n")
}
我喜欢吃 苹果 
我喜欢吃 香蕉 
我喜欢吃 橙子 

2.3 用循环累积结果

循环的一个经典用法:边循环边把结果存起来

▶️ 查看代码
# 计算 1 到 100 的和
总和 <- 0          # 先初始化一个"容器"

for (i in 1:100) {
  总和 <- 总和 + i  # 每次循环把 i 加进去
}

cat("1到100的和:", 总和, "\n")
1到100的和: 5050 
▶️ 查看代码
# 用向量收集循环结果(推荐方式:预分配空间)
n <- 10
结果向量 <- numeric(n)    # 预先创建长度为n的空向量

for (i in 1:n) {
  结果向量[i] <- i^2      # 把每次的结果存到对应位置
}

结果向量
 [1]   1   4   9  16  25  36  49  64  81 100

提示

预先用 numeric(n) / character(n) 等分配好向量,比每次循环都 c(结果向量, 新值) 拼接要高效得多——后者每次都复制整个向量,数据量大时会很慢。

2.4 遍历数据框的列

▶️ 查看代码
# 对 penguins 的每个数值列计算均值
library(palmerpenguins)

数值列 <- c("bill_length_mm", "bill_depth_mm",
            "flipper_length_mm", "body_mass_g")

for (列名 in 数值列) {
  均值 <- mean(penguins[[列名]], na.rm = TRUE)
  cat(列名, "的均值:", round(均值, 2), "\n")
}
bill_length_mm 的均值: 43.9 
bill_depth_mm 的均值: 17.1 
flipper_length_mm 的均值: 201 
body_mass_g 的均值: 4202 

注记

注意这里用 penguins[[列名]] 而不是 penguins$列名——因为 列名 是一个变量(字符串),$ 后面不能跟变量,只能跟字面量列名。[[变量]] 才能动态取列。

2.5 嵌套循环:双重遍历

▶️ 查看代码
# 乘法表(3 × 3 演示)
for (i in 1:3) {
  for (j in 1:3) {
    cat(i, "×", j, "=", i * j, "  ")
  }
  cat("\n")   # 每行结束换行
}
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   
▶️ 查看代码
# 实际应用:统计各物种各岛屿的企鹅数量
物种 <- levels(penguins$species)
岛屿 <- levels(penguins$island)

for (sp in 物种) {
  for (isl in 岛屿) {
    n <- nrow(subset(penguins, species == sp & island == isl))
    if (n > 0) {
      cat(sp, "在", isl, ":", n, "只\n")
    }
  }
}
Adelie 在 Biscoe : 44 只
Adelie 在 Dream : 56 只
Adelie 在 Torgersen : 52 只
Chinstrap 在 Dream : 68 只
Gentoo 在 Biscoe : 124 只

2.6 breaknext:控制循环流程

▶️ 查看代码
# break:遇到条件,立刻退出整个循环
for (i in 1:10) {
  if (i == 5) {
    cat("找到了5,停止!\n")
    break
  }
  cat(i, " ")
}
1  2  3  4  找到了5,停止!
▶️ 查看代码
# next:跳过本次迭代,继续下一次(相当于其他语言的 continue)
for (i in 1:10) {
  if (i %% 2 == 0) next   # 跳过偶数
  cat(i, " ")              # 只打印奇数
}
1  3  5  7  9  
▶️ 查看代码
cat("\n")

提示

  • break:紧急出口,找到想要的就停下来
  • next:跳过不需要处理的情况,继续下一个

两者都只影响最近一层的循环。

2.7 R 中的"反循环"思想:向量化

R 的核心哲学:能向量化就别用循环

R 是为向量运算设计的语言。很多循环可以直接用向量操作代替,代码更短、更快:

▶️ 查看代码
# 向量化方式(快,简洁)
x <- 1:10
结果 <- x^2   # R 自动对每个元素计算
结果
 [1]   1   4   9  16  25  36  49  64  81 100
▶️ 查看代码
# 什么时候必须用循环?
# 当每次迭代依赖上一次的结果时(如斐波那契数列)
fib <- numeric(10)
fib[1] <- 1
fib[2] <- 1
for (i in 3:10) {
  fib[i] <- fib[i-1] + fib[i-2]
}
fib
 [1]  1  1  2  3  5  8 13 21 34 55

Part 3 while 循环

条件驱动的循环

3.1 while 与 for 的区别

▶️ 查看代码
# for:知道要循环多少次(遍历一个序列)
for (i in 1:10) { ... }

# while:不知道循环多少次,只知道"什么时候停"
while (条件) {
  # 只要条件为 TRUE,就一直执行
  # 注意:循环体内必须有能改变条件的代码,否则死循环!
}
▶️ 查看代码
# 示例:从1开始累加,直到超过100
总和 <- 0
i <- 1

while (总和 <= 100) {
  总和 <- 总和 + i
  i <- i + 1
}

cat("累加到第", i - 1, "个数时,总和首次超过100,总和 =", 总和, "\n")
累加到第 14 个数时,总和首次超过100,总和 = 105 

3.2 while 的实际应用:模拟投硬币

▶️ 查看代码
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 次

3.3 repeatbreak:先执行再判断

▶️ 查看代码
# repeat 是"无限循环",必须配合 break 来退出
# 适合:至少执行一次,然后再判断是否继续

set.seed(123)
次数 <- 0

repeat {
  次数 <- 次数 + 1
  抽到 <- sample(1:6, 1)   # 模拟掷骰子
  cat("第", 次数, "次:", 抽到, "\n")
  
  if (抽到 == 6) {
    cat("掷出6点,停止!\n")
    break
  }
}
第 1 次: 3 
第 2 次: 6 
掷出6点,停止!

警告

使用 whilerepeat 时,务必确保循环能终止!如果忘记更新条件或忘记写 break,程序会陷入死循环。遇到卡死,按 Esc 键或点 RStudio 的停止按钮强制终止。

Part 4 switch 语句

多路分支的优雅写法

4.1 多个 else if 的困境

▶️ 查看代码
# 用 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 个分支已经让代码很冗长,如果有几十个分支……

4.2 switch:更优雅的多路分支

▶️ 查看代码
# switch 版本:简洁得多
查星期_switch <- function(数字) {
  switch(as.character(数字),
    "1" = "星期一",
    "2" = "星期二",
    "3" = "星期三",
    "4" = "星期四",
    "5" = "星期五",
    "6" = "星期六",
    "7" = "星期日",
    "无效数字"    # 最后一项无名字,作为默认值
  )
}

查星期_switch(3)
[1] "星期三"
▶️ 查看代码
查星期_switch(7)
[1] "星期日"
▶️ 查看代码
查星期_switch(9)
[1] "无效数字"

注记

switch() 的第一个参数是表达式(通常是字符串),然后是一系列 "值" = 结果 的对应关系,最后可以跟一个默认值(没有名字的那一项)。

4.3 switch 的高级用法

▶️ 查看代码
# 根据操作名称执行不同计算
计算 <- function(x, y, 操作) {
  switch(操作,
    "加" = x + y,
    "减" = x - y,
    "乘" = x * y,
    "除" = if (y != 0) x / y else stop("除数不能为0"),
    stop(paste("未知操作:", 操作))   # 默认:报错
  )
}

计算(10, 3, "加")
[1] 13
▶️ 查看代码
计算(10, 3, "乘")
[1] 30
▶️ 查看代码
# 多个值对应同一结果(省略 = 右边的值,相当于"贯穿"到下一项)
分类 <- function(动物) {
  switch(动物,
    "狗" =,
    "猫" =,
    "兔" = "宠物",
    "狮" =,
    "虎" =,
    "豹" = "野生动物",
    "未知"
  )
}

分类("猫")
[1] "宠物"
▶️ 查看代码
分类("虎")
[1] "野生动物"

4.4 if vs switch vs ifelse 选择指南

场景 推荐方式
条件是范围判断(x > 10 if-else if
条件是固定值枚举(x == "A" switch
批量处理数据列/向量 ifelse()
复杂多条件组合 if-else if(配合 & |

提示

switch 的优势在于可读性——当你有 5 个以上固定值的分支时,switch 比一大串 else if 更清晰,也更容易修改和扩展。

Part 5 自定义函数

把代码"打包"复用

5.1 为什么要写函数?

▶️ 查看代码
# 这段代码有什么问题?
班级A <- c(85, 92, 78, 96, 88)
班级B <- c(72, 68, 85, 91, 77)
班级C <- c(90, 95, 88, 82, 79)

cat("A班均值:", mean(班级A), ",标准差:", round(sd(班级A), 2), "\n")
A班均值: 87.8 ,标准差: 6.87 
▶️ 查看代码
cat("B班均值:", mean(班级B), ",标准差:", round(sd(班级B), 2), "\n")
B班均值: 78.6 ,标准差: 9.4 
▶️ 查看代码
cat("C班均值:", mean(班级C), ",标准差:", round(sd(班级C), 2), "\n")
C班均值: 86.8 ,标准差: 6.38 

重要

DRY 原则(Don't Repeat Yourself):如果你复制粘贴了同一段代码超过两次,就应该把它写成函数。

重复代码的问题:① 难以修改:改一处要改所有地方;② 容易出错:复制时可能漏改变量名;③ 可读性差:核心逻辑被淹没在重复代码里。

5.2 函数的基本语法

▶️ 查看代码
# 定义函数
函数名 <- function(参数1, 参数2, ...) {
  # 函数体:具体做什么
  结果 <- 参数1 + 参数2
  return(结果)   # 返回值(可省略,默认返回最后一个表达式)
}

# 调用函数
函数名(值1, 值2)
▶️ 查看代码
# 改写前面的重复代码:封装成函数
成绩统计 <- function(成绩向量, 班级名称) {
  cat(班级名称, "均值:", round(mean(成绩向量), 2),
      ",标准差:", round(sd(成绩向量), 2), "\n")
}

班级A <- c(85, 92, 78, 96, 88)
班级B <- c(72, 68, 85, 91, 77)
班级C <- c(90, 95, 88, 82, 79)

成绩统计(班级A, "A班")
A班 均值: 87.8 ,标准差: 6.87 
▶️ 查看代码
成绩统计(班级B, "B班")
B班 均值: 78.6 ,标准差: 9.4 
▶️ 查看代码
成绩统计(班级C, "C班")
C班 均值: 86.8 ,标准差: 6.38 

5.3 参数的默认值

▶️ 查看代码
# 给参数设定默认值:调用时如不提供,使用默认值
问候 <- function(姓名, 语言 = "中文") {
  if (语言 == "中文") {
    cat("你好,", 姓名, "!\n")
  } else if (语言 == "英文") {
    cat("Hello,", 姓名, "!\n")
  } else {
    cat("Bonjour,", 姓名, "!\n")
  }
}

问候("张三")           # 使用默认语言(中文)
你好, 张三 !
▶️ 查看代码
问候("Alice", "英文")  # 指定语言
Hello, Alice !
▶️ 查看代码
问候("Marie", "法文")  # 其他情况
Bonjour, Marie !
▶️ 查看代码
# 实用示例:汇总函数,默认去除缺失值
安全均值 <- function(x, 去除NA = TRUE, 小数位 = 2) {
  结果 <- mean(x, na.rm = 去除NA)
  round(结果, 小数位)
}

安全均值(c(1, 2, NA, 4, 5))                   # 使用默认参数
[1] 3
▶️ 查看代码
安全均值(c(1, 2, NA, 4, 5), 小数位 = 4)       # 只改小数位
[1] 3

5.4 函数的返回值

▶️ 查看代码
# return() 显式返回——可以在函数中途返回(提前退出)
安全开方 <- function(x) {
  if (x < 0) {
    return(NA)   # 提前返回,后面的代码不执行
  }
  return(sqrt(x))
}

安全开方(16)
[1] 4
▶️ 查看代码
安全开方(-4)
[1] NA
▶️ 查看代码
# 用列表返回多个值
成绩分析 <- function(成绩向量) {
  list(
    均值   = round(mean(成绩向量), 2),
    中位数 = median(成绩向量),
    标准差 = round(sd(成绩向量), 2),
    最高分 = max(成绩向量),
    最低分 = min(成绩向量),
    及格率 = mean(成绩向量 >= 60)
  )
}

结果 <- 成绩分析(c(85, 92, 58, 76, 43, 95, 61, 88))
结果$均值
[1] 74.8
▶️ 查看代码
结果$及格率
[1] 0.75

5.5 函数的作用域:局部变量 vs 全局变量

▶️ 查看代码
x <- 100   # 全局变量

修改x <- function() {
  x <- 999   # 这是局部变量,只在函数内部存在
  cat("函数内部 x =", x, "\n")
}

修改x()
函数内部 x = 999 
▶️ 查看代码
cat("函数外部 x =", x, "\n")   # 全局变量没有被改变!
函数外部 x = 100 

作用域的核心规则

  • 函数内部创建的变量是局部变量,函数结束就消失
  • 函数可以读取全局变量,但不能直接修改它(用 <<- 可以,但不推荐)
  • 建议:函数通过参数接收输入,通过 return() 返回输出,不要用全局变量传递数据

5.6 匿名函数:一次性使用的小函数

▶️ 查看代码
# 不给函数起名字,直接使用(常用于 apply 系列函数中)
数字 <- list(a = 1:5, b = 6:10, c = 11:15)

# 用命名函数
sapply(数字, mean)
 a  b  c 
 3  8 13 
▶️ 查看代码
# 用匿名函数(R 4.1+ 的 \(x) 语法,更简洁)
sapply(数字, \(x) round(mean(x), 1))
 a  b  c 
 3  8 13 
▶️ 查看代码
# 匿名函数 + lapply:对 penguins 各数值列计算摘要统计
library(palmerpenguins)

数值列 <- penguins[, c("bill_length_mm", "flipper_length_mm", "body_mass_g")]

lapply(数值列, \(x) c(
  均值   = round(mean(x, na.rm = TRUE), 1),
  标准差 = round(sd(x, na.rm = TRUE), 1)
))
$bill_length_mm
  均值 标准差 
  43.9    5.5 

$flipper_length_mm
  均值 标准差 
 200.9   14.1 

$body_mass_g
  均值 标准差 
  4202    802 

5.7 函数式编程:apply 函数族

函数 输入 输出 用途
apply(矩阵, 1或2, 函数) 矩阵 向量 对矩阵的行(1)或列(2)应用函数
lapply(列表, 函数) 列表/向量 列表 返回列表,保持原结构
sapply(列表, 函数) 列表/向量 向量/矩阵 尽量简化为向量
tapply(向量, 分组, 函数) 向量 + 分组 表格 按组汇总
▶️ 查看代码
# tapply:按物种计算各测量值的均值
tapply(penguins$body_mass_g, penguins$species, mean, na.rm = TRUE)
   Adelie Chinstrap    Gentoo 
     3701      3733      5076 

提示

apply 函数族是 R 里"向量化循环"的核心工具。学好 lapply/sapply/tapply,很多场景就不需要手写 for 循环了。

5.8 综合实战:编写完整的数据分析函数

▶️ 查看代码
# 编写一个函数,自动分析任意数值列与分组变量的关系
分组分析 <- 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

Part 6 R 内置函数

善用现成工具

6.1 数学函数

▶️ 查看代码
# 常用数学函数
abs(-5)           # 绝对值:5
[1] 5
▶️ 查看代码
sqrt(16)          # 平方根:4
[1] 4
▶️ 查看代码
exp(1)            # e^1 ≈ 2.718
[1] 2.72
▶️ 查看代码
log(100)          # 自然对数(以e为底)
[1] 4.61
▶️ 查看代码
log10(100)        # 以10为底的对数:2
[1] 2
▶️ 查看代码
log(8, base = 2)  # 以2为底的对数:3
[1] 3
▶️ 查看代码
ceiling(3.2)      # 向上取整:4
[1] 4
▶️ 查看代码
floor(3.8)        # 向下取整:3
[1] 3
▶️ 查看代码
round(3.567, 2)   # 四舍五入到2位小数:3.57
[1] 3.57
▶️ 查看代码
trunc(3.9)        # 截断小数部分:3
[1] 3
▶️ 查看代码
# 三角函数(参数以弧度为单位)
sin(pi / 2)       # sin(90°) = 1
[1] 1
▶️ 查看代码
cos(0)            # cos(0°) = 1
[1] 1

6.2 统计函数

▶️ 查看代码
x <- c(4, 7, 2, 9, 1, 5, 8, 3, 6, 10)

# 基础统计
sum(x)       # 求和:55
[1] 55
▶️ 查看代码
mean(x)      # 均值:5.5
[1] 5.5
▶️ 查看代码
median(x)    # 中位数:5.5
[1] 5.5
▶️ 查看代码
var(x)       # 方差
[1] 9.17
▶️ 查看代码
sd(x)        # 标准差
[1] 3.03
▶️ 查看代码
range(x)     # 范围:最小值和最大值
[1]  1 10
▶️ 查看代码
# 排序与排名
sort(x)                        # 从小到大排序
 [1]  1  2  3  4  5  6  7  8  9 10
▶️ 查看代码
sort(x, decreasing = TRUE)     # 从大到小
 [1] 10  9  8  7  6  5  4  3  2  1
▶️ 查看代码
order(x)                       # 排序的下标(原位置索引)
 [1]  5  3  8  1  6  9  2  7  4 10
▶️ 查看代码
# 分位数
quantile(x, 0.25)              # 第25百分位数(Q1)
 25% 
3.25 
▶️ 查看代码
quantile(x, c(0.25, 0.5, 0.75))   # 四分位数
 25%  50%  75% 
3.25 5.50 7.75 

6.3 字符串函数

▶️ 查看代码
文本 <- "数据挖掘与R语言"

nchar(文本)                      # 字符数量:8
[1] 8
▶️ 查看代码
toupper("hello world")           # 转大写
[1] "HELLO WORLD"
▶️ 查看代码
tolower("HELLO WORLD")           # 转小写
[1] "hello world"
▶️ 查看代码
substr(文本, 1, 4)               # 截取第1到第4个字符
[1] "数据挖掘"
▶️ 查看代码
# 拼接与分割
paste("R", "语言", sep = "-")      # 带分隔符拼接
[1] "R-语言"
▶️ 查看代码
paste0("第", 1:3, "讲")           # 批量拼接(无分隔符)
[1] "第1讲" "第2讲" "第3讲"
▶️ 查看代码
strsplit("A,B,C,D", split = ",")   # 按分隔符分割
[[1]]
[1] "A" "B" "C" "D"
▶️ 查看代码
# 搜索与替换
grepl("R", 文本)                                 # 是否包含"R"?
[1] TRUE
▶️ 查看代码
grep("R", c("R语言", "Python", "R包"))           # 哪些元素包含"R"?
[1] 1 3
▶️ 查看代码
sub("R", "Python", "学好R语言,再学R包")         # 替换第一个匹配
[1] "学好Python语言,再学R包"
▶️ 查看代码
gsub("R", "Python", "学好R语言,再学R包")        # 替换所有匹配
[1] "学好Python语言,再学Python包"

6.4 缺失值处理函数

▶️ 查看代码
x <- c(1, 2, NA, 4, NA, 6)

is.na(x)              # 哪些是 NA?
[1] FALSE FALSE  TRUE FALSE  TRUE FALSE
▶️ 查看代码
sum(is.na(x))         # 有多少个 NA?
[1] 2
▶️ 查看代码
which(is.na(x))       # NA 在哪些位置?
[1] 3 5
▶️ 查看代码
x[!is.na(x)]          # 去掉 NA,只保留有效值
[1] 1 2 4 6
▶️ 查看代码
# na.rm 参数:大多数统计函数都支持
mean(x)                   # NA 会"传染",结果是 NA
[1] NA
▶️ 查看代码
mean(x, na.rm = TRUE)     # 忽略 NA,计算有效值的均值
[1] 3.25
▶️ 查看代码
# 替换 NA:用均值填充
x_填充 <- x
x_填充[is.na(x_填充)] <- mean(x, na.rm = TRUE)
x_填充
[1] 1.00 2.00 3.25 4.00 3.25 6.00

6.5 whichanyall:逻辑判断三件套

▶️ 查看代码
成绩 <- c(85, 92, 58, 76, 43, 95, 61)

which(成绩 >= 90)        # 满足条件的位置下标
[1] 2 6
▶️ 查看代码
成绩[which(成绩 >= 90)]  # 等价于 成绩[成绩 >= 90]
[1] 92 95
▶️ 查看代码
any(成绩 < 60)    # 有没有人不及格?(任一为TRUE则TRUE)
[1] TRUE
▶️ 查看代码
all(成绩 >= 60)   # 是否所有人都及格了?(全为TRUE才TRUE)
[1] FALSE
▶️ 查看代码
# 实用:在数据清洗中检查数据质量
library(palmerpenguins)

any(is.na(penguins$body_mass_g))          # 有缺失值吗?
[1] TRUE
▶️ 查看代码
sum(is.na(penguins$body_mass_g))          # 共几个缺失值?
[1] 2
▶️ 查看代码
mean(is.na(penguins$body_mass_g)) * 100   # 缺失值比例(%)
[1] 0.581

6.6 table()prop.table():频率统计

▶️ 查看代码
# table():计数
table(penguins$species)

   Adelie Chinstrap    Gentoo 
      152        68       124 
▶️ 查看代码
# prop.table():转为比例
round(prop.table(table(penguins$species)) * 100, 1)  # 转为百分比

   Adelie Chinstrap    Gentoo 
     44.2      19.8      36.0 
▶️ 查看代码
# 二维交叉表
交叉表 <- table(penguins$species, penguins$island)
交叉表
           
            Biscoe Dream Torgersen
  Adelie        44    56        52
  Chinstrap      0    68         0
  Gentoo       124     0         0
▶️ 查看代码
# 按行的比例(每种企鹅分布在各岛的比例)
round(prop.table(交叉表, margin = 1) * 100, 1)
           
            Biscoe Dream Torgersen
  Adelie      28.9  36.8      34.2
  Chinstrap    0.0 100.0       0.0
  Gentoo     100.0   0.0       0.0

6.7 综合实战:函数 + 循环 + 内置函数

▶️ 查看代码
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)——善用这些工具,事半功倍

课后练习

基础练习(必做)

  1. 编写函数 BMI计算(体重kg, 身高m),返回 BMI 值和对应分类(偏瘦 / 正常 / 超重 / 肥胖),用 if-else 实现分类逻辑
  2. for 循环计算斐波那契数列的前20项,并筛选出其中所有的偶数项
  3. switch 编写一个"单位换算器"函数,支持:千米↔︎英里、摄氏度↔︎华氏度、千克↔︎磅

进阶挑战(选做)

  1. 编写函数 批量描述(数据框),自动对数据框每一列输出摘要(数值列输出均值/标准差,字符/因子列输出频率最高的类别)
  2. while 循环模拟"蒙特卡洛法估算π":在单位正方形内随机撒点,统计落在单位圆内的比例,当估算误差小于 0.001 时停止,输出总撒点次数
  3. 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讲:控制语句与函数


"程序就是把无限大的问题,拆解成有限次的重复判断。"

——编程实践中的领悟