Menu

R 向量:c()、seq()、索引和向量化运算

向量是 R 的基本单位,就连单个数字也是向量。学习如何用 c() 创建向量、索引向量(从 1 开始!)、用逻辑掩码筛选,以及一次对整个向量做运算。

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

R 中的一切都是向量

大多数语言都有标量(单个值),再加上某种用来存放多个值的容器类型。R 跳过了标量。向量是一组有序的值,所有值都是同一类型,它是构建其他一切的基本单位。就连看起来孤零零的一个数字,也是长度为 1 的向量:

c() 是 combine(合并)的缩写,用来手动构建向量。length() 告诉你向量有多少个元素。单独的 5 用 is.vector 检查结果是 TRUE,长度为 1:R 从来没有停止使用向量,只是有时用的向量非常短。

正是这种设计让 R 代码看起来和 Python 或 JavaScript 不同:在其他语言里需要写循环的操作,在 R 中一个表达式就能作用于整个向量。下面会讲到这一点。

现在就要记住一条规则:向量的所有元素都是同一类型。如果你给 c() 传入混合类型,它不会报错,而是悄悄把所有元素转换成其中最灵活的类型:

只要混入一个字符串,所有元素都会变成字符串("1"、"TRUE")。数字中的逻辑值会变成 1 和 0。这种强制类型转换遵循固定的优先顺序:logical → integer → double → character,而且不会给出任何警告,所以一列数字里混入一个多余的 "7",就可能悄悄把整个数据集变成文本。完整的层级在数据类型中有介绍;如果确实需要存放不同类型的值,那正是列表的用途。

生成序列::、seq() 和 rep()

把每个元素都手动写进 c() 很快就会让人厌烦。R 为有规律的模式提供了三种快捷方式:

  • 1:10 最常用:从一个边界到另一个边界的整数,步长为 1。
  • seq() 是它的通用版:用 by = 指定步长,或者用 length.out = 指定想要的元素个数,让 R 自己算出间隔。
  • seq_len(n) 生成 1 到 n,是构建循环索引的安全写法:与 1:n 不同,当 n 为 0 时,它会正确地返回空向量,而不是倒着数出 1 0。
  • rep() 用来重复:times = 把整个向量首尾相接地重复(1 2 1 2 1 2),each = 把每个元素原地重复(1 1 1 2 2 2)。两者很容易混淆,运行代码对比一下。

索引:R 从 1 开始计数

方括号用来取出元素。第一个元素是 [1],不是 [0]。如果你是从 Python 或 JavaScript 转过来的,这是初期最常见的绊脚石:

这里有三点值得停下来看看:

  • 你可以用一个由位置组成的向量来索引:fruits[c(2, 4)] 一次取出第二个和第四个元素。
  • 负索引表示“除了这些之外的所有元素”。fruits[-1] 是去掉第一个元素后的向量。这和 Python 完全不同,在 Python 中 -1 表示最后一个元素。同一次索引中不能混用正索引和负索引。
  • fruits[0] 不会报错,它返回一个空的 character(0)。在 R 中,差一错误往往只会悄悄产生空结果,而不会让程序崩溃,所以当后面的结果莫名其妙为空时,检查一下 length() 很有帮助。

元素还可以带有名字,这就有了第三种索引方式:按标签索引:

命名向量就像一个轻量的查找表,能让代码保持可读:prices["tea"] 一看就知道是什么意思,prices[2] 则不然。

逻辑掩码与 which()

最强大的索引方式是用逻辑向量,也就是一个与数据等长、由 TRUE/FALSE 组成的掩码。对向量做任何比较,得到的正是这样的结果:

temps > 24 给出的不是一个答案,而是五个,每个元素一个。把这个掩码放进 [ ],就会保留掩码为 TRUE 的那些元素。用 &(与)和 |(或)组合条件,其他运算符也在那一节介绍。

