lapply与"$"函数的使用

43

假设我有一个数据框列表

dflist <- list(data.frame(a=1:3), data.frame(b=10:12, a=4:6))

如果我想从列表中的每个项目中提取第一列,我可以这样做:

lapply(dflist, `[[`, 1)
# [[1]]
# [1] 1 2 3
# 
# [[2]]
# [1] 10 11 12

为什么我不能以同样的方式使用"$"函数?
lapply(dflist, `$`, "a")
# [[1]]
# NULL
# 
# [[2]]
# NULL

但是这两种方式都可以使用:

lapply(dflist, function(x) x$a)
`$`(dflist[[1]], "a")

我明白在这种情况下可以使用


lapply(dflist, `[[`, "a")

但是我正在使用一个S4对象,似乎不允许使用[[进行索引。例如:

library(adegenet)
data(nancycats)
catpop <- genind2genpop(nancycats)
mylist <- list(catpop, catpop)

#works
catpop[[1]]$tab

#doesn't work
lapply(mylist, "$", "tab")
# Error in slot(x, name) : 
#   no slot of name "..." for this object of class "genpop"

#doesn't work
lapply(mylist, "[[", "tab")
# Error in FUN(X[[1L]], ...) : this S4 class is not subsettable

这个可以工作 lapply(dflist, function(x) "$"(x, "a")) - Tim
1
不错的问题。顺便提一下,可以使用 methods("$", dflist[[1]]) 找到答案的某些线索。 - Frank
2
嗯,@Frank,我并不是不知道$.data.frame的存在,只是我很惊讶问题是由方法分派引起的。我想不出还有多少其他情况需要显式调用通用函数的某种形式。 - MrFlick
@MrFlick -- 我同意你的观点。这可能与lapply()的奇怪的惰性求值特性有关,但由于其中一些(或者说全部)操作是在C代码层面进行的,我从未完全理解它在底层做了什么。 - Josh O'Brien
1
@JoshO'Brien 我认为这可能更多地与使用 $ 解析参数有关,而不是特别与 lapply 有关。请参见此示例:f<-function(x,...) \$`(x, ...); f(dflist[[1]], "a"); `$`(dflist[[1]], "a")。这是因为 $不是一个“典型”的通用函数,它是一个.Primitive()`,所以我敢打赌秘密就在这里 - MrFlick
谢谢提供这个链接。我做了一些搜索,但没有找到这个。也许 $ 不太适合在谷歌上搜索。 - mt1022
2个回答

31

第一个例子中,你只需要这样做:

lapply(dflist, `$.data.frame`, "a")

第二种方法是使用slot()访问器函数。

lapply(mylist, "slot", "tab")

我不确定为什么第一种情况下方法派遣不起作用,但 ?lapplyNote 部分确实解决了其对像 $ 这样的原始函数的破坏性方法派遣问题:


 Note:

 [...]

 For historical reasons, the calls created by ‘lapply’ are
 unevaluated, and code has been written (e.g., ‘bquote’) that
 relies on this.  This means that the recorded call is always of
 the form ‘FUN(X[[i]], ...)’, with ‘i’ replaced by the current
 (integer or double) index.  This is not normally a problem, but it
 can be if ‘FUN’ uses ‘sys.call’ ormatch.call’ or if it is a
 primitive function that makes use of the call.  This means that it
 is often safer to call primitive functions with a wrapper, so that
 e.g. ‘lapply(ll, function(x) is.numeric(x))’ is required to ensure
 that method dispatch for ‘is.numeric’ occurs correctly.

非常感谢您的见解。最终,我真正需要的是slot()函数,所以感谢您引起了我的注意。我添加了另一个答案,我认为它更接近这种情况的“原因”。从我现在的理解来看,这实际上更多地涉及通用的$实现,而不是lapply() - MrFlick

13

所以看起来这个问题更多与$有关,它通常希望未引用的名称作为第二个参数而不是字符串。看一下这个例子。

dflist <- list(
    data.frame(a=1:3, z=31:33), 
    data.frame(b=10:12, a=4:6, z=31:33)
)
lapply(dflist, 
    function(x, z) {
        print(paste("z:",z)); 
        `$`(x,z)
    }, 
    z="a"
)
我们看到了结果。
[1] "z: a"
[1] "z: a"
[[1]]
[1] 31 32 33

[[2]]
[1] 31 32 33

因此,z值被设置为"a",但$没有评估第二个参数。因此返回的是"z"列而不是"a"列。这导致了这一组有趣的结果。

a<-"z"; `$`(dflist[[1]], a)
# [1] 1 2 3
a<-"z"; `$`(dflist[[1]], "z")
# [1] 31 32 33

a<-"z"; `$.data.frame`(dflist[[1]], a)
# [1] 31 32 33
a<-"z"; `$.data.frame`(dflist[[1]], "z")
# [1] 31 32 33

当我们直接调用$.data.frame时,我们绕开了原始基元中发生的标准解析,这发生在分配附近(源代码中在这里)。

lapply的增加捕获是通过...机制将参数传递给函数。例如

lapply(dflist, function(x, z) sys.call())
# [[1]]
# FUN(X[[2L]], ...)

# [[2]]
# FUN(X[[2L]], ...)

这意味着当调用$时,它将...解析为字符串"..."。这解释了这种行为。

dflist<- list(data.frame(a=1:3, "..."=11:13, check.names=F))
lapply(dflist, `$`, "a")
# [[1]]
# [1] 11 12 13

当你尝试使用...时,同样的事情会发生

f<-function(x,...) `$`(x, ...); 

f(dflist[[1]], "a");
# [1] 11 12 13
`$`(dflist[[1]], "a")
# [1] 1 2 3

我感觉自己可能有点迟钝,但我不太明白这个解释为什么会导致“lapply(dflist, $, "a")”返回“NULL”。毕竟,“"$"(dflist[[1]], "z")”返回“31:33”,但显然等效的调用“lapply(dflist[1], $, "z")”返回“NULL”... 我错过了什么? - Josh O'Brien
啊,好的。嗯,lapply 还有一个额外的层级。它使用 ... 传递参数。所以如果你拦截调用,你会发现传递给函数的第二个参数是“...”。所以你是对的。这也与 lapply 如何通过 ... 传递参数有关。我也会加上这一点。 - MrFlick
1
哦,我明白你的意思了。太有趣了!看看这个:df <- data.frame("..."=1:3, z=31:33); dflist <- list(df, df); lapply(dflist, `$`, "z") - Josh O'Brien
@JoshO'Brien 哈哈,是的,那正是我正在编辑的内容! :) - MrFlick

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