Передавать имена столбцов в dplyr::coalesce() при написании пользовательской функции

#r #function #dplyr #tidyselect

#r #функция #dplyr #tidyselect

Вопрос:

Я пытаюсь написать функцию, которая будет переносить dplyr::coalesce() и будет использовать объект данных и имена столбцов для объединения. До сих пор мои попытки не увенчались успехом.

Пример данных

 library(dplyr)

df <-
  data.frame(col_a = c("bob", NA, "bob", NA, "bob"), 
                 col_b = c(NA, "danny", NA, NA, NA), 
                 col_c = c("paul", NA, NA, "paul", NA))

##   col_a col_b col_c
## 1   bob  <NA>  paul
## 2  <NA> danny  <NA>
## 3   bob  <NA>  <NA>
## 4  <NA>  <NA>  paul
## 5   bob  <NA>  <NA>
 

Использование заглушки при написании пользовательской функции

 coalesce_plus_1 <- function(data, vars) {

  data %>%
    mutate(coalesced_col = coalesce(!!! rlang::syms(tidyselect::vars_select(names(.), vars))))

}
 
 coalesce_plus_2 <- function(data, vars) {
  
  data %>%
    mutate(coalesced_col = coalesce(!!! rlang::syms(vars)))
  
}
 
 coalesce_plus_3 <- function(data, vars) {
  
  data %>%
    mutate(coalesced_col = coalesce({{ vars }}))
  
}
 

Результаты…

coalesce_plus_1()

 df %>%
  coalesce_plus_1(data = ., vars = c(col_a, col_b, col_c))
 

Ошибка: объект ‘col_a’ не найден.

Однако:

 df %>%
  coalesce_plus_1(data = ., vars = all_of(starts_with("col")))

##   col_a col_b col_c coalesced_col
## 1  <NA>  <NA>  paul          paul
## 2  <NA> danny  <NA>         danny
## 3   bob  <NA>  <NA>           bob
## 4  <NA>  <NA>  paul          paul
## 5   bob  <NA>  <NA>           bob
 

coalesce_plus_2()

 df %>%
  coalesce_plus_2(data = ., vars = c(col_a, col_b, col_c))
 

Ошибка в lapply(.x, .f, …) : объект ‘col_a’ не найден

А также

 df %>%
  coalesce_plus_2(data = ., vars = all_of(starts_with("col")))
 

Ошибка: starts_with() должно использоваться в функции выбора.
я вижу https://tidyselect.r-lib.org/reference/faq-selection-context.html .
Запустите rlang::last_error() , чтобы увидеть, где произошла ошибка.


coalesce_plus_3()

 df %>%
  coalesce_plus_3(data = ., vars = c(col_a, col_b, col_c))
 

Ошибка: проблема с mutate() вводом coalesced_col . ввод x
coalesced_col не может быть переработан до размера 5. i Ввод coalesced_col
coalesce(c(col_a, col_b, col_c)) . i ввод coalesced_col должен быть
размером 5 или 1, а не 15.

А также

 df %>%
  coalesce_plus_3(data = ., vars = all_of(starts_with("col")))
 

Ошибка: проблема с mutate() вводом coalesced_col .
x starts_with() должен использоваться в функции выбора.
я вижу https://tidyselect.r-lib.org/reference/faq-selection-context.html .
я ввожу coalesced_col coalesce(all_of(starts_with("col"))) .

Итог

Как я могу написать пользовательскую функцию coalesce() , которая будет использовать объект данных и конкретные имена столбцов для объединения, позволяя использовать как конкретные имена, например, c(col_a, col_b, col_c) так и вспомогательные функции, например, starts_with("col") в аргументе функции vars ?

Комментарии:

1. Вы видели tidyselect виньетку о реализации интерфейсов tidyselect?

2. очень полезно, спасибо!

Ответ №1:

Это простая реализация, которая будет возвращать только выбранные столбцы, но может быть довольно легко расширена, чтобы сохранить все столбцы (я бы bind_cols вернул их в конце …).

Это просто, потому что мы полагаемся на select то, что он сделает всю работу за нас, как было предложено в начале реализации виньетки tidyselect

 # edited to keep all columns
coalesce_df = function(data, ...) {
  data %>%
    select(...) %>%
    transmute(result = invoke(coalesce, .)) %>%
    bind_cols(data, .)
}



df %>%
   coalesce_df(everything())
#   col_a col_b col_c result
# 1   bob  <NA>  paul    bob
# 2  <NA> danny  <NA>  danny
# 3   bob  <NA>  <NA>    bob
# 4  <NA>  <NA>  paul   paul
# 5   bob  <NA>  <NA>    bob

df %>% coalesce_df(col_a, col_b)
#   col_a col_b col_c result
# 1   bob  <NA>  paul    bob
# 2  <NA> danny  <NA>  danny
# 3   bob  <NA>  <NA>    bob
# 4  <NA>  <NA>  paul   <NA>
# 5   bob  <NA>  <NA>    bob
 

Комментарии:

1. Я понял ваш намек bind_cols и сделал следующее в функции: cols <- data %>% select(...) %>% names , затем канал с mutate(result = invoke(coalesce, .)) , как вы предложили, и в конце канала bind_cols(., select(data, -all_of(cols))) .

2. Я просто отредактировал, чтобы использовать transmute вместо mutate , что делает хранение всех столбцов немного более чистым — сохраняет их в том же порядке и добавляет result столбец в конце.

Ответ №2:

На самом деле, ваша первая функция работает, просто напишите vars как символ. Посмотрите:

 df %>% coalesce_plus_1(data = ., vars = c("col_a","col_b","col_c"))
 

Вот еще один хороший вариант:

 library(dplyr)

df <- data.frame(col_a = c("bob", NA, "bob", NA, "bob"), 
                 col_b = c(NA, "danny", NA, NA, NA), 
                 col_c = c("paul", NA, NA, "paul", NA))

coalesce_plus <- function(data,vars){
      x <- as.list(select(data,vars))
      data.frame(data, coalesced_col=coalesce(!!!x))
}

df %>% coalesce_plus(data = ., vars = c("col_a","col_b","col_c"))