【发布时间】:2022-01-12 02:51:07
【问题描述】:
R 提供了两种不同的方法来访问列表或数据框的元素:[] 和[[]]。
两者之间有什么区别,我什么时候应该使用一个而不是另一个?
【问题讨论】:
标签: r list dataframe extract r-faq
R 提供了两种不同的方法来访问列表或数据框的元素:[] 和[[]]。
两者之间有什么区别,我什么时候应该使用一个而不是另一个?
【问题讨论】:
标签: r list dataframe extract r-faq
R 语言定义可以方便地回答这些类型的问题:
R 有三个基本的索引运算符,语法如下所示
x[i] x[i, j] x[[i]] x[[i, j]] x$a x$"a"对于向量和矩阵,很少使用
[[形式,尽管它们与[形式有一些细微的语义差异(例如,它删除任何名称或 dimnames 属性,并且部分匹配用于字符索引)。当使用单个索引对多维结构进行索引时,x[[i]]或x[i]将返回x的第i个顺序元素。对于列表,通常使用
[[来选择任何单个元素,而[返回所选元素的列表。
[[形式仅允许使用整数或字符索引选择单个元素,而[允许通过向量进行索引。请注意,对于列表,索引可以是向量,向量的每个元素依次应用于列表、所选组件、该组件的所选组件等。结果仍然是单个元素。
【讨论】:
[ 总是返回一个列表意味着无论v 的长度如何,您都会为x[v] 获得相同的输出类。例如,有人可能想要 lapply 列表的一个子集:lapply(x[v], fun)。如果 [ 将删除长度为 1 的向量的列表,则每当 v 的长度为 1 时,这将返回错误。
这两种方法之间的显着差异在于它们在用于提取时返回的对象的类,以及它们是否可以接受一系列值,或者在赋值期间只接受单个值。
考虑以下列表中数据提取的情况:
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 中的插槽保留了它们的原始名称。
【讨论】:
双括号访问列表元素,而单个括号会返回一个包含单个元素的列表。
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]]中的错误:递归索引在级别 2 失败由于此代码实际上尝试计算
r[[1]][[2]][[3]],并且r的嵌套在第一层停止,因此通过递归索引提取的尝试在[[2]]处失败,即在第二层。
r[[c("foo", "far")]]中的错误:下标越界在这里,R 正在寻找不存在的
r[["foo"]][["far"]],因此我们得到下标越界错误。如果这两个错误都给出相同的消息,可能会更有帮助/更一致。
【讨论】:
作为术语,[[ 运营商提取物列表中的元素,而 [ 运算符采用子集的一个列表。
【讨论】:
它们都是子集化的方式。 单个括号将返回列表的一个子集,它本身就是一个列表。即,它可能包含也可能不包含多个元素。 另一方面,双括号将只返回列表中的一个元素。
-单括号会给我们一个列表。如果我们希望从列表中返回多个元素,我们也可以使用单括号。 考虑以下列表:
>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.
我将举几个例子。请记下粗体字,并在完成以下示例后返回:
双括号将返回索引处的实际值。(它将不是返回列表)
> 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
【讨论】:
[] 返回一个列表类,即使它是一个数字,这一事实非常不直观。他们应该为列表创建另一种语法,例如 ([]),而用于访问实际元素的 [[]] 就可以了。我更喜欢将 [[]] 视为其他语言中的原始值。
[[ 会很乐意为您返回一个列表,如果这是被选中的元素。正确答案是 [ 将所选项目作为其父对象的子集返回,而 [[ 返回原始所选项目,其本身没有父对象。
为了帮助新手在手动迷雾中导航,将 [[ ... ]] 符号视为崩溃函数 - 换句话说,当您只想从命名向量、列表或数据框中“获取数据”时。如果您想使用来自这些对象的数据进行计算,那么这样做很好。这些简单的例子将说明。
(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]返回一个data.frame
对于另一个具体用例,当您要选择由 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"
【讨论】:
请参考以下详细说明。
我在 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 的第 9 列向量,我们编写 mtcars[[9]]。
汽车[[9]] [1] 1 1 1 0 0 0 0 0 0 0 0 ...
例如 2:- 我们可以通过名称检索相同的列向量。
mtcars[[“上午”]] [1] 1 1 1 0 0 0 0 0 0 0 0 ...
【讨论】: