Uso de avaliação não padronizada baseada em arrumação na recodificação no lado direito do mutate

13

Considere uma tag em que cada coluna é um vetor de caracteres que pode assumir muitos valores - digamos "A" a "F".

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

Desejo criar uma função que tome o nome de uma coluna como argumento e recodifique essa coluna para que qualquer resposta "A" se torne um NA e o df seja retornado como está. O motivo para projetá-lo dessa maneira é se encaixar em um pipeline mais amplo que executa uma série de operações usando uma determinada coluna.

Existem diversas formas de fazer isto. Mas estou interessado em entender qual seria a melhor abordagem idiomática tidy_eval / tidyverse. Primeiro, o nome da pergunta precisa estar no lado esquerdo de um verbo mutado; portanto, usamos os operadores !!e :=adequadamente. Mas então, o que colocar no lado direito?

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

Meu pensamento inicial era que isso funcionaria:

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

Mas é claro que o bang-bang dentro da função apenas retorna a cadeia de caracteres literal (por exemplo, "q1"). Acabei pegando o que parece uma rota hacky para referenciar os dados no lado direito, usando o [[operador R base e confiando na .construção do dplyr, e funciona, então, em certo sentido, resolvi meu problema subjacente:

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

Estou interessado em receber feedback de pessoas que são muito boas em termos de arrumação quanto à existência de uma maneira mais idiomática de fazer isso, na esperança de que ver um exemplo funcionado melhore minha compreensão da função arrumada de maneira mais geral. Alguma ideia?

Aaron
fonte
Obrigado, essa é uma abordagem inteligente - eu uso a abordagem funcional em outras partes do meu código e poderia ter pensado em fazê-lo aqui também. Eu sei que algumas pessoas desaprovam o estilo do código, falam sobre SO, mas ver alguns estilos diferentes de resposta tão rapidamente foi muito proveitoso para mim.
aaron
11
Combinando várias ideias nesta questão, creio que esta é a versão sucinta mais que funciona tanto com q1(símbolo) e "q1"(string):df %>% mutate_at( vars(!!ensym(question)), recode, A = NA_character_)
Artem Sokolov

Respostas:

6

Aqui, no lado direito :=, podemos especificar sympara converter em símbolo e depois avaliar ( !!)

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    

Uma abordagem melhor que funcionaria para as entradas citadas e não citadas é 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
fonte
2
Eu tentei mexer com algumas das funções de conversão do rlang, mas obviamente não escolhi a correta, mas sua abordagem funciona - acho que realmente preciso trabalhar com as conversões de tipo na minha cabeça. Minha pergunta !! não funciona porque avalia literalmente uma cadeia de caracteres. O seu funciona porque primeiro converte a cadeia de caracteres em um símbolo e depois avalia o símbolo, retornando o vetor. Eu simplesmente não conseguia entender que essa era a ordem das operações. Obrigado novamente.
Aaron
8

Você pode usar o método "curly curly" agora se tiver rlang> = 0.4.0 .

Explicação graças a @ eipi10:

Isso combina o processo de duas etapas de cotação e cotação em uma etapa, portanto, {{question}}é equivalente 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    

Observe que, diferentemente da ensymabordagem, isso não funciona com nomes de personagens. Pior ainda, faz a coisa errada, em vez de apenas dar um erro.

fix_question(sample_df, 'q1')

# # A tibble: 3 x 2
#   q1    q2   
#   <chr> <chr>
# 1 q1    B    
# 2 q1    B    
# 3 q1    A    
IceCreamToucan
fonte
2
Ainda não adotei o hábito "encaracolado encaracolado". Você sabe por que isso funciona, enquanto a versão aparentemente idêntica do "bang bang" do OP não?
Camille
Obrigado por mencionar curly-curly, que eu tinha ouvido falar que estava por vir. A resposta não funciona para qualquer versão do rlang / dplyr que eu instalei; Eu recebo um erro com o LHS. Se eu substituir o LHS pelo meu LHS e citar q1, obtenho o mesmo problema que tive acima; se eu não citar o q1, recebo um erro. Isso é possivelmente uma versão.
Aaron
11
Sim rlang 0.4.0 foi liberada apenas no final de Junho por isso, se você não tiver atualizado desde então isso não vai funcionar para você
IceCreamToucan
2
Acho que o bang-bang não funcionou porque questionprimeiro precisa ser transformado em um quosure ( question = enquo(question)) antes de ser usado no dplyr pipe. {{question}}é equivalente a !!enquo(question).
Eipi10
2
Você também precisa da primeira instância da pergunta para que isso seja equivalente.
IceCreamToucan
7

Você pode tornar a função um pouco mais flexível, permitindo que um vetor de valores recodificados também seja inserido como argumento. Por exemplo:

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

Observe que recode.vecé "sem aspas" com !!!. Você pode ver o que isso está fazendo neste exemplo, adaptado da vinheta Programação com dplyr (procure por "emenda" para ver os exemplos relevantes). Note como !!!"emendas" os pares de recodificação valores para a recodefunção de modo que eles são usados como ...argumento em 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 você deseja executar potencialmente a função de recodificação em várias colunas, pode transformá-la em uma função que leva apenas um nome de coluna e um vetor de recodificação. Essa abordagem parece que seria mais fácil de usar.

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 para recodificar uma única coluna:

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