do.call()和整洁评估

6

尝试在整洁评估的上下文中使do.call()起作用:

library(rlang)
library(dplyr)

data <- tibble(item_name = c("apple", "bmw", "bmw"))

mutate(data, category = case_when(item_name == "apple" ~ "fruit",
                                  item_name == "bmw" ~ "car"))

# # A tibble: 3 x 2
#   item_name category
#   <chr>     <chr>   
# 1 apple     fruit   
# 2 bmw       car     
# 3 bmw       car 

区别在于:

category_fn <- function(df, ...){
  # browser()
  cat1 <- quos(...)
  mutate(df, category = case_when(!!! cat1))
}

category_fn(df = data, item_name == "apple" ~ "fruit",
                       item_name == "bmw" ~ "car")

# # A tibble: 3 x 2
#   item_name category
#   <chr>     <chr>   
# 1 apple     fruit   
# 2 bmw       car     
# 3 bmw       car 

并且:

cat <- list(item_name == "apple" ~ "fruit", item_name == "bmw" ~ "car")

do.call(category_fn, c(list(df = data), cat), quote = FALSE)
# Or:
do.call(category_fn, c(list(df = data), cat), quote = TRUE)
# Or:
rlang::invoke(category_fn, c(list(df = data), cat))

这些都会出现相同的错误:

# Error in mutate_impl(.data, dots) : 
#   Evaluation error: object 'item_name' not found.

我使用了`browser()`进入了函数,检查了参数,并在那里运行了`expr(mutate(df, category = case_when(!!! cat1)))`(如在http://rpubs.com/lionel-/programming-draft中建议的一种通用调试策略),在两种情况下输出相同:mutate(df, category = case_when(~(item_name == "apple" ~ "fruit"), ~(item_name == "bmw" ~ "car")))
我还尝试了调整`envir`或` .env`参数,但没有成功。
我的理解是这可能与不同的quosure环境有关,但`environment(cat1[[1]])`也是相同的(<environment: R_GlobalEnv>)。
注:这是我试图回答的Tidy evaluation programming with dplyr::case_when问题的后续步骤。
> sessioninfo::session_info()
─ Session info ────────────────────────────────────────────────────────
 setting  value                       
 version  R version 3.4.3 (2017-11-30)
 os       Linux Mint 18               
 system   x86_64, linux-gnu           
 [...]                 

─ Packages ────────────────────────────────────────────────────────────
 package     * version    date       source                             
 [...]                
 dplyr       * 0.7.4      2017-09-28 CRAN (R 3.4.3)                     
 [...]                    
 rlang       * 0.1.6      2017-12-21 CRAN (R 3.4.3)                     
 [...]

到目前为止,答案很有趣(谢谢!),但我需要一个解释do.call()发生了什么的答案,而不是提供一个(好的)解决方法。 - Aurèle
3个回答

3
我们可以将'cat'创建为一个quosure,然后使用!!!进行评估。
cat <-  quos(item_name == "apple" ~ "fruit", item_name == "bmw" ~ "car")
category_fn(data, !!!(cat))
# A tibble: 3 x 2
#  item_name category
#  <chr>     <chr>   
#1 apple     fruit   
#2 bmw       car     
#3 bmw       car    

2
我认为这与其他帖子中的问题类似;引用列表本身并不等同于逐个引用列表元素。
我修改了cat定义,引用了各个元素,并稍微修改了函数以删除quosure语句并明确命名参数。在do.call语句中,第二个参数是要提供给函数的参数列表,我已将cat元素包含在列表中。
通过这些修改,两个do.call语句和invoke返回的结果与您的帖子中直接执行的结果相同:
data <- tibble(item_name = c("apple", "bmw", "bmw"))

cat <- list(quo(item_name == "apple" ~ "fruit"), 
            quo(item_name == "bmw" ~ "car"))

category_fn <- function(df, category){
  mutate(df, category = case_when(!!! category))
}

> do.call(category_fn, list(data, cat), quote = FALSE)
# A tibble: 3 x 2
  item_name category
      <chr>    <chr>
1     apple    fruit
2       bmw      car
3       bmw      car
> # Or:
> do.call(category_fn, list(data, cat), quote = TRUE)
# A tibble: 3 x 2
  item_name category
      <chr>    <chr>
1     apple    fruit
2       bmw      car
3       bmw      car
> # Or:
> rlang::invoke(category_fn, list(df = data, cat))
# A tibble: 3 x 2
  item_name category
      <chr>    <chr>
1     apple    fruit
2       bmw      car
3       bmw      car

在这两个do.call示例中,quote参数的值并不重要。

我发现quosures的概念很难理解,并且当前Cran上使用dplyr进行编程的文献资料(Programming with dplyr)也没有使它变得更容易。


1
如果您使用这种方式,那么 category_fn(data, cat) 应该可以工作。 - akrun
@akrun,是的,当以这种方式提供两个参数时,category_fn函数会直接起作用。然而,我喜欢你的解决方案的简单性 - 非常直接! - Stewart Ross
很不幸,您现在已更改了category_fn的签名。 - Konrad Rudolph
@Konrad: 我同意,但据我所知,没有要求传递其他参数列表以供子函数使用。这个问题中的原始category_fn函数来自另一个示例,该示例复制了在使用dplyr进行编程的vignette中 group_by 案例中所示的 ... 参数的用法。在vignette的情况下,需要...参数来设置可以使用多个group_by变量的quosures的能力,但在这些示例中不需要这样做 - 或者至少就我所知,OPs并没有声明需要这样做。 - Stewart Ross
你说“引用列表本身并不等同于逐个引用列表的元素”,通常情况下我会同意,但在这里我们传递的是公式(而且我们实际上也没有引用列表)。此外,正如 OP 所指出的那样,在检查时参数确实看起来是相同的,甚至它们的环境也相同。那么,在这种情况下,它们有什么不同之处呢? - Konrad Rudolph
根据您的示例本身,我发现如果我使用我建议的单独引用列表执行do.call,则do.call有效,但在没有它的列表上会出现您指出的错误,然而参数的typeof和class似乎相同。直接调用该函数对任何参数都不起作用。我注意到从原始问题的评论中,有一个开放的GitHub请求来澄清如何向case_when提供公式参数,因此这显然是由软件包作者解决的问题。 - Stewart Ross

2

我的回答中第一部分(1a)对于“Tidy evaluation programming with dplyr::case_when”也适用。

如果catdatacategory_fn与当前问题相同,则此方法可行。第一行将cat转换为cat_,这种形式在此处可以使用。

cat_ <- lapply(cat, function(x) do.call("substitute", list(x))) 
do.call("category_fn", c(list(df = data), cat_))

提供:

# A tibble: 3 x 2
  item_name category
      <chr>    <chr>
1     apple    fruit
2       bmw      car
3       bmw      car

关于在我之前链接的原问题中回答中似乎要求提供引号替代方案的问题,使用wrapr包和基础R可以解决这个问题。作者编写的seplyr包也可能是一种替代方案。请注意保留HTML标签。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接