使用dplyr将变量作为函数的默认参数

Axe*_*man 17 r scoping dplyr lazyeval

目标

我的目标是定义一些在dplyr动词中使用的函数,它们使用预定义的变量.这是因为我有一些这些函数带有一堆参数,其中许多参数都是相同的变量名.

我的理解:这很难(也许是不可能的)因为dplyr稍后会懒惰地评估用户指定的变量,但是任何默认参数都不在函数调用中,因此不可见dplyr.

玩具示例

考虑以下示例,我用它dplyr来计算变量是否已更改(在这种情况下相当无意义):

library(dplyr)
mtcars  %>%
  mutate(cyl_change = cyl != lag(cyl))
Run Code Online (Sandbox Code Playgroud)

现在,lag还支持备用排序,如下所示:

mtcars  %>%
  mutate(cyl_change = cyl != lag(cyl, order_by = gear))
Run Code Online (Sandbox Code Playgroud)

但是,如果我想创建自己的版本lag,总是按顺序排序gear呢?

尝试失败

天真的方法是这样的:

lag2 <- function(x, n = 1L, order_by = gear) lag(x, n = n, order_by = order_by)

mtcars %>%
  mutate(cyl_change = cyl != lag2(cyl))
Run Code Online (Sandbox Code Playgroud)

但这显然会引发错误:

没有找到名为'gear'的对象

更现实的选择是这些,但它们也不起作用:

lag2 <- function(x, n = 1L) lag(x, n = n, order_by = ~gear)
lag2 <- function(x, n = 1L) lag(x, n = n, order_by = get(gear))
lag2 <- function(x, n = 1L) lag(x, n = n, order_by = getAnywhere(gear))
lag2 <- function(x, n = 1L) lag(x, n = n, order_by = lazyeval::lazy(gear))
Run Code Online (Sandbox Code Playgroud)

有没有办法在正在运行的data.frame中lag2正确查找?geardplyr

  • 一个人应该能够在lag2不必提供的情况下打电话gear.
  • 一个人应该能够使用lag2未被调用的数据集mtcars(但确实有gear一个变量).
  • 最好gear是函数的默认参数,因此如果需要它仍然可以更改,但这并不重要.

edd*_*ddi 10

这里有两种方法data.table,但我不相信它们中的任何一种都能在dplyr目前使用.

data.table,无论是内部j-expression(又名的第二参数[.data.table)被解析所述data.table封装第一,而不是通过常规ř解析器.在某种程度上,您可以将其视为一个独立的语言解析器,它位于R的常规语言解析器中.这个解析器的作用是,它查找您使用的实际是data.table您正在操作的列的变量,以及无论它发现它把它放在环境中j-expression.

这意味着,你必须让这个解析器以某种方式知道gear将被使用,或者它根本不会成为环境的一部分.以下是实现这一目标的两个想法.

"简单"的方法是j-expression在你调用的地方实际使用列名lag2(除了一些monkeying lag2):

dt = as.data.table(mtcars)

lag2 = function(x) lag(x, order_by = get('gear', sys.frame(4)))

dt[, newvar := {gear; lag2(cyl)}]
# or
dt[, newvar := {.SD; lag2(cyl)}]
Run Code Online (Sandbox Code Playgroud)

这个解决方案有两个不受欢迎的属性 - 首先,我不确定它sys.frame(4)是多么脆弱- 你把这个东西放在一个函数或一个包中,我不知道会发生什么.你可以解决它并弄清楚正确的框架,但这是一种痛苦.第二 - 您必须在表达式中的任何位置提及您感兴趣的特定变量,或者在.SD任何地方再次使用,将所有这些变量转储到环境中.

我更喜欢的第二个选项是利用data.table解析器在变量查找之前评估eval表达式的事实,所以如果你在某个表达式中使用一个变量,那么你可以使用:eval

lag3 = quote(function(x) lag(x, order_by = gear))

dt[, newvar := eval(lag3)(cyl)]
Run Code Online (Sandbox Code Playgroud)

这不会受到其他解决方案的影响,而且必须输入额外的明显的缺点eval.


Axe*_*man 4

这个解决方案即将到来:

考虑一个稍微简单一点的玩具示例:

mtcars %>%
  mutate(carb2 = lag(carb, order_by = gear))
Run Code Online (Sandbox Code Playgroud)

我们仍然使用lagand 它的order_by参数,但不再用它做任何进一步的计算。我们不再坚持使用 SE mutate,而是切换到 NSEmutate_lag2构建一个函数调用作为字符向量。

lag2 <- function(x, n = 1, order_by = gear) {
  x <- deparse(substitute(x))
  order_by <- deparse(substitute(order_by))
  paste0('dplyr::lag(x = ', x, ', n = ', n, ', order_by = ', order_by, ')')
}

mtcars %>%
  mutate_(carb2 = lag2(carb))
Run Code Online (Sandbox Code Playgroud)

这给了我们与上面相同的结果。

原始玩具示例可以通过以下方式实现:

mtcars %>%
  mutate_(cyl_change = paste('cyl !=', lag2(cyl)))
Run Code Online (Sandbox Code Playgroud)

缺点:

  1. 我们必须使用SE mutate_
  2. 对于原始示例中的扩展用法,我们还需要使用paste.
  3. 这不是特别安全,即不能立即清楚gear应该来自哪里。gear向全局环境或在全局环境中赋值carb似乎没问题,但我的猜测是,在某些情况下可能会出现意外的错误。使用公式而不是字符向量会更安全,但这需要为其分配正确的环境才能工作,这对我来说仍然是一个很大的问号。