Programando com o tidyverse

O {dplyr} é um pacote incrível pois permite realizar operações difíceis de forma iterada e intuitiva. Uma grande facilidade do {dplyr} é a possibilidade de utilizar os nomes das variáveis sem encapsular com aspas, o que torna a programação mais fluida e compreensível.

Por exemplo, é fácil argumentar que

mtcars %>% 
  summarise(soma = sum(cyl ^ 2))

é mais intuitivo que

mtcars %>% 
  summarise(cyl = sum(mtcars$cyl ^ 2))

ou ainda

mtcars %>% 
  mutate(cyl = sum(mtcars[["cyl"]] ^ 2))

No entanto, ao usar o {dplyr} com frequência, passamos a querer colocá-lo em todos os contextos possíveis, como novas função ou Shiny apps. Assim, gostaríamos de fazer algo do tipo

minha_fn <- function(dados, variavel) {
  dados %>% 
    summarise(nova_variavel = sum(variavel ^ 2))
}

No entanto, ao experimentar isso, temos o erro

mtcars %>% 
  minha_fn(cyl)
# > Error: object 'cyl' not found 

Você já se deparou com essa situação? É bem frustrante. A verdade é que o tidyverse foi desenvolvido com foco em facilitar o trabalho de análise, com o custo de dificultar o trabalho de programação.

Porém, graças a avanços recentes no pacote {rlang}, isso está ficando mais fácil. Nesse post vou mostrar três casos casos comuns de programação com o tidyverse, e suas soluções. Para casos mais complicados, recomendo dar uma olhada no livro sobre Tidyeval.

Para reproduzir esse post, você precisará ter pelo menos a versão 0.4.0 do {rlang} instalado na sua máquina!

Quero que minha função receba um nome sem aspas

Para isso, podemos usar o quentíssimo operador {{}}, que foi oficialmente apresentado na useR!2019, em Tolouse. Esse operador informa as funções do {dplyr} (e seus amigos {tidyr}, {ggplot2} etc) que olhem para a variável de dentro da base de dados, ao invés de um objeto supostamente passado como argumento da função.

Com isso, a função anterior fica simples assim:

minha_fn_sem_aspas <- function(dados, variavel) {
  dados %>% 
    summarise(nova_variavel = sum({{variavel}} ^ 2))
}

E sua utilização:

mtcars %>% 
  minha_fn_sem_aspas(cyl)
nova_variavel
1324

Zero trauma.

Quero que minha função receba uma string

Para isso, teremos de usar o objeto especial .data. Ele permite que você acesse a informação dos dados antes de aplicar a nova função. É muito similar ao . do pacote {magrittr}, para quem já conhece.

minha_fn_com_aspas <- function(dados, variavel) {
  dados %>% 
    summarise(nova_variavel = sum(.data[[variavel]] ^ 2))
}

E sua utilização:

mtcars %>% 
  minha_fn_com_aspas("cyl")
nova_variavel
1324

Show! Esse provavelmente é o caso da maioria dos Shiny apps, pois acessamos as informações através de input$id_input, que geralmente é uma string.

Observação: uma diferença essencial entre usar .data e . é que o primeiro consegue lidar com grupos, e o segundo não. Por exemplo, esses códigos têm resultados diferentes:

mtcars %>% 
  group_by(cyl) %>% 
  summarise(nova_variavel = sum(.data[["cyl"]] ^ 2))
cyl nova_variavel
4 176
6 252
8 896
mtcars %>% 
  group_by(cyl) %>% 
  summarise(nova_variavel = sum(.[["cyl"]] ^ 2))
cyl nova_variavel
4 1324
6 1324
8 1324

Para a lista completa de diferenças, veja ?rlang::.data.

Quero que minha função crie uma coluna com nome variável

E se você quiser mudar o nome de nova_variavel e incluir isso como argumento da função? Nesse caso, é necessário introduzir o operador :=, e o resto é resolvido com {{}}:

minha_fn_sem_aspas_novo_nome <- function(dados, variavel, nome) {
  dados %>% 
    summarise({{nome}} := sum({{variavel}} ^ 2))
}

E sua utilização:

mtcars %>% 
  minha_fn_sem_aspas_novo_nome(cyl, novo_nome)
novo_nome
1324

Curiosamente, essa solução também funciona com as aspas:

mtcars %>% 
  minha_fn_sem_aspas_novo_nome(cyl, "novo_nome")
novo_nome
1324

Wrap-up

  • O pacote que está por trás da programação com {dplyr} e amigos é o {rlang}.
  • Use {{variavel}} quando não quiser colocar aspas no argumento da função.
  • Use .data[["variavel"]] quando quiser colocar aspas no argumento da função.
  • Use {{nome}} := ... quando quiser criar colunas com nomes que estão no argumento da função.

É isso. Happy coding ;)

comments powered by Disqus