which() 把掩码转换成位置:这里是 2 3 5,即高温读数的索引。当你需要知道匹配项在哪里时就用它,比如报告位置,或者在另一个向量的相同位置取值。如果只是筛选,temps[temps > 24] 比 temps[which(temps > 24)] 更直接。

这种用掩码筛选的模式几乎是 R 中所有数据处理的核心,筛选数据框的行时你会再次用到它。

向量化运算与循环补齐规则

R 中的算术运算会对整个向量逐元素进行,不需要循环:

第一行展示的是招牌特性:prices * 2 把每个元素都乘以 2。其他语言需要 for 循环的地方,R 只需要一个运算符,而且向量化写法更短也更快,因为循环是在底层经过优化的 C 代码中完成的。当每一步依赖于前一步时,就写 for 循环;如果是逐元素运算,就用向量化。

第三行展示了循环补齐规则:当向量长度不同时,R 会重复较短的向量来对齐。c(10, 20) 被循环补齐为 10 20 10 20,得到 11 22 13 24。prices * 2 之所以能运行,正是因为 2 被循环补齐到长度为 3 的向量上:标量不过是一个被循环补齐的长度为 1 的向量。

当较长的长度恰好是较短长度的整数倍时,循环补齐干净利落。如果不是,R 仍会算出结果,同时给出警告:

你会得到 11 22 13,外加一条警告,说较长对象的长度不是较短对象长度的整数倍。把这个警告当作 bug 报告:部分循环补齐几乎从来都不是你想要的,它通常意味着两个本应等长的向量长度不一致。

排序、反转、去重

三个你会经常用到的小工具:

sort() 对值排序,rev() 把现有顺序反过来而不排序,unique() 去掉重复值并保留首次出现的元素。它们都不会修改 x:和 R 中几乎所有操作一样,它们返回一个新向量,原向量保持不变。

本节要点

  • 向量是 R 的基本单位:有序,所有元素类型相同,就连单个值也是长度为 1 的向量。
  • 用 c() 构建,用 :、seq() 和 rep() 生成有规律的序列,并记住 c() 会悄悄转换混合类型。
  • 索引从 1 开始;负索引会去掉元素;名字让你可以按标签索引。
  • 逻辑掩码(x[x > 5])是整个语言的筛选惯用法;which() 把掩码转换成位置。
  • 运算是向量化的,较短的向量会被循环补齐,出现部分循环补齐的警告就说明有 bug。

下一节:列表,当你的值不是同一类型时使用的容器。

常见问题

R 中如何创建向量?

用 c() 函数(combine 的缩写,意为“合并”):x <- c(10, 20, 30)。对于有规律的序列,可以用 1:10、seq(0, 1, by = 0.25) 或 rep(0, 5)。所有元素最终都必须是同一类型:如果混合了不同类型,R 会悄悄把它们全部转换成最灵活的那种类型。

R 的索引从 0 还是 1 开始?

从 1 开始。x[1] 是第一个元素,x[length(x)] 是最后一个。从 Python、JavaScript 或 C 转过来的人都会在这里栽跟头:不存在元素 x[0](访问它会返回一个空向量而不是报错,所以这种 bug 很隐蔽)。

R 中的循环补齐规则是什么?

对两个长度不同的向量做算术运算时,R 会重复较短的向量,直到和较长的向量长度一致。c(1, 2, 3, 4) + c(10, 20) 得到 11 22 13 24。如果较长的长度不是较短长度的整数倍,R 仍会算出结果,但会给出警告:把这个警告当作 bug 来处理。

R 中 which() 有什么作用?

它把一个逻辑向量转换成其中 TRUE 值所在的位置。如果 x > 5 得到 FALSE TRUE TRUE,那么 which(x > 5) 得到 2 3。需要位置本身时用它;如果只是筛选,直接用逻辑向量索引(x[x > 5])更简单。

Coddy 编程语言插图

和 Coddy 一起学编程

开始