编程安全版本的子集 - 在从另一个函数调用时评估其条件

Ali*_*Ali 11 r

作为subset()手册说明:

警告:这是一个便于交互使用的便利功能

我从中学到这篇大文章,不仅这样的警告背后的秘密,但有很好的理解substitute(),match.call(),eval(),quote(), call,promise以及其他相关的研究课题,是一个有点复杂.

现在我明白上面的警告是什么了.一个超级简单的实现subset()可能如下:

subset = function(x, condition) x[eval(substitute(condition), envir=x),]
Run Code Online (Sandbox Code Playgroud)

虽然subset(mtcars, cyl==4)收益中的行表mtcars满足cyl==4,包围subset()在另一个函数失败:

sub = function(x, condition) subset(x, condition)

sub(mtcars, cyl == 4)
# Error in eval(expr, envir, enclos) : object 'cyl' not found
Run Code Online (Sandbox Code Playgroud)

使用原始版本subset()也会产生完全相同的错误条件.这是由于的限制substitute()-eval()对:它正常工作,而conditioncyl==4,但是当condition通过包络函数传递sub()condition的参数subset()将不再cyl==4,但嵌套conditionsub()身体和eval()失败-这是一个有点复杂.

但它是否存在subset()具有完全相同的参数的任何其他实现,这些参数将是编程安全的 - 即能够在被另一个函数调用时评估其条件?

Jus*_*tin 8

[功能是你正在寻找的.? "[".mtcars[mtcars$cyl == 4,]等效于子命令,并且"编程"安全.

sub = function(x, condition) {
 x[condition,]
}

sub(mtcars, mtcars$cyl==4)
Run Code Online (Sandbox Code Playgroud)

如果没有with()函数调用中的隐含,你会问什么.细节很复杂,但功能如下:

sub = function(x, quoted_condition) {
  x[with(x, eval(parse(text=quoted_condition))),]
}

sub(mtcars, 'cyl==4')
Run Code Online (Sandbox Code Playgroud)

Sorta可以满足你的需求,但是有一些边缘情况会产生意想不到的结果.


使用data.table[子集函数,你可以获得with(...)你正在寻找的隐含.

library(data.table)
MT = data.table(mtcars)

MT[cyl==4]
Run Code Online (Sandbox Code Playgroud)

有更好,更快的方法来进行这种子集化data.table,但这很好地说明了这一点.


使用data.table您还可以构造要在以后进行评估的表达式

cond = expression(cyl==4)

MT[eval(cond)]
Run Code Online (Sandbox Code Playgroud)

这两个现在可以通过函数传递:

wrapper = function(DT, condition) {
  DT[eval(condition)]
}
Run Code Online (Sandbox Code Playgroud)

  • @AliSharifi贾斯汀(基本上)是正确的.与`subset`完全相同但"编程安全"的函数是`[.data.frame`.仔细观察,你会发现他们有四个相同的论点. (4认同)

Jos*_*ien 7

这是一个替代版本,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
Run Code Online (Sandbox Code Playgroud)

**有关for循环内部构造的一些解释,请参阅R语言定义手册的第6.2节第6段.

  • 虽然这回答了这个问题,但我认为这是一个危险的功能,因为它比原版要复杂得多,我怀疑在某些设置中容易出现故障.通常无法使用非标准评估的功能安全地进行编程 - 这是在便利性和安全性之间进行权衡.你不能两者兼得. (3认同)
  • 顺便说一下,我现在得出的结论是,你的功能是天才,邪恶的天才,但天才一点也不少;) (3认同)
  • @JoshO'Brien:或称它为"..DAT"或其他东西.如果有人使用`..DAT`作为变量名,他们总是要求麻烦. (2认同)

Jos*_*ien 4

正因为它是如此令人费解的乐趣(??),这里有一个稍微不同的解决方案,它解决了哈德利在我接受的解决方案的评论中指出的问题。

哈德利发布了一个要点,展示了我接受的功能出错的情况。该示例(复制如下)的不同之处在于,传递给的符号SUBSET()是在调用函数之一的主体(而不是参数)中定义的;因此它被捕获而substitute()不是预期的全局变量。我知道,这是令人困惑的事情。

f <- function() {
  cyl <- 4
  g()
}

g <- function() {
  SUBSET(mtcars, cyl == 4)$cyl
}
f()
Run Code Online (Sandbox Code Playgroud)

这是一个更好的函数,它只会替换调用函数的参数列表中找到的符号值。它适用于哈德利或我迄今为止提出的所有情况。

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
Run Code Online (Sandbox Code Playgroud)

重要提示:请注意,这仍然不是(也不能制成)一个普遍有用的函数。该函数根本无法知道您希望它在调用堆栈中执行的所有替换中使用哪些符号。在很多情况下,用户希望使用分配给函数体内的符号值,但该函数始终会忽略这些值。