在 R 中使用 mapply 对子集参数的非标准评估

Tho*_*mas 9 evaluation scope r subset mapply

我不能使用subset的参数xtabsaggregate(或I测试,包括任何功能ftablelm)用mapply。以下调用因subset参数而失败,但它们可以在没有的情况下工作:

mapply(FUN = xtabs,
       formula = list(~ wool,
                      ~ wool + tension),
       subset = list(breaks < 15,
                     breaks < 20),
       MoreArgs = list(data = warpbreaks))

# Error in mapply(FUN = xtabs, formula = list(~wool, ~wool + tension), subset = list(breaks <  : 
#   object 'breaks' not found
# 
# expected result 1/2:
# wool
# A B 
# 2 2
# 
# expected result 2/2:
#     tension
# wool L M H
#    A 0 4 3
#    B 2 2 5

mapply(FUN = aggregate,
       formula = list(breaks ~ wool,
                      breaks ~ wool + tension),
       subset = list(breaks < 15,
                     breaks < 20),
       MoreArgs = list(data = warpbreaks,
                       FUN = length))

# Error in mapply(FUN = aggregate, formula = list(breaks ~ wool, breaks ~  : 
#   object 'breaks' not found
# 
# expected result 1/2:
#   wool breaks
# 1    A      2
# 2    B      2
# 
# expected result 2/2:
#   wool tension breaks
# 1    B       L      2
# 2    A       M      4
# 3    B       M      2
# 4    A       H      3
# 5    B       H      5
Run Code Online (Sandbox Code Playgroud)

错误似乎是由于subset没有在正确的环境中评估参数。我知道我可以作为一种解决方法在data参数中进行子集化data = warpbreaks[warpbreaks$breaks < 20, ],但我希望提高我对 R 的了解。

我的问题是:

  • 我如何使用subset参数mapply?我尝试使用match.calland eval.parent,但到目前为止没有成功(更多细节在我之前的问题中)。
  • 为什么formula参数在 中求值data = warpbreaks,但subset参数不是?

All*_*ron 8

简短的回答是,当您创建一个列表作为参数传递给函数时,它会在创建时进行评估。您收到的错误是因为 R 尝试创建您要在调用环境中传递的列表。

为了更清楚地看到这一点,假设您尝试创建要在调用之前传递的参数mapply

f_list <- list(~ wool, ~ wool + tension)
d_list <- list(data = warpbreaks)
mapply(FUN = xtabs, formula = f_list, MoreArgs = d_list)
#> [[1]]
#> wool
#>  A  B 
#> 27 27 
#> 
#> [[2]]
#>     tension
#> wool L M H
#>    A 9 9 9
#>    B 9 9 9
Run Code Online (Sandbox Code Playgroud)

创建公式列表没有问题,因为在需要之前不会对它们进行评估,并且当然warpbreaks可以从全局环境访问,因此此调用mapply有效。

当然,如果您尝试在mapply通话前创建以下列表:

subset_list <- list(breaks < 15, breaks < 20)
Run Code Online (Sandbox Code Playgroud)

然后R会告诉你breaks没有找到。

但是,如果您warpbreaks在搜索路径中创建列表,则不会有问题:

subset_list <- list(breaks < 15, breaks < 20)
Run Code Online (Sandbox Code Playgroud)

所以你会认为我们可以把它传递给mapply一切都会好的,但现在我们得到一个新的错误:

subset_list <- with(warpbreaks, list(breaks < 15, breaks < 20))
subset_list
#> [[1]]
#>  [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [14]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE
#> [27] FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [40] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE
#> [53] FALSE FALSE
#> 
#> [[2]]
#>  [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE
#> [14]  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE  TRUE
#> [27] FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE
#> [40]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE
#> [53]  TRUE FALSE
Run Code Online (Sandbox Code Playgroud)

那么为什么我们会得到这个?

问题在于传递给mapply该调用的任何函数eval,或者它们本身调用使用eval.

如果您查看源代码,mapply您会发现它接受您传递的额外参数并将它们放入名为 的列表中dots,然后它将传递给内部mapply调用:

mapply(FUN = xtabs, formula = f_list, subset = subset_list, MoreArgs = d_list)
#> Error in eval(substitute(subset), data, env) : object 'dots' not found
Run Code Online (Sandbox Code Playgroud)

如果您FUN自己调用另一个调用eval其任何参数的函数,则它将因此尝试eval使用 object dots,该对象在eval调用的环境中不存在。通过在包装器mapply上执行操作很容易看出这一点match.call

mapply(function(x) match.call(), x = list(1))
[[1]]
(function(x) match.call())(x = dots[[1L]][[1L]])
Run Code Online (Sandbox Code Playgroud)

所以我们的错误的最小可重现示例是

mapply(function(x) eval(substitute(x)), x = list(1))
#> Error in eval(substitute(x)) : object 'dots' not found
Run Code Online (Sandbox Code Playgroud)

那么有什么解决办法呢?似乎您已经找到了一个非常好的方法,即手动设置您希望传递的数据框的子集。其他人可能会建议您探索purrr::map以获得更优雅的解决方案。

但是,它可能得到mapply做你想要什么,这个秘密就是修改FUN把它变成一个匿名包装xtabs上的苍蝇子集:

mapply
#> function (FUN, ..., MoreArgs = NULL, SIMPLIFY = TRUE, USE.NAMES = TRUE) 
#> {
#>     FUN <- match.fun(FUN)
#>     dots <- list(...)
#>     answer <- .Internal(mapply(FUN, dots, MoreArgs))
#> ...
Run Code Online (Sandbox Code Playgroud)

  • 多么美好的一天啊!在你今天早上的出色回答之后,这是另一个。感谢您的教学法,我很高兴我对 R 有了更多了解。我已经对您的答案投了赞成票,但我会等到截止日期来验证它并授予赏金以保持问题的吸引力。 (2认同)