方括号 [ ] 和双方括号 [[ ]] 在访问列表或数据框元素时的区别

669

R提供两种不同的方法来访问列表或数据框中的元素:[][[]]

这两者之间有什么区别,我应该在何时使用其中之一?

11个回答

418

R语言定义可用于回答这些类型的问题:

R有三种基本的索引运算符,其语法如下所示:

    x[i]
    x[i, j]
    x[[i]]
    x[[i, j]]
    x$a
    x$"a"

对于向量和矩阵,[[形式很少使用,尽管它们与[形式有一些细微的语义差异(例如,会删除任何名称或dimnames属性,并且在字符索引中使用部分匹配)。当使用单个索引索引多维结构时,x[[i]]或者x[i]将返回x的第i个顺序元素。

对于列表,一般使用[[选择任何单个元素,而[返回所选元素的列表。

[[表达式只能使用整数或字符索引来选择单个元素,而[允许使用向量进行索引。但请注意,对于一个列表,索引可以是一个向量,向量的每个元素依次应用于该列表、所选组件、该组件的所选组件等,结果仍然是一个单一元素。


10
使用[[]]与[]来用单个数字或向量进行索引的推理是什么?为什么不能只使用[ ?我猜你可以使用[[]]来返回单个条目,而[一个索引返回长度为1的列表...但为什么不让 [ 通过一个索引返回单个条目,而不是一个列表?为什么您可能希望返回长度为1的列表? - wordsforthewise
11
在编程时,你可能会有一个长度未定义的向量用于索引。将 [ 始终返回一个列表意味着无论 v 的长度如何,你都会得到相同的输出类别作为 x[v] 的输出。例如,你可能想要对列表的子集进行 lapply 操作:lapply(x[v], fun)。如果 [ 对于长度为一的向量会删除列表,则每当 v 长度为一时都会返回错误。 - Axeman
8
我认为这篇文章更清晰地解释了:http://adv-r.had.co.nz/Subsetting.html - Nate Anderson

208

这两种方法之间的显著差异在于它们用于提取时返回的对象类别,以及它们是否可以接受一系列值或仅单个值进行赋值。

考虑以下列表上的数据提取情况:

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中的插槽仍保留了它们原来的名称。


147

双括号访问列表元素,而单括号只返回具有一个元素的列表。

lst <- list('one','two','three')

a <- lst[1]
class(a)
## returns "list"

a <- lst[[1]]
class(a)
## returns "character"

1
clear and simple - cloudscomputes

128

来自Hadley Wickham:

来自Hadley Wickham

我(看起来有点糟糕)修改后的使用tidyverse/purrr示例:

输入图像描述


4
太棒了!你掌握了 Grace Hopper 的“皮秒”! - Steve Pitchers
@StevePitchers 嗯? - jzadra
3
Grace Hopper在Letterman节目上展示纳秒的视频链接是https://www.dailymotion.com/video/x35dsz7。 - Steve Pitchers

62

[] 提取列表,[[]] 提取列表中的元素

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"

24

补充一点,[[ 还支持 递归索引

@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"]],但它并不存在,因此我们得到了“下标越界”错误。

如果这两个错误都给出相同的信息,可能会更有帮助/一致。


您好,Micheal先生,我们可以使用[[]]进行多重索引吗? - Therii

22

术语上,[[ 运算符提取列表中的元素,而 [ 运算符则取出列表的子集


17

它们都是子集的方式。 单个方括号将返回列表的子集,该子集本身将是一个列表。也就是说,它可能包含一个或多个元素。 另一方面,双方括号将仅从列表中返回单个元素。

单个方括号将给我们一个列表。如果我们希望从列表中返回多个元素,我们也可以使用单个方括号。 考虑以下列表:

>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

在这个例子中,我们给定了一个索引“1”,然后得到了一个只有一个元素的列表(该元素是由10个数字组成的数组)。
> r[2]

$foo

[1] 1

在上面的例子中,我们给定了一个索引值 "2",然后得到了一个只包含一个元素的列表。
> r["foo"];

$foo

[1] 1

在这个例子中,我们传入了一个元素的名称,并返回了一个包含一个元素的列表。
您也可以传递一个元素名称向量,例如:
> x<-c("foo","far")

> r[x];

$foo

[1] 1

$far
[1] 2

在这个例子中,我们传入一个包含两个元素名称"foo"和"far"的向量。
作为返回结果,我们得到了一个包含两个元素的列表。
简而言之,单个括号始终会返回另一个列表,其元素数量等于您传递给单个括号的索引或元素数量。
相比之下,双括号始终仅返回一个元素。 在转到双括号之前,请记住以下注意事项。 注意:两者之间的主要区别在于,单个括号将返回您希望的任意数量的元素列表,而双括号永远不会返回列表。反而,双括号将只从列表中返回一个元素。
我会举几个例子。请记下粗体字中的单词,并在完成以下示例后回到它:
双括号将返回您所需索引处的实际值。(它不会返回一个列表)
  > 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

3
因为“传递向量会导致错误,只是因为它不是为满足那个需求而构建的”这句话是不正确的,所以被踩了;请看我的新回答。 - MichaelChirico
4
因为它声称“WHILE A DOUBLE BRACKET WILL NEVER RETURN A LIST”等强有力的主张,所以被踩了。这是不正确的——如果我们有一个由列表组成的列表对象,双括号将返回另一个列表。 - dabsingh
即使只有一个数字,[] 返回列表类的事实非常不直观。他们应该创建另一种语法,如 ([]) 用于列表,而 [[]] 用于访问实际元素是可以接受的。我更喜欢将 [[]] 视为原始值,就像其他语言一样。 - TokyoToo
这是客观上不正确的,因为其他人已经指出 [[ 如果选择的元素是列表,它会愉快地返回一个列表。 正确的答案是 [ 将所选项作为其父项的子集返回,而 [[ 返回原始的所选项本身,摆脱了其父对象。 - D3SL

16

为了帮助新手穿越手册的迷雾,将[[...]]符号看作一种折叠函数可能会有所帮助 - 换句话说,当您只想从命名向量、列表或数据框中“获取数据”时,就会使用该符号。如果您想要使用这些对象中的数据进行计算,则最好这样做。下面这些简单的示例将说明问题。

(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

1
作为一名新手,我在三个任务中发现使用“<-”将x(即x=1)替换为w=1很有帮助,以避免与成为“<-”目标的x混淆。 - user36800
虽然非常简单,但我真的很喜欢这个解释。另一个简单的演示:iris[[1]]返回一个向量,而iris[1]返回一个数据框。 - stevec

8

再举一个具体的用例,当你想选择由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"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接