首页 > 解决方案 > Tidyverse、Rlang 和 tidyeval:Bang bang (!!) 在函数内部失败,但它似乎在没有引号的情况下工作

问题描述

我在具有两个主要组的长数据库(full_database)上运行一个函数,我需要在每个组的多个子集上执行各种线性模型。

然后,我将 R^2、调整后的 R^2 和 p.value 提取到一个数据框中,其中每一行对应一个比较。由于有 30 种不同的情况,我有另一个 tibble 列出了函数参数所在的所有可能性(可能性)。

原始函数的脚本是:

database_correlation <-  function(id, group) {

    require(dplyr)
    require(tidyr)
    require(rlang)

    id_name <- quo_name(id)
    id_var <- enquo(id)
    group_name <- quo_name(group)
    group_var <- enquo(group)

    corr_db <- full_database %>%
      filter(numid==!!id_name) %>%
      filter(major_group==!!group_name) %>%
      droplevels()

    correlation <- summary(lm(yvar~xvar, corr_db))

    id.x <- as.character(!!id_var) #Gives out an error: "invalid argument type"
    group.x <- as.character(!!group_var) #Gives out an error: "invalid argument type"
    r_squared <- correlation$r.squared
    r_squared_adj <- correlation$adj.r.squared
    p_value <- correlation$coefficients[2,4]

    data.frame(id.x, group.x, r_squared, r_squared_adj, p_value, stringsAsFactors=FALSE)
  }

然后我运行该函数:

correlation_all <- lapply(seq(nrow(possibilities)), function(index) {
    current <- possibilities[index,]
    with(current, database_correlation(id, database))
  }) %>%
    bind_rows()

我已经评论了出现错误的部分(id.x 和 group.x 分配),并且我尝试了多种替代方法(我将使用 id.x 作为示例):

  1. id_var <- enquo(id) & id.x <- print(!!id_var)
  2. id_var <- sym(id) & id.x <- as.character(!!id_var)
  3. id_var <- sym(id) & id.x <- print(!!id_var)
  4. 没有 id_var & id.x <- !!id_name
  5. 没有 id_var & id.x <- id_name

最后一个选项(粗体)即使没有取消引号也可以工作,如果我在过滤full_database时删除 bang bang (!!) 也是如此,直接使用filter(numid==id_name)但我只是可以不明白为什么。通过使用 TRUE 和 FALSE 进行测试,R 可能会将 bang bang 解释为双重否定,并且由于它期待一个布尔值,它会抛出一个错误。

感谢您的帮助!

标签: rtidyrrlangtidyeval

解决方案


直接使用idand group- 我假设这些是传入的字符串,所以我认为没有必要将 quosure 强制为字符串。此外, !!可以在支持整洁评估的函数内部使用。确定这一点的简单第一步是“来自基本 R 包的函数”。as.character()是,所以它不起作用。

如果您确定要将quosure转换为字符串,则可以使用rlang::as_name()将相应的符号检索为字符串。这是这样做的推荐方式。

通过使用 TRUE 和 FALSE 进行测试,R 可能会将 bang bang 解释为双重否定,并且由于它期待一个布尔值,它会抛出一个错误。

你的假设是正确的。

最后一个选项(粗体)即使没有取消引号也可以工作,如果我在过滤 full_database 时删除 bang bang (!!) 也是如此,使用 filter(numid==id_name)

Tidy-evaluation 的核心是在正确的环境中评估符号,或者至少这是我的看法。这filter()是有效的,因为它查找符号id_name,在数据中没有找到它(它首先查找的位置),然后在封闭环境中查找,找到它并评估语句。

想象一下,如果您有一个id_name在数据中命名的列。您将如何区分数据id_name和封闭环境中的数据。好吧,如果你想要数据的价值,你可以使用.data$id_name(另一个 rlang 结构)。如果您想要数据之外的值,请使用!!. 这告诉支持整洁评估的函数查看 quosure。quosure 标识它是在哪个环境中定义的。然后它在该环境中评估该符号,确保与数据中的名称没有冲突。


推荐阅读