13

我尝试编写一个简单的函数来包装 dplyr::case_when() 函数。我在https://cran.r-project.org/web/packages/dplyr/vignettes/programming.html上阅读了dplyr文档的编程,但无法弄清楚它如何与 case_when() 函数一起使用。

我有以下数据:

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

以及以下列表:

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

然后我想写一个像这样的函数:

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

不幸的是category_fn(data,cat),在这种情况下给出了评估错误。我想获得与通过以下方式获得的输出相同的输出:

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

这样做的方法是什么?

4

3 回答 3

8

1)通过letwrapr 包中的列表使用datacat问题,这可以在不以任何方式修改输入的情况下工作。

library(dplyr)
library(wrapr)

category_fn <- function(data, List) {
  let(c(CATEGORY = toString(sapply(List, format))),
      data %>% mutate(category = case_when(CATEGORY)),
      subsMethod = "stringsubs",
      strict = FALSE)
}
category_fn(data, cat) # test

给予:

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

1a)使用 tidyeval/rlangdatacat从问题:

category_fn <- function(data, List) {
  cat_ <- lapply(List, function(x) do.call("substitute", list(x)))
  data %>% mutate(category = case_when(!!!cat_))
}
category_fn(data, cat)

给出与上述相同的结果。

2)单独传递列表组件如果您的意图是cat单独传递每个组件而不是cat它本身,那么这有效:

category_fn <- function(data, ...) eval.parent(substitute({
   data %>% mutate(category = case_when(...))
}))

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

给予:

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

2a)如果您更喜欢 tidyeval/rlang 那么这种情况很简单:

library(dplyr)
library(rlang)

category_fn <- function(data, ...) {
   cat_ <- quos(...)
   data %>% mutate(category = case_when(!!!cat_))
}

category_fn(data, item_name == "apple" ~ "fruit",
                   item_name == "bmw" ~ "car") # test
于 2017-12-29T14:51:35.893 回答
7

首先引用列表中的每个元素:

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

然后,您的函数不必引用 cat 对象本身。我还更改了“其他所有”...参数的使用,以在调用中显式引用类别参数:

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

然后函数的输出与预期的一样:

category_fn(data, cat)
# A tibble: 3 x 2
  item_name category
      <chr>    <chr>
1     apple    fruit
2       bmw      car
3       bmw      car

为了完整起见,我注意到类别列表在使用基本 R quote() 函数定义时也适用于您的函数:

cat <- list(
  quote(item_name == "apple" ~ "fruit"),
  quote(item_name == "bmw" ~ "car")
)
> cat
[[1]]
item_name == "apple" ~ "fruit"

[[2]]
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
于 2017-12-29T14:34:14.763 回答
2

这是另一种以 tidyverse 为中心的方法

cat <- tribble(
    ~name, ~category,
    "apple", "fruit",
    "bmw", "car"
) %>% 
    str_glue_data("item_name == '{name}' ~ '{category}'")

data %>% 
    mutate(category = case_when(!!! map(cat, rlang::parse_expr)))
于 2020-10-04T07:32:50.723 回答