【问题标题】:Dplyr standard evaluation using a vector of multiple strings with mutate function使用具有 mutate 函数的多个字符串的向量进行 Dplyr 标准评估
【发布时间】:2018-07-28 10:51:06
【问题描述】:

我正在尝试使用dplyr 包为mutate() 调用提供一个包含多个列名的向量。下面的可重现示例:

stackdf <- data.frame(jack = c(1,NA,2,NA,3,NA,4,NA,5,NA),
                      jill = c(1,2,NA,3,4,NA,5,6,NA,7),
                      jane = c(1,2,3,4,5,6,NA,NA,NA,NA))
two_names <- c('jack','jill')
one_name <- c('jack')

#   jack jill jane
#    1    1    1
#   NA    2    2
#    2   NA    3
#   NA    3    4
#    3    4    5
#   NA   NA    6
#    4    5   NA
#   NA    6   NA
#    5   NA   NA
#   NA    7   NA

我能够弄清楚如何使用“一个变量”版本,但不知道如何将其扩展到多个变量?

# the below works as expected, and is an example of the output I desire
stackdf %>% rowwise %>% mutate(test = anyNA(c(jack,jill)))

# A tibble: 10 x 4
    jack  jill  jane  test
   <dbl> <dbl> <dbl> <lgl>
 1     1     1     1 FALSE
 2    NA     2     2  TRUE
 3     2    NA     3  TRUE
 4    NA     3     4  TRUE
 5     3     4     5 FALSE
 6    NA    NA     6  TRUE
 7     4     5    NA FALSE
 8    NA     6    NA  TRUE
 9     5    NA    NA  TRUE
10    NA     7    NA  TRUE


# using the one_name variable works if I evaluate it and then convert to 
# a name before unquoting it
stackdf %>% rowwise %>% mutate(test = anyNA(!!as.name(eval(one_name))))

# A tibble: 10 x 4
    jack  jill  jane  test
   <dbl> <dbl> <dbl> <lgl>
 1     1     1     1 FALSE
 2    NA     2     2  TRUE
 3     2    NA     3 FALSE
 4    NA     3     4  TRUE
 5     3     4     5 FALSE
 6    NA    NA     6  TRUE
 7     4     5    NA FALSE
 8    NA     6    NA  TRUE
 9     5    NA    NA FALSE
10    NA     7    NA  TRUE

如何扩展上述方法以便可以使用two_names 向量?使用 as.name 只需要一个对象,因此它不起作用。

这里的问题类似:Pass a vector of variable names to arrange() in dplyr。该解决方案“有效”,因为我可以使用以下代码:

two_names2 <- quos(c(jack, jill))
stackdf %>% rowwise %>% mutate(test = anyNA(!!!two_names2))

但是,如果我必须直接输入c(jack, jill) 而不是使用two_names 变量,这将达不到目的。是否有一些类似的程序可以直接使用two_names?此答案How to pass a named vector to dplyr::select using quosures? 使用rlang::syms,但尽管这适用于选择变量(即stackdf %&gt;% select(!!! rlang::syms(two_names)),但它似乎不适用于在变异时提供参数(即stackdf %&gt;% rowwise %&gt;% mutate(test = anyNA(!!! rlang::syms(two_names)))。此答案类似但不起作用:How to evaluate a constructed string with non-standard evaluation using dplyr?

【问题讨论】:

    标签: r dplyr rlang nse standard-evaluation


    【解决方案1】:

    解决这个问题有几个关键:

    • 访问字符向量中的字符串并将其与dplyr 一起使用
    • 提供给与mutate 一起使用的函数的参数格式,此处为anyNA

    这里的目标是复制此调用,但使用命名变量two_names 而不是手动输入c(jack,jill)

    stackdf %>% rowwise %>% mutate(test = anyNA(c(jack,jill)))
    
    # A tibble: 10 x 4
        jack  jill  jane  test
       <dbl> <dbl> <dbl> <lgl>
     1     1     1     1 FALSE
     2    NA     2     2  TRUE
     3     2    NA     3  TRUE
     4    NA     3     4  TRUE
     5     3     4     5 FALSE
     6    NA    NA     6  TRUE
     7     4     5    NA FALSE
     8    NA     6    NA  TRUE
     9     5    NA    NA  TRUE
    10    NA     7    NA  TRUE
    

    1.在 dplyr 中使用动态变量

    1. 使用quo/quos:不接受字符串作为输入。使用这种方法的解决方案是:

      two_names2 <- quos(c(jack, jill))
      stackdf %>% rowwise %>% mutate(test = anyNA(!!! two_names2))
      

      请注意,quo 采用单个参数,因此使用 !! 不带引号,对于多个参数,您可以分别使用 quos!!!。这是不可取的,因为我不使用two_names,而是必须输入我希望使用的列。

    2. 使用as.namerlang::sym/rlang::symsas.namesym 只接受一个输入,但syms 将接受多个并返回符号对象的列表作为输出。

      > two_names
      [1] "jack" "jill"
      > as.name(two_names)
      jack
      > syms(two_names)
      [[1]]
      jack
      
      [[2]]
      jill
      

      请注意,as.name 会忽略第一个元素之后的所有内容。但是,syms 似乎在这里可以正常工作,所以现在我们需要在 mutate 调用中使用它。

    2。在mutate 中使用动态变量,使用anyNA 或其他变量

    1. 直接使用symsanyNA 实际上不会产生正确的结果。

      > stackdf %>% rowwise %>% mutate(test = anyNA(!!! syms(two_names)))
          jack  jill  jane  test
         <dbl> <dbl> <dbl> <lgl>
       1     1     1     1 FALSE
       2    NA     2     2  TRUE
       3     2    NA     3 FALSE
       4    NA     3     4  TRUE
       5     3     4     5 FALSE
       6    NA    NA     6  TRUE
       7     4     5    NA FALSE
       8    NA     6    NA  TRUE
       9     5    NA    NA FALSE
      10    NA     7    NA  TRUE
      

      检查test 表明这仅考虑了第一个元素,而忽略了第二个元素。但是,如果我使用不同的功能,例如sumpaste0,很明显这两个元素都在使用:

      > stackdf %>% rowwise %>% mutate(test = sum(!!! syms(two_names), 
                                                  na.rm = TRUE))
          jack  jill  jane  test
         <dbl> <dbl> <dbl> <dbl>
       1     1     1     1     2
       2    NA     2     2     2
       3     2    NA     3     2
       4    NA     3     4     3
       5     3     4     5     7
       6    NA    NA     6     0
       7     4     5    NA     9
       8    NA     6    NA     6
       9     5    NA    NA     5
      10    NA     7    NA     7
      

      当您查看 anyNAsum 的论点时,其原因就很清楚了。

      function (x, recursive = FALSE) .Primitive("anyNA")

      function (..., na.rm = FALSE) .Primitive("sum")

      anyNA 需要一个对象x,而sum 可以获取对象(...) 的变量列表。

    2. 只需提供 c() 即可解决此问题(请参阅 alistaire 的回答)。

      > stackdf %>% rowwise %>% mutate(test = anyNA(c(!!! syms(two_names))))
          jack  jill  jane  test
         <dbl> <dbl> <dbl> <lgl>
       1     1     1     1 FALSE
       2    NA     2     2  TRUE
       3     2    NA     3  TRUE
       4    NA     3     4  TRUE
       5     3     4     5 FALSE
       6    NA    NA     6  TRUE
       7     4     5    NA FALSE
       8    NA     6    NA  TRUE
       9     5    NA    NA  TRUE
      10    NA     7    NA  TRUE
      
    3. 或者...出于教育目的,可以使用sapplyanyanyNA 的组合来产生正确的结果。这里我们使用list,以便将结果作为单个列表对象提供。

      # this produces an error an error because the elements of !!!
      # are being passed to the arguments of sapply (X =, FUN = )
      > stackdf %>% rowwise %>% 
          mutate(test = any(sapply(!!! syms(two_names), anyNA)))
      Error in mutate_impl(.data, dots) : 
        Evaluation error: object 'jill' of mode 'function' was not found.
      

      提供list 解决了这个问题,因为它将所有结果绑定到一个对象中。

      # the below table is the familiar incorrect result that uses only the `jack`
      > stackdf %>% rowwise %>% 
          mutate(test = any(sapply(X=as.list(!!! syms(two_names)), 
                                   FUN=anyNA)))
      
          jack  jill  jane  test
         <dbl> <dbl> <dbl> <lgl>
       1     1     1     1 FALSE
       2    NA     2     2  TRUE
       3     2    NA     3 FALSE
       4    NA     3     4  TRUE
       5     3     4     5 FALSE
       6    NA    NA     6  TRUE
       7     4     5    NA FALSE
       8    NA     6    NA  TRUE
       9     5    NA    NA FALSE
      10    NA     7    NA  TRUE
      
      # this produces the correct answer
      > stackdf %>% rowwise %>% 
          mutate(test = any(X = sapply(list(!!! syms(two_names)), 
                            FUN = anyNA)))
      
      jack  jill  jane  test
      <dbl> <dbl> <dbl> <lgl>
       1     1     1     1 FALSE
       2    NA     2     2  TRUE
       3     2    NA     3  TRUE
       4    NA     3     4  TRUE
       5     3     4     5 FALSE
       6    NA    NA     6  TRUE
       7     4     5    NA FALSE
       8    NA     6    NA  TRUE
       9     5    NA    NA  TRUE
      10    NA     7    NA  TRUE
      

      在比较它们的行为时,了解为什么这两者表现不同是有意义的!

      > as.list(two_names)
      [[1]]
      [1] "jack"
      
      [[2]]
      [1] "jill"
      
      > list(two_names)
      [[1]]
      [1] "jack" "jill"
      

    【讨论】:

      【解决方案2】:

      您可以使用rlang::syms(由dplyr重新导出;或者直接调用它)将字符串强制为quosures,所以

      library(dplyr)
      
      stackdf <- data.frame(jack = c(1,NA,2,NA,3,NA,4,NA,5,NA),
                            jill = c(1,2,NA,3,4,NA,5,6,NA,7),
                            jane = c(1,2,3,4,5,6,NA,NA,NA,NA))
      two_names <- c('jack','jill')
      
      stackdf %>% rowwise %>% mutate(test = anyNA(c(!!!syms(two_names))))
      #> Source: local data frame [10 x 4]
      #> Groups: <by row>
      #> 
      #> # A tibble: 10 x 4
      #>     jack  jill  jane test 
      #>    <dbl> <dbl> <dbl> <lgl>
      #>  1    1.    1.    1. FALSE
      #>  2   NA     2.    2. TRUE 
      #>  3    2.   NA     3. TRUE 
      #>  4   NA     3.    4. TRUE 
      #>  5    3.    4.    5. FALSE
      #>  6   NA    NA     6. TRUE 
      #>  7    4.    5.   NA  FALSE
      #>  8   NA     6.   NA  TRUE 
      #>  9    5.   NA    NA  TRUE 
      #> 10   NA     7.   NA  TRUE
      

      或者,使用一点基础 R 代替整洁的 eval:

      stackdf %>% mutate(test = rowSums(is.na(.[two_names])) > 0)
      #>    jack jill jane  test
      #> 1     1    1    1 FALSE
      #> 2    NA    2    2  TRUE
      #> 3     2   NA    3  TRUE
      #> 4    NA    3    4  TRUE
      #> 5     3    4    5 FALSE
      #> 6    NA   NA    6  TRUE
      #> 7     4    5   NA FALSE
      #> 8    NA    6   NA  TRUE
      #> 9     5   NA   NA  TRUE
      #> 10   NA    7   NA  TRUE
      

      ...这可能会快很多,因为迭代 rowwise 会产生 n 调用而不是矢量化调用。

      【讨论】:

      • 这实际上很接近,但是当我尝试这种方法时遇到了同样的问题。你能看到我的答案吗?调用list() 函数似乎有效,但我不清楚为什么as.listlist 在这里有不同的行为。
      • 糟糕,已编辑。我们都忘记了c!!! 将它们拼接为单独的参数,因此第二个被传递给 anyNArecursive 参数,这是不正确的。
      • 为什么需要rowwisemutate 不是按行排列吗?
      • @Jelena-bioinf 不是天生的;它取决于其中使用的功能,例如+ 返回一个向量,但sum 将所有内容折叠为一个数字(如此处的anyNA,因此如果没有rowwise,它将返回一个TRUEmutate 将循环到一个列中)。 rowwise 总是有替代品(现在这是一个不受欢迎的成语),但它们通常需要更深入地了解函数和对函数式编程的理解。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-17
      • 2020-04-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多