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

Yuk*_*mon 12 r dplyr nse tidyverse tidyeval

我使用动态变量(例如ID)作为引用列名的方式,该列名将根据我当时正在处理的基因而改变。然后我使用case_wheninsidemutate创建一个新列,该列的值取决于动态列。

我认为!!(bang-bang) 是我强制对变量内容进行 eval 所需要的;但是,我没有在我的新专栏中得到预期的输出。只有!!as.name给了我期望的输出,我不完全明白为什么。有人可以解释为什么在这种情况下使用 only!!是不合适的,以及发生了!!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
Run Code Online (Sandbox Code Playgroud)

干杯!

AEF*_*AEF 11

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

准备工作:

library(tidyverse)
ID <- "birth_year"

## Test without quasiquotation:
starwars %>% 
  filter(birth_year < 50)
Run Code Online (Sandbox Code Playgroud)

实验一:

quo(
  starwars %>% 
    filter(ID < 50)
)
## result: starwars %>% filter(ID < 50)
Run Code Online (Sandbox Code Playgroud)

我们了解到:filter()ID视为变量,而是“原样”。所以我们需要一种机制来告诉filter()它应该将其ID视为变量,并且应该使用它的值。

-->!!运算符可用于告诉filter()它应该将表达式视为变量并替换其值。

实验二:

quo(
  starwars %>% 
    filter(!!ID < 50)
) 
## result: starwars %>% filter("birth_year" < 50)
Run Code Online (Sandbox Code Playgroud)

我们了解到:!!操作符确实有效:ID被它的值所取代。但是:的值ID字符串 "birth_year"。注意结果中的引号。但是您可能知道,tidyverse 函数不将变量名作为字符串,它们需要原始名称,不带引号。与实验 1 比较:filter()“按原样”接受所有内容,因此它查找名为"birth_year"(包括引号!)的列

函数as.name()有什么作用?

这是一个基本的 R 函数,它接受一个字符串(或一个包含字符串的变量)并返回字符串的内容作为变量名。因此,如果您调用as.name(ID)base R,结果是birth_year,这次没有引号 - 就像 tidyverse 所期望的那样。让我们试试看:

实验3:

quo(
  starwars %>% 
    filter(as.name(ID) < 50)
) 
## result: starwars %>% filter(as.name(ID) < 50)
Run Code Online (Sandbox Code Playgroud)

我们了解到:这行不通,因为再次filter()将一切“按原样”处理。所以现在它查找名为 的列as.name(ID),这当然不存在。

--> 我们需要把这两件事结合起来才能让它工作:

  1. 使用as.name()的字符串转换为一个变量名。
  2. 使用!!告诉filter()它不应该带的东西“原样”,但替换的实际价值。

实验四:

quo(
  starwars %>% 
    filter(!!as.name(ID) < 50)
) 
## result: starwars %>% filter(birth_year < 50)
Run Code Online (Sandbox Code Playgroud)

现在它起作用了!:)

filter()在我的实验中使用过,但它mutate()与其他 tidyverse 函数的工作原理完全相同。