我使用动态变量(例如 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/