列表什么都能装
向量有一条硬性规定:每个元素都必须是同一种类型。列表没有这条规定。列表的每个元素可以是不同的类型、不同的长度,甚至可以是另一个列表。它是 R 的通用容器:
length() 返回 4,也就是四个元素,尽管其中一个(scores)本身包含三个数字。列表统计的是它有几个格子,而不是格子里装了什么。str()(structure,结构)是查看列表实际内容最快的方式:每个元素一行,显示名称、类型和预览。养成用 str() 的习惯:每当某个函数交给你一个东西、而你不确定它是什么时,就 str() 一下。
名称是可选的(list(1, "a", TRUE) 是合法的),但几乎总是值得加上:带名称的列表自己就能说明自己。
三种访问方式:[、[[ 和 $
这是列表中每个人都会搞错一次的部分,所以我们要把它彻底弄清楚。访问运算符有三个,它们做的是两种不同的事:
[返回一个更小的列表:同类型的容器,里面装着你选中的内容。[[返回元素本身:格子里实际的值。$是带字面名称的[[的简写:person$age就是person[["age"]]。
随手打印时你看不出区别,可一旦要做计算,区别就非常明显:
person["scores"] 的类是 "list":一个长度为 1、里面装着分数向量的列表。person[["scores"]] 的类是 "numeric":向量本身,可以直接交给 mean()。如果对单方括号的版本调用 mean(),你会得到一个提示参数不是数值的错误;看到这个错误信息,几乎总是说明你在该用 [[ 的地方用了 [。
一个好记的比喻:列表是一列火车。[ 给你一列更短的火车,它仍然是火车。[[ 则打开一节车厢,把货物交给你。
那么什么时候该用 [?当你想要多个元素,并且想让它们继续打包在一起时:
你无法一次性取出两个元素“本身”,两个值需要一个容器来装,所以选取多个元素总是用单方括号,结果也总是一个列表。
还有一个区别:$ 会做部分匹配(只要没有歧义,person$sc 就能找到 scores),这在控制台里很方便,在脚本里却是隐患。在需要长期稳定运行的代码中,优先使用写全名称的 [[,它还能接受保存在变量里的名称,这是 $ 做不到的。
添加、修改、删除
列表通过普通的赋值来增长和缩短,不需要专门的追加方法:
- 给一个还不存在的名称赋值,会添加该元素。
- 给一个已存在的名称赋值,会替换它。
- 给位置
length(x) + 1赋值,会追加一个没有名称的元素。 - 赋值
NULL会彻底删除该元素,列表随之变短。(这是NULL充当删除命令的唯一场合;如果你确实需要在某个格子里保存“空”,请用x["k"] <- list(NULL)。)
要把两个列表首尾相接,c() 对列表和对向量一样有效:c(list_a, list_b) 返回一个更长的列表。
嵌套列表
因为列表的元素可以是另一个列表,列表可以嵌套任意多层,这让它天然适合表示结构化数据,比如解析后的 JSON 或分组后的结果:
逐层深入只需要把访问符串起来,每个 $ 或 [[ 往下走一层。当嵌套结构变得难以理解时,str(company) 会一次性显示整棵树,str(company, max.level = 1) 则只显示最顶层。
用 unlist() 展平
unlist() 会把一个列表(无论嵌套多深)压平成单个向量:
有两点值得注意。第一,unlist() 会为你生成名称(math1、math2、art),所以你仍然能看出每个值来自哪里。第二,也就是陷阱所在:向量只能保存一种类型,所以展平一个混合类型的列表时,所有内容都会被强制转换成其中最灵活的类型。数字 1 变成了字符串 "1"。如果你展平之后数字变成了文本,说明这个列表并不像你以为的那样统一。
列表为什么重要
列表可能让人觉得是初学者话题,以后用不上。恰恰相反,它们在整个语言中都起着承重作用:
- 返回多个结果的函数会返回一个列表。 R 函数只返回一个对象;列表让这一个对象既能装拟合值,又能装系数表。做回归时,你得到的模型对象就是一个(很大、带有类的)列表,对它用
str()就能证明。 - 数据框就是由列组成的列表:等长的向量,再加上一些额外的行为。你刚学到的一切(
$、[[、通过赋值NULL删除元素)都可以原封不动地用在数据框的列上。 - apply 函数族(
lapply()等)接收一个列表,对每个元素应用一个函数,再返回一个列表。列表进、列表出,是 R 中重复计算的标准形态。
要点回顾
- 列表什么都能装:混合类型、不等长度、其他列表、整个数据框。
[返回一个更小的列表;[[返回元素本身;$是带字面名称的[[。要做数学运算?你需要的是[[或$。- 给新名称赋值来添加,赋值
NULL来删除,用c()合并。 unlist()展平为向量,过程中会强制转换混合类型,记得检查结果的类。- 数据框是由列组成的列表,所以这些知识可以直接迁移过去。
下一篇:矩阵,与列表相对应的、只能装一种类型、按行和列组织的结构。
常见问题
R 语言中 [ 和 [[ 有什么区别?
单方括号 [ 总是返回一个包含所选元素的列表,也就是一个同类型的更小容器。双方括号 [[ 会深入内部,返回元素本身。如果 x$scores 保存的是一个数值向量,那么 x["scores"] 是长度为 1 的列表,而 x[["scores"]] 才是那个数值向量。当你要真正使用这个值时,用 [[(或 $)。
R 语言怎么向列表添加元素?
给一个还不存在的名称赋值:x$email <- "rosa@example.com" 或 x[["email"]] <- ...,列表会自动变长。要追加一个没有名称的元素,就给下一个位置赋值:x[[length(x) + 1]] <- value。要删除一个元素,给它赋值 NULL:x$email <- NULL。
R 语言怎么把列表转换成向量?
unlist(x) 会把一个列表(包括嵌套列表)展平成单个向量。因为向量只能保存一种类型,所有内容都会被强制转换为其中最灵活的那种类型:一个包含数字和字符串的列表会变成全是字符的向量。展平之后,用 class() 检查一下结果。