R: R 列表 (List):R 的"万能容器"

最后更新:2026-08-26

前面 3 课我们学了向量、矩阵——它们都要求所有元素同类型。但现实中数据往往是异构的:学生信息包含学号(数字)、姓名(字符)、成绩(向量)。R 用列表 (list) 解决异构存储问题。

列表是 R 真正的"瑞士军刀"——可以装任何类型(向量、矩阵、数据框、函数、另一个列表)。数据框 data.frame 底层就是"等长向量的列表"。这一课是 Phase 1 的关键。

1. 你将学到



2. 一个学生信息管理的故事

(1) 痛点:异构数据存储

Ivan是 HR,要存储 1 个员工的完整信息:

TEXT 📖 仅展示
工号:2024001(数字)
姓名:Alice(字符)
部门:数据分析部(字符)
工资:15000(数字)
技能:c("R", "Python", "SQL")(字符向量)

如果用向量,所有元素必须同类型——存不了。如果用数据框,每列要等长——也存不了

(2) 列表的解法

R
# 一个 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) 基本语法

R
list(key1 = value1, key2 = value2, ...)

(2) 5 种创建方式

R
# 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 向量对比

100%
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 [ ]:核心差异

R
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"]]

R
# $ 是 [[ "name" ]] 的语法糖
person$name           # 等价 person[["name"]]
person[["name"]]      # 等价 person$name

# $ 支持部分匹配(不推荐)
person$na             # 自动匹配到 "name"
# [1] "Alice"

# [[ ]] 不支持部分匹配(更安全)
person[["na"]]        # 报错
💡 提示生产代码用 [[ "name" ]]——明确无误,不依赖部分匹配。

(4) 嵌套列表访问

R
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) 修改元素

R
person <- list(name = "Alice", age = 25)

# 修改([[ ]] 或 $ 都能改)
person$age <- 26
person[["age"]]
# [1] 26

# 批量改
person$age <- person$age + 1
person$age  # [1] 27

(2) 添加元素

R
# 方法 1:用 $ 添加
person$email <- "alice@example.com"

# 方法 2:用 [[ ]] 添加
person[["phone"]] <- "13800000000"

# 方法 3:用 c() 合并
person <- c(person, address = "北京市海淀区")

(3) 删除元素

R
# 赋 NULL 删除
person$phone <- NULL

# 或
person[["address"]] <- NULL

(4) 查看列表结构

R
# 查看结构(重要调试函数)
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 让我们对每个元素应用函数

R
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 + 自定义函数

R
# 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() 简化结果

R
# lapply 返回列表,sapply 简化为向量
sapply(mixed_list, length)
# numbers    chars  logical 
#       5        3        3


7. 列表合并:c() 和 unlist()

(1) c() 合并列表

R
# 合并多个列表
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() 展平为向量

R
# 把列表展平成向量(只对"原子"元素有效)
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():列表转环境

把列表的每个元素变成全局变量

R
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) 列表转数据框

R
# 等长向量的列表可以转 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. 完整示例:员工档案管理

下面是一个完整工作流示例,把本课所有列表特性串起来。

▶ 示例:公司员工档案管理

R 📖 仅展示
# ============================================
# 公司员工档案管理
# 功能:用列表管理 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
逻辑代码 56 行(超过 40 行限制,仅展示)

预期输出(节选):

TEXT 📖 仅展示
=== 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"))。


📖 小节


📝 作业

  1. 基础题:创建一个 student 列表包含 name(字符)、age(数字)、scores(数值向量 c(85, 90, 92))、passed(逻辑值)4 个字段。打印列表结构(str()),访问 namescores[2]

  2. 基础题:创建一个嵌套列表 family 包含 father mother child 三个子列表(每个有 nameage)。访问 child$age 并输出。

  3. 基础题:用 lapply() 对 3 个员工列表(自己构造)批量计算"年薪"(salary * 12),把结果用 sapply() 简化为向量输出。

  4. 进阶题:写一个函数 create_employee(name, dept, salary) 返回一个命名列表(包含 id、name、dept、salary、skills=空向量、is_active=TRUE)。调用 3 次创建 3 个员工,存到一个大列表里。用 sapply() 提取所有员工的"年薪"。

  5. 挑战题:写一段脚本模拟"图书馆"管理:① 创建一个列表 library 包含 3 本书(每本书有 title author year available 4 个字段);② 用 lapply 把所有书的 available 设为 FALSE(借出);③ 用 sapply 找出所有可借的书(available == TRUE);④ 添加新书到列表。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