提问



R提供了两种不同的方法来访问列表或data.frame的元素 - [][****]运算符。


两者有什么区别?在什么情况下我应该使用一个而不是另一个?

最佳参考


R语言定义对于回答这些类型的问题非常方便:



  • http://cran.r-project.org/doc/manuals/R-lang.html#Indexing





R有三个基本的索引操作符,语法由以下示例显示


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


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

对于列表,通常使用[**来选择任何单个元素,而[返回所选元素的列表。

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


其它参考1


两种方法之间的显着差异是它们在用于提取时返回的对象的类别,以及它们是否可以接受一系列值,或者在赋值期间只接受一个值。[75]


考虑以下列表中的数据提取案例:


foo <- list( str='R', vec=c(1,2,3), bool=TRUE )


假设我们想从foo中提取bool存储的值,并在if()语句中使用它。这将说明[][****]的返回值在用于数据提取时的差异。 []方法返回类列表的对象(如果foo是data.frame则返回data.frame),而[****]方法返回其类由其值的类型确定的对象。


因此,使用[]方法会产生以下结果:


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中的插槽保留了原始名称。

其它参考2


双括号访问列表元素,而单个括号返回一个包含单个元素的列表。


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

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

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

其它参考3


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


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"

其它参考4


只需添加[**也可以为递归索引配备。


@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**]中的错误:递归索引在级别2失败



由于此代码实际上试图评估r[**1**][**2**][**3**],并且r的嵌套在第一级停止,因此通过递归索引提取的尝试在[**2**]处失败,即在第2级。



  r[**c("foo", "far")**]中的错误:下标越界



在这里,R正在寻找r[**"foo"**][**"far"**],它不存在,所以我们得到下标超出界限的错误。


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

其它参考5


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


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


>r<-list(c(1:10),foo=1,far=2);


现在请注意我尝试显示列表时返回列表的方式。
我输入r然后按回车键


>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的元素到目前为止。
我们也可以选择将单个索引或元素名称作为单个括号的输入。
例如:


> 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的向量


作为回报,我们有一个包含两个元素的列表。


简而言之,单一括号将始终返回另一个列表,其中元素的数量等于您传递到单个括号中的元素数量或索引数量。


相反,双括号将始终只返回一个元素。
在移动到双支架之前要记住一个注意事项。
NOTE:THE MAJOR DIFFERENCE BETWEEN THE TWO IS THAT SINGLE BRACKET RETURNS YOU A LIST WITH AS MANY ELEMENTS AS YOU WISH WHILE A DOUBLE BRACKET WILL NEVER RETURN A LIST. RATHER A DOUBLE BRACKET WILL RETURN ONLY A SINGLE ELEMENT FROM THE LIST.


我将举几个例子。请记下粗体字,并在完成以下示例后再回过头来:


双括号将返回索引处的实际值。( NOT 返回列表)


  > 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

其它参考6


来自哈德利威克姆:


[76]


使用tidyverse/purrr显示我的(糟糕的)修改:


[77]

其它参考7


为了帮助新手浏览手动模糊,将[** ... **]表示法视为折叠函数可能会有所帮助 - 换句话说,就是当你只想获取数据时来自命名向量,列表或数据框。如果要使用来自这些对象的数据进行计算,最好这样做。这些简单的例子将说明。


(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

其它参考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"

其它参考9


作为术语,[**运算符从列表中提取元素,而[运算符采用列表的子集