5.2 列表
不严格地说,列表是一个向量,其中每个元素的类型可以不同。本节谈一谈如何创建、索引和操作列表。
5.2.1 创建列表
列表由list
函数创建,且能像c
函数那样指定内容。你只需简单地用逗号分隔每个参数即可指定列表中的内容。列表中的元素变量的类型不限,可以是向量、矩阵,甚至函数:
(a_list <- list(
c(1, 1, 2, 5, 14, 42), #请查看http://oeis.org/A000108
month.abb,
matrix(c(3, -8, 1, -3), nrow = 2),
asin
))
##[[1]]
##[1] 1 1 2 5 14 42
##
##[[2]]
## [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"
##
##[[3]]
## [,1] [,2]
##[1,] 3 1
##[2,] -8 -3
##
##[[4]]
##function (x) .Primitive("asin")
与向量的命名类似,你可以在构造列表时就给元素命名,或在构造之后使用names
函数命名:
names(a_list) <- c("catalan", "months", "involutary", "arcsin")
a_list
##$catalan
##[1] 1 1 2 5 14 42
##
##$months
## [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"
##
##$involutary
## [,1] [,2]
##[1,] 3 1
##[2,] -8 -3
##
##$arcsin
##function (x) .Primitive("asin")
(the_same_list <- list(
catalan = c(1, 1, 2, 5, 14, 42),
months = month.abb,
involutary = matrix(c(3, -8, 1, -3), nrow = 2),
arcsin = asin
))
##$catalan
##[1] 1 1 2 5 14 42
##
##$months
## [1] "Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"
##
##$involutary
## [,1] [,2]
##[1,] 3 1
##[2,] -8 -3
##
##$arcsin
##function (x) .Primitive("asin")
尽管这并非是强制性的,但你在给元素命名时最好使用有效的变量名。
你甚至可以把列表作为一个列表的元素:
(main_list <- list(
middle_list = list(
element_in_middle_list = diag(3),
inner_list = list(
element_in_inner_list = pi ^ 1:4,
another_element_in_inner_list = "a"
)
),
element_in_main_list = log10(1:10)
))
##$middle_list
##$middle_list$element_in_middle_list
## [,1] [,2] [,3]
##[1,] 1 0 0
##[2,] 0 1 0
##[3,] 0 0 1
##
##$middle_list$inner_list
##$middle_list$inner_list$element_in_inner_list
##[1] 3.141593
##
##$middle_list$inner_list$another_element_in_inner_list
##[1] "a"
##
##
##
##$element_in_main_list
## [1] 0.0000000 0.3010300 0.4771213 0.6020600 0.6989700 0.7781513 0.8450980
## [8] 0.9030900 0.9542425 1.0000000
从理论上讲,你可以一直这样嵌套下去。实际上,一旦嵌套列表达到成千上万层(确切的数字在不同机器上有所不同),当前版本的R会抛出一个错误。幸好,在现实中这不成问题,因为嵌套超过三、四层的代码是极少见的。
5.2.2 原子变量和递归变量
由于列表具有这种把其他列表包含在内的能力,它被认为是递归变量。与之相对,向量、矩阵和数组则是原子变量(变量或是递归的,或是原子的,从不两者兼具。附录A的表解释了哪些变量类型是原子的,哪些是递归的)。函数is.recursive
和is.atomic
可测试它们的类型:
is.atomic(list())
## [1] FALSE
is.recursive(list())
## [1] TRUE
is.atomic(numeric())
## [1] TRUE
is.recursive(numeric())
## [1] FALSE
5.2.3 列表的维度和算术运算
列表与向量一样也有长度,其长度是它顶层元素的数目:
length(a_list)
## [1] 4
length(main_list) #不包括嵌套列表的长度
## [1] 2
仍与向量类似但异于矩阵的是,列表没有维度。因此,把dim
函数作用于列表上会返回NULL
:
dim(a_list)
## NULL
nrow
、NROW
及相应的列函数作用于列表时,与作用于矢量上的原理基本相同:
nrow(a_list)
## NULL
ncol(a_list)
## NULL
NROW(a_list)
## [1] 4
NCOL(a_list)
## [1] 1
与向量不同的是,算术运算对列表不起作用。由于每个元素的类型可以不同,将两个列表相加或相乘没有任何意义。然而,如果两个列表中的元素类型相同,则可对它们进行算术运算。在这种情况下,一般的算术运算法都适用。例如:
l1 <- list(1:5)
l2 <- list(6:10)
l1[[1]] + l2[[1]]
## [1] 7 9 11 13 15
更常见的是,你可能要对列表中的每一个元素执行算术运算(或其他操作)。这需要使用循环,第8章中详细介绍。
5.2.4 索引列表
考虑这个测试列表:
l <- list(
first = 1,
second = 2,
third = list(
alpha = 3.1,
beta = 3.2
)
)
与向量类似,我们可通过方括号[]
、正或负的下标数字、元素名称或逻辑索引这四种方法访问列表中的元素。以下四行代码的结果相同:
l[1:2]
## $first
## [1] 1
##
## $second
## [1] 2
l[-3]
## $first
## [1] 1
##
## $second
## [1] 2
l[c("first", "second")]
## $first
## [1] 1
##
## $second
## [1] 2
l[c(TRUE, TRUE, FALSE)]
## $first
## [1] 1
##
## $second
## [1] 2
这些索引操作催生了另一个列表。有时,我们要访问的是列表元素中的内容。有两个操作符能帮助我们做到这一点:给双方括号[[ ]]
传入正整数,它代表返回的索引下标,或指定该元素的名称字符串:
l[[1]]
## [1] 1
l[["first"]]
## [1] 1
如果输入的是一个列表,is.list
函数将返回TRUE
,否则将返回FALSE
。作为比较,观察以下两个索引操作符:
is.list(l[1])
## [1] TRUE
is.list(l[[1]])
## [1] FALSE
对于列表中的命名元素,我们也可以使用美元符号运算符$
。这与向双方括号传入一个命名字符串的工作方式几乎一样,且另有两个好处。首先,许多IDE能自动补全名字(在R的GUI下,按下Tab键可实现此功能)。其次,R能接受部分匹配的元素名称:
l$first
## [1] 1
l$f # 部分匹配将“f”解释为“first”
## [1] 1
我们可以通过嵌套方括号或传入向量来访问嵌套元素,尽管后者不常用且往往难以阅读:
l[["third"]]["beta"]
## $beta
## [1] 3.2
l[["third"]][["beta"]]
## [1] 3.2
l[[c("third", "beta")]]
## [1] 3.2
当尝试访问不存在的元素列表时,根据你所使用的索引类型,行为会有所不同。在下例中,回想一下列表l
,它只有三个元素。
如果我们使用单方括号的索引,那么将返回只带一个NULL
元素的列表(且名称为NA
,如原列表有名字的话)。与之相比,对于一个无效的向量索引,其返回值将是NA
:
l[c(4, 2, 5)]
## $<NA>
## NULL
##
## $second
## [1] 2
##
## $<NA>
## NULL
l[c("fourth", "second", "fifth")]
## $<NA>
## NULL
##
## $second
## [1] 2
##
## $<NA>
## NULL
无论是以双方括号还是美元符号来访问元素的内容,如果名字错误,都将返回NULL
:
l[["fourth"]]
## NULL
l$fourth
## NULL
最后,如果以错误的数字索引访问元素内容,则会抛出一个错误,说明下标越界。对于这种不一致的行为,你只能接受,而最好的防范措施是确保在使用前对索引作相应的检查:
l[[4]] #这里会抛出一个错误
5.2.5 向量和列表之间的转换
向量可使用as.list
函数来转换成列表。所创建的可见列表与向量中元素的值一一对应:
busy_beaver <- c(1, 6, 21, 107) #请查看http://oeis.org/A060843
as.list(busy_beaver)
## [[1]]
## [1] 1
##
## [[2]]
## [1] 6
##
## [[3]]
## [1] 21
##
## [[4]]
## [1] 107
如果列表中每个元素都是标量值,则亦可使用之前出现过的函数(as.numeric
、as.character
等)将其转换成向量:
as.numeric(list(1, 6, 21, 107))
## [1] 1 6 21 107
如果列表中包含非标量元素,这种方法将不起作用。这真的是一个问题,因为列表既能存储不同类型的数据亦可存储相同类型的数据,但后者不是存储成矩阵的形式:
(prime_factors <- list( two = 2,
three = 3,
four = c(2, 2),
five = 5,
six = c(2, 3),
seven = 7,
eight = c(2, 2, 2),
nine = c(3, 3),
ten = c(2, 5)
))
## $two
## [1] 2
##
## $three
## [1] 3
##
## $four
## [1] 2 2
##
## $five
## [1] 5
##
## $six
## [1] 2 3
##
## $seven
## [1] 7
##
## $eight
## [1] 2 2 2
##
## $nine
## [1] 3 3
##
## $ten
## [1] 2 5
对于这样的列表,可用unlist
函数将其转换为向量(对于混合类型的列表,有时技术上可行,但没什么用):
unlist(prime_factors)
## two three four1 four2 five six1 six2 seven eight1 eight2 eight3
## 2 3 2 2 5 2 3 7 2 2 2
## nine1 nine2 ten1 ten2
## 3 3 2 5
5.2.6 组合列表
c
函数既能用于拼接向量,亦能用于拼接列表:
c(list(a = 1, b = 2), list(3))
## $a
## [1] 1
##
## $b
## [1] 2
##
## [[3]]
## [1] 3
如果我们用它来拼接列表和向量,向量在拼接之前将被转换为列表(就像调用了as.list
函数):
c(list(a = 1, b = 2), 3)
## $a
## [1] 1
##
## $b
## [1] 2
##
## [[3]]
## [1] 3
亦可对列表使用cbind
和rbind
函数,但结果中出现的对象相当奇怪。它们或是含有可能为非标量元素的矩阵,或是有维度的列表,这取决于你看它们的方式:
(matrix_list_hybrid <- cbind(
list(a = 1, b = 2),
list(c = 3, list(d = 4))
))
## [,1] [,2]
##a 1 3
##b 2 List,1
str(matrix_list_hybrid)
## List of 4
## $ : num 1
## $ : num 2
## $ : num 3
## $ :List of 1
## ..$ d: num 4
## - attr(*, "dim")= int [1:2] 2 2
## - attr(*, "dimnames")=List of 2
## ..$ : chr [1:2] "a" "b"
## ..$ : NULL
在此,你应避免常用cbind
和rbind
,最好完全不用它们。这再次证明R有点过于灵活和包容了,否则它会直接抛出一个错误表明你干了蠢事。