Zastosowanie niestandardowej oceny opartej na tidyeval w przekodowaniu po prawej stronie mutacji

13

Rozważmy tabliczkę, w której każda kolumna jest wektorem znaków, który może przyjmować wiele wartości - powiedzmy „A” do „F”.

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

Chciałbym utworzyć funkcję, która przyjmuje nazwę kolumny jako argument i koduje tę kolumnę tak, aby każda odpowiedź „A” stała się NA, a plik df jest zwracany w niezmienionej postaci. Powodem zaprojektowania go w ten sposób jest dopasowanie do szerszego potoku, który wykonuje szereg operacji przy użyciu danej kolumny.

Istnieje wiele sposobów, aby to zrobić. Ale jestem zainteresowany zrozumieniem, jakie byłoby najlepsze idiomatyczne podejście tidy_eval / tidyverse. Po pierwsze, nazwa pytania musi znajdować się po lewej stronie zmutowanego czasownika, więc używamy odpowiednio operatorów !!i :=. Ale co umieścić po prawej stronie?

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

Początkowo myślałem, że to zadziała:

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

Ale oczywiście bang-bang wewnątrz funkcji po prostu zwraca dosłowny ciąg znaków (np. „Q1”). Skończyło się na czymś, co wydaje się hacką trasą, aby odwoływać się do danych po prawej stronie, używając podstawowego [[operatora R i polegając na .konstrukcji z dplyr, i działa, więc w pewnym sensie rozwiązałem swój podstawowy problem:

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

Jestem zainteresowany otrzymywaniem opinii od ludzi, którzy są bardzo dobrzy w tidyeval na temat tego, czy istnieje bardziej idiomatyczny sposób, aby to zrobić, w nadziei, że zobaczenie działającego przykładu poprawi moje rozumienie ogólnie ustawionej funkcji tidyeval. jakieś pomysły?

aaron
źródło
Dzięki, to jest sprytne podejście - używam funkcjonalnego podejścia w innych częściach mojego kodu i mogłem pomyśleć o zrobieniu tego tutaj. Wiem, że niektórzy ludzie krzywo patrzą na styl kodowania na SO, ale zobaczenie kilku różnych stylów odpowiedzi tak szybko było dla mnie bardzo owocne.
aaron,
1
Łącząc kilka pomysłów w tym pytaniu, uważam, że jest to najbardziej zwięzła wersja, która działa zarówno z q1(symbolem), jak i "q1"(ciągiem):df %>% mutate_at( vars(!!ensym(question)), recode, A = NA_character_)
Artem Sokolov,

Odpowiedzi:

6

Tutaj, po prawej stronie :=, możemy określić symkonwersję na symbol, a następnie ocenę ( !!)

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    

Lepszym podejściem, które sprawdzałoby się zarówno w przypadku cytowania, jak i cytowania, jest 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
źródło
2
Próbowałem łączyć z kilkoma funkcjami konwersji rlang, ale oczywiście nie wybrałem tej właściwej, ale twoje podejście działa - myślę, że naprawdę potrzebuję po prostu przerobić konwersje typu w mojej głowie. Moje pytanie !! nie działa, ponieważ dosłownie ocenia ciąg znaków. Twój działa, ponieważ najpierw konwertuje ciąg znaków na symbol, a następnie ocenia symbol, zwracając wektor. Po prostu nie mogłem owinąć głowy, że taka była kolejność operacji. Dzięki jeszcze raz.
aaron
8

Możesz teraz użyć metody „curly curly”, jeśli masz rlang> = 0.4.0 .

Wyjaśnienie dzięki @ eipi10:

Łączy to dwuetapowy proces cytowania, a następnie wycofywania w jednym kroku, więc {{question}}jest to równoważne z!!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    

Zauważ, że w przeciwieństwie do ensympodejścia, nie działa to z nazwami postaci. Co gorsza, robi coś złego zamiast po prostu dawać błąd.

fix_question(sample_df, 'q1')

# # A tibble: 3 x 2
#   q1    q2   
#   <chr> <chr>
# 1 q1    B    
# 2 q1    B    
# 3 q1    A    
IceCreamToucan
źródło
2
Nie przyzwyczaiłem się jeszcze do „kręconego, kręconego” nawyku. Czy wiesz, dlaczego to działa, podczas gdy pozornie identyczna wersja „bang bang” OP nie?
camille
Dzięki za wzmiankę o curly-curly, o której słyszałem, że się zbliża. Odpowiedź nie działa dla żadnej zainstalowanej wersji rlang / dplyr; Wystąpił błąd z LHS. Jeśli zastąpię LHS moim LHS i zacytuję q1, otrzymam ten sam problem, który miałem powyżej; jeśli nie cytuję q1, pojawia się błąd. Jest to prawdopodobnie kwestia wersji.
aaron
1
Tak, rlang 0.4.0 został właśnie wydany pod koniec czerwca, więc jeśli nie zaktualizujesz go od tego czasu, nie zadziała to dla ciebie
IceCreamToucan
2
Myślę, że bang-bang nie zadziałał, ponieważ questionnajpierw musi zostać zamieniony w quosure ( question = enquo(question)), zanim zostanie użyty w rurze dplyr. {{question}}jest równoważne z !!enquo(question).
eipi10
2
Potrzebne jest także enquo dla pierwszej instancji pytania, aby było to równoważne.
IceCreamToucan
7

Możesz uczynić tę funkcję nieco bardziej elastyczną, pozwalając również na wprowadzenie wektora przekodowanych wartości jako argumentu. Na przykład:

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

Zauważ, że recode.vecjest to „bez cudzysłowu” !!!. Możesz zobaczyć, co to robi z tym przykładem, zaadaptowanym z programowania z winietą dplyr (wyszukaj „splice”, aby zobaczyć odpowiednie przykłady). Zwróć uwagę, jak !!!„splata” pary wartości przekodowywania do recodefunkcji, aby były one używane jako ...argument w 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

Jeśli chcesz potencjalnie uruchomić funkcję przekodowywania na wielu kolumnach, możesz przekształcić ją w funkcję, która pobiera tylko nazwę kolumny i wektor przekodowywania. Takie podejście wydaje się być bardziej przyjazne dla rur.

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>

Lub, aby przekodować jedną kolumnę:

sample_df %>% 
  mutate(q1 = fix_question(q1, c(A=NA_character_, B="Was B")))
eipi10
źródło