Sử dụng đánh giá không chuẩn dựa trên tidyeval trong recode ở phía bên phải của đột biến


13

Hãy xem xét một tibble trong đó mỗi cột là một vectơ ký tự có thể nhận nhiều giá trị - giả sử "A" đến "F".

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

Tôi muốn tạo một hàm lấy tên cột làm đối số và mã hóa lại cột đó để bất kỳ câu trả lời "A" nào trở thành NA và df được trả lại như cũ. Lý do để thiết kế nó theo cách này là để phù hợp với một đường ống rộng hơn thực hiện một loạt các hoạt động bằng cách sử dụng một cột nhất định.

Có rất nhiều cách để làm điều này. Nhưng tôi quan tâm đến việc hiểu cách tiếp cận thành ngữ tidy_eval / tidyverse tốt nhất sẽ là gì. Đầu tiên, tên câu hỏi cần nằm ở phía bên trái của một động từ đột biến, vì vậy chúng tôi sử dụng các toán tử !!:=toán tử một cách thích hợp. Nhưng sau đó, những gì để đặt ở phía bên tay phải?

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

Suy nghĩ ban đầu của tôi là điều này sẽ hoạt động:

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

Nhưng tất nhiên, bang-bang bên trong hàm chỉ trả về chuỗi ký tự bằng chữ (ví dụ "q1"). Cuối cùng tôi đã lấy cảm giác giống như một tuyến đường hack để tham chiếu dữ liệu ở phía bên tay phải, sử dụng [[toán tử R cơ sở và dựa vào .cấu trúc từ dplyr, và nó hoạt động, vì vậy theo nghĩa tôi đã giải quyết được vấn đề tiềm ẩn của mình:

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

Tôi quan tâm đến việc nhận phản hồi từ những người rất giỏi về tidyeval về việc liệu có cách nào thành ngữ hơn để làm điều này hay không, với hy vọng rằng việc xem một ví dụ hoạt động sẽ tăng cường sự hiểu biết của tôi về chức năng ngăn nắp nói chung hơn. Có suy nghĩ gì không?


Cảm ơn, đây là một cách tiếp cận thông minh - tôi cũng sử dụng cách tiếp cận chức năng trong các phần khác trong mã của mình và có thể đã nghĩ về việc thực hiện nó ở đây. Tôi biết một số người cau mày khi nói về phong cách mã trên SO, nhưng thấy một vài kiểu trả lời khác nhau rất nhanh đã mang lại kết quả rất tốt cho tôi.
aaron

1
Kết hợp một số ý tưởng trong câu hỏi này, tôi tin rằng đây là phiên bản ngắn gọn nhất hoạt động với cả q1(biểu tượng) và "q1"(chuỗi):df %>% mutate_at( vars(!!ensym(question)), recode, A = NA_character_)
Artem Sokolov

Câu trả lời:


6

Ở đây, ở phía bên phải :=, chúng ta có thể chỉ định symchuyển đổi thành biểu tượng và sau đó đánh giá ( !!)

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    

Một cách tiếp cận tốt hơn sẽ hoạt động cho cả đầu vào được trích dẫn và không trích dẫn là 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
Tôi đã cố gắng sử dụng một vài chức năng chuyển đổi rlang nhưng rõ ràng là không chọn đúng, nhưng cách tiếp cận của bạn hoạt động - Tôi nghĩ rằng tôi thực sự chỉ cần xử lý các chuyển đổi loại trong đầu. Câu hỏi của tôi !! không hoạt động vì nó đánh giá một chuỗi ký tự theo nghĩa đen. Bạn làm việc vì đầu tiên nó chuyển đổi chuỗi ký tự thành biểu tượng và sau đó đánh giá biểu tượng, trả về vectơ. Tôi chỉ không thể quấn đầu rằng đó là thứ tự của hoạt động. Cảm ơn một lần nữa.
aaron

8

Bạn có thể sử dụng phương pháp "xoăn xoăn" ngay bây giờ nếu bạn có rlang> = 0.4.0 .

Giải thích nhờ @ eipi10:

Điều này kết hợp quá trình hai bước trích dẫn-sau đó bỏ qua thành một bước, do đó {{question}}tương đương với!!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    

Lưu ý rằng không giống như ensymcách tiếp cận, điều này không hoạt động với tên nhân vật. Thậm chí tệ hơn, nó làm điều sai trái thay vì chỉ đưa ra một lỗi.

fix_question(sample_df, 'q1')

# # A tibble: 3 x 2
#   q1    q2   
#   <chr> <chr>
# 1 q1    B    
# 2 q1    B    
# 3 q1    A    

2
Tôi vẫn chưa có thói quen "xoăn xoăn". Bạn có biết tại sao điều này hoạt động, trong khi phiên bản "bang bang" dường như giống hệt của OP không?
camille

Cảm ơn đã đề cập đến xoăn-xoăn, mà tôi đã nghe nói sắp tới. Câu trả lời không hoạt động đối với bất kỳ phiên bản nào của rlang / dplyr mà tôi đã cài đặt; Tôi gặp lỗi với LHS. Nếu tôi thay thế LHS bằng LHS của tôi và trích dẫn q1, tôi sẽ gặp vấn đề tương tự như tôi đã gặp ở trên; nếu tôi không trích dẫn q1, tôi sẽ gặp lỗi. Đây có thể là một điều phiên bản.
aaron

1
Yeah rlang 0.4.0 vừa được phát hành vào cuối tháng 6, vì vậy nếu bạn không cập nhật nó kể từ đó thì điều này sẽ không hiệu quả với bạn
IceCreamToucan

2
Tôi nghĩ rằng bang-bang đã không hoạt động bởi vì questiontrước tiên cần phải biến thành một quences ( question = enquo(question)) trước khi được sử dụng trong ống dplyr. {{question}}tương đương với !!enquo(question).
eipi10

2
Bạn cũng cần enquo cho trường hợp đầu tiên của câu hỏi để tương đương.
IceCreamToucan

7

Bạn có thể làm cho hàm linh hoạt hơn một chút bằng cách cho phép nhập một vectơ các giá trị được mã hóa lại làm đối số. Ví dụ:

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

Lưu ý rằng recode.vec"không ghép nối" với !!!. Bạn có thể thấy những gì nó đang làm với ví dụ này, được điều chỉnh từ Lập trình với họa tiết dplyr (tìm kiếm "splice" để xem các ví dụ liên quan). Lưu ý cách !!!"ghép" các cặp giá trị mã hóa lại vào recodehàm để chúng được sử dụng làm ...đối số trong 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

Nếu bạn muốn có khả năng chạy chức năng mã hóa trên nhiều cột, bạn có thể biến nó thành một hàm chỉ lấy một tên cột và một vectơ mã hóa. Cách tiếp cận này có vẻ như sẽ thân thiện với đường ống hơn.

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>

Hoặc để mã hóa lại một cột đơn:

sample_df %>% 
  mutate(q1 = fix_question(q1, c(A=NA_character_, B="Was B")))
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.