subset
是解决此问题的基本函数。然而,像所有使用非标准评估的基本R函数一样,subset
不执行完全卫生的代码扩展。因此,在非全局范围(例如在lapply循环中)使用subset()
时,它会评估错误的变量。
例如,在这里我们在两个地方定义变量var
,首先在全局范围内定义值为40
,然后在本地范围内定义值为30
。这里使用local()
是为了简单起见,但在函数内部也具有相同的行为。直观上,我们希望subset
在评估时使用值30
。然而,在执行以下代码时,我们看到使用的是值40
(因此没有返回任何行)。
var <- 40
local({
var <- 30
dfs <- list(mtcars, mtcars)
lapply(dfs, subset, mpg > var)
})
这是因为在
subset()
中使用的
parent.frame()
是
lapply()
内部环境,而不是局部块。由于所有环境最终都继承自全局环境,变量
var
在那里被找到并具有值
40
。
通过引用展开进行卫生变量(如
rlang 包 中实现的那样)可以解决此问题。我们可以使用整洁评估定义一个
subset
的变体,它可以在所有上下文中正常工作。该代码源自
base::subset.data.frame()
的代码,并且大部分相同。
subset2 <- function (x, subset, select, drop = FALSE, ...) {
r <- if (missing(subset))
rep_len(TRUE, nrow(x))
else {
r <- rlang::eval_tidy(rlang::enquo(subset), x)
if (!is.logical(r))
stop("'subset' must be logical")
r & !is.na(r)
}
vars <- if (missing(select))
TRUE
else {
nl <- as.list(seq_along(x))
names(nl) <- names(x)
rlang::eval_tidy(rlang::enquo(select), nl)
}
x[r, vars, drop = drop]
}
这个版本的subset与base::subset.data.frame()
完全一样。
subset2(mtcars, gear > 4, disp:wt)
然而,subset2()
没有遭受 subset 的作用域问题。在我们之前的示例中,变量 var
使用值 30
,就像我们从词法作用域规则中所期望的那样。
local({
var <- 30
dfs <- list(mtcars, mtcars)
lapply(dfs, subset2, mpg > var)
})
这使得非标准计算可以在所有上下文中稳健地使用,而不仅仅是像以前的方法一样只能在顶级上下文中使用。
这使得使用非标准计算的函数更加有用。以前,虽然它们在交互式使用时很好用,但在编写函数和包时需要使用更冗长的标准计算函数。现在,同一个函数可以在所有上下文中使用,无需修改代码!
关于非标准计算的更多细节,请参见Lionel Henry的Tidy evaluation (hygienic fexprs) presentation、tidy evaluation的rlang说明文档以及使用dplyr进行编程说明文档。
with(cars, cars[speed == 4 & dist < 10, ])
的翻译是:使用with()
函数,筛选出速度为 4 且距离小于 10 的汽车。 - G5Wwith()
需要两次输入数据框名称,就像cars[eval(substitute(speed == 4 & dist < 10, cars)), ]
一样。 - Richie Cottonsubset()
可以解决这个问题。我更想知道是否有一种使用[
的方法来解决它。 - Richie Cotton