R提供两种不同的方法来访问列表或数据框中的元素:[]
和[[]]
。
这两者之间有什么区别,我应该在何时使用其中之一?
R语言定义可用于回答这些类型的问题:
R有三种基本的索引运算符,其语法如下所示:
x[i] x[i, j] x[[i]] x[[i, j]] x$a x$"a"
对于向量和矩阵,
[[
形式很少使用,尽管它们与[
形式有一些细微的语义差异(例如,会删除任何名称或dimnames属性,并且在字符索引中使用部分匹配)。当使用单个索引索引多维结构时,x[[i]]
或者x[i]
将返回x
的第i个顺序元素。对于列表,一般使用
[[
选择任何单个元素,而[
返回所选元素的列表。
[[
表达式只能使用整数或字符索引来选择单个元素,而[
允许使用向量进行索引。但请注意,对于一个列表,索引可以是一个向量,向量的每个元素依次应用于该列表、所选组件、该组件的所选组件等,结果仍然是一个单一元素。
这两种方法之间的显著差异在于它们用于提取时返回的对象类别,以及它们是否可以接受一系列值或仅单个值进行赋值。
考虑以下列表上的数据提取情况:
foo <- list( str='R', vec=c(1,2,3), bool=TRUE )
假设我们想从foo中提取存储在bool中的值,并在if()
语句中使用它。这将说明在数据提取时,[]
和[[]]
的返回值之间的差异。使用[]
方法返回类列表对象(如果foo是数据框,则返回数据框),而[[]]
方法返回其值类型所确定的对象的类。
因此,使用[]
方法会产生以下结果:
if( foo[ 'bool' ] ){ print("Hi!") }
Error in if (foo["bool"]) { : argument is not interpretable as logical
class( foo[ 'bool' ] )
[1] "list"
这是因为[]
方法返回的是一个列表,而列表不是可以直接传递到if()
语句中的有效对象。在这种情况下,我们需要使用[[]]
, 因为它将返回存储在'bool'中的“裸”对象,该对象具有适当的类:
if( foo[[ 'bool' ]] ){ print("Hi!") }
[1] "Hi!"
class( foo[[ 'bool' ]] )
[1] "logical"
第二个不同点是[]
运算符可以用于访问列表中的范围或数据帧中的列,而[[]]
运算符仅限于访问单个位置或列。考虑使用第二个列表bar()
进行值分配的情况:bar <- list( mat=matrix(0,nrow=2,ncol=2), rand=rnorm(1) )
假设我们想用bar中包含的数据覆盖foo的最后两个插槽。如果我们尝试使用[[]]
运算符,会发生以下情况:
foo[[ 2:3 ]] <- bar
Error in foo[[2:3]] <- bar :
more elements supplied than there are to replace
这是因为[[]]
只能访问单个元素。我们需要使用[]
:
foo[ 2:3 ] <- bar
print( foo )
$str
[1] "R"
$vec
[,1] [,2]
[1,] 0 0
[2,] 0 0
$bool
[1] -0.6291121
请注意,虽然赋值成功了,但是foo中的插槽仍保留了它们原来的名称。
双括号访问列表元素,而单括号只返回具有一个元素的列表。
lst <- list('one','two','three')
a <- lst[1]
class(a)
## returns "list"
a <- lst[[1]]
class(a)
## returns "character"
[]
提取列表,[[]]
提取列表中的元素
alist <- list(c("a", "b", "c"), c(1,2,3,4), c(8e6, 5.2e9, -9.3e7))
str(alist[[1]])
chr [1:3] "a" "b" "c"
str(alist[1])
List of 1
$ : chr [1:3] "a" "b" "c"
str(alist[[1]][1])
chr "a"
补充一点,[[
还支持 递归索引。
@JijoMatthew 的答案中提到了这一点,但没有深入探讨。
如 ?"[["
中所述,类似 x[[y]]
的语法,其中 length(y) > 1
,被解释为:
x[[ y[1] ]][[ y[2] ]][[ y[3] ]] ... [[ y[length(y)] ]]
请注意,这并不改变您对[
和[[
之间差异的主要把握——即前者用于子集提取,后者用于提取单个列表元素。
例如,
x <- list(list(list(1), 2), list(list(list(3), 4), 5), 6)
x
# [[1]]
# [[1]][[1]]
# [[1]][[1]][[1]]
# [1] 1
#
# [[1]][[2]]
# [1] 2
#
# [[2]]
# [[2]][[1]]
# [[2]][[1]][[1]]
# [[2]][[1]][[1]][[1]]
# [1] 3
#
# [[2]][[1]][[2]]
# [1] 4
#
# [[2]][[2]]
# [1] 5
#
# [[3]]
# [1] 6
要得到值3,我们可以这样做:
x[[c(2, 1, 1, 1)]]
# [1] 3
回到@JijoMatthew上面的答案,回想一下r
:
r <- list(1:10, foo=1, far=2)
特别是,这解释了我们在误用[[
时常遇到的错误,即:
r[[1:3]]
r[[1:3]]
出错:在第二层级递归索引失败。
由于这段代码实际上尝试计算 r[[1]][[2]][[3]]
,而 r
嵌套只到一级,因此在进行递归索引的尝试在 [[2]]
处(即在第二层级)失败了。
r[[c("foo", "far")]]
出错:下标越界。
在这里,R 寻找的是 r[["foo"]][["far"]]
,但它并不存在,因此我们得到了“下标越界”错误。
如果这两个错误都给出相同的信息,可能会更有帮助/一致。
术语上,[[
运算符提取列表中的元素,而 [
运算符则取出列表的子集。
它们都是子集的方式。 单个方括号将返回列表的子集,该子集本身将是一个列表。也就是说,它可能包含一个或多个元素。 另一方面,双方括号将仅从列表中返回单个元素。
单个方括号将给我们一个列表。如果我们希望从列表中返回多个元素,我们也可以使用单个方括号。 考虑以下列表:
>r<-list(c(1:10),foo=1,far=2);
现在请注意当我尝试显示列表时返回的方式。我输入r并按Enter键。
>r
#the result is:-
[[1]]
[1] 1 2 3 4 5 6 7 8 9 10
$foo
[1] 1
$far
[1] 2
现在我们来看一下单括号的魔力:
>r[c(1,2,3)]
#the above command will return a list with all three elements of the actual list r as below
[[1]]
[1] 1 2 3 4 5 6 7 8 9 10
$foo
[1] 1
$far
[1] 2
这与我们尝试在屏幕上显示r的值完全相同,这意味着单括号的使用返回了一个列表,在索引1处我们有一个由10个元素组成的向量,然后我们有两个具有名称foo和far的元素。我们也可以选择将单个索引或元素名称作为单个括号的输入。
例如:
> r[1]
[[1]]
[1] 1 2 3 4 5 6 7 8 9 10
> r[2]
$foo
[1] 1
> r["foo"];
$foo
[1] 1
> x<-c("foo","far")
> r[x];
$foo
[1] 1
$far
[1] 2
> r[[1]]
[1] 1 2 3 4 5 6 7 8 9 10
>r[["foo"]]
[1] 1
> r[[c(1:3)]]
Error in r[[c(1:3)]] : recursive indexing failed at level 2
> r[[c(1,2,3)]]
Error in r[[c(1, 2, 3)]] : recursive indexing failed at level 2
> r[[c("foo","far")]]
Error in r[[c("foo", "far")]] : subscript out of bounds
[]
返回列表类的事实非常不直观。他们应该创建另一种语法,如 ([])
用于列表,而 [[]]
用于访问实际元素是可以接受的。我更喜欢将 [[]]
视为原始值,就像其他语言一样。 - TokyoToo[[
如果选择的元素是列表,它会愉快地返回一个列表。 正确的答案是 [
将所选项作为其父项的子集返回,而 [[
返回原始的所选项本身,摆脱了其父对象。 - D3SL为了帮助新手穿越手册的迷雾,将[[...]]
符号看作一种折叠函数可能会有所帮助 - 换句话说,当您只想从命名向量、列表或数据框中“获取数据”时,就会使用该符号。如果您想要使用这些对象中的数据进行计算,则最好这样做。下面这些简单的示例将说明问题。
(x <- c(x=1, y=2)); x[1]; x[[1]]
(x <- list(x=1, y=2, z=3)); x[1]; x[[1]]
(x <- data.frame(x=1, y=2, z=3)); x[1]; x[[1]]
所以从第三个例子开始:
> 2 * x[1]
x
1 2
> 2 * x[[1]]
[1] 2
iris[[1]]
返回一个向量,而iris[1]
返回一个数据框。 - stevec再举一个具体的用例,当你想选择由split()
函数创建的数据框时,请使用双括号。如果你不知道,split()
会根据关键字段将列表/数据框分成子集。当你想对多个组进行操作、绘图等时,这非常有用。
> class(data)
[1] "data.frame"
> dsplit<-split(data, data$id)
> class(dsplit)
[1] "list"
> class(dsplit['ID-1'])
[1] "list"
> class(dsplit[['ID-1']])
[1] "data.frame"
[
始终返回一个列表意味着无论v
的长度如何,你都会得到相同的输出类别作为x[v]
的输出。例如,你可能想要对列表的子集进行lapply
操作:lapply(x[v], fun)
。如果[
对于长度为一的向量会删除列表,则每当v
长度为一时都会返回错误。 - Axeman