安全版本的子集
作为subset()
手册指出:
警告 :这是一个旨在交互使用的便利功能
我从这篇大文章,不仅这样的警告背后的秘密,但有很好的理解了解到substitute()
match.call()
eval()
, quote()
call
, promise
和其他相关的研究课题,是一个有点复杂。
现在我明白了上面的警告是什么。 subset()
一个超简单实现可以如下所示:
subset = function(x, condition) x[eval(substitute(condition), envir=x),]
虽然subset(mtcars, cyl==4)
返回满足cyl==4
mtcars
中的行的表,但在另一个函数中包含subset()
失败:
sub = function(x, condition) subset(x, condition)
sub(mtcars, cyl == 4)
# Error in eval(expr, envir, enclos) : object 'cyl' not found
使用原始版本的subset()
也会产生完全相同的错误条件。 这是由于substitute()-eval()
对的限制造成的:当condition
是cyl==4
时它工作正常,但是当condition
通过包络函数sub()
, subset()
的condition
参数将会是不再是cyl==4
,而是sub()
体中的嵌套condition
,而eval()
失败 - 这有点复杂。
但它是否存在具有完全相同的参数的subset()
任何其他实现, 这些参数是编程安全的 - 也就是说,当它被另一个函数调用时能够评估它的条件吗?
仅仅因为它是这种引人入胜的乐趣(??),这里有一个稍微不同的解决方案,解决了Hadley在我接受的解决方案中提出的问题。
哈德利发表了一个要点,表明了我接受的功能出错的情况。 该例中的扭曲(复制如下)是传递给SUBSET()
的符号在其中一个调用函数的主体(而不是参数)中定义; 它因此被substitute()
而不是预期的全局变量捕获。 我知道,令人困惑的东西。
f <- function() {
cyl <- 4
g()
}
g <- function() {
SUBSET(mtcars, cyl == 4)$cyl
}
f()
这是一个更好的函数,它只会替代调用函数参数列表中找到的符号值。 它适用于Hadley或我迄今为止提出的所有情况。
SUBSET <- function(`_dat`, expr) {
ff <- sys.frames()
n <- length(ff)
ex <- substitute(expr)
ii <- seq_len(n)
for(i in ii) {
## 'which' is the frame number, and 'n' is # of frames to go back.
margs <- as.list(match.call(definition = sys.function(n - i),
call = sys.call(sys.parent(i))))[-1]
ex <- eval(substitute(substitute(x, env = ll),
env = list(x = ex, ll = margs)))
}
`_dat`[eval(ex, envir = `_dat`),]
}
## Works in Hadley's counterexample ...
f()
# [1] 4 4 4 4 4 4 4 4 4 4 4
## ... and in my original test cases.
sub <- function(x, condition) SUBSET(x, condition)
sub2 <- function(AA, BB) sub(AA, BB)
a <- SUBSET(mtcars, cyl == 4) ## Direct call to SUBSET()
b <- sub(mtcars, cyl == 4) ## SUBSET() called one level down
c <- sub2(mtcars, cyl == 4)
all(identical(a, b), identical(b, c))
# [1] TRUE
重要提示:请注意,这仍然不是(也不能成为)通常有用的功能。 当函数调用调用堆栈时,函数根本无法知道您希望它在所有替换中使用哪些符号。 在许多情况下,用户希望它使用分配给函数体内的符号值,但是这个函数总是忽略这些符号值。
[功能就是你要找的东西。 ? “[”。 mtcars[mtcars$cyl == 4,]
等同于子集命令,并且是“编程”安全的。
sub = function(x, condition) {
x[condition,]
}
sub(mtcars, mtcars$cyl==4)
如果在函数调用中没有使用隐含with()
,你会问什么。 具体情况很复杂,但功能如下:
sub = function(x, quoted_condition) {
x[with(x, eval(parse(text=quoted_condition))),]
}
sub(mtcars, 'cyl==4')
Sorta做你正在寻找的东西,但有边缘情况下,这将有意想不到的结果。
使用data.table
和[
子集函数,你可以得到隐含with(...)
你正在寻找。
library(data.table)
MT = data.table(mtcars)
MT[cyl==4]
在data.table
有更好更快的方法来完成这个子集,但这很好地说明了这一点。
使用data.table
你也可以构造表达式来稍后评估
cond = expression(cyl==4)
MT[eval(cond)]
这两个现在可以通过函数传递:
wrapper = function(DT, condition) {
DT[eval(condition)]
}
以下是subset()
的一个替代版本,它甚至在嵌套时也能继续工作 - 至少只要逻辑子集表达式(例如cyl == 4
)被提供给顶层函数调用。
它通过爬上调用堆栈来工作,在每个步骤中substitute()
以最终捕获由用户传入的逻辑子集表达式。 在调用sub2()
以下,例如,在for
循环从调用堆栈的工作原理向上expr
到x
到AA
和最后到cyl ==4
。
SUBSET <- function(`_dat`, expr) {
ff <- sys.frames()
ex <- substitute(expr)
ii <- rev(seq_along(ff))
for(i in ii) {
ex <- eval(substitute(substitute(x, env=sys.frames()[[n]]),
env = list(x = ex, n=i)))
}
`_dat`[eval(ex, envir = `_dat`),]
}
## Define test functions that nest SUBSET() more and more deeply
sub <- function(x, condition) SUBSET(x, condition)
sub2 <- function(AA, BB) sub(AA, BB)
## Show that it works, at least when the top-level function call
## contains the logical subsetting expression
a <- SUBSET(mtcars, cyl == 4) ## Direct call to SUBSET()
b <- sub(mtcars, cyl == 4) ## SUBSET() called one level down
c <- sub2(mtcars, cyl == 4) ## SUBSET() called two levels down
identical(a,b)
# [1] TRUE
> identical(a,c)
# [1] TRUE
a[1:5,]
# mpg cyl disp hp drat wt qsec vs am gear carb
# Datsun 710 22.8 4 108.0 93 3.85 2.320 18.61 1 1 4 1
# Merc 240D 24.4 4 146.7 62 3.69 3.190 20.00 1 0 4 2
# Merc 230 22.8 4 140.8 95 3.92 3.150 22.90 1 0 4 2
# Fiat 128 32.4 4 78.7 66 4.08 2.200 19.47 1 1 4 1
# Honda Civic 30.4 4 75.7 52 4.93 1.615 18.52 1 1 4 2
**有关for
循环内结构的一些解释,请参见R语言定义手册的第6.2节第6段。