我试图熟悉在我的代码中使用 NSE 的必要条件。假设我有几对列,并且想要为每一对生成一个新的字符串变量,以指示该对中的值是否相同。
library(tidyverse)
library(magrittr)
df <- tibble(one.x = c(1,2,3,4),
one.y = c(2,2,4,3),
two.x = c(5,6,7,8),
two.y = c(6,7,7,9),
# not used but also in df
extra = c(5,5,5,5))
我正在尝试编写可以完成与以下代码相同的事情的代码:
df.mod <- df %>%
# is one.x the same as one.y?
mutate(one.x_suffix = case_when(
one.x == one.y ~ "same",
TRUE ~ "different")) %>%
# is two.x the same as two.y?
mutate(two.x_suffix = case_when(
two.x == two.y ~ "same",
TRUE ~ "different"))
df.mod
#> # A tibble: 4 x 6
#> one.x one.y two.x two.y one.x_suffix two.x_suffix
#> <dbl> <dbl> <dbl> <dbl> <chr> <chr>
#> 1 1. 2. 5. 6. different different
#> 2 2. 2. 6. 7. same different
#> 3 3. 4. 7. 7. different same
#> 4 4. 3. 8. 9. different different
在我的实际数据中,我有任意数量的这样的对(例如three.x
和three.y
,...),所以我想使用mutate_at
.
我的策略是在“.X”变量的传递.vars
,然后gsub
在‘X’上的内部平等测试的一侧‘Y’ case_when
,就像这样:
df.mod <- df %>%
mutate_at(vars(one.x, two.x),
funs(suffix = case_when(
. == !!sym(gsub("x", "y", deparse(substitute(.)))) ~ "same",
TRUE ~ "different")))
#> Error in mutate_impl(.data, dots): Evaluation error: object 'value' not found.
这是我遇到异常的时候。看起来这gsub
部分工作正常:
df.debug <- df %>%
mutate_at(vars(one.x, two.x),
funs(suffix = gsub("x", "y", deparse(substitute(.)))))
df.debug
#> # A tibble: 4 x 6
#> one.x one.y two.x two.y one.x_suffix two.x_suffix
#> <dbl> <dbl> <dbl> <dbl> <chr> <chr>
#> 1 1. 2. 5. 6. one.y two.y
#> 2 2. 2. 6. 7. one.y two.y
#> 3 3. 4. 7. 7. one.y two.y
#> 4 4. 3. 8. 9. one.y two.y
这是!!sym()
导致这里异常的操作。我做错了什么?
由reprex 包(v0.2.1)于 2018 年 11 月 7 日创建
问题不在!!sym
,正如您在以下示例中看到的那样:
df %>% mutate_at( vars(one.x, two.x),
funs(suffix = case_when(
. == !!sym("one.y") ~ "same",
TRUE ~ "different")))
# # A tibble: 4 x 6
# one.x one.y two.x two.y one.x_suffix two.x_suffix
# <dbl> <dbl> <dbl> <dbl> <chr> <chr>
# 1 1 2 5 6 different different
# 2 2 2 6 7 same different
# 3 3 4 7 7 different different
# 4 4 3 8 9 different different
问题在于试图在substitute(.)
内部取消引用case_when
:
df %>% mutate_at( vars(one.x, two.x),
funs(suffix = case_when(
. == !!substitute(.) ~ "same",
TRUE ~ "different")))
# Error in mutate_impl(.data, dots) :
# Evaluation error: object 'value' not found.
这样做的原因是运算符优先级。从帮助页面!!
:
这 !!运算符取消引用其参数。它会立即在周围的上下文中进行评估。
在上面的例子中,上下文 for!!substitute(.)
是公式,它本身就在 里面case_when
。这导致表达式立即被替换为value
,它在内部定义case_when
并且在您的数据框中没有任何意义。
您希望将表达式放在他们的环境旁边,这就是quosures的用途。通过替换substitute
为rlang::enquo
,您可以捕获.
与其定义环境(您的数据框)一起产生的表达式。为了保持整洁,让我们将您的gsub
操作移到一个单独的函数中:
x2y <- function(.x)
{
## Capture the expression and its environment
qq <- enquo(.x)
## Retrieve the expression and deparse it
txt <- rlang::get_expr(qq) %>% rlang::expr_deparse()
## Replace x with y, as before
txty <- gsub("x", "y", txt)
## Put the new expression back into the quosure
rlang::set_expr( qq, sym(txty) )
}
您现在可以x2y
直接在代码中使用新函数。对于 quosures,不需要取消引用,因为表达式已经带有它们的环境;您可以简单地使用rlang::eval_tidy
以下方法评估它们:
df %>% mutate_at(vars(one.x, two.x),
funs(suffix = case_when(
. == rlang::eval_tidy(x2y(.)) ~ "same",
TRUE ~ "different" )))
# # A tibble: 4 x 6
# one.x one.y two.x two.y one.x_suffix two.x_suffix
# <dbl> <dbl> <dbl> <dbl> <chr> <chr>
# 1 1 2 5 6 different different
# 2 2 2 6 7 same different
# 3 3 4 7 7 different same
# 4 4 3 8 9 different different
编辑以解决您评论中的问题:将所有代码合并为一行几乎总是一个坏主意™,我强烈建议不要这样做。然而,由于这个问题是关于 NSE 的,我认为理解为什么简单地获取内容x2y
并将其粘贴在里面case_when
会导致问题是很重要的。
enquo()
,如substitute()
,查看函数的调用环境并将参数替换为提供给该函数的表达式。substitute()
只向上一个环境(当你取消引用它时找到value
里面case_when
),同时enquo()
只要调用堆栈中的函数正确处理quasiquotation 就会继续向上移动。(大多数 dplyr/tidyverse 函数都这样做。)因此,当您调用enquo(.x)
inside 时x2y
,它会将提供给调用堆栈上的每个函数的表达式向上移动,最终找到one.x
。
当您调用enquo()
inside 时mutate_at
,它现在与 处于同一级别one.x
,因此它也将参数(one.x
在本例中)替换为定义它的表达式(c(1,2,3,4)
在本例中为向量)。这不是你想要的。您现在希望保持与one.x
. 为此,请使用rlang::quo()
代替rlang::enquo()
:
library( rlang ) ## To maintain at least a little bit of sanity
df %>%
mutate_at(vars(one.x, two.x),
funs(suffix = case_when(
. == eval_tidy(set_expr(quo(.),
sym(gsub("x","y", expr_deparse(get_expr(quo(.)))))
)
) ~ "same",
TRUE ~ "different" )))
# Now works as expected
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句