R中具有mapply的子集参数的非标准评估

问题描述 投票:5回答:3

我不能将subsetxtabs(或我测试过的任何功能,包括aggregateftablelm参数)与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

错误似乎是由于未在正确的环境中评估subset参数。我知道我可以使用data作为data = warpbreaks[warpbreaks$breaks < 20, ]参数的子集,但我希望提高对R的了解。

我的问题是:

  • 如何将subset参数与mapply一起使用?我尝试使用match.calleval.parent,但到目前为止没有成功(previous questions中有更多详细信息)。
  • 为什么formula参数在data = warpbreaks中求值,但是subset参数不是吗?
r scope subset evaluation mapply
3个回答
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

创建公式列表没有问题,因为只有在需要时才对它们进行求值,并且当然可以从全局环境访问warpbreaks,因此对mapply的调用有效。

当然,如果您尝试在mapply调用之前创建以下列表:

subset_list <- list(breaks < 15, breaks < 20)

然后R会告诉您找不到breaks

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

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

所以您会认为我们可以将其传递给mapply,一切都会好起来,但是现在我们收到了一个新错误:

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

所以我们为什么要得到这个?

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

[如果查看mapply的源代码,您将看到它接受了传递的额外参数,并将它们放在名为dots的列表中,然后它将传递给内部mapply调用:] >

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

如果您的FUN本身调用了另一个在其任何参数上调用eval的函数,则它将尝试eval对象dots,该对象在eval的环境中将不存在叫做。通过在mapply包装器上执行match.call可以很容易地看到:

mapply(function(x) match.call(), x = list(1))
[[1]]
(function(x) match.call())(x = dots[[1L]][[1L]])

所以我们的错误的一个最小的可复制示例是

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

那么解决方案是什么?看来您已经找到了一个非常好的选择,那就是手动设置希望传递的数据帧。其他人可能建议您探索purrr::map以获得更优雅的解决方案。

但是,[[是

可能使mapply做您想做的事,而秘密只是修改FUN以将其转换为正在运行的子集的xtabs的匿名包装:] >mapply(FUN = function(formula, subset, data) xtabs(formula, data[subset,]), formula = list(~ wool, ~ wool + tension), subset = with(warpbreaks, list(breaks < 15, breaks < 20)), MoreArgs = list(data = warpbreaks)) #> [[1]] #> wool #> A B #> 2 2 #> #> [[2]] #> tension #> wool L M H #> A 0 4 3 #> B 2 2 5
@@ AllanCameron暗示了purrr::map解决方案的可能性。以下是一些选项:

    [由于我们知道我们想通过breaks列进行子集化,因此我们只需要提供截止值,因此不必担心延迟对表达式的求值(Allan在其答案中对此进行了详细讨论)。在这里和其他示例中,我们在中断列表中命名值,以便输出也将具有告诉我们使用了什么breaks截止值的名称。同样,在所有示例中,我们都利用dplyr::filter函数来过滤data参数中的数据,而不是subset参数中的数据:
  • library(tidyverse) map2(list(breaks.lt.15=15, breaks.lt.20=20), list(~ wool, ~ wool + tension), ~ xtabs(.y, data=filter(warpbreaks, breaks < .x)) ) #> $breaks.lt.15 #> wool #> A B #> 2 2 #> #> $breaks.lt.20 #> tension #> wool L M H #> A 0 4 3 #> B 2 2 5
    1. 类似于上面,但是我们提供了完整的过滤器表达式,并将过滤器表达式包装在quos中以延迟评估。 !!.x在我们过滤warpbreaks中的xtabs数据帧的点评估表达式。
  • map2(quos(breaks.lt.15=breaks < 15, breaks.lt.20=breaks < 20), list(~ wool, ~ wool + tension), ~ xtabs(.y, data=filter(warpbreaks, !!.x)) ) #> $breaks.lt.15 #> wool #> A B #> 2 2 #> #> $breaks.lt.20 #> tension #> wool L M H #> A 0 4 3 #> B 2 2 5
    1. [如果需要过滤器和xtabs公式的所有组合,则可以使用crossing函数生成组合,然后将其传递给pmap(“并行映射”),后者可以接受任意数量的参数,所有包含在一个列表中。在这种情况下,我们使用rlang::exprs而不是quos延迟评估。 rlang::exprs在上面也可以使用,但是quos在这里不起作用。我不确定我是否真正理解为什么,但是与捕获表达式及其环境(quos)和仅捕获表达式(exprs)有关,如here所述。
  • # map over all four combinations of breaks and xtabs formulas crossing( rlang::exprs(breaks.lt.15=breaks < 15, breaks.lt.20=breaks < 20), list(~ wool, ~ wool + tension) ) %>% pmap(~ xtabs(.y, data=filter(warpbreaks, !!.x))) #> $breaks.lt.15 #> wool #> A B #> 2 2 #> #> $breaks.lt.15 #> tension #> wool L M H #> A 0 1 1 #> B 1 0 1 #> #> $breaks.lt.20 #> wool #> A B #> 7 9 #> #> $breaks.lt.20 #> tension #> wool L M H #> A 0 4 3 #> B 2 2 5
  • 您也可以使用tidyverse函数代替xtabs作为摘要,并返回一个数据帧。例如:

    map2_df(c(15,20), list("wool", c("wool", "tension")), ~ warpbreaks %>% filter(breaks < .x) %>% group_by_at(.y) %>% tally() %>% bind_cols(max.breaks=.x - 1) ) %>% mutate_if(is.factor, ~replace_na(fct_expand(., "All"), "All")) %>% select(is.factor, everything()) #> # A tibble: 7 x 4 #> wool tension n max.breaks #> <fct> <fct> <int> <dbl> #> 1 A All 2 14 #> 2 B All 2 14 #> 3 A M 4 19 #> 4 A H 3 19 #> 5 B L 2 19 #> 6 B M 2 19 #> 7 B H 5 19

    并且如果您想包括边际计数以供参考,则可以这样做:

    crossing( c(Inf,15,20), list(NULL, "wool", c("wool", "tension")) ) %>% pmap_df( ~ warpbreaks %>% filter(breaks < .x) %>% group_by_at(.y) %>% tally() %>% bind_cols(max.breaks=.x - 1) ) %>% mutate_if(is.factor, ~replace_na(fct_expand(., "All"), "All")) %>% select(is.factor, everything()) #> wool tension n max.breaks #> 1 All All 4 14 #> 2 A All 2 14 #> 3 B All 2 14 #> 4 A M 1 14 #> 5 A H 1 14 #> 6 B L 1 14 #> 7 B H 1 14 #> 8 All All 16 19 #> 9 A All 7 19 #> 10 B All 9 19 #> 11 A M 4 19 #> 12 A H 3 19 #> 13 B L 2 19 #> 14 B M 2 19 #> 15 B H 5 19 #> 16 All All 54 Inf #> 17 A All 27 Inf #> 18 B All 27 Inf #> 19 A L 9 Inf #> 20 A M 9 Inf #> 21 A H 9 Inf #> 22 B L 9 Inf #> 23 B M 9 Inf #> 24 B H 9 Inf

    这是NSE的问题。一种方法是直接在调用中注入子集条件,以便可以将它们应用于相关上下文(数据中存在breaks的位置)。

    可以使用alist()而不是list()来获得带引号的表达式列表,然后建立正确的调用(使用bquote()是最简单的方法)并对其进行评估。

    mapply( FUN = function(formula, data, subset) eval(bquote(xtabs(formula, data, .(subset)))), formula = list(~ wool, ~ wool + tension), subset = alist(breaks < 15, breaks < 20), MoreArgs = list(data = warpbreaks)) #> [[1]] #> wool #> A B #> 2 2 #> #> [[2]] #> tension #> wool L M H #> A 0 4 3 #> B 2 2 5 mapply(FUN = function(formula, data, FUN, subset) eval(bquote(aggregate(formula, data, FUN, subset = .(subset)))), formula = list(breaks ~ wool, breaks ~ wool + tension), subset = alist(breaks < 15, breaks < 20), MoreArgs = list(data = warpbreaks, FUN = length)) #> [[1]] #> wool breaks #> 1 A 2 #> 2 B 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

    您实际上不再需要MoreArgs,因为您可以在调用中直接使用参数,因此您可能希望将其简化如下:

    mapply( FUN = function(formula, subset) eval(bquote(xtabs(formula, warpbreaks, subset = .(subset)))), formula = list(~ wool, ~ wool + tension), subset = alist(breaks < 15, breaks < 20)) #> [[1]] #> wool #> A B #> 2 2 #> #> [[2]] #> tension #> wool L M H #> A 0 4 3 #> B 2 2 5 mapply(FUN = function(formula, subset) eval(bquote(aggregate(formula, warpbreaks, length, subset = .(subset)))), formula = list(breaks ~ wool, breaks ~ wool + tension), subset = alist(breaks < 15, breaks < 20)) #> [[1]] #> wool breaks #> 1 A 2 #> 2 B 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

    您还可以通过构建数据集以使用lapply循环来避免调用操作和即席FUN参数:

    mapply( FUN = xtabs, formula = list(~ wool, ~ wool + tension), data = lapply(c(15, 20), function(x) subset(warpbreaks, breaks < x))) #> [[1]] #> wool #> A B #> 2 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), data = lapply(c(15, 20), function(x) subset(warpbreaks, breaks < x)), MoreArgs = list(FUN = length)) #> [[1]] #> wool breaks #> 1 A 2 #> 2 B 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


    3
    投票
    @@ AllanCameron暗示了purrr::map解决方案的可能性。以下是一些选项:

    2
    投票
    这是NSE的问题。一种方法是直接在调用中注入子集条件,以便可以将它们应用于相关上下文(数据中存在breaks的位置)。
    © www.soinside.com 2019 - 2024. All rights reserved.