R: R 列表 (List):R 的"万能容器"
最后更新:2026-08-26
前面 3 课我们学了向量、矩阵——它们都要求所有元素同类型。但现实中数据往往是异构的:学生信息包含学号(数字)、姓名(字符)、成绩(向量)。R 用列表 (list) 解决异构存储问题。
列表是 R 真正的"瑞士军刀"——可以装任何类型(向量、矩阵、数据框、函数、另一个列表)。数据框 data.frame 底层就是"等长向量的列表"。这一课是 Phase 1 的关键。
1. 你将学到
- list() 创建任意类型容器
- 命名列表(按名字访问)
- 4 种访问方式(
[[ ]][ ]$[[ "name" ]]) - 列表的修改、添加、删除
- lapply() 批量处理列表
- 嵌套列表
- list2env() 把列表转环境变量
2. 一个学生信息管理的故事
(1) 痛点:异构数据存储
Ivan是 HR,要存储 1 个员工的完整信息:
工号:2024001(数字)
姓名:Alice(字符)
部门:数据分析部(字符)
工资:15000(数字)
技能:c("R", "Python", "SQL")(字符向量)
如果用向量,所有元素必须同类型——存不了。如果用数据框,每列要等长——也存不了。
(2) 列表的解法
# 一个 list 装下所有类型
employee <- list(
id = 2024001,
name = "Alice",
department = "数据分析部",
salary = 15000,
skills = c("R", "Python", "SQL"),
is_active = TRUE
)
# 访问任何字段
employee$name # [1] "Alice"
employee$skills[1] # [1] "R"
employee$salary * 12 # [1] 180000 (年薪)
1 个 list 装 6 个不同类型字段。这就是 R list 的威力。
3. list():创建列表
(1) 基本语法
list(key1 = value1, key2 = value2, ...)
(2) 5 种创建方式
# 1. 命名列表(最常用)
person <- list(name = "Alice", age = 25, scores = c(85, 90, 92))
person
# $name
# [1] "Alice"
#
# $age
# [1] 25
#
# $scores
# [1] 85 90 92
# 2. 匿名列表(按位置访问)
unnamed <- list("a", 1, TRUE)
# [[1]] "a"
# [[2]] 1
# [[3]] TRUE
# 3. 嵌套列表
company <- list(
hr = list(name = "HR", count = 5),
it = list(name = "IT", count = 20)
)
# 4. 空列表
empty <- list()
length(empty) # [1] 0
# 5. 用 list() 转换向量(保持异构)
as.list(c(1, 2, 3))
# [[1]] 1
# [[2]] 2
# [[3]] 3
(3) 列表 vs 向量对比
graph TB
A[R 数据结构] --> B[向量<br/>同质<br/>一维]
A --> C[矩阵<br/>同质<br/>二维]
A --> D[列表<br/>异质<br/>任意类型]
A --> E[数据框<br/>等长向量列表<br/>二维]
style A fill:#fff3cd
style B fill:#cce5ff
style C fill:#d4edda
style D fill:#f8d7da
style E fill:#e1d4ff
| 特性 | 向量 c(1, 2, 3) |
列表 list(1, "a", TRUE) |
|---|---|---|
| 元素类型 | 必须相同 | 可以不同 |
| 元素类型 | 6 种基本类型 | 任意 R 对象(含函数、数据框) |
| 访问 | v[1] |
l[[1]] |
| 长度可变 | ❌ | ✅(可任意增删) |
| 类似 Python | tuple |
dict / list |
4. 4 种访问方式(重点)
(1) 4 种访问速查表
| 方式 | 语法 | 返回 | 用途 |
|---|---|---|---|
[[i]] |
第 i 个元素 | 元素本身 | 取单个元素 |
[i] |
第 i 个元素 | 子列表 | 取子集 |
$name |
按名字 | 元素本身 | 取命名元素 |
[["name"]] |
按名字 | 元素本身 | 等价 $name |
(2) [[ ]] vs [ ]:核心差异
person <- list(name = "Alice", age = 25, scores = c(85, 90, 92))
# [[ ]] 返回元素本身
person[[1]]
# [1] "Alice" ← 字符
# [ ] 返回子列表
person[1]
# $name
# [1] "Alice" ← 还是列表
# 关键差异:用 [ ] 取出后再访问会出错
person[1]$name # ✅ $name 还能访问(语法糖)
person[1][1] # ❌ 这是子列表的第 1 个元素,但子列表只有一个 $name
# 实战:计算平均分
mean(person[[3]]) # [1] 89 ← [[3]] 取出向量
mean(person$scores) # [1] 89 ← $scores 取出向量
mean(person[3]) # ❌ 报错:need numeric data
[[ ]] 或 $,取子集用 [ ]。这是 R 列表访问的最容易混淆的点。
(3) $ vs [["name"]]
# $ 是 [[ "name" ]] 的语法糖
person$name # 等价 person[["name"]]
person[["name"]] # 等价 person$name
# $ 支持部分匹配(不推荐)
person$na # 自动匹配到 "name"
# [1] "Alice"
# [[ ]] 不支持部分匹配(更安全)
person[["na"]] # 报错
[[ "name" ]]——明确无误,不依赖部分匹配。
(4) 嵌套列表访问
company <- list(
hr = list(name = "HR", count = 5, head = "Alice"),
it = list(name = "IT", count = 20, head = "Bob")
)
# 访问嵌套元素
company$hr$head # [1] "Alice"
company[["it"]][["head"]] # [1] "Bob"
company$it$count # [1] 20
5. 列表的修改、添加、删除
(1) 修改元素
person <- list(name = "Alice", age = 25)
# 修改([[ ]] 或 $ 都能改)
person$age <- 26
person[["age"]]
# [1] 26
# 批量改
person$age <- person$age + 1
person$age # [1] 27
(2) 添加元素
# 方法 1:用 $ 添加
person$email <- "alice@example.com"
# 方法 2:用 [[ ]] 添加
person[["phone"]] <- "13800000000"
# 方法 3:用 c() 合并
person <- c(person, address = "北京市海淀区")
(3) 删除元素
# 赋 NULL 删除
person$phone <- NULL
# 或
person[["address"]] <- NULL
(4) 查看列表结构
# 查看结构(重要调试函数)
str(person)
# List of 4
# $ name : chr "Alice"
# $ age : num 27
# $ email : chr "alice@example.com"
# $ address : chr "北京市海淀区"
# 查看所有名字
names(person)
# [1] "name" "age" "email" "address"
6. lapply():批量处理列表
(1) 为什么用 lapply?
列表的元素类型可以不同——不能直接用 sum() mean()。lapply 让我们对每个元素应用函数:
mixed_list <- list(
numbers = 1:5,
chars = c("a", "b", "c"),
logical = c(TRUE, FALSE, TRUE)
)
# 对每个元素求长度
lapply(mixed_list, length)
# $numbers
# [1] 5
#
# $chars
# [1] 3
#
# $logical
# [1] 3
(2) lapply + 自定义函数
# Calculate每个元素的"摘要"
summary_list <- lapply(mixed_list, function(x) {
if (is.numeric(x)) {
return(list(mean = mean(x), sum = sum(x)))
} else {
return(list(length = length(x), class = class(x)))
}
})
summary_list$numbers
# $mean
# [1] 3
#
# $sum
# [1] 15
(3) sapply() 简化结果
# lapply 返回列表,sapply 简化为向量
sapply(mixed_list, length)
# numbers chars logical
# 5 3 3
7. 列表合并:c() 和 unlist()
(1) c() 合并列表
# 合并多个列表
list1 <- list(a = 1, b = 2)
list2 <- list(c = 3, d = 4)
merged <- c(list1, list2)
merged
# $a 1
# $b 2
# $c 3
# $d 4
(2) unlist() 展平为向量
# 把列表展平成向量(只对"原子"元素有效)
numbers_list <- list(a = 1, b = 2, c = 3)
unlist(numbers_list)
# a b c
# 1 2 3
# 注意:展平后类型会强制转换
mixed <- list(1, "a", TRUE)
unlist(mixed)
# [1] "1" "a" "TRUE" ← 全部转字符串
8. list2env():列表转环境
把列表的每个元素变成全局变量:
config <- list(
host = "localhost",
port = 3306,
user = "root",
password = "secret"
)
# 把列表元素变成环境变量
list2env(config, envir = .GlobalEnv)
# 现在可以直接用
host # [1] "localhost"
port # [1] 3306
list2env() 会污染全局环境,慎用。多用于"从 YAML/JSON 加载配置"场景。
9. 常见操作对比
(1) c() vs list() vs unlist()
| 函数 | 用途 | 输入 → 输出 |
|---|---|---|
c() |
拼接原子元素 | c(1, 2, 3) → 向量 |
list() |
创建异构容器 | list(1, "a") → 列表 |
unlist() |
展平列表为向量 | unlist(list(1,2,3)) → 向量 |
(2) 列表转数据框
# 等长向量的列表可以转 data.frame
df <- as.data.frame(list(
name = c("A", "B", "C"),
age = c(20, 25, 30),
score = c(85, 90, 78)
))
print(df)
# name age score
# 1 A 20 85
# 2 B 25 90
# 3 C 30 78
10. 完整示例:员工档案管理
下面是一个完整工作流示例,把本课所有列表特性串起来。
▶ 示例:公司员工档案管理
# ============================================
# 公司员工档案管理
# 功能:用列表管理 3 个员工的完整信息
# ============================================
# 1. 创建 3 个员工的列表
employees <- list(
alice = list(
id = 2024001,
name = "Alice",
department = "数据分析部",
salary = 15000,
skills = c("R", "Python", "SQL"),
projects = c("用户画像", "销售预测")
),
bob = list(
id = 2024002,
name = "Bob",
department = "工程部",
salary = 18000,
skills = c("Java", "Go", "Docker"),
projects = c("API 重构", "微服务迁移")
),
charlie = list(
id = 2024003,
name = "Charlie",
department = "产品部",
salary = 20000,
skills = c("Figma", "Axure"),
projects = c("V2.0 设计", "用户研究")
)
)
# 2. 查看某个员工完整信息
cat("=== Alice 完整信息 ===\n")
str(employees$alice)
# List of 6
# $ id : num 2024001
# $ name : chr "Alice"
# $ department : chr "数据分析部"
# $ salary : num 15000
# $ skills : chr [1:3] "R" "Python" "SQL"
# $ projects : chr [1:2] "用户画像" "销售预测"
# 3. 用 lapply 批量处理
cat("\n=== 所有员工年薪 ===\n")
annual_salaries <- sapply(employees, function(emp) emp$salary * 12)
print(annual_salaries)
# alice bob charlie
# 180000 216000 240000
# 4. 用 lapply 统计技能数
cat("\n=== 每个员工的技能数 ===\n")
skill_counts <- sapply(employees, function(emp) length(emp$skills))
print(skill_counts)
# alice bob charlie
# 3 3 2
# 5. 用 lapply 按部门分组
cat("\n=== 按部门分组 ===\n")
dept_groups <- split(
sapply(employees, function(emp) emp$name),
sapply(employees, function(emp) emp$department)
)
print(dept_groups)
# $数据分析部
# [1] "Alice"
#
# $工程部
# [1] "Bob"
#
# $产品部
# [1] "Charlie"
# 6. 添加新员工
employees$diana <- list(
id = 2024004,
name = "Diana",
department = "数据分析部",
salary = 16000,
skills = c("R", "Tableau"),
projects = c("数据可视化")
)
cat("\n=== 添加 Diana 后 ===\n")
cat("总员工数:", length(employees), "\n")
cat("Diana 部门:", employees$diana$department, "\n")
# 7. 找出工资最高的员工
top_salary <- which.max(sapply(employees, function(emp) emp$salary))
cat("\n工资最高:", names(employees)[top_salary], "(", max(sapply(employees, function(emp) emp$salary)), "元)\n")
# 8. 提取所有员工的技能(合并所有技能向量)
all_skills <- unlist(lapply(employees, function(emp) emp$skills))
cat("\n=== 全公司技能汇总 ===\n")
print(table(all_skills))
# all_skills
# Axure Docker Figma Go Java Python R SQL Tableau
# 1 1 1 1 1 1 2 1 1
预期输出(节选):
=== Alice 完整信息 ===
List of 6
$ id : num 2024001
$ name : chr "Alice"
...
=== 所有员工年薪 ===
alice bob charlie
180000 216000 240000
=== 全公司技能汇总 ===
all_skills
Axure Docker Figma Go Java Python R SQL Tableau
1 1 1 1 1 1 2 1 1
❓ 常见问题
Q:
[[ ]]和[ ]区别是什么? A:[[ ]]返回元素本身(可继续操作),[ ]返回子列表(不能再操作元素)。取元素用[[ ]]或$,取子集用[ ]。这是 R 最容易混淆的语法点。
Q:
$和[[ "name" ]]一样吗? A:基本等价。但$支持部分匹配($na自动匹配 "name"),[[ ]]不支持。生产代码用[[ "name" ]]更安全。
Q:列表和向量什么关系? A:向量是同质一维(所有元素同类型),列表是异构任意维(元素可任意类型)。
data.frame底层是"等长向量的列表"——理解列表就理解了数据框。
Q:
lapply()和sapply()怎么选? A:lapply()返回列表(结构保留),sapply()简化为向量/矩阵(更易读)。日常用sapply,函数式编程管道用lapply。
Q:
unlist()什么时候用? A:把列表"展平"为向量——但只能展平原子元素。混合类型会被强制转换(如 list(1, "a") → c("1", "a"))。
📖 小节
- 列表是 R 的"万能容器":可装任意类型(向量、矩阵、函数、另一个列表)
- 4 种访问方式:
[[i]]$name[[ "name" ]](取元素) vs[i](取子集) $name支持部分匹配(不推荐),[[ "name" ]]严格匹配(推荐)- 增删改:
l$new <- x(增)/l$old <- NULL(删)/l$old <- new_x(改) lapply()对每个元素应用函数返回列表;sapply()简化为向量unlist()把列表展平为向量(异构元素会被强制转换)- 数据框
data.frame底层是"等长向量的列表"——列表是数据框的前置知识
📝 作业
-
基础题:创建一个
student列表包含name(字符)、age(数字)、scores(数值向量 c(85, 90, 92))、passed(逻辑值)4 个字段。打印列表结构(str()),访问name和scores[2]。 -
基础题:创建一个嵌套列表
family包含fathermotherchild三个子列表(每个有name和age)。访问child$age并输出。 -
基础题:用
lapply()对 3 个员工列表(自己构造)批量计算"年薪"(salary * 12),把结果用sapply()简化为向量输出。 -
进阶题:写一个函数
create_employee(name, dept, salary)返回一个命名列表(包含 id、name、dept、salary、skills=空向量、is_active=TRUE)。调用 3 次创建 3 个员工,存到一个大列表里。用sapply()提取所有员工的"年薪"。 -
挑战题:写一段脚本模拟"图书馆"管理:① 创建一个列表
library包含 3 本书(每本书有titleauthoryearavailable4 个字段);② 用lapply把所有书的available设为 FALSE(借出);③ 用sapply找出所有可借的书(available == TRUE);④ 添加新书到列表。