Utilisation d'une évaluation non standard basée sur le rangement dans le recodage du côté droit du muté

13

Considérez un tibble où chaque colonne est un vecteur de caractères qui peut prendre plusieurs valeurs - disons "A" à "F".

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

Je souhaite créer une fonction qui prend un nom de colonne comme argument et recode cette colonne de sorte que toute réponse "A" devienne une NA et que le df soit retourné tel quel. La raison de sa conception de cette manière est de s'intégrer dans un pipeline plus large qui effectue une série d'opérations à l'aide d'une colonne donnée.

Il existe plusieurs façons de procéder. Mais je suis intéressé à comprendre quelle serait la meilleure approche idiomatique tidy_eval / tidyverse. Tout d'abord, le nom de la question doit être à gauche d'un verbe muté, nous utilisons donc les opérateurs !!et de :=manière appropriée. Mais alors, que mettre du côté droit?

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

Ma pensée initiale était que cela fonctionnerait:

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

Mais bien sûr, le bang-bang à l'intérieur de la fonction renvoie juste la chaîne de caractères littérale (par exemple "q1"). J'ai fini par prendre ce qui ressemble à une route hacky pour référencer les données sur le côté droit, en utilisant l' [[opérateur de base R et en s'appuyant sur la .construction de dplyr, et cela fonctionne, donc dans un sens, j'ai résolu mon problème sous-jacent:

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

Je suis intéressé à obtenir des commentaires de personnes très douées pour savoir s'il existe une façon plus idiomatique de le faire, dans l'espoir que voir un exemple concret améliorerait ma compréhension de l'ensemble des fonctions de rangement. Des pensées?

Aaron
la source
Merci, c'est une approche intelligente - j'utilise l'approche fonctionnelle dans d'autres parties de mon code et j'aurais pu penser à le faire ici également. Je sais que certaines personnes désapprouvent les discussions sur le style de code sur SO, mais voir si rapidement différents styles de réponse a été très fructueux pour moi.
Aaron
1
Combinant plusieurs idées dans cette question, je crois que c'est la version la plus succincte qui fonctionne avec q1(symbole) et "q1"(chaîne):df %>% mutate_at( vars(!!ensym(question)), recode, A = NA_character_)
Artem Sokolov

Réponses:

6

Ici, sur le côté droit de :=, nous pouvons spécifier symde convertir en symbole puis d'évaluer ( !!)

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    

Une meilleure approche qui fonctionnerait pour les entrées cotées et non cotées est 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
la source
2
J'avais essayé de tourner autour de quelques-unes des fonctions de conversion rlang mais évidemment je n'ai pas choisi la bonne, mais votre approche fonctionne - je pense vraiment que j'ai juste besoin de workflow les conversions de type dans ma tête. Ma question !! ne fonctionne pas car elle évalue littéralement une chaîne de caractères. Le vôtre fonctionne car il convertit d'abord la chaîne de caractères en symbole, puis évalue le symbole, renvoyant le vecteur. Je ne pouvais tout simplement pas comprendre que c'était l'ordre des opérations. Merci encore.
aaron
8

Vous pouvez utiliser la méthode "curly curly" maintenant si vous avez rlang> = 0.4.0 .

Explication grâce à @ eipi10:

Ceci combine le processus en deux étapes de soumission puis suppression en une seule étape, {{question}}est donc équivalent à!!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    

Notez que contrairement à l' ensymapproche, cela ne fonctionne pas avec les noms de personnages. Pire encore, il fait la mauvaise chose au lieu de simplement donner une erreur.

fix_question(sample_df, 'q1')

# # A tibble: 3 x 2
#   q1    q2   
#   <chr> <chr>
# 1 q1    B    
# 2 q1    B    
# 3 q1    A    
IceCreamToucan
la source
2
Je n'ai pas encore pris l'habitude "bouclés bouclés". Savez-vous pourquoi cela fonctionne, contrairement à la version "bang bang" apparemment identique de l'OP?
camille
Merci d'avoir mentionné bouclé-bouclé, dont j'avais entendu parler était à venir. La réponse ne fonctionne pas pour n'importe quelle version de rlang / dplyr que j'ai installée; Je reçois une erreur avec le LHS. Si je remplace le LHS par mon LHS et que je cite q1, j'obtiens le même problème que j'avais ci-dessus; si je ne cite pas q1, j'obtiens une erreur. C'est peut-être une version.
aaron
1
Oui, rlang 0.4.0 vient de sortir fin juin, donc si vous ne l'avez pas mis à jour depuis, cela ne fonctionnera pas pour vous
IceCreamToucan
2
Je pense que le bang-bang n'a pas fonctionné car il questionfaut d'abord le transformer en une quosure ( question = enquo(question)) avant de l'utiliser dans le pipe dplyr. {{question}}est équivalent à !!enquo(question).
eipi10
2
Vous devez également enquo pour la première instance de question pour que cela soit équivalent.
IceCreamToucan
7

Vous pouvez rendre la fonction un peu plus flexible en permettant à un vecteur de valeurs recodées d'être également entré comme argument. Par exemple:

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

Notez que recode.vecc'est "sans guillemet" avec !!!. Vous pouvez voir ce que cela fait avec cet exemple, adapté de la programmation avec vignette dplyr (recherchez "épissure" pour voir les exemples pertinents). Notez comment !!!«épissure» les paires de valeurs de recodage dans la recodefonction afin qu'elles soient utilisées comme ...argument dans 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

Si vous souhaitez potentiellement exécuter la fonction de recodage sur plusieurs colonnes, vous pouvez la transformer en une fonction qui ne prend qu'un nom de colonne et un vecteur de recodage. Cette approche semble être plus adaptée aux tuyaux.

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>

Ou pour recoder une seule colonne:

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