使用非标准评估方式按多列排序

3

假设我想使用多列和非标准升序来排序data.frame。我可能有一个类似以下代码的函数。

my_order <- function(data, ...) {
  with(data, order(...))
}

当我使用这个函数时,我会出现错误,因为我的列没有在with的上下文中进行评估。

my_order(mtcars, mpg, cyl)
# Error in order(...) : object 'mpg' not found 

注意:对于这个问题,我不希望使用 dplyr::arrange() ,因为它会添加一个依赖项。

2个回答

4
以下是使用基本R传递未解决的符号的一种方法:
my_order <- function(data, ...) {
  dots <- substitute(...())
  with(data, do.call("order", as.list(dots)))
}
my_order(mtcars, mpg, cyl)

基本上,我们使用substitute来捕捉符号名称,然后使用do.call将其注入到对order的调用中。
或者,您可以考虑重写函数调用并更改评估环境。
my_order <- function(data, ...) {
  call <-match.call()
  call[[1]] <- quote(order)
  call[2] <- NULL
  eval(call, data, parent.frame())
}
my_order(mtcars, mpg, cyl)

没错,这很完美。我一直在尝试使用 do.call,但是一直无法正确使用(我使用的是 do.call(order, list(with(data, ...))),已经快到了解决问题的地步)。顺便说一下,你可以使用 do.call(order, as.list(dots)) 来代替引号标注 order,我认为这样可能会更快一些。谢谢 :) - nathaneastwood

4

一个选项是将表达式包装到 eval.parent(substitute(...)) 中:

my_order <- function( data, ... ) {
  eval.parent(substitute( with(data, order(...)) ))
}

my_order( mtcars, cyl, mpg )
# [1] 32 21  3  9  8 27 26 19 28 18 20 11  6 10 30  1  2  4 15 16 24  7 17 31 14
# [26] 23 22 29 12 13  5 25

请注意,我们使用eval.parent()而不是eval(),因为eval / substitute组合与嵌套函数不兼容eval.parent()技巧已经被@MoodyMudskipper提出作为解决这个问题的一种方法,并且允许我们无缝地在其他函数中使用my_order(),包括magrittr管道:
mtcars %>% my_order(cyl)
# [1]  3  8  9 18 19 20 21 26 27 28 32  1  2  4  6 10 11 30  5  7 12 13 14 15 16
# [26] 17 22 23 24 25 29 31

需要注意的一点是,如果您使用带有句点的参数,比如 .data,这种方法似乎不起作用。 - nathaneastwood
@nathaneastwood:我无法复制该问题。对我来说,使用.datadata一样好。 - Artem Sokolov
似乎当您尝试将my_order()与管道结合使用时,会出现此问题,而与变量名称无关。例如,mtcars %>% my_order(cyl)将导致错误。 - nathaneastwood
1
@nathaneastwood:好的,我知道问题出在哪里了。请查看更新后的答案。 - Artem Sokolov

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