【问题标题】:Referential transparency in dplyr::filter: making column name variabledplyr::filter 中的引用透明性:使列名可变
【发布时间】:2017-10-11 08:50:01
【问题描述】:

核心问题(似乎归结为什么)

我如何构造对rlang::quo 的调用,其中 “left” 而不是表达式的“right”侧是引用透明的

取自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 允许“表达式的两边”是可变的,因为表达式的两个部分都可以引用透明 (see vignette):

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::filterdplyr::mutate 在他们期望的表达式类型方面有所不同。所以基于the vignette,我想出了这个版本,其中要评估的整个表达式作为参数传递:

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::quodplyr::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,但我不知道该怎么做

【问题讨论】:

    标签: r dplyr rlang referential-transparency


    【解决方案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_idTRUE,所以整个表达式返回FALSE。因此,我们实际上正在运行 `df %>% filter(FALSE) 这显然不是我们想要的 ;-)

    【讨论】:

      猜你喜欢
      • 2020-02-01
      • 2021-09-21
      • 2016-03-17
      • 2022-08-19
      • 2020-09-21
      • 1970-01-01
      • 2019-12-04
      • 2010-11-03
      • 1970-01-01
      相关资源
      最近更新 更多