Ссылочная прозрачность в dplyr::filter: создание переменной имени столбца

Основной вопрос (к чему он, кажется, сводится)

Как сконструировать вызов rlang::quo с "левой" вместо "правой" стороны выражения, ссылочно прозрачной

Взято со страницы справки rlang::quo, это работает

quo(foo(!! quo(bar)))
# <quosure: global>
# ~foo(~bar)

в то время как это не:

quo(!! quo(foo)(bar))
# Error in (function (x)  : attempt to apply non-function

Вопрос помещен в немного больше контекста

dplyr::mutate позволяет «обеим сторонам выражения» быть переменными в том смысле, что обе части выражения могут быть сделаны ссылочно прозрачными (см. виньетку):

library(dplyr)
set.seed(89234)
df <- data.frame(id = rep(1:3, 3), value = rpois(9, 10))

c_id <- as.name("id")
c_value <- as.name("value")
# NOTE: in our prototyping, actual columns names are often subject to
# change (e.g. `id` might become `id_global`), thus I would like to stay 
# as flexible as possible in all of my subsequent `dplyr` calls.

my_multiply <- function(x, by) x * by

df %>% mutate(!!c_value := my_multiply(!!c_value, 10))
#   id value
# 1  1    70
# 2  2    90
# 3  3   130
# 4  1    80
# 5  2    80
# 6  3   120
# 7  1   140
# 8  2   120
# 9  3   110

Как я могу реализовать то же самое/что-то подобное в dplyr::filter, чтобы фокус мог сделать имя столбца («левая сторона») ссылочно прозрачным/гибким.

В идеале я хотел бы получить что-то вроде этого (псевдокод):

v_id <- 1
df %>% filter(!!c_id :== v_id)

Что я пробовал

Я знаю, что dplyr::filter отличается от dplyr::mutate типом выражений, которые они ожидают. Итак, на основе виньетки я придумал эту версию, в которой оценивается все выражение передается в качестве аргумента:

my_filter <- function(x, expr) {
  quo_expr <- enquo(expr)
  print(quo_expr)
  x %>% filter(!!quo_expr)
}
v_id <- 1
my_filter(df, id == v_id)
# <quosure: global>
# ~id == v_id
#   id value
# 1  1     7
# 2  1     8
# 3  1    14

Однако это «вынуждает» меня действительно использовать фактическое имя столбца, в то время как я хотел бы использовать ссылку c_id:

my_filter(df, c_id == v_id)
# <quosure: global>
# ~c_id == v_id
# [1] id    value
# <0 rows> (or 0-length row.names)

Я в основном не понимаю, как построить вызов dplyr::quo или dplyr::enquo, где левая часть содержит вычисленную ссылку на имя столбца, а правая часть содержит **неоцененную* ссылку на логический запрос для оценки:

my_filter <- function(x, left, right) {
  quo_expr <- quo(quo(!!left) == right)
  print(quo_expr)
  x %>% filter(!!quo_expr)
}
my_filter(df, c_id, v_id)

# <quosure: frame>
# ~quo(id) >= right
# [1] id    value
# <0 rows> (or 0-length row.names)

Иными словами, я думаю, что quosure должно получиться ~id == right, и я не знаю, как это сделать.


person Rappster    schedule 11.10.2017    source источник


Ответы (1)


С некоторой помощью от другого сообщества я смог собрать все воедино, чтобы прийти к гораздо более простому решению:

df %>% filter((!! c_id) == v_id)
#   id value
# 1  1     7
# 2  1     8
# 3  1    14

Так что все сводится к заключению вызова !! в круглые скобки!

Использование !! без круглых скобок не сработает, потому что ! имеет низкий приоритет оператора, поэтому он в основном захватывает все, что находится справа, и поэтому жалуется:

df %>% filter(!! c_id == v_id)
# [1] id    value
# <0 rows> (or 0-length row.names)

Что здесь происходит за кулисами, так это то, что на самом деле выполняется логическая операция ! (!c_id == v_id). Поскольку !c_id == v_id равно TRUE, все выражение возвращает FALSE. Таким образом, мы на самом деле запускаем `df %>% filter(FALSE), что явно не то, что нам нужно ;-)

person Rappster    schedule 11.10.2017