Penggunaan evaluasi non-standar berbasis rapi dalam recode di sisi kanan mutasi

13

Pertimbangkan tibble di mana setiap kolom adalah vektor karakter yang dapat mengambil banyak nilai - misalkan "A" hingga "F".

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

Saya ingin membuat fungsi yang menggunakan nama kolom sebagai argumen, dan mengode ulang kolom itu sehingga jawaban "A" menjadi NA dan df dikembalikan seperti apa adanya. Alasan untuk mendesainnya dengan cara ini adalah untuk masuk ke dalam pipa yang lebih luas yang melakukan serangkaian operasi menggunakan kolom yang diberikan.

Ada banyak cara untuk melakukan ini. Tapi saya tertarik untuk memahami apa pendekatan tidy_eval / tidyverse idiomatik terbaik. Pertama, nama pertanyaan harus berada di sebelah kiri kata kerja bermutasi, jadi kami menggunakan !!dan :=operator dengan tepat. Tapi kemudian, apa yang harus diletakkan di sisi kanan?

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")

Pikiran awal saya adalah bahwa ini akan berhasil:

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

Tapi tentu saja bang-bang di dalam fungsi hanya mengembalikan string karakter literal (mis. "Q1"). Saya akhirnya mengambil apa yang terasa seperti rute peretasan untuk merujuk data di sisi kanan, menggunakan [[operator base R dan mengandalkan .konstruk dari dplyr, dan itu berfungsi, jadi dalam beberapa hal saya telah memecahkan masalah mendasar saya:

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

Saya tertarik untuk mendapatkan umpan balik dari orang-orang yang sangat mahir dalam merapikan apakah ada cara yang lebih idiomatis untuk melakukan ini, dengan harapan bahwa melihat contoh yang berhasil akan meningkatkan pemahaman saya tentang fungsi rapi yang ditetapkan secara lebih umum. Adakah pikiran?

Harun
sumber
Terima kasih, ini adalah pendekatan yang cerdas - Saya memang menggunakan pendekatan fungsional di bagian lain dalam kode saya dan bisa berpikir untuk melakukannya di sini juga. Saya tahu beberapa orang tidak menyukai gaya bicara kode di SO, tetapi melihat beberapa gaya jawaban yang begitu cepat sangat bermanfaat bagi saya.
Harun
1
Menggabungkan beberapa ide dalam pertanyaan ini, saya percaya ini adalah versi yang paling ringkas yang bekerja dengan baik q1(simbol) dan "q1"(string):df %>% mutate_at( vars(!!ensym(question)), recode, A = NA_character_)
Artem Sokolov

Jawaban:

6

Di sini, di sisi kanan :=, kita dapat menentukan symuntuk mengkonversi ke simbol dan kemudian mengevaluasi ( !!)

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    

Pendekatan yang lebih baik yang akan bekerja untuk input yang dikutip dan tidak dikutip 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    
akrun
sumber
2
Saya telah mencoba bergaul dengan beberapa fungsi konversi rlang tetapi jelas tidak memilih yang benar, tetapi pendekatan Anda bekerja - saya pikir saya benar-benar hanya perlu bekerja alur konversi jenis di kepala saya. Pertanyaan saya !! tidak berfungsi karena mengevaluasi string karakter secara harfiah. Milik Anda berfungsi karena pertama-tama mengubah string karakter menjadi simbol, dan kemudian mengevaluasi simbol, mengembalikan vektor. Aku hanya tidak bisa membungkus kepalaku bahwa itu adalah urutan operasi. Terima kasih lagi.
Harun
8

Anda dapat menggunakan metode "keriting keriting" sekarang jika Anda memiliki rlang> = 0.4.0 .

Penjelasan terima kasih kepada @ eipi10:

Ini menggabungkan dua langkah proses kutipan-kemudian-tanda kutip menjadi satu langkah, sehingga {{question}}setara dengan!!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    

Perhatikan bahwa tidak seperti ensympendekatan, ini tidak bekerja dengan nama karakter. Lebih buruk lagi, itu melakukan hal yang salah, bukan hanya memberikan kesalahan.

fix_question(sample_df, 'q1')

# # A tibble: 3 x 2
#   q1    q2   
#   <chr> <chr>
# 1 q1    B    
# 2 q1    B    
# 3 q1    A    
IceCreamToucan
sumber
2
Saya belum masuk ke kebiasaan "keriting keriting" belum. Apakah Anda tahu mengapa ini berhasil, sedangkan versi "bang bang" OP yang tampaknya identik tidak?
camille
Terima kasih telah menyebutkan keriting-keriting, yang saya dengar akan datang. Jawabannya tidak berfungsi untuk rlang / dplyr versi apa pun yang telah saya instal; Saya mendapatkan kesalahan dengan LHS. Jika saya mengganti LHS dengan LHS saya dan mengutip q1, saya mendapatkan masalah yang sama dengan yang saya miliki di atas; jika saya tidak mengutip q1, saya mendapatkan kesalahan. Ini mungkin versi versi.
Harun
1
Yeah rlang 0.4.0 baru saja dirilis pada akhir Juni jadi jika Anda belum memperbarui sejak itu ini tidak akan bekerja untuk Anda
IceCreamToucan
2
Saya pikir bang-bang tidak berfungsi karena questionpertama-tama harus diubah menjadi quosure ( question = enquo(question)) sebelum digunakan dalam pipa dplyr. {{question}}setara dengan !!enquo(question).
eipi10
2
Anda perlu enquo untuk contoh pertanyaan pertama agar setara.
IceCreamToucan
7

Anda dapat membuat fungsi ini sedikit lebih fleksibel dengan memungkinkan vektor nilai yang sudah dikodekan ulang dimasukkan sebagai argumen. Sebagai contoh:

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

Perhatikan bahwa recode.vec"spliced ​​unquote" dengan !!!. Anda dapat melihat apa yang dilakukan dengan contoh ini, diadaptasi dari Pemrograman dengan sketsa dplyr (cari "splice" untuk melihat contoh yang relevan). Perhatikan bagaimana !!!"splices" pasangan nilai pengodean ulang ke dalam recodefungsi sehingga mereka digunakan sebagai ...argumen di 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

Jika Anda ingin menjalankan fungsi pengodean ulang pada banyak kolom, Anda dapat mengubahnya menjadi fungsi yang hanya menggunakan nama kolom dan vektor pengodean ulang. Pendekatan ini sepertinya akan lebih ramah pipa.

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>

Atau untuk mengode ulang satu kolom:

sample_df %>% 
  mutate(q1 = fix_question(q1, c(A=NA_character_, B="Was B")))
Eipi10
sumber