Uso di una valutazione non standard basata su tidyeval nella ricodifica nella parte destra del mutato


13

Prendi in considerazione una tabella in cui ogni colonna è un vettore di caratteri che può assumere molti valori - diciamo da "A" a "F".

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

Vorrei creare una funzione che prende il nome di una colonna come argomento e ricodifica quella colonna in modo tale che qualsiasi risposta "A" diventi una NA e il df venga altrimenti restituito così com'è. Il motivo per progettarlo in questo modo è adattarsi a una pipeline più ampia che esegue una serie di operazioni utilizzando una determinata colonna.

Ci sono molti modi per farlo. Ma sono interessato a capire quale sarebbe il miglior approccio idiomatico tidy_eval / tidyverse. Innanzitutto, il nome della domanda deve essere sul lato sinistro di un verbo mutato, quindi utilizziamo gli operatori !!e in modo :=appropriato. Ma allora, cosa mettere sul lato destro?

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

Il mio pensiero iniziale era che avrebbe funzionato:

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

Ma ovviamente il bang-bang all'interno della funzione restituisce semplicemente la stringa di caratteri letterali (ad esempio "q1"). Ho finito per prendere quello che sembra un percorso caotico per fare riferimento ai dati sul lato destro, usando l' [[operatore R di base e .basandomi sul costrutto da dplyr, e funziona, quindi in un certo senso ho risolto il mio problema di fondo:

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

Sono interessato a ricevere feedback da persone che sono molto brave in ordine sul fatto che esista un modo più idiomatico per farlo, nella speranza che vedere un esempio funzionante migliorerebbe la mia comprensione della funzione di ordine generale più in generale. qualche idea?


Grazie, questo è un approccio intelligente: uso l'approccio funzionale in altre parti del mio codice e avrei potuto pensare di farlo anche qui. So che alcune persone aggrottano le sopracciglia su conversazioni in stile codice su SO, ma vedere alcuni stili di risposta così rapidamente è stato molto fruttuoso per me.
Aaron,

1
Combinando diverse idee in questa domanda, credo che questa sia la versione più succinta che funziona con q1(simbolo) e "q1"(stringa):df %>% mutate_at( vars(!!ensym(question)), recode, A = NA_character_)
Artem Sokolov,

Risposte:


6

Qui, sul lato destro di :=, possiamo specificare symdi convertire in simbolo e quindi valutare ( !!)

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    

È un approccio migliore che funzionerebbe sia per l'input quotato che per quello non quotato 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
Avevo provato a mettere in giro alcune delle funzioni di conversione di rlang, ma ovviamente non ho scelto quella giusta, ma il tuo approccio funziona - penso davvero che ho solo bisogno di fluire le conversioni di tipo nella mia testa. La mia domanda !! non funziona perché valuta letteralmente una stringa di caratteri. Il tuo funziona perché prima converte la stringa di caratteri in un simbolo, quindi valuta il simbolo, restituendo il vettore. Non potevo semplicemente avvolgere la testa che quello era l'ordine delle operazioni. Grazie ancora.
Aaron,

8

Ora puoi usare il metodo "ricci ricci" se hai rlang> = 0.4.0 .

Spiegazione grazie a @ eipi10:

Questo combina il processo in due fasi di quote-quindi-non quotate in una fase, quindi {{question}}equivale a!!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    

Nota che a differenza ensymdell'approccio, questo non funziona con i nomi dei personaggi. Ancora peggio, fa la cosa sbagliata invece di dare solo un errore.

fix_question(sample_df, 'q1')

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

2
Non ho ancora preso l'abitudine "ricci ricci". Sai perché funziona, mentre la versione "bang bang" apparentemente identica dell'OP non lo ha fatto?
Camille,

Grazie per aver parlato di ricci e ricci, che avevo sentito dire che stava arrivando. La risposta non funziona per qualunque versione di rlang / dplyr abbia installato; Ottengo un errore con l'LHS. Se sostituisco LHS con il mio LHS e cito q1, ottengo lo stesso problema che ho avuto sopra; se non cito q1, ricevo un errore. Questa è probabilmente una versione della versione.
Aaron,

1
Sì rlang 0.4.0 è stato appena rilasciato alla fine di giugno, quindi se non l'hai aggiornato da allora, questo non funzionerà per te
IceCreamToucan,

2
Penso che il bang-bang non abbia funzionato perché questionprima deve essere trasformato in un quosure ( question = enquo(question)) prima di essere utilizzato nella pipe dplyr. {{question}}è equivalente a !!enquo(question).
eipi10

2
È necessario enquo anche per la prima istanza della domanda affinché ciò sia equivalente.
IceCreamToucan

7

È possibile rendere la funzione un po 'più flessibile consentendo di inserire anche un argomento di valori ricodificati come argomento. Per esempio:

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

Si noti che recode.vecè "non quotato" con!!! . Puoi vedere cosa sta facendo questo esempio, adattato dalla Programmazione con vignetta dplyr (cerca "splice" per vedere gli esempi pertinenti). Nota come !!!"giunge" le coppie di valori di ricodifica nella recodefunzione in modo che vengano utilizzati come ...argomento in 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

Se si desidera eseguire potenzialmente la funzione di ricodifica su più colonne, è possibile trasformarla in una funzione che accetta solo un nome di colonna e un vettore di ricodifica. Questo approccio sembra essere più amichevole.

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>

O per ricodificare una singola colonna:

sample_df %>% 
  mutate(q1 = fix_question(q1, c(A=NA_character_, B="Was B")))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.