为什么在数据框上使用“subset”和“[”会得到略微不同的结果?

17

有人能解释一下为什么我在下面的代码中最后两行(identical()调用)得到不同的结果吗?这两个对象似乎是相同的对象,但当我在一个应用函数中使用它们时,我遇到了一些麻烦:

df <- data.frame(a = 1:5, b = 6:2, c = rep(7,5))
df_ab <- df[,c(1,2)]
df_AB <- subset(df, select = c(1,2))
identical(df_ab,df_AB)
[1] TRUE

apply(df_ab,2,function(x) identical(1:5,x))
    a     b 
TRUE FALSE

apply(df_AB,2,function(x) identical(1:5,x))
    a     b 
FALSE FALSE
4个回答

13

apply() 函数在调用每一列的函数之前,将第一个参数强制转换为矩阵。因此你的数据框被强制转换为矩阵对象。该转换的一个结果是 as.matrix(df_AB) 具有非空行名称,而 as.matrix(df_ab) 则没有:

> str(as.matrix(df_ab))
 int [1:5, 1:2] 1 2 3 4 5 6 5 4 3 2
 - attr(*, "dimnames")=List of 2
  ..$ : NULL
  ..$ : chr [1:2] "a" "b"
> str(as.matrix(df_AB))
 int [1:5, 1:2] 1 2 3 4 5 6 5 4 3 2
 - attr(*, "dimnames")=List of 2
  ..$ : chr [1:5] "1" "2" "3" "4" ...
  ..$ : chr [1:2] "a" "b"

当你使用apply()函数对df_AB的列进行子集操作时,你会得到一个命名向量,它与未命名向量不完全相同。

apply(df_AB, 2, str)
 Named int [1:5] 1 2 3 4 5
 - attr(*, "names")= chr [1:5] "1" "2" "3" "4" ...
 Named int [1:5] 6 5 4 3 2
 - attr(*, "names")= chr [1:5] "1" "2" "3" "4" ...
NULL

与此相比,subset()函数使用逻辑向量来选择行的值,而看起来用非缺失值的i对数据框进行子集操作会导致row.names属性的差异:

> str(as.matrix(df[1:5, 1:2]))
 int [1:5, 1:2] 1 2 3 4 5 6 5 4 3 2
 - attr(*, "dimnames")=List of 2
  ..$ : chr [1:5] "1" "2" "3" "4" ...
  ..$ : chr [1:2] "a" "b"
> str(as.matrix(df[, 1:2]))
 int [1:5, 1:2] 1 2 3 4 5 6 5 4 3 2
 - attr(*, "dimnames")=List of 2
  ..$ : NULL
  ..$ : chr [1:2] "a" "b"

您可以使用.Internal(inspect(x))函数查看数据框之间的所有差异细节。如果您感兴趣,可以自行查看。

正如Roland在评论中指出的那样,您可以使用.row_names_info()函数仅查看行名称的差异。

请注意,当i缺失时,.row_names_info()的结果是负数,但如果您使用非缺失的i进行子集划分,则结果为正数。

> .row_names_info(df_ab, type=1)
[1] -5
> .row_names_info(df_AB, type=1)
[1] 5

这些值的含义在?.row_names_info中解释:

type: integer.  Currently ‘type = 0’ returns the internal
      ‘"row.names"’ attribute (possibly ‘NULL’), ‘type = 2’ the
      number of rows implied by the attribute, and ‘type = 1’ the
      latter with a negative sign for ‘automatic’ row names.

2
原因是 [ 创建了“自动”行名(请参见.row_names_info(df_ab, type=1)),而 subset 则创建了显式行名(请参见.row_names_info(df_AB, type=1))。as.matrix 只是传播了这一点(矩阵不必具有行名)。 - Roland
+1. 看起来 identical(df_ab, df_AB) 应该返回 false? - Señor O
2
@SeñorO -- identical(df_AB, df_ab, attrib.as.set=FALSE) 返回 FALSE。 - Josh O'Brien
row.names的值不同,但不应该由as.matrix自动命名。这不是subset做的命名。 - IRTFM
请查看我的评论BondedDust的答案,以获取有关as.matrix.data.frame中两个对象分歧处(即df_ab在其行名称上获取NULL的位置)更精确的信息。 - Josh O'Brien
你能否根据@Roland的评论写一个更短、更清晰的答案?没有必要使用.Internal(inspect(x))转储,这会使事情变得模糊。 - smci

8
如果您想将值1:5与列中的值进行比较,则不应使用apply,因为apply在应用函数之前会将数据框转换为矩阵。由于使用[创建的子集中存在行名称(请参见@Joshua Ulrich的答案),因此值1:5与包括相同值的命名向量并不相同。相反,您应该使用sapply将相同函数应用于列。这样可以避免将数据框转换为矩阵:
> sapply(df_ab, identical, 1:5)
    a     b 
 TRUE FALSE 
> sapply(df_AB, identical, 1:5)
    a     b 
 TRUE FALSE 

正如你所看到的,在这两个数据帧中,第一列的值都与1:5相同。


5
在一个版本中(使用[),你的列是整数,而在另一个版本中(使用subset),你的列是命名整数。
apply(df_ab, 2, str)

 int [1:5] 1 2 3 4 5
 int [1:5] 6 5 4 3 2
NULL


apply(df_AB, 2, str)

 Named int [1:5] 1 2 3 4 5
 - attr(*, "names")= chr [1:5] "1" "2" "3" "4" ...
 Named int [1:5] 6 5 4 3 2
 - attr(*, "names")= chr [1:5] "1" "2" "3" "4" ...
NULL

这并不完全正确。如果使用applyas.matrix会产生这种差异。请参见lapply(df_AB, str) - Roland

3

在提交给apply之前查看这两个对象的结构,只显示了一个差异: 在行名称中,但这不是我预期会产生你看到的差异的差异。我不认为乔舒亚目前的“subset”作为逻辑索引来解释这一点。为什么使用row.names = c(NA,-5L))在使用“ [”提取时会产生命名结果,至今未被解释。

> dput(df_AB)
structure(list(a = 1:5, b = c(6L, 5L, 4L, 3L, 2L)), .Names = c("a", 
"b"), row.names = c(NA, 5L), class = "data.frame")
> dput(df_ab)
structure(list(a = 1:5, b = c(6L, 5L, 4L, 3L, 2L)), .Names = c("a", 
"b"), class = "data.frame", row.names = c(NA, -5L))

我同意需要进一步研究的是as.matrix强制转换:

> attributes(df_AB[,1])
NULL
> attributes(df_ab[,1])
NULL
> attributes(as.matrix(df_AB)[,1])
$names
[1] "1" "2" "3" "4" "5"

我认为as.matrix.data.frame并不是一个问题。它不应该确定[.data.frame是否导致行名变得显式而不是隐式。 - Joshua Ulrich
1
@BondedDust:我将 c(NA, -5L) 解释为“完全隐式”,而将 c(NA, 5L) 解释为“显式,标准的1:nrow(x)”。 - Joshua Ulrich
@BondedDust 请参阅 help(".row_names_info")。 "类型:整数。目前,type = 0返回内部的“row.names”属性(可能为NULL),type = 2返回属性所暗示的行数,而type = 1则将后者用负号表示为'自动'行名。" - Roland
type参数被传给了.row_names_info函数。它并没有提及行名称的内部表示方式。而.set_row_names函数则没有type参数。 - IRTFM
@JoshO'Brien:它只是删除了“自动”行名称(因为数据框必须有行名称)。因此,你可以认为它们一开始就不存在,因为用户没有放置它们,并且它们没有执行任何可能依赖它们存在的操作。 - Joshua Ulrich
显示剩余6条评论

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