R: R 矩阵与数组:二维与多维数据结构
最后更新:2026-08-26
上一课我们学了"一维"向量,这一课进入"二维"和"多维"——矩阵 (matrix) 和数组 (array)。矩阵是数据分析的基础(Excel 表格、图像像素、神经网络权重都是矩阵),这一课要把矩阵吃透。
矩阵的"灵魂"是 apply() 函数族——它让我们不用写循环就能按行/按列/按维度运算。这是 R 比 Python 简洁的地方。
1. 你将学到
- matrix() 创建矩阵(按行/按列填充)
- 矩阵的属性(dim、nrow、ncol、rownames、colnames)
- rbind/cbind 合并矩阵
- apply 系列函数(apply/lapply/sapply/tapply/mapply)
- 数组 array 的多维扩展
- 矩阵运算(转置
%*%逆solve()) - 实战:成绩单统计
2. 一个成绩单的故事
(1) 痛点:多科目成绩统计
小赵是教务员,5 个学生 3 门课的成绩单如下:
数学 语文 英语
Alice 85 78 92
Bob 72 88 80
Charlie 90 85 87
赵六 65 70 75
钱七 95 92 88
要算:① 每个学生的总分 ② 每科平均分 ③ 找出数学最高分的学生
如果用 Excel 公式要写半天;用 Python 写循环嵌套——
(2) R 的解法
# 5x3 矩阵
scores <- matrix(c(85, 78, 92,
72, 88, 80,
90, 85, 87,
65, 70, 75,
95, 92, 88),
nrow = 5, byrow = TRUE)
colnames(scores) <- c("数学", "语文", "英语")
rownames(scores) <- c("Alice", "Bob", "Charlie", "赵六", "钱七")
# 1. 每个学生总分(一行求和)
row_sums <- apply(scores, 1, sum)
# 2. 每科平均(一列求平均)
col_means <- apply(scores, 2, mean)
# 3. 找出数学最高分
top_math <- which.max(scores[, "数学"])
5 行代码解决 3 个任务。这就是矩阵 + apply 的威力。
3. 矩阵的本质:二维向量
(1) 向量 → 矩阵的转换
graph LR
A["一维向量 c 1,2,3,4,5,6"] --> B["reshape 成 2x3 矩阵"]
B --> C["1 2 3 / 4 5 6"]
B --> D["1 4 / 2 5 / 3 6"]
style A fill:#cce5ff
style B fill:#fff3cd
# 一维向量
v <- 1:6
v
# [1] 1 2 3 4 5 6
# 转成 2 行 3 列矩阵
m <- matrix(v, nrow = 2, ncol = 3)
m
# [,1] [,2] [,3]
# [1,] 1 3 5
# [2,] 2 4 6
(2) 矩阵的 4 个关键属性
| 属性 | 含义 | 示例 |
|---|---|---|
dim(m) |
维度向量 | dim(m) → c(2, 3) |
nrow(m) |
行数 | nrow(m) → 2 |
ncol(m) |
列数 | ncol(m) → 3 |
length(m) |
总元素数 | length(m) → 6 |
m <- matrix(1:6, nrow = 2)
dim(m) # [1] 2 3
nrow(m) # [1] 2
ncol(m) # [1] 3
length(m) # [1] 6 ← 总元素数
4. matrix():创建矩阵
(1) 基本语法
matrix(data, nrow = 1, ncol = 1, byrow = FALSE, dimnames = NULL)
(2) 4 种创建方式
# 1. 直接给数据 + 行列数
m1 <- matrix(1:6, nrow = 2)
# [,1] [,2] [,3]
# [1,] 1 3 5 ← 默认按列填充
# [2,] 2 4 6
# 2. byrow = TRUE(按行填充)
m2 <- matrix(1:6, nrow = 2, byrow = TRUE)
# [,1] [,2] [,3]
# [1,] 1 2 3
# [2,] 4 5 6
# 3. 加行列名
m3 <- matrix(1:6, nrow = 2, byrow = TRUE,
dimnames = list(c("行1", "行2"), c("列A", "列B", "列C")))
# 列A 列B 列C
# 行1 1 2 3
# 行2 4 5 6
# 4. 只指定 nrow,ncol 自动推断
m4 <- matrix(1:6, nrow = 3) # 3 行 2 列
# [,1] [,2]
# [1,] 1 4
# [2,] 2 5
# [3,] 3 6
byrow = FALSE 是默认(按列填充)。多数场景下我们想要按行填充(一行是一个学生/一条记录),要显式写 byrow = TRUE。
(3) 加行列名(更易读)
m <- matrix(1:6, nrow = 2, byrow = TRUE)
rownames(m) <- c("Alice", "Bob")
colnames(m) <- c("数学", "语文", "英语")
m
# 数学 语文 英语
# Alice 1 2 3
# Bob 4 5 6
5. 访问矩阵元素
(1) 3 种访问方式
| 方式 | 语法 | 用途 |
|---|---|---|
[i, j] |
第 i 行第 j 列 | 单个元素 |
[i, ] |
第 i 行 | 整行 |
[, j] |
第 j 列 | 整列 |
[i, "列名"] |
第 i 行的某列 | 按名字 |
m <- matrix(1:9, nrow = 3, byrow = TRUE,
dimnames = list(c("Alice", "Bob", "Charlie"),
c("数学", "语文", "英语")))
m
# 数学 语文 英语
# Alice 1 2 3
# Bob 4 5 6
# Charlie 7 8 9
# 单个元素
m[2, 3] # [1] 6
m["Bob", "英语"] # [1] 6
# 整行
m[1, ] # 数学 1 语文 2 英语 3
m["Alice", ] # 同上
# 整列
m[, 1] # Alice 1 Bob 4 Charlie 7
m[, "数学"] # 同上
(2) 逻辑索引
# 找出数学成绩 > 5 的学生
m[m[, "数学"] > 5, ]
# 数学 语文 英语
# Bob 4 ... ← 不会被选中
# Charlie 7 8 9
6. rbind/cbind:合并矩阵
graph TB
A["矩阵 A 3x2"] --> C["rbind 按行拼接"]
B["矩阵 B 2x2"] --> C
C --> D["新矩阵 5x2"]
E["矩阵 A 3x2"] --> F["cbind 按列拼接"]
G["矩阵 B 3x2"] --> F
F --> H["新矩阵 3x4"]
style A fill:#cce5ff
style B fill:#cce5ff
style E fill:#d4edda
style G fill:#d4edda
a <- matrix(1:6, nrow = 3, byrow = TRUE)
b <- matrix(7:12, nrow = 3, byrow = TRUE)
# 按行拼接(行数相加)
rbind(a, b)
# [,1] [,2]
# [1,] 1 2
# [2,] 3 4
# [3,] 5 6
# [4,] 7 8
# [5,] 9 10
# [6,] 11 12
# 按列拼接(列数相加)
cbind(a, b)
# [,1] [,2] [,3] [,4]
# [1,] 1 2 7 8
# [2,] 3 4 9 10
# [3,] 5 6 11 12
rbind(a, b) 要求 ncol(a) == ncol(b);cbind(a, b) 要求 nrow(a) == nrow(b),否则报错。
7. apply 系列函数(灵魂)
(1) 为什么需要 apply?
R 的循环很慢,apply 让我们不写循环就能按行/按列运算:
m <- matrix(1:9, nrow = 3)
# 求每行和(不用循环)
apply(m, 1, sum)
# [1] 12 15 18
# 求每列和
apply(m, 2, sum)
# [1] 12 15 18
(2) apply() 核心
apply(X, MARGIN, FUN)
| 参数 | 含义 | 取值 |
|---|---|---|
X |
矩阵/数组 | matrix / array |
MARGIN |
维度 | 1=行, 2=列, c(1,2)=行列 |
FUN |
要应用的函数 | sum mean max function(x) ... |
# 1=行(按行处理),2=列(按列处理)
apply(m, 1, mean) # 每行平均
apply(m, 2, max) # 每列最大
# 自定义函数
apply(m, 1, function(x) max(x) - min(x)) # 每行极差
(3) apply 家族 5 个函数
| 函数 | 输入 | 输出 | 典型用途 |
|---|---|---|---|
apply() |
矩阵/数组 | 向量/矩阵 | 按行/列运算 |
lapply() |
列表/向量 | 列表 | 列表处理 |
sapply() |
列表/向量 | 向量/矩阵 | 简化 lapply |
tapply() |
向量+分组 | 数组 | 分组统计 |
mapply() |
多向量 | 列表/向量 | 多参数函数 |
(4) lapply() vs sapply()
# lapply 返回列表
numbers <- list(a = 1:3, b = 4:6, c = 7:9)
result_l <- lapply(numbers, sum)
# $a 6
# $b 15
# $c 24
# sapply 返回向量(更常用)
result_s <- sapply(numbers, sum)
# a b c
# 6 15 24
sapply()——返回向量更方便。lapply() 在函数式编程管道中更常用。
(5) tapply() 分组统计
# 5 个学生 3 门课成绩,按"性别"分组
scores <- c(85, 78, 92, 72, 88, 80, 90, 85, 87, 65, 70, 75, 95, 92, 88)
gender <- c("男", "女", "男", "男", "女", "女", "男", "男", "女", "男", "女", "男", "女", "男", "女")
# 按性别分组求平均
tapply(scores, gender, mean)
# 女 男
# 84.6 81.5
tapply() 在做"分组统计"时极其常用——但下一阶段用 dplyr::group_by() | summarise() 会更优雅。
(6) mapply() 多参数函数
# 对两个向量对应元素求和
mapply(function(a, b) a + b, 1:3, 10:12)
# [1] 11 13 15
8. 数组 array:多维扩展
# 2x3x4 的三维数组
arr <- array(1:24, dim = c(2, 3, 4))
# 第 1 个 2x3 矩阵
arr[, , 1]
# [,1] [,2] [,3]
# [1,] 1 3 5
# [2,] 2 4 6
# apply 可以对多维运算
apply(arr, c(1, 2), mean) # 对第 3 维求平均
data.frame(下一阶段)和 array(图像/物理)才是多维数据的主流。
9. 矩阵运算(线性代数)
| 运算 | 语法 | 说明 |
|---|---|---|
| 转置 | t(m) |
行变列 |
| 矩阵乘法 | m1 %*% m2 |
线性代数乘法 |
| 元素乘法 | m1 * m2 |
对应元素乘 |
| 求逆 | solve(m) |
逆矩阵 |
| 求和(行/列) | rowSums(m) / colSums(m) |
快速求和 |
m <- matrix(1:4, nrow = 2)
# [,1] [,2]
# [1,] 1 3
# [2,] 2 4
# 转置
t(m)
# [,1] [,2]
# [1,] 1 2
# [2,] 3 4
# 矩阵乘法(用 m %*% m)
m %*% m
# [,1] [,2]
# [1,] 7 15
# [2,] 10 22
# 求逆
solve(m)
# [,1] [,2]
# [1,] -2 1.5
# [2,] 1 -0.5
10. 完整示例:成绩单综合分析
下面是一个完整工作流示例,把本课所有矩阵 + apply 串起来。
▶ 示例:班级成绩综合分析
# ============================================
# 班级成绩综合分析
# 功能:5 学生 3 课程的成绩矩阵完整分析
# ============================================
# 1. 创建成绩矩阵(按行填充:每个学生一行)
scores <- matrix(c(
85, 78, 92, # Alice
72, 88, 80, # Bob
90, 85, 87, # Charlie
65, 70, 75, # 赵六
95, 92, 88 # 钱七
), nrow = 5, byrow = TRUE)
# 加行列名
rownames(scores) <- c("Alice", "Bob", "Charlie", "赵六", "钱七")
colnames(scores) <- c("数学", "语文", "英语")
cat("=== 成绩矩阵 ===\n")
print(scores)
# 2. 每个学生总分(按行求和)
cat("\n=== 学生总分 ===\n")
row_totals <- apply(scores, 1, sum)
print(row_totals)
# 3. 每科平均/最高/最低(按列统计)
cat("\n=== 各科统计 ===\n")
col_stats <- apply(scores, 2, function(x) {
c(mean = round(mean(x), 2),
max = max(x),
min = min(x),
sd = round(sd(x), 2))
})
print(col_stats)
# 4. 每个学生平均分(按行求平均)
cat("\n=== 学生平均分 ===\n")
row_means <- apply(scores, 1, mean)
print(round(row_means, 2))
# 5. 找出数学最高分学生
top_math <- which.max(scores[, "数学"])
cat("\n数学最高分:", rownames(scores)[top_math], "(", max(scores[, "数学"]), "分)\n")
# 6. 找出总分最高学生
top_total <- which.max(row_totals)
cat("总分最高:", rownames(scores)[top_total], "(", max(row_totals), "分)\n")
# 7. 找出单科 < 70 的学生(用逻辑索引)
fail <- scores < 70
cat("\n不及格科目(< 70):\n")
print(scores)
cat("\n是否不及格(TRUE = 不及格):\n")
print(fail)
# 8. 综合排名
cat("\n=== 综合排名(按总分降序)===\n")
ranking <- order(row_totals, decreasing = TRUE)
result <- data.frame(
排名 = 1:5,
姓名 = rownames(scores)[ranking],
总分 = row_totals[ranking],
平均 = round(row_totals[ranking] / 3, 2)
)
print(result)
预期输出(节选):
=== 成绩矩阵 ===
数学 语文 英语
Alice 85 78 92
Bob 72 88 80
Charlie 90 85 87
赵六 65 70 75
钱七 95 92 88
=== 学生总分 ===
Alice Bob Charlie 赵六 钱七
255 240 262 210 275
=== 学生平均分 ===
Alice Bob Charlie 赵六 钱七
85.00 80.00 87.33 70.00 91.67
=== 综合排名(按总分降序)===
排名 姓名 总分 平均
1 1 钱七 275 91.67
2 2 Charlie 262 87.33
3 3 Alice 255 85.00
4 4 Bob 240 80.00
5 5 赵六 210 70.00
❓ 常见问题
byrow = TRUE 和 FALSE 区别?matrix(1:6, nrow=2, byrow=FALSE)(默认)按列填充:1 3 5 / 2 4 6;byrow=TRUE 按行填充:1 2 3 / 4 5 6。数据科学场景几乎都按行填(每个样本一行)。apply() 和 for 循环怎么选?apply() 就用 apply()——R 循环慢 10-100 倍。apply(m, 1, fun) 替代"按行循环",apply(m, 2, fun) 替代"按列循环"。tapply() 在实际项目中怎么用?tapply(x, group, fun) 快速做"分组统计"——按性别、地区、月份等分组。但下一阶段用 dplyr::group_by() \| summarise() 更优雅,tapply 是基础。📖 小节
- 矩阵是二维向量,数组是多维向量;
matrix()创建,array()扩展到多维 byrow = TRUE按行填充(数据科学标准),FALSE(默认)按列填充- 访问方式
m[i, j]/m[i, ]/m[, j]/m["行", "列"] rbind()按行拼,cbind()按列拼- apply 家族:
apply()按维度、lapply()列表→列表、sapply()列表→向量、tapply()分组统计、mapply()多参数 - 矩阵运算:
t()转置、%*%矩阵乘法、solve()逆矩阵、rowSums()colSums()快速求和 - apply 替代循环:R 循环慢 10-100 倍,能用 apply 就不写循环
📝 作业
-
基础题:创建一个 4×4 矩阵,元素为 1-16,按行填充,行列名分别为
c("A","B","C","D")和c("X","Y","Z","W")。打印矩阵,验证行列名正确。 -
基础题:用
apply()计算上题矩阵的每行和、每列和、每行平均、每列最大。 -
基础题:用
rbind()和cbind()合并两个 3×3 矩阵(自己构造),验证行列数变化。 -
进阶题:模拟 6 个学生 4 门课成绩:① 用
apply()计算每个学生总分;② 用apply()计算每科平均分;③ 用which.max()找出每科最高分学生姓名;④ 用order()排名。 -
挑战题:写一段脚本,对 100 个学生 5 门课成绩做"分层次分析":① 用
tapply()按"性别"分组计算总平均;② 找出数学 90 分以上、英语不及格(< 60)的学生数量;③ 计算每个学生 5 门课的标准差(用apply()+ 自定义函数)。