R: R 数据框与因子:tidyverse 数据科学基础
最后更新:2026-08-26
前面 9 课我们学了向量、矩阵、列表——它们各自有局限。数据框 (data.frame) 解决了所有问题:像 Excel 表格一样好读,又像矩阵一样高效。这一课我们学 R 真正的"主角"——数据框。
数据框 + tidyverse 是 R 数据科学的核心。从这一课开始,我们正式进入"用 R 做数据分析"的世界。
1. 你将学到
- data.frame() 与 tibble() 的差异
- 数据框的创建、查看、访问、修改
- 筛选(filter)、选择(select)、排序(arrange)
- 因子 (factor) 分类变量
- dplyr 入门(数据处理的"五件套")
- 分组聚合(group_by + summarise)
- 实战:员工数据综合分析
2. 一个 Excel 转 R 的故事
(1) 痛点:Excel 撑不住
小陈的市场调研表有 10,000 行 × 8 列:
员工ID 姓名 部门 工资 工龄 绩效 性别 入职日期
2024001 Alice 数据部 15000 3 A 男 2021-03-15
2024002 Bob 工程部 18000 5 A+ 男 2019-08-20
...
要按"部门"分组算平均工资、按"绩效"排序、找出高薪员工……Excel 要 30 分钟,公式嵌套到崩溃。
(2) R 的解法
library(dplyr)
# 5 行代码完成 5 个分析
df |>
group_by(部门) |>
summarise(平均工资 = mean(工资), 人数 = n()) |>
arrange(desc(平均工资)) |>
filter(平均工资 > 15000)
5 行代码替代 Excel 30 分钟。这就是数据框 + dplyr 的威力。
3. data.frame:R 的"主角"
(1) 什么是数据框?
graph TB
A["数据框 data.frame"] --> B["每列 = 1 个等长向量"]
A --> C["每行 = 1 条记录"]
A --> D["可混合类型(每列独立)"]
A --> E["底层是'等长向量的列表'"]
style A fill:#fff3cd
style B fill:#cce5ff
style C fill:#d4edda
style D fill:#f8d7da
style E fill:#e1d4ff
数据框是"等长向量的列表":
- 每列是一个向量(同类型)
- 所有列长度相等
- 不同列可以是不同类型(数字列 + 字符列 + 逻辑列)
(2) 创建数据框
# 方法 1:data.frame()(基础 R)
df <- data.frame(
name = c("Alice", "Bob", "Charlie"),
age = c(25, 30, 35),
score = c(95, 88, 92),
passed = c(TRUE, TRUE, TRUE)
)
print(df)
# name age score passed
# 1 Alice 25 95 TRUE
# 2 Bob 30 88 TRUE
# 3 Charlie 35 92 TRUE
# 方法 2:tibble(tidyverse 版本,推荐)
# install.packages("tibble")
library(tibble)
df2 <- tibble(
name = c("Alice", "Bob", "Charlie"),
age = c(25, 30, 35),
score = c(95, 88, 92)
)
print(df2)
(3) data.frame vs tibble
| 特性 | data.frame | tibble |
|---|---|---|
| 打印 | 默认打印所有行(大数据卡死) | 默认前 10 行 + 列类型 |
| 列名 | 支持特殊字符 | 强制合法 |
| 子集取子集 | df[, "col"] 返回向量 |
始终返回 tibble |
| 性能 | 较慢 | 更快 |
| 字符串 | 默认转 factor | 保持字符 |
# 查看结构
str(df)
# 'data.frame': 3 obs. of 4 variables:
# $ name : chr "Alice" "Bob" "Charlie"
# $ age : num 25 30 35
# $ score : num 95 88 92
# $ passed: logi TRUE TRUE TRUE
# 摘要统计
summary(df)
# name age score passed
# Length:3 Min. :25 Min. :88 Mode :logical
# Class :character 1st Qu.:27 Median :92 TRUE:3
# Mode :character Mean :30 Mean :91 NA's :0
# 3rd Qu.:32 3rd Qu.:93
# Max. :35 Max. :95
4. 访问数据框
(1) 4 种访问方式
| 方式 | 语法 | 返回 | 用途 |
|---|---|---|---|
df[i, j] |
第 i 行第 j 列 | 单值/向量/数据框 | 行列组合 |
df[i, ] |
第 i 行 | 数据框 | 取行 |
df[, j] |
第 j 列 | 向量(base R)/ 数据框 | 取列 |
df$name |
按列名 | 向量 | 取列 |
df[["name"]] |
按列名 | 向量 | 取列 |
df <- data.frame(
name = c("Alice", "Bob", "Charlie"),
age = c(25, 30, 35),
score = c(95, 88, 92)
)
# 取单个元素
df[2, 3] # [1] 88
df[2, "score"] # [1] 88
# 取整行
df[1, ] # 整行(数据框)
# name age score
# 1 Alice 25 95
# 取整列
df[, "age"] # 数字向量 [1] 25 30 35
df$age # 同上
df[["age"]] # 同上
(2) 逻辑索引(筛选)
# 找分数 > 90 的行
df[df$score > 90, ]
# name age score
# 1 Alice 25 95
# 3 Charlie 35 92
# 多条件
df[df$age > 25 & df$score > 85, ]
(3) ⚠️ 重要:base R 取列 vs tibble
# base R:取列返回向量
df[, "age"] # [1] 25 30 35 ← 数字向量
# tibble:取列返回 tibble
library(tibble)
tb <- as_tibble(df)
tb[, "age"] # ← tibble(1 行 1 列)
# 想从 tibble 取出向量:用 [[ ]] 或 $
tb[["age"]] # [1] 25 30 35 ← 数字向量
[, "col"] 不自动转向量。这是"防意外"设计——避免大数据框误以为是向量后整个打印卡死。
5. 修改数据框
(1) 添加列
# 用 $ 添加
df$city <- c("北京", "上海", "广州")
# 用 transform()
df <- transform(df, bonus = score * 100)
# 用 within() 引用列名
df <- within(df, level <- ifelse(score >= 90, "A", "B"))
print(df)
# name age score city bonus level
# 1 Alice 25 95 北京 9500 A
# 2 Bob 30 88 上海 8800 B
# 3 Charlie 35 92 广州 9200 A
(2) 修改列
# 整列赋值
df$age <- df$age + 1
# 条件修改
df$score[df$name == "Bob"] <- 90
(3) 删除列
# 赋 NULL
df$bonus <- NULL
df$level <- NULL
(4) 添加/删除行
# 添加行:用 rbind
new_row <- data.frame(name = "Diana", age = 28, score = 85, city = "深圳")
df <- rbind(df, new_row)
# 删除行:用负索引
df <- df[-1, ] # 删第 1 行
6. 因子 factor:分类变量
(1) 为什么需要因子?
R 用因子 (factor) 表示分类变量(如"低/中/高"),比字符向量强大:
# 字符向量(无序)
gender_char <- c("男", "女", "男", "女", "男")
# 因子(有序 + 有限取值)
gender_factor <- factor(gender_char, levels = c("男", "女"))
gender_factor
# [1] 男 女 男 女 男
# Levels: 男 女
(2) 因子的优势
graph TB
A["字符向量 '低' '中' '高'"] --> B["因子 factor<br/>+ levels 顺序<br/>+ 有限取值<br/>+ 节省内存"]
A --> C["缺点: 无法排序比较"]
B --> D["优点: 可比较大小<br/>可按顺序分组<br/>统计/建模知道类别数"]
style A fill:#f8d7da
style B fill:#d4edda
style D fill:#cce5ff
# 比较大小(字符不能,因子可以)
sizes <- factor(c("小", "大", "中", "大", "小"),
levels = c("小", "中", "大"),
ordered = TRUE)
sizes[1] < sizes[2] # TRUE ← 字符做不到!
(3) 因子常用操作
# 查看 levels
levels(sizes)
# [1] "小" "中" "大"
# 统计频次
table(sizes)
# sizes
# 小 中 大
# 2 1 2
# 重新排序
sizes <- factor(sizes, levels = c("大", "中", "小")) # 反转顺序
levels(sizes)
# [1] "大" "中" "小"
forcats 包(第 11 课学)处理因子,比 base R 更优雅。
(4) 数据框里的因子
# data.frame() 默认把字符列转因子(这是坑!)
df <- data.frame(
name = c("Alice", "Bob"), # 字符向量
stringsAsFactors = FALSE # 禁用自动转换(推荐)
)
str(df)
# $ name: chr "Alice" "Bob" ← 保持字符
stringsAsFactors = FALSE,但老代码可能不同。建议显式写 stringsAsFactors = FALSE。
7. dplyr 入门:数据处理五件套
dplyr 是 tidyverse 的核心,5 个函数覆盖 80% 数据处理场景:
(1) 安装和加载
install.packages("dplyr")
library(dplyr)
(2) 五件套函数
| 函数 | 作用 | 类比 SQL |
|---|---|---|
filter() |
筛选行 | WHERE |
select() |
选择列 | SELECT |
mutate() |
添加/修改列 | — |
arrange() |
排序 | ORDER BY |
summarise() |
聚合统计 | GROUP BY |
(3) 链式操作符 |>
# |> 是 R 4.1+ 的"管道符",把上一步结果传给下一步
df |> filter(age > 25) |> select(name, age)
(4) 实战:员工数据处理
# 准备数据
employees <- tibble(
id = 2024001:2024010,
name = c("Alice", "Bob", "Charlie", "Diana", "Eve",
"Frank", "Grace", "Henry", "Ivy", "Jack"),
department = c("数据部", "工程部", "数据部", "产品部", "工程部",
"数据部", "工程部", "产品部", "数据部", "工程部"),
salary = c(15000, 18000, 16000, 20000, 17000,
15500, 19000, 21000, 16500, 17500),
years = c(3, 5, 4, 7, 4, 2, 6, 8, 3, 5),
performance = factor(
c("A", "A+", "B", "A+", "A", "B", "A", "A+", "B", "A"),
levels = c("B", "A", "A+"),
ordered = TRUE
)
)
# 1. filter:筛选数据部员工
data_dept <- employees |> filter(department == "数据部")
# 2. select:选择部分列
basic_info <- employees |> select(name, department, salary)
# 3. mutate:添加年薪列
employees <- employees |> mutate(annual_salary = salary * 12)
# 4. arrange:按工资降序
ranked <- employees |> arrange(desc(salary))
# 5. summarise:按部门统计
dept_stats <- employees |>
group_by(department) |>
summarise(
人数 = n(),
平均工资 = mean(salary),
最高工资 = max(salary),
总年薪 = sum(annual_salary)
) |>
arrange(desc(平均工资))
print(dept_stats)
# # A tibble: 3 × 5
# department 人数 平均工资 最高工资 总年薪
# <chr> <int> <dbl> <dbl> <dbl>
# 1 产品部 2 20500 21000 492000
# 2 工程部 4 17875 19000 858000
# 3 数据部 4 15750 16500 756000
(5) 5 步组合示例
# 综合示例:找出高薪优秀员工
top_talent <- employees |>
filter(performance %in% c("A+", "A")) |> # ① 筛选绩效 A+ 或 A
filter(salary > 16000) |> # ② 筛选高薪
mutate(salary_level = ifelse(salary > 18000, "高", "中高")) |> # ③ 添加分类
arrange(desc(salary)) |> # ④ 排序
select(name, department, salary, performance, salary_level) # ⑤ 选择列
print(top_talent)
# # A tibble: 3 × 5
# name department salary performance salary_level
# <chr> <chr> <dbl> <ord> <chr>
# 1 Henry 产品部 21000 A+ 高
# 2 Diana 产品部 20000 A+ 高
# 3 Bob 工程部 18000 A+ 中高
8. 完整示例:员工数据综合分析
下面是一个完整工作流示例,把本课所有知识串起来。
▶ 示例:公司员工数据综合分析
# ============================================
# 公司员工数据综合分析
# 功能:用数据框 + dplyr 完整分析 10 个员工
# ============================================
library(dplyr)
# 1. Prepare data
employees <- tibble(
id = 2024001:2024010,
name = c("Alice", "Bob", "Charlie", "Diana", "Eve",
"Frank", "Grace", "Henry", "Ivy", "Jack"),
department = c("数据部", "工程部", "数据部", "产品部", "工程部",
"数据部", "工程部", "产品部", "数据部", "工程部"),
salary = c(15000, 18000, 16000, 20000, 17000,
15500, 19000, 21000, 16500, 17500),
years = c(3, 5, 4, 7, 4, 2, 6, 8, 3, 5),
performance = factor(
c("A", "A+", "B", "A+", "A", "B", "A", "A+", "B", "A"),
levels = c("B", "A", "A+"),
ordered = TRUE
)
)
cat("=== 原始数据 ===\n")
print(employees)
# 2. 查看结构
cat("\n=== 数据结构 ===\n")
str(employees)
# 3. 摘要统计
cat("\n=== 摘要 ===\n")
summary(employees)
# 4. 按部门统计
cat("\n=== 按部门统计 ===\n")
dept_summary <- employees |>
group_by(department) |>
summarise(
人数 = n(),
平均工资 = mean(salary),
中位工资 = median(salary),
最高工资 = max(salary),
最低工资 = min(salary),
平均工龄 = round(mean(years), 1)
) |>
arrange(desc(平均工资))
print(dept_summary)
# 5. 找出绩效优秀员工(A+ 或 A)
cat("\n=== 绩效优秀员工 ===\n")
top_perf <- employees |>
filter(performance %in% c("A+", "A")) |>
arrange(desc(salary)) |>
select(name, department, salary, performance, years)
print(top_perf)
# 6. 添加"工资级别"列
cat("\n=== 工资级别分类 ===\n")
employees <- employees |>
mutate(
salary_level = factor(
ifelse(salary >= 18000, "高",
ifelse(salary >= 16000, "中", "低")),
levels = c("低", "中", "高"),
ordered = TRUE
),
annual_salary = salary * 12
)
# 按工资级别统计
level_summary <- employees |>
group_by(salary_level) |>
summarise(人数 = n(), 平均工资 = mean(salary))
print(level_summary)
# 7. 找出最优员工(高薪 + 高绩效 + 资深)
cat("\n=== 最优员工(高薪 + 优秀绩效 + 资深)===\n")
top_talent <- employees |>
filter(salary >= 17000, performance == "A+", years >= 5) |>
select(name, department, salary, years, performance, annual_salary)
print(top_talent)
预期输出(节选):
=== 按部门统计 ===
# A tibble: 3 × 7
department 人数 平均工资 中位工资 最高工资 最低工资 平均工龄
<chr> <int> <dbl> <dbl> <dbl> <dbl> <dbl>
1 产品部 2 20500 20500 21000 20000 7.5
2 工程部 4 17875 17750 19000 17000 4.5
3 数据部 4 15750 15750 16500 15000 3
=== 绩效优秀员工 ===
# A tibble: 6 × 5
name department salary performance years
<chr> <chr> <dbl> <ord> <dbl>
1 Henry 产品部 21000 A+ 8
2 Diana 产品部 20000 A+ 7
3 Bob 工程部 18000 A+ 5
4 Grace 工程部 19000 A 6
5 Eve 工程部 17000 A 4
6 Jack 工程部 17500 A 5
❓ 常见问题
ordered = TRUE) ② 节省内存(只存整数编码) ③ 统计/建模知道类别数(避免"未知值"问题)。📖 小节
- 数据框是"等长向量的列表",每列独立类型,行记录数据
data.frame()基础 R,tibbletidyverse 现代版(推荐新项目)- 4 种访问:
df[i, j]df$namedf[["name"]]df[, j] - 因子 factor 是"有序分类变量",levels 控制顺序,节省内存
- dplyr 五件套:
filterselectmutatearrangesummarise group_by() | summarise()做分组聚合,|>管道符串联多步操作- mutate 引用刚创建的列(transform 做不到),是 mutate 在 tidyverse 中更受欢迎的原因
📝 作业
-
基础题:用
data.frame()创建一个students数据框(6 个学生 × 4 列:name/age/score/passed)。打印数据框、str()、summary(),访问第 3 行的所有列。 -
基础题:把上题的
name列转成factor(3 种 level),比较as.character()和as.numeric()的区别。验证因子支持比较大小(>)。 -
基础题:安装并加载
dplyr,对上题的students用filter()筛选score > 85的学生,用arrange()按分数降序排列。 -
进阶题:用
tibble创建一个sales数据框(10 行 × 4 列:product/region/amount/date)。① 用mutate()加discount = amount * 0.1;② 用group_by(region) + summarise()算各区域总销售额;③ 用arrange(desc())排名。 -
挑战题:模拟员工数据(10 人,3 个部门,工资/工龄/绩效),完成 7 步分析:① 加载 dplyr ② 按部门统计人数/平均工资 ③ 找出工龄 ≥ 5 年的高薪员工 ④ 用
mutate加年薪列 ⑤ 用case_when(或ifelse)分等级 ⑥ 按绩效排序 ⑦ 写入新数据框top_employees并保存为 CSV(write.csv())。