使用tidyr::expand进行非标准评估

5

我在使用tidyr包时遇到了非标准评估(nse)表达式的问题。

基本上,我想要做的是扩展两列,这两列可能相同也可能不同,以获得一个包含所有可能组合的数据框。问题在于这将是一个函数,因此我事先不知道列名。

这里是一个最小示例:

library(tidyr)

dummy <- data.frame(x = c("ex1", "ex2"), y = c('cat1', 'cat2')) # dataset

tidyr::expand(dummy, x, y) # using standard evaluation works
tidyr::expand_(dummy, c("x", "y"))  # using the deprecated syntax works

# The following did not work:

  tidyr::expand(dummy, one_of('x'), y) # using select syntax
  tidyr::expand(dummy, vars('x', 'y')) # mutate_at style
  tidyr::expand(dummy, .data[[cnae_agg]], .data[[cnae_agg]])  # mutate current style  
  tidyr::expand(dummy, sym('x'), sym('y')) # trying to convert to symbols
  tidyr::expand(dummy, !!!enquos('x', 'y')) 
  tidyr::expand(dummy, !!('x'), y) # unquosure just one element
  tidyr::expand(dummy, !!!c("x", "y")) # unquosure vector of strings
  tidyr::expand(dummy, !!!c(quo("x"), quo("y"))) # unquosure vector that is being quosured before

所以,我有两个问题:

1)tidyr扩展函数应该应用什么正确的语法?

2)我可能已经多次阅读了 Advanced R关于准引用的章节,但我仍然不清楚为什么在使用tidyverse时有几种不同的“风格”来使用nse,以及在哪里使用每个风格。

基本上,我可以把任何东西都投入到选择/汇总中,它都能正常工作,但当使用mutate时,事情就会有所不同。

例如:

  # mutate
  mutate(dummy, new_var = .data[['x']]) # mutate basic style
  mutate(dummy, new_var = !!'x') # this just attributes 'x' to all rows


  # mutate at
  mutate_at(dummy, .vars=vars('y'), list(~'a')) # this works
  mutate_at(dummy, .vars=vars(!!'y'), list(~'a')) # this also works
  mutate_at(dummy, .vars=vars('y'), list(~`<-`(.,!!'x'))) # if we try to use unquote to create an attribution it does not work
  mutate_at(dummy, .vars=vars('y'), list(~`<-`(.,vars(!!'x')))) # even using vars, which works for variable selection, doesnt suffice

  # select 
  select(dummy, x) # this works
  select(dummy, 'x') # this works
  select_at(dummy, vars(!!'x')) # this works
  select_at(dummy, 'x') # this works
  select_at(dummy, !!'x') # this doesnt work

这让我想到了我的第二个问题。
是否有一份更新的指南,列出了整个tidyverse风格的所有当前语法,重点关注每个“动词”使用上的差异,例如在'mutate'和'select'中(即一个何时起作用而另一个不起作用)?
如何知道我是否需要在其他tidyverse包(例如tidyr)中使用mutate或select样式的nse?

关于这里的逻辑不是很清楚 mutate_at(dummy, .vars=vars('y'), list(~<-(.,!!'x')))。您正在选择一个名为“y”的列,然后将其分配给另一列吗?如果是这种情况,您可以使用renamerename_at在单独的步骤中完成。 - akrun
我同意这样做肯定会更好。我只是强调一些你可以使用常规变异操作完成的操作,但是当使用nse和不同的风格时,这些操作会变得非常混乱。例如,使用mutate进行归因很容易:mutate(dummy, x = y),但是使用mutate_at和nse来完成这个操作似乎很困难。 - Elijah
2个回答

4
我们需要对符号(symbols)进行求值(!!)。
tidyr::expand(dummy,  !!! syms(c('x', 'y')))
# A tibble: 4 x 2
#  x     y    
#  <fct> <fct>
#1 ex1   cat1 
#2 ex1   cat2 
#3 ex2   cat1 
#4 ex2   cat2 

当列名存储在vector中并且想要进行expand时,这将特别有用。

nm1 <- c('x', 'y')
tidyr::expand(dummy, !!! syms(nm1))

在其他一些组合中,要么从字符向量中省略了 !!!,要么省略了转换为sym符号。

谢谢Akrun!那对我很有帮助。不过,我需要更多的澄清。在哪些情况下我需要先将字符串转换为符号(使用sym术语)?例如,我不需要在mutate中这样做,但我记得以前在filter中使用as.name时做过类似的事情。 - Elijah
@Elijah 如果您在函数中传递列名,只需在 mutate/summarise 中传递未引用的列名。在 mutate_at 中,将字符串或未引用的内容传递到 vars 中即可。 - akrun
@Elijah 一些函数已经被弃用了。filter也有不同的变体,如filter_atfilter_all等。如果您有具体问题,回答会更容易,因为有很多方法可以进行评估。 - akrun
谢谢Akrun。不过我需要再考虑一下这些答案。我脑海中有三种不同版本的dplyr语法,分别从旧的lazyeval方式开始,所以对于我目前了解的最新语法,我还不清楚我可以做什么和不能做什么。 - Elijah
@Elijah 不要再考虑旧版本的lazyeval,现在集中精力于quo/enquo/sym/ensym等。当您将变量传递给函数时,使用带有前缀en的那些函数。 - akrun

3
更新的关于nse的指南是Tidy Evaluation 指南。其中第8章详细介绍了其与 dplyr 的关系,以及一般模式。在您的情况下,有几种可能的模式,这取决于您想向用户公开什么。 模式1:简单地将点扩展传递,让用户完全控制底层的expand()
f <- function(...) {tidyr::expand(dummy, ...)}
f( x, y )    # End users specifies the columns via NSE

模式 2:按变量逐个捕获用户输入,并使用新的 "curly curly" 操作符将其传递给 expand()

g <- function( var1, var2 ) {tidyr::expand(dummy, {{var1}}, {{var2}})}
g( x, y )    # Once again, NSE, but the number of arguments is controlled

模式3:允许用户使用变量名或字符串来提供参数。使用rlang::ensyms将字符串转换为变量名:

h <- function(...) {tidyr::expand(dummy, !!!rlang::ensyms(...))}

# The interface now works with strings or NSE
h( "x", "y" )
h( x, y )

模式 3b:如果您想禁用 NSE 支持,并强制用户仅使用字符串作为参数,则对上述模式进行轻微修改即可仅接受字符串:

h2 <- function(...) {tidyr::expand(dummy, !!!rlang::syms(list(...)))}
h2( "x", "y" )    # Strings OK
h2( x, y )        # Error: object 'x' not found

请注意,NSE函数需要使用准引用来处理存储在外部变量中的符号:
# Handling strings in external variables
str_name <- "x"
h( !!str_name, "y" )
h2( str_name, "y" )    # h2 doesn't support NSE; no !! needed

# Handling variable names as unevaluated expressions (NOT strings)
var_name <- quote(y)
f( x, !!var_name )
g( x, !!var_name )
h( x, !!var_name )

# Handling lists of variable names using !!! unquote-splice
# Works with functions that accept dots
arg_names <- rlang::exprs( x, y )
f( !!!arg_names )
h( !!!arg_names )

感谢你的回答,Artem。但是我发现了一些奇怪的事情。如果我首先将变量名传递给一个字符串,例如 str_name <- "x",那么你展示的所有方法似乎都不起作用。然而,如果我这样做 `i <- function(var1, var2){tidyr::expand(dummy, !!sym(var1), !!sym(var2)) }` 它就可以工作了。+1 链接关于tidyevaluation。 - Elijah
1
这是因为该函数正在寻找名为 str_name 的列。使用 !! 运算符告诉函数查找存储在 str_name 中的列名。请参见我的编辑。 - Artem Sokolov
@Elijah:添加了一个“仅限字符串”的模式,以防万一。 - Artem Sokolov
谢谢,我现在明白了之前我所缺少的! - Elijah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接