Ссылочная прозрачность в 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)
Другими словами, я думаю, что в конечном итоге ~id == right
и я не знаю как это сделать
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), что явно не то, что мы хотели;-)