Стандартная оценка Dplyr с использованием вектора из нескольких строк с функцией mutate

Я пытаюсь предоставить вектор, который содержит несколько имен столбцов для 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 вектор? С помощью as.name принимает только один объект, поэтому он не работает.

Этот вопрос здесь похож: передайте вектор имен переменных в range() в dplyr. Это решение "работает" в том, что я могу использовать следующий код:

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

Но это побеждает цель, если я должен напечатать c(jack, jill) напрямую, а не используя two_names переменная. Есть ли похожая процедура, где я могу использовать two_names напрямую? Этот ответ Как передать именованный вектор в dplyr::select с помощью кавычек? использования rlang::syms но хотя это работает для выбора переменных (т.е. stackdf %>% select(!!! rlang::syms(two_names)) кажется, не работает для предоставления аргументов при мутировании (то есть stackdf %>% rowwise %>% mutate(test = anyNA(!!! rlang::syms(two_names))), Этот ответ похож, но не работает: Как оценить построенную строку с нестандартной оценкой, используя dplyr?

2 ответа

Решение

Есть несколько ключей к решению этого вопроса:

  • Доступ к строкам в символьном векторе и использование их с 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.name или же 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. Использование динамических переменных внутри mutate с помощью anyNA или другие переменные

  1. С помощью syms а также anyNA напрямую не дает правильного результата.

    > 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 показывает, что это учитывает только первый элемент и игнорирует второй элемент. Однако, если я использую другую функцию, например, sum или же 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 против sum,

    функция (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. В качестве альтернативы... в образовательных целях можно использовать комбинацию sapply, any, а также 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"
    

Ты можешь использовать rlang::syms (который экспортируется версией dplyr для разработки; поочередно вызывайте ее напрямую) для приведения строк к предложениям, поэтому

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 звонки вместо одного векторизованного.

Другие вопросы по тегам