2017-10-12 67 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

相關問題