【问题标题】:Why do quosures work in group_by() but not filter()?为什么 quosures 在 group_by() 中有效,但在 filter() 中无效?
【发布时间】:2017-10-12 15:05:45
【问题描述】:

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

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")

我了解问题所在:在损坏的版本中取消引用作为 filter() 的参数的 quosure 并未引用实际列 anyA。

我不明白为什么它在summarize() 中有效,但在filter() 中无效——为什么会有区别?

【问题讨论】:

    标签: r dplyr nse


    【解决方案1】:

    现在您正在对字符串进行引用,而不是符号名称。这不是那些应该被使用的方式。 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
    

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

    【讨论】:

    • 啊,我明白了! quo() 将符号转换为 quosure,enquo() 将函数参数的值转换为 quosure,sym() 将字符串转换为 quosure。所以我传入了一个字符串,但把它当作一个符号来对待。它似乎只在summarize_my_df_works() 中起作用,因为您可以基于函数进行汇总,而不是因为它实际上在做我期望的事情。
    【解决方案2】:

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

    除此之外,我不确定您是否可以在更大的表达式中插入引号。例如,您可以在这里尝试:

    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)
    

    }

    【讨论】:

    • 这是不对的。你可以让像orig_df %&gt;% filter(anyA) 这样的过滤器工作得很好,因为anyA 是一列布尔值。此外,如果您打算使用vars(),那么您实际上并不需要quosures,因为该函数也可以接受字符串:orig_df %&gt;% filter_at(vars(paste0("any","A")), all_vars(. == TRUE))
    • 使用 filter_at() 是一个好主意,当然可以解决手头的问题——MrFlick 的解决方案是解决示例问题的好方法。但是,它没有解决我的主要问题,即为什么 quosure 在summarize() 中起作用,但在filter() 中不起作用?我怀疑我对 NSE 缺少一些基本的理解。
    猜你喜欢
    • 2011-03-12
    • 2015-09-12
    • 2011-07-19
    • 2014-11-13
    • 2018-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多