为什么 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)
}
我正在构建一个函数,我将根据字符串操作数据框。在该函数中,我将从字符串中构建一个列名并使用它来操作数据框,如下所示:
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)
}