3

我正在尝试为使用包的mutate()调用提供一个包含多个列名的向量。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')
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向量?Usingas.name只需要一个对象,所以它不起作用。

这里的问题类似:将变量名向量传递给 dplyr 中的arrange()。该解决方案“有效”,因为我可以使用以下代码:

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 %>% select(!!! rlang::syms(two_names))它似乎不适用于在变异时提供参数(即stackdf %>% rowwise %>% mutate(test = anyNA(!!! rlang::syms(two_names)))。这个答案是相似的但不起作用:如何使用 dplyr 评估具有非标准评估的构造字符串?

4

2 回答 2

5

您可以使用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 而不是 tidy 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

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

于 2018-02-17T21:27:24.710 回答
5

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

  • 访问字符向量中的字符串并将其与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.nameor rlang::sym/ rlang::syms:as.name并且sym只接受一个输入,但是syms将接受多个并返回一个符号对象列表作为输出。

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

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

2. 在mutateusinganyNA或其他变量中使用动态变量

  1. 直接使用symsandanyNA并不会产生正确的结果。

    > 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表明,这仅考虑了第一个要素,而忽略了第二个要素。但是,如果我使用不同的函数,例如sumor paste0,很明显这两个元素都在使用:

    > 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
    

    anyNA当您查看vs的论点时,其原因就很清楚了sum

    函数 (x, recursive = FALSE) .Primitive("anyNA")

    函数 (..., 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. 或者...出于教育目的,可以使用 、 和 的组合sapplyany产生anyNA正确的结果。在这里,我们使用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"
    
于 2018-02-17T22:12:43.283 回答