2017-10-12 3 views
4

Je travaille sur la construction d'une fonction que je vais manipuler une trame de données basée sur une chaîne. Au sein de la fonction, je vais construire un nom de colonne à partir de la chaîne et l'utiliser pour manipuler la trame de données, quelque chose comme ceci:Pourquoi les quosures fonctionnent dans group_by() mais pas 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") 

Je comprends ce que le problème est: unquoting le quosure comme argument pour filter() dans la version brisée ne fait pas référence à la colonne réelle anyA. Qu'est-ce que je ne comprends pas pourquoi cela fonctionne dans summarize(), mais pas dans filter() - pourquoi y a-t-il une différence?

Répondre

4

En ce moment vous faites des quosures de chaînes, pas de noms de symboles. Ce n'est pas comme ça qu'on les utilise. Il y a une grande différence entre quo("hello") et quo(hello). Si vous souhaitez créer un nom de symbole correct à partir d'une chaîne, vous devez utiliser rlang::sym. Donc, une solution rapide serait

summarize_my_df_broken <- function(df, my_string) { 

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

Si vous regardez de plus près, je pense que vous verrez le group_by/summarize ne fonctionne pas vraiment la façon dont vous attendez soit (bien que vous ne soyez pas seulement le même message d'erreur). Ces deux ne produisent pas les mêmes résultats

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 

Encore une fois le problème est d'utiliser une chaîne au lieu d'un symbole.

+0

Ah, je vois! 'quo()' transforme un symbole en quosure, 'enquo()' transforme la valeur d'un argument de fonction en quosure, et 'sym()' transforme une chaîne en une quosure. Donc je passais dans une ficelle mais en la traitant comme un symbole. Il semblerait que cela fonctionne dans 'summarize_my_df_works()' parce que vous pouvez résumer en fonction d'une fonction, et non pas parce qu'elle faisait ce que j'attendais. – crazybilly

0

Vous n'avez aucune condition pour filter() dans votre fonction 'cassée', il vous suffit de spécifier le nom de la colonne. Au-delà de cela, je ne suis pas sûr si vous pouvez insérer des quosures dans des expressions plus grandes. Par exemple, ici, vous pouvez essayer quelque chose comme:

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

Mais je ne pense pas que cela fonctionnerait. Au lieu de cela, je suggère d'utiliser la fonction conditionnelle filter_at() pour cibler la colonne appropriée. Dans ce cas, vous séparez les quosure de l'état du filtre:

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

Ce n'est pas correct. Vous pouvez avoir un filtre comme 'orig_df%>% filter (anyA)' fonctionne parfaitement bien puisque 'anyA' est une colonne de valeurs booléennes. De plus, si vous voulez utiliser 'vars()' alors vous n'avez pas vraiment besoin de quosures car cette fonction accepte aussi bien les chaînes: 'orig_df%>% filter_at (vars (paste0 (" any "," A ")), all_vars (. == TRUE)) ' – MrFlick

+0

Bons points, MrFlick! –

+0

L'utilisation de filter_at() est une bonne idée et résout certainement le problème - la solution de MrFlick est une bonne solution au problème de l'exemple. Cependant, cela ne m'amène pas à ma question principale, à savoir pourquoi le quosure fonctionne-t-il dans summarize() mais pas dans filter()? Je soupçonne qu'il y a une compréhension fondamentale qui me manque en ce qui concerne NSE. – crazybilly