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

R提供了两种不同的方法来访问list或data.frame中的元素:[][[]]

这两者之间的区别是什么,什么时候我应该使用其中一个而不是另一个?

306219 次浏览

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

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

<代码> < >之前 x[我] x (i, j) x[[我]] x [[i, j]] x美元一个 x美元“a” > < / pre > < /代码

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

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

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

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

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"

这两种方法之间的显著区别在于它们用于提取时返回的对象的类别,以及它们是否可以接受一个范围的值,或者在赋值时只接受一个值。

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

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

对于另一个具体的用例,当您希望选择由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"

为了帮助新手在手动迷雾中导航,将[[ ... ]]符号视为崩溃函数可能会有所帮助——换句话说,它是当你只想从命名向量、列表或数据帧中“获取数据”时使用的符号。如果您想使用来自这些对象的数据进行计算,那么这样做是很好的。这些简单的例子将说明。

(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

这两个都是子集。 单括号将返回列表的一个子集,该子集本身就是一个列表。也就是说,它可能包含也可能不包含一个以上的元素。 另一方面,双括号将只返回列表中的单个元素

-单括号将给出一个列表。如果希望从列表中返回多个元素,也可以使用单个括号。

>r<-list(c(1:10),foo=1,far=2);
现在,请注意当我试图显示列表时返回的方式。

>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的元素。 我们也可以选择一个索引或元素名作为单个括号的输入。 如:< / p >

> 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

在这个例子中,我们传递了一个有两个元素名称的向量" fooquot;和“;far"。

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

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

相比之下,双括号总是只返回一个元素。 在转到双括号之前,要记住一点。 # EYZ1 < / em >

我将举几个例子。请记下加粗的单词,在你完成下面的例子后再回头看:

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

  > 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

只是在这里添加[[也装备了递归的索引

@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的嵌套在第1级停止,因此通过递归索引提取的尝试在[[2]](即第2级)失败。

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

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

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

从术语上讲,[[操作符提取是列表中的元素,而[操作符是列表中的元素子集

哈德利·维克汉姆:

From Hadley Wickham

我(蹩脚的外观)修改显示使用tidyverse / purrr:

enter image description here

请参考以下详细说明。

我使用了R中的内置数据框架,称为mtcars。

> mtcars
mpg cyl disp  hp drat   wt ...
Mazda RX4     21.0   6  160 110 3.90 2.62 ...
Mazda RX4 Wag 21.0   6  160 110 3.90 2.88 ...
Datsun 710    22.8   4  108  93 3.85 2.32 ...
............

表的顶部行被称为标题,它包含列名。之后的每条水平线表示一个数据行,它以行名开始,然后跟着实际数据。 一行中的每个数据成员称为单元格

单个方括号“[]”运算符

要检索单元格中的数据,可以在单个方括号“[]”操作符中输入它的行坐标和列坐标。这两个坐标用逗号分隔。换句话说,坐标以行位置开始,然后以逗号结尾,以列位置结束。顺序很重要。

例1:-这是mtcars的第一行第二列的单元格值。

> mtcars[1, 2]
[1] 6

例2:-此外,我们可以使用行名和列名来代替数字坐标。

> mtcars["Mazda RX4", "cyl"]
[1] 6

双方括号“[[]]”操作符

我们用双方括号“[[]]”操作符引用数据帧列。

例1:-为了检索内置数据集mtcars的第九列向量,我们编写mtcars[[9]]。

< p > mtcars [[9]]

. [1] 1 1 1 0 0 0 0 0 0 0 0 0

例2:-我们可以通过名称检索相同的列向量。

< p > mtcars [["]] [1] 1 1 1 0 0 0 0 0 0 0 0…< / p >