r - 为什么 !! (bang-bang) 结合 as.name() 给出与 !! 不同的输出。还是 as.name() 单独?

标签 r dplyr tidyverse tidyeval nse

我使用动态变量(例如 ID )作为引用列名的方法,该列名将根据我当时正在处理的基因而改变。然后我使用 case_when 内的 mutate 创建一个新列,该列的值取决于动态列。
我认为 !! (bang-bang) 是我强制对变量内容进行 eval 所需要的;但是,我没有在我的新专栏中得到预期的输出。只有 !!as.name 给了我期望的输出,我不完全明白为什么。有人可以解释为什么在这种情况下只使用 !! 是不合适的,以及 !!as.name 中发生了什么?
这是我制作的一个简单的可重现示例,用于演示我所体验的内容:

library(tidyverse)

ID <- "birth_year"

# Correct output
test <- starwars %>%
  mutate(FootballLeague = case_when(
    !!as.name(ID) < 10 ~ "U10",
    !!as.name(ID) >= 10 & !!as.name(ID) < 50 ~ "U50",
    !!as.name(ID) >= 50 & !!as.name(ID) < 100 ~ "U100",
    !!as.name(ID) >= 100 ~ "Senior",
    TRUE ~ "Others"
  ))

# Incorrect output
test2 <- starwars %>%
  mutate(FootballLeague = case_when(
    !!(ID) < 10 ~ "U10",
    !!(ID) >= 10 & !!(ID) < 50 ~ "U50",
    !!(ID) >= 50 & !!(ID) < 100 ~ "U100",
    !!(ID) >= 100 ~ "Senior",
    TRUE ~ "Others"
  ))

# Incorrect output
test3 <- starwars %>%
  mutate(FootballLeague = case_when(
    as.name(ID) < 10 ~ "U10",
    as.name(ID) >= 10 & as.name(ID) < 50 ~ "U50",
    as.name(ID) >= 50 & as.name(ID) < 100 ~ "U100",
    as.name(ID) >= 100 ~ "Senior",
    TRUE ~ "Others"
  ))

identical(test, test2)
# FALSE

identical(test2, test3)
# TRUE

sessionInfo()
#R version 4.0.2 (2020-06-22)
#Platform: x86_64-centos7-linux-gnu (64-bit)
#Running under: CentOS Linux 7 (Core)

# tidyverse_1.3.0
# dplyr_1.0.2
干杯!

最佳答案

您可以将表达式包装在函数 quo() 中,以查看应用 !! 运算符后的运算结果。为简单起见,我将使用较短的表达式进行演示:
准备工作:

library(tidyverse)
ID <- "birth_year"

## Test without quasiquotation:
starwars %>% 
  filter(birth_year < 50)
实验 1:
quo(
  starwars %>% 
    filter(ID < 50)
)
## result: starwars %>% filter(ID < 50)
我们了解到: filter() 不会将 ID 视为变量,而是“按原样”处理。所以我们需要一种机制来告诉 filter() 它应该将 ID 视为变量,并且它应该使用它的值。
--> !! 运算符可用于告诉 filter() 应将表达式视为变量并替换其值。
实验 2:
quo(
  starwars %>% 
    filter(!!ID < 50)
) 
## result: starwars %>% filter("birth_year" < 50)
我们了解到:!! 运算符确实有效:ID 被替换为其值。但是: ID 的值是 字符串 "birth_year" 。注意结果中的引号。但是您可能知道,tidyverse 函数不将变量名作为字符串,它们需要原始名称,不带引号。与实验 1 相比:filter() 将所有内容“按原样”处理,因此它查找名为 "birth_year" 的列(包括引号!)
as.name() 函数有什么作用?
这是一个基本的 R 函数,它接受一个字符串(或一个包含字符串的变量)并返回字符串的内容作为变量名。
因此,如果您在基数 R 中调用 as.name(ID),结果是 birth_year ,这次没有引号 - 就像 tidyverse 所期望的那样。让我们试试看:
实验3:
quo(
  starwars %>% 
    filter(as.name(ID) < 50)
) 
## result: starwars %>% filter(as.name(ID) < 50)
我们了解到:这不起作用,因为 filter() 再次“按原样”处理所有内容。所以现在它查找名为 as.name(ID) 的列,这当然不存在。
--> 我们需要把这两件事结合起来才能让它工作:
  • 使用 as.name() 将字符串转换为变量名。
  • 使用 !! 告诉 filter() 它不应该“按原样”处理,而是替换实际值。

  • 实验 4:
    quo(
      starwars %>% 
        filter(!!as.name(ID) < 50)
    ) 
    ## result: starwars %>% filter(birth_year < 50)
    
    现在它起作用了! :)
    我在实验中使用了 filter(),但它与 mutate() 和其他 tidyverse 函数的工作原理完全相同。

    关于r - 为什么 !! (bang-bang) 结合 as.name() 给出与 !! 不同的输出。还是 as.name() 单独?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65025731/

    相关文章:

    r - 根据数据集中的列对图中的分面进行排序

    r - 如何使用 dplyr 和数据框在 R 中创建百分位数?

    r - 结合基于多个条件的观察

    r - 使用 R/tidyverse 将数据框的一列分隔为未定义的列数

    r - listcolumns 和 multidplyr

    r - left_join 在 R 中的 by 参数中使用字符向量

    r - 如何将使用 enquo() 创建的动态变量名称传递给 dplyr 的 mutate 以进行评估?

    r - 使用 mutate_all 将所有列除以选定的列

    R:运行函数,直到函数先前输出的条件测试对于所有行都为 True

    r - 用 R 中的最后一个或下一个非 NA 值填充 NA