Menu

R 语言矩阵:matrix()、cbind、rbind 与矩阵运算

如何用 matrix()、cbind() 和 rbind() 构造矩阵,按行和列索引,并分清逐元素相乘的 * 与真正的矩阵乘法 %*%。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

矩阵是带维度的向量

R 中的矩阵是一个由值组成的矩形,有行有列,每个单元格保存的都是同一种类型,通常是数字。在底层,它其实就是一个附加了 dim 属性的向量,这解释了它的大部分行为:全部是同一种类型,到处都能做向量化运算。

用 matrix() 对一个向量变形,就能构造出矩阵:

六个值、两行,R 会算出需要三列。dim() 同时返回两个维度 2 3;nrow() 和 ncol() 则分别给出行数和列数。

仔细看打印出来的矩阵:值 1 2 沿第一列向下排列,然后 3 4 沿下一列向下排列。R 默认按列填充矩阵。 如果你的数据是按行读的(人通常就是这么写的),就用 byrow = TRUE 说明:

现在第一行是 1 2 3。忘记写 byrow = TRUE 不会报错,只会悄悄给你一个看起来像转置过的、由同样数字组成的排列。所以每当你用原始值构造矩阵时,都要养成检查打印布局的习惯。

索引:m[row, column]

矩阵索引在一对方括号里接收两个位置,逗号前是行,逗号后是列,都从 1 开始计数:

把某个位置留空表示“全部”:m[1, ] 是整个第一行,m[, 2] 是整个第二列。注意两者返回的都是普通向量:R 会丢弃缩减为 1 的那个维度。交互使用时这很方便,在代码中却是个陷阱,因为期望得到矩阵的函数遇到向量会出错。用 drop = FALSE 让 R 保持形状:

现在 dim() 返回 1 3,它仍然是矩阵。只要在函数内部截取单独的一行或一列,就写上 drop = FALSE;它所防止的 bug(代码在多列数据上正常,在单列数据上出错)追查起来非常痛苦。

逻辑掩码在这里同样适用:m[m > 3] 以向量形式返回所有大于 3 的单元格。

用 cbind() 和 rbind() 构造

除了对一个长向量变形,你还可以用零件拼装矩阵:cbind() 把向量按列绑定在一起,rbind() 按行绑定。这两个函数也可以扩展已有的矩阵:

名称会跟着向量一起走:cbind() 自动把 heights 和 weights 用作列名,让打印出来的矩阵易于阅读。两个函数都要求长度对得上(长度为 1 的值会循环补齐);把长度为 3 的向量绑定到 4 行的矩阵上会收到警告。

也可以通过 colnames(m) <- ... 和 rownames(m) <- ... 直接设置列名和行名,之后就能按名称索引:people[, "weights"]。

逐元素的 * 与真正的矩阵乘法 %*%

这是整篇文章中最重要的区别。R 中矩阵有两个乘法运算符,它们计算的东西完全不同:

  • a * a 是逐元素相乘:每个单元格乘以对应位置的单元格。1 2 3 4 变成 1 4 9 16,排列形状不变。这只是向量化算术,和你在向量上用的 * 是同一个。
  • a %*% a 是线性代数中的矩阵乘法:结果的每个单元格,是第一个矩阵的一行乘以第二个矩阵的一列再求和。同样的输入得到 7 10 15 22,是完全不同的数字。

运行这段代码,把两个输出并排比较一下;亲眼看到同样的输入得出不同结果,才能把这个区别牢牢记住。如果你在数学上需要 %*% 的地方写了 *,R 不会警告你(对于方阵,两种写法的形状都兼容),你只会得到错误的数字。在统计代码中(协方差矩阵、线性模型的代数运算),这是经典的无声 bug 之一。

t() 用于转置,即交换行和列。它经常出现在 %*% 旁边,因为矩阵乘法要求内侧的维度一致:

补充一下:solve(m) 求矩阵的逆,%*% 遇到向量时会把它当成一列的矩阵。大多数数据工作用到这个深度就够了。

行与列的汇总

按行或按列求和、求平均实在太常见了,R 为此提供了专门的快速函数:

rowSums() 把每一行压缩成一个数(这里是 6 15),colSums() 对每一列做同样的事(5 7 9),Means 系列则改为求平均。优先使用它们,而不是手写的循环,甚至也不是 apply(m, 1, sum):它们更清晰,也更快。对于这四个函数覆盖不到的汇总(比如每列的最大值),通用工具是 apply 函数族:apply(m, 2, max)。

矩阵还是数据框?

两者都是矩形的,那该选哪个?

  • 矩阵:所有单元格类型相同,而且你在意数学运算。数值计算、线性代数、距离计算、类似图像的网格。正因为保证了只有一种类型,矩阵更轻量,运算也更快。
  • 数据框:各列的类型不同,名字、年龄和逻辑标记并排放在一起。这才是现实中的表格数据,几乎所有数据分析函数期望的也是它。

一条好用的规则:如果你会很自然地在电子表格里打开它,列有名称、类型混杂,那它就是数据框。如果它是一个你打算拿来做代数运算的数字网格,那它就是矩阵。两者之间的转换很容易(as.matrix()、as.data.frame()),但对含有任何文本列的数据框使用 as.matrix(),会把所有内容都强制转换成字符,所以只转换数值列。

要点回顾

  • 矩阵是带维度的向量:全部是同一种类型,用 matrix(data, nrow, ncol) 构造;除非传入 byrow = TRUE,否则它会按列填充。
  • 用 m[row, col] 索引;留空的位置表示“全部”;在代码中截取单行或单列时,加上 drop = FALSE。
  • cbind() 和 rbind() 用向量拼装矩阵,或扩展已有的矩阵。
  • * 是逐元素相乘,%*% 是真正的矩阵乘法:输入相同,答案不同,而且没有警告。
  • rowSums() / colSums() / rowMeans() / colMeans() 负责日常的汇总。

下一篇:因子,R 如何表示分类数据,以及随之而来的陷阱。

常见问题

R 语言怎么创建矩阵?

matrix(1:6, nrow = 2) 把一个向量变形成 2 行 3 列,按列依次填充。加上 byrow = TRUE 就改为按行填充。你也可以用向量拼装矩阵:cbind() 把它们按列拼接,rbind() 按行拼接。

R 语言中 * 和 %*% 有什么区别?

* 是逐元素相乘:每个单元格乘以对应位置的单元格,形状必须对得上。%*% 是线性代数中真正的矩阵乘法(行乘以列,所以内侧的维度必须一致)。对同样的矩阵,它们给出的结果完全不同;在该用 %*% 的地方用了 *,是一个经典的无声 bug。

R 语言怎么取出矩阵的某一行或某一列?

把另一个位置留空:m[1, ] 是第一行,m[, 2] 是第二列。默认情况下两者都返回普通向量。加上 drop = FALSE,比如 m[1, , drop = FALSE],就能让结果保持为一行或一列的矩阵;当后续代码需要二维数据时,这一点很重要。

Coddy 编程语言插图

和 Coddy 一起学编程

开始