2017-10-12 34 views
4

我正在构建一个函数,我将基于一个字符串操纵数据框架。在函数,我将建立一个列名作为从字符串并用它来操纵数据帧,这样的事情:为什么在group_by()而不是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") 

我的理解是什么问题:unquoting的quosure作为参数传递给filter()在破碎的版本中没有引用实际的列anyA。

我不明白的是为什么它在summarize()中有效,但在filter()中没有 - 为什么有区别?

回答

4

现在你正在制造字符串而不是符号名称。这不是应该如何使用这些。 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 

再次,问题是使用字符串而不是符号。

+0

啊,我明白了! 'quo()'把一个符号变成一个平静,'enquo()'把一个函数参数的值变成一个平静,'sym()'把一个字符串变成一个平静。所以我传递一个字符串,但把它当作符号来对待。它只出现在'summarize_my_df_works()'中,因为你可以基于函数进行总结,而不是因为它实际上正在做我所期望的。 – crazybilly

0

您的'破损'功能中没有关于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) 

}

+0

这是不对的。你可以像'orig_df%>%filter(anyA)'这样的过滤器完美地工作,因为'anyA'是一列布尔值。此外,如果你想使用'vars()',那么你并不需要quards,因为该函数也接受字符串就好了:'orig_df%>%filter_at(vars(paste0(“any”,“A”))), all_vars(。== TRUE))' – MrFlick

+0

好点子,MrFlick! –

+0

使用filter_at()是一个好主意,当然可以解决手头的问题 - MrFlick的解决方案对于示例问题是一个很好的解决方案。然而,它并没有得到我的主要问题,即为什么在总结()而不是在filter()中工作?我怀疑有一些基本理解,我错过了NSE。 – crazybilly

相关问题