purrr::map
解决方案的可能性。以下是一些选项:我不能将subset
或xtabs
(或我测试过的任何功能,包括aggregate
和ftable
的lm
参数)与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.call
和eval.parent
,但到目前为止没有成功(previous questions中有更多详细信息)。formula
参数在data = warpbreaks
中求值,但是subset
参数不是吗?简短的答案是,当您创建要作为参数传递给函数的列表时,将在创建时对其进行评估。您收到的错误是因为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
类似于上面,但是我们提供了完整的过滤器表达式,并将过滤器表达式包装在
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
[如果需要过滤器和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
purrr::map
解决方案的可能性。以下是一些选项:breaks
的位置)。