Verwendung einer auf Tidyeval basierenden nicht standardmäßigen Bewertung bei der Rekodierung auf der rechten Seite des Mutats

13

Stellen Sie sich ein Tibble vor, bei dem jede Spalte ein Zeichenvektor ist, der viele Werte annehmen kann - sagen wir "A" bis "F".

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

Ich möchte eine Funktion erstellen, die einen Spaltennamen als Argument verwendet und diese Spalte neu codiert, sodass jede Antwort "A" zu einer NA wird und der df ansonsten unverändert zurückgegeben wird. Der Grund für das Entwerfen auf diese Weise besteht darin, in eine breitere Pipeline zu passen, die eine Reihe von Operationen unter Verwendung einer bestimmten Spalte ausführt.

Es gibt viele Möglichkeiten, dies zu tun. Aber ich bin daran interessiert zu verstehen, was der beste idiomatische tidy_eval / tidyverse-Ansatz wäre. Erstens muss sich der Name der Frage auf der linken Seite eines mutierten Verbs befinden, daher verwenden wir die Operatoren !!und :=entsprechend. Aber was soll man dann auf die rechte Seite stellen?

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

Mein erster Gedanke war, dass dies funktionieren würde:

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

Aber natürlich gibt der Bang-Bang in der Funktion nur die Literalzeichenfolge zurück (z. B. "q1"). Am Ende habe ich einen hackigen Weg eingeschlagen, um auf die Daten auf der rechten Seite zu verweisen, indem ich den Basis-R- [[Operator verwendet und .mich auf das Konstrukt von dplyr verlassen habe. Es funktioniert, also habe ich in gewissem Sinne mein zugrunde liegendes Problem gelöst:

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

Ich bin daran interessiert, Feedback von Leuten zu erhalten, die sehr gut in Tidyeval sind, ob es einen idiomatischeren Weg gibt, dies zu tun, in der Hoffnung, dass ein funktionierendes Beispiel mein Verständnis der Tidyeval-Funktion allgemeiner verbessern würde. Irgendwelche Gedanken?

Aaron
quelle
Vielen Dank, dies ist ein kluger Ansatz - ich verwende den funktionalen Ansatz in anderen Teilen meines Codes und hätte auch hier darüber nachdenken können. Ich weiß, dass einige Leute über Code-Style-Gespräche über SO die Stirn runzeln, aber ein paar verschiedene Antwortstile so schnell zu sehen, war für mich sehr fruchtbar.
Aaron
1
Ich kombiniere mehrere Ideen in dieser Frage und glaube, dass dies die prägnanteste Version ist, die sowohl mit q1(Symbol) als auch mit "q1"(Zeichenfolge) funktioniert :df %>% mutate_at( vars(!!ensym(question)), recode, A = NA_character_)
Artem Sokolov

Antworten:

6

Hier auf der rechten Seite :=können wir angeben sym, dass in ein Symbol konvertiert und dann ausgewertet werden soll ( !!)

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    

Ein besserer Ansatz, der sowohl für zitierte als auch für nicht zitierte Eingaben funktioniert, ist 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
quelle
2
Ich hatte versucht, mit einigen der Rlang-Konvertierungsfunktionen herumzuspielen, aber offensichtlich nicht die richtige ausgewählt, aber Ihr Ansatz funktioniert - ich denke wirklich, ich muss nur die Typkonvertierungen in meinem Kopf abwickeln. Meine !! Frage funktioniert nicht, weil sie eine Zeichenkette buchstäblich auswertet. Ihre funktioniert, weil sie zuerst die Zeichenfolge in ein Symbol konvertiert und dann das Symbol auswertet und den Vektor zurückgibt. Ich konnte einfach nicht meinen Kopf wickeln, dass das die Reihenfolge der Operationen war. Danke noch einmal.
Aaron
8

Sie können jetzt die Methode "Curly Curly" verwenden, wenn Sie rlang> = 0.4.0 haben .

Erklärung dank @ eipi10:

Dies kombiniert den zweistufigen Prozess des Zitierens und Nicht-Zitierens in einem Schritt und {{question}}entspricht damit!!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    

Beachten Sie, dass dies im Gegensatz zum ensymAnsatz bei Zeichennamen nicht funktioniert. Schlimmer noch, es macht das Falsche, anstatt nur einen Fehler zu machen.

fix_question(sample_df, 'q1')

# # A tibble: 3 x 2
#   q1    q2   
#   <chr> <chr>
# 1 q1    B    
# 2 q1    B    
# 3 q1    A    
IceCreamToucan
quelle
2
Ich habe mich noch nicht an die "lockige lockige" Gewohnheit gewöhnt. Wissen Sie, warum dies funktioniert, während die scheinbar identische "Bang Bang" -Version des OP dies nicht tat?
Camille
Vielen Dank, dass Sie Curly-Curly erwähnt haben, von dem ich gehört hatte, dass es bevorsteht. Die Antwort funktioniert nicht für die von mir installierte Version von rlang / dplyr. Ich erhalte einen Fehler mit der LHS. Wenn ich die LHS durch meine LHS ersetze und q1 zitiere, erhalte ich das gleiche Problem wie oben. Wenn ich q1 nicht zitiere, erhalte ich eine Fehlermeldung. Dies ist möglicherweise eine Versionssache.
Aaron
1
Ja, rlang 0.4.0 wurde erst Ende Juni veröffentlicht. Wenn Sie es seitdem nicht mehr aktualisiert haben, funktioniert dies nicht für Sie
IceCreamToucan
2
Ich denke, der Bang-Bang hat nicht funktioniert, weil er questionzuerst in ein Quosure ( question = enquo(question)) umgewandelt werden muss, bevor er in der Dplyr-Pipe verwendet wird. {{question}}ist äquivalent zu !!enquo(question).
Eipi10
2
Sie benötigen enquo auch für die erste Instanz der Frage, damit dies gleichwertig ist.
IceCreamToucan
7

Sie können die Funktion etwas flexibler gestalten, indem Sie auch die Eingabe eines Vektors mit neu codierten Werten als Argument zulassen. Zum Beispiel:

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

Beachten Sie, dass recode.vecmit "unquote-gespleißt" ist !!!. Sie können sehen, was dies mit diesem Beispiel tut, angepasst an die Programmierung mit dplyr-Vignette (suchen Sie nach "Spleiß", um die relevanten Beispiele zu sehen). Beachten Sie, wie !!!die Paare von Rekodierungswerten in die recodeFunktion "gespleißt" werden, damit sie als ...Argument in verwendet werden 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

Wenn Sie die Rekodierungsfunktion möglicherweise für mehrere Spalten ausführen möchten, können Sie sie in eine Funktion umwandeln, die nur einen Spaltennamen und einen Rekodierungsvektor verwendet. Dieser Ansatz scheint pfeifenfreundlicher zu sein.

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>

Oder um eine einzelne Spalte neu zu codieren:

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