为什么 quosures 在 group_by() 中起作用但在 filter() 中不起作用?

Why do quosures work in group_by() but not filter()?

我正在构建一个函数,我将根据字符串操作数据框。在该函数中,我将从字符串中构建一个列名并使用它来操作数据框,如下所示:

library(dplyr)

orig_df  <- data_frame(
     id = 1:3
   , amt = c(100, 200, 300)
   , anyA = c(T,F,T)
   , othercol = c(F,F,T)
)


summarize_my_df_broken <- function(df, my_string) {

  my_column <- quo(paste0("any", my_string))

  df %>% 
    filter(!!my_column) %>% 
    group_by(othercol) %>% 
    summarize(
        n = n()
      , total = sum(amt)
    ) %>%
    # I need the original string as new column which is why I can't
    # pass in just the column name
    mutate(stringid = my_string)


}


summarize_my_df_works <- function(df, my_string) {

  my_column <- quo(paste0("any", my_string))

  df %>% 
    group_by(!!my_column, othercol) %>% 
    summarize(
        n = n()
      , total = sum(amt)
    )  %>%
    mutate(stringid = my_string)

}

# throws an error: 
# Argument 2 filter condition does not evaluate to a logical vector
summarize_my_df_broken(orig_df, "A")

# works just fine
summarize_my_df_works(orig_df, "A")

我明白问题是什么:在损坏的版本中将 quosure 作为参数取消引用 filter() 并没有引用实际的列 anyA。

我不明白的是为什么它在 summarize() 中有效,但在 filter() 中无效——为什么会有差异?

现在您正在对字符串进行排序,而不是符号名称。这不是应该使用的方式。 quo("hello")quo(hello) 之间有很大的区别。如果你想从一个字符串中得到一个正确的符号名称,你需要使用 rlang::sym。所以一个快速的解决办法是

summarize_my_df_broken <- function(df, my_string) {

  my_column <- rlang::sym(paste0("any", my_string))
  ...
}

如果您仔细观察,我想您会发现 group_by/summarize 实际上并没有按照您预期的方式工作(尽管您只是没有收到相同的错误消息)。这两个不会产生相同的结果

summarize_my_df_works(orig_df, "A")
#  `paste0("any", my_string)` othercol     n total
#                        <chr>    <lgl> <int> <dbl>
# 1                       anyA    FALSE     2   300
# 2                       anyA     TRUE     1   300

orig_df  %>% 
  group_by(anyA, othercol) %>% 
  summarize(
    n = n()
    , total = sum(amt)
  )  %>%
  mutate(stringid = "A")
#    anyA othercol     n total stringid
#   <lgl>    <lgl> <int> <dbl>    <chr>
# 1 FALSE    FALSE     1   200        A
# 2  TRUE    FALSE     1   100        A
# 3  TRUE     TRUE     1   300        A

同样的问题是使用字符串而不是符号。

您的 'broken' 函数中没有 filter() 的任何条件,您只需指定列名。

除此之外,我不确定您是否可以将 quosures 插入到更大的表达式中。例如,在这里你可以尝试这样的事情:

df %>% filter((!!my_column) == TRUE)

但我认为这行不通。

相反,我建议使用条件函数 filter_at() 来定位适当的列。在那种情况下,您将 quosure 与过滤条件分开:

summarize_my_df_broken <- function(df, my_string) {

  my_column <- quo(paste0("any", my_string))

  df %>% 
    filter_at(vars(!!my_column), all_vars(. == TRUE)) %>% 
    group_by(othercol) %>% 
    summarize(
      n = n()
      , total = sum(amt)
    ) %>%
mutate(stringid = my_string)

}