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])更简单。