Використання нестандартного оцінювання на основі тидевалу в перекодуванні в правій частині мутації


13

Розглянемо таблицю, де кожен стовпець є символьним вектором, який може приймати багато значень - скажімо, "A" через "F".

library(tidyverse)
sample_df <- tibble(q1 = c("A", "B", "C"), q2 = c("B", "B", "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_))

Мені цікаво отримати зворотній зв'язок від людей, які дуже добре займаються питанням, чи є більш ідіоматичний спосіб зробити це, сподіваючись, що бачення відпрацьованого прикладу покращить моє розуміння функції, встановленої на тривалість, загалом. Будь-які думки?


Дякую, це розумний підхід - я використовую функціональний підхід в інших частинах свого коду і міг би подумати про те, щоб зробити це і тут. Я знаю, що деякі люди нахмурилися говорити про стиль коду на SO, але побачити кілька різних стилів відповіді так швидко було для мене дуже плідно.
aaron

1
Поєднуючи в цьому питанні декілька ідей, я вважаю, що це найкоротша версія, яка працює як із q1символом, так і з "q1"рядком:df %>% mutate_at( vars(!!ensym(question)), recode, A = NA_character_)
Артем Соколов

Відповіді:


6

Тут, з правого боку :=, ми можемо вказати 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    

2
Я намагався поставити навколо себе кілька функцій перетворення rlang, але, очевидно, не вибрав правильну, але ваш підхід працює - я думаю, що мені справді потрібно просто переробити типи перетворень в моїй голові. Моє питання !! не працює, оскільки він оцінює символьний рядок буквально. Ваш працює, тому що спочатку перетворює рядок символів у символ, а потім оцінює символ, повертаючи вектор. Я просто не міг загорнути голову, що це був порядок операцій. Знову дякую.
aaron

8

Тепер ви можете скористатися методом "фігурних фігурних", якщо у вас є 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    

2
Я ще не ввійшов у звичку «кучерявого кучерявого». Чи знаєте ви, чому це працює, тоді як, здавалося б, ідентична версія ОП "Ударний удар" не стала?
Камілла

Дякуємо, що згадали про кучеряве-кучеряве, про яке я чув, що це буде. Відповідь не працює для будь-якої версії rlang / dplyr, яку я встановив; Я отримую помилку з LHS. Якщо я заміню LHS моїм LHS і цитую q1, я отримую ту саму проблему, що була у мене вище; якщо я не цитую q1, я отримую помилку. Це, можливо, версія версії.
aaron

1
Так rlang 0.4.0 була випущена тільки в кінці червня , так що якщо ви не оновлювали його з тих пір це не буде працювати для вас
IceCreamToucan

2
Я думаю, що вибух-удар не спрацював, тому що questionспочатку його потрібно перетворити на quosure ( question = enquo(question)) перед тим, як використовувати його в трубі dplyr. {{question}}еквівалентно !!enquo(question).
eipi10

2
Вам також потрібна enquo для першої інстанції питання, щоб це було рівнозначно.
IceCreamToucan

7

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

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 (шукайте "сплайс", щоб переглянути відповідні приклади). Зауважте, як !!!"зрощено" пари перекодуючих значень у 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")))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.