Использование нестандартной оценки на основе Tidyeval при перекодировании в правой части mutate - PullRequest
13 голосов
/ 11 октября 2019

Рассмотрим тиббл, где каждый столбец - это символьный вектор, который может принимать много значений - скажем, от «A» до «F».

library(tidyverse)
sample_df <- tibble(q1 = c("A", "B", "C"), q2 = c("B", "B", "A"))

Я хочу создать функцию, которая принимает имя столбца в качестве аргумента и перекодирует этот столбец, так что любой ответ «A» становится NA, а df в противном случае возвращается как есть. Причиной его разработки является встраивание в более широкий конвейер, который выполняет серию операций с использованием данного столбца.

Есть много способов сделать это. Но мне интересно понять, каким будет лучший идиоматический подход tidy_eval / tidyverse. Во-первых, имя вопроса должно быть слева от глагола-мутанта, поэтому мы соответствующим образом используем операторы !! и :=. Но тогда, что поставить на правую сторону?

fix_question <- function(df, question) {
    df %>% mutate(!!question := recode(... something goes here...))
}

fix_question(sample_df, "q1") # should produce a tibble whose first column is (NA, "B", "C")

Моя первоначальная мысль состояла в том, что это сработает:

df %>% mutate(!!question := recode(!!question, "A" = NA_character_))

Но, конечно же, ударная функция внутри функциипросто возвращает буквенную строку символов (например, «q1»). В итоге я выбрал то, что похоже на хакерский маршрут, для ссылки на данные справа, используя базовый оператор R [[ и опираясь на конструкцию . из dplyr, и это работает, так что в некотором смысле я решилмоя основная проблема:

df %>% mutate(!!question := recode(.[[question]], "A" = NA_character_))

Мне интересно получить отзывы от людей, которые очень хорошо разбираются в том, есть ли более идиоматический способ сделать это, в надежде, что просмотр работающего примера улучшитмое понимание функции tidyeval установлено более широко. Есть мысли?

Ответы [ 3 ]

8 голосов
/ 11 октября 2019

Вы можете использовать метод "curly curly" прямо сейчас, если у вас есть rlang> = 0.4.0 .

Объяснение благодаря @ eipi10:

Это объединяет двухэтапный процесс цитата-затем-кавычка в один шаг, поэтому {{question}} эквивалентно !!enquo(question)

fix_question <- function(df, question){
  df %>% mutate({{question}} := recode({{question}}, A = NA_character_))
}

fix_question(sample_df, q1)
# # A tibble: 3 x 2
#   q1    q2   
#   <chr> <chr>
# 1 NA    B    
# 2 B     B    
# 3 C     A    

Обратите внимание, что в отличие от подхода ensym, он не работает с именами персонажей. Хуже того, он делает не то, что нужно, а просто выдает ошибку.

fix_question(sample_df, 'q1')

# # A tibble: 3 x 2
#   q1    q2   
#   <chr> <chr>
# 1 q1    B    
# 2 q1    B    
# 3 q1    A    
7 голосов
/ 11 октября 2019

Вы можете сделать функцию немного более гибкой, позволяя также вводить вектор перекодированных значений в качестве аргумента. Например:

library(tidyverse)
sample_df <- tibble(q1 = c("A", "B", "C"), q2 = c("B", "B", "A"))

fix_question <- function(df, question, recode.vec) {

  df %>% mutate({{question}} := recode({{question}}, !!!recode.vec))

}

fix_question(sample_df, q1, c(A=NA_character_, B="Was B"))
  q1    q2   
1 <NA>  B    
2 Was B B    
3 C     A

Обратите внимание, что recode.vec "без кавычек" с !!!. Вы можете увидеть, что это делает с этим примером, адаптированным из Программирование с помощью dplyr vignette (ищите «соединение», чтобы увидеть соответствующие примеры). Обратите внимание, как !!! "склеивает" пары значений перекодировки в функцию recode, чтобы они использовались в качестве аргумента ... в recode.

x = c("A", "B", "C")
args = c(A=NA_character_, B="Was B")

quo(recode(x, !!!args))

<quosure>
expr: ^recode(x, A = <chr: NA>, B = "Was B")
env:  global

Если вы хотите запустить функцию записи для нескольких столбцов, вы можете превратить ее в функцию, которая принимает только имя столбца и вектор записи. Такой подход кажется более подходящим для конвейера.

fix_question <- function(question, recode.vec) {

  recode({{question}}, !!!recode.vec)

}

sample_df %>% 
  mutate_at(vars(matches("q")), list(~fix_question(., c(A=NA_character_, B="Was B"))))
  q1    q2   
1 <NA>  Was B
2 Was B Was B
3 C     <NA>

Или перекодировать один столбец:

sample_df %>% 
  mutate(q1 = fix_question(q1, c(A=NA_character_, B="Was B")))
6 голосов
/ 11 октября 2019

Здесь, справа от :=, мы можем указать sym для преобразования в символ и затем оценить (!!)

fix_question <- function(df, question) {
    df %>%
       mutate(!!question := recode(!! rlang::sym(question), "A" = NA_character_))
  }

fix_question(sample_df, "q1") 
# A tibble: 3 x 2
#  q1    q2   
#  <chr> <chr>
#1 <NA>  B    
#2 B     B    
#3 C     A    

Лучший подход, который будет работатьдля ввода в кавычках и без кавычек ensym

fix_question <- function(df, question) {
    question <- ensym(question)
    df %>%
       mutate(!!question := recode(!! question, "A" = NA_character_))
  }


fix_question(sample_df, q1)
# A tibble: 3 x 2
#  q1    q2   
#  <chr> <chr>
#1 <NA>  B    
#2 B     B    
#3 C     A    

fix_question(sample_df, "q1")
# A tibble: 3 x 2
#  q1    q2   
#  <chr> <chr>
#1 <NA>  B    
#2 B     B    
#3 C     A    
...