7 Pipe
Dificilmente alguém que programa R passa muito tempo sem ouvir falar do operador
pipe (%>%
). Saber o que ele faz e significa, no entanto, é algo mais complexo.
Apesar de não passar de uma função como outra qualquer, os efeitos que ele pode
ter no visual e na compreensibilidade de um código são imensos.
Entender o pipe em profundidade pode levar muito tempo, mas o básico já é suficiente para a maioria das pessoas. É importante ter pelo menos uma ideia do que ele faz caso você acabe se deparando com um código que o utiliza e, quem sabe um dia, usá-lo nos seus próprios programas.
No fundo, o conceito de pipe existe pelo menos desde os anos 1970. De acordo com seu criador, Douglas McIlroy, o operador foi concebido em “uma noite febril” e tinha o objetivo de simplificar comandos cujos resultados deveriam ser passados para outros comandos.
ls | cat
# Desktop
# Documents
# Downloads
# Music
# Pictures
# Public
# Templates
# Videos
Com esse exemplo já é possível ter uma ideia de onde vem o seu nome: pipe em inglês significa “cano”, referindo-se ao transporte das saídas dos comandos. Em português o termo é traduzido preferencialmente como “encadeamento”, mas no dia-a-dia é mais comum usar o termo em inglês.
A partir daí o pipe tem aparecido nas mais diversas aplicações, desde HTML até o próprio R. Ele pode aparecer em diferentes formas, mas o seu objetivo é sempre o mesmo: canalizar resultados de um comando para o outro.
7.1 Como funciona
Em R, o pipe tem uma aparência bastante particular (%>%
), mas no fundo ele não
passa de uma função infixa, ou seja, uma função que aparece entre os seus
argumentos (como a + b
ou a %in% b
). Na verdade é por isso mesmo que ele tem
porcentagens antes e depois: no R uma função infixa só pode ser declarada assim
(vide o próprio %in%()
).
Se você estiver no RStudio, como sugerido anteriormente, para usar o pipe basta
carregar a biblioteca magrittr
e utilizar o atalho Ctrl + Shift + M; essas
inocentes teclas irão fazê-lo aparecer magicamente diante dos seus olhos. Abaixo
é possível notar como o pipe não passa de uma função como outra qualquer (ignore
os acentos graves):
library(magrittr)
`%>%`("oi", print)
#> [1] "oi"
Perceba que, no código acima, o primeiro argumento do pipe ("oi"
) virou a
entrada do seu segundo argumento (a função print()
). Abaixo o pipe está na sua
forma mais tradicional entre seus dois argumentos:
"oi" %>% print()
#> [1] "oi"
Observe agora o comando abaixo. Queremos primeiro somar 3 a uma sequência de números e depois dividí-los por 2:
<- function(x) { x + 3 }
mais_tres <- function(x) { x / 2 }
sobre_dois
<- 1:3
x
sobre_dois(mais_tres(x))
#> [1] 2.0 2.5 3.0
Perceba como fica difícil de entender o que acontece primeiro. A linha relevante começa com a divisão por 2, depois vem a soma com 3 e, ao fim, os valores de entrada. É exatamente o oposto da nossa ordem de leitura da esquerda para a direita.
Nesse tipo de situação é mais legível usar a notação de composição de funções, com as funções sendo exibidas na ordem em que serão aplicadas: \(f \circ g\). Não é necessário conhecer essa notação, basta imaginar quão mais legível ficaria aquele comando se houvesse algum recurso que passasse o que o resultado do que está à sua esquerda para a função que está à sua direita. Esse é o pipe.
%>% mais_tres() %>% sobre_dois()
x #> [1] 2.0 2.5 3.0
No comando acima fica evidente que pegamos o objeto x
, somamos 3 e dividimos
por 2.
Perceba que a entrada de um pipe (esquerda) sempre é passada como o primeiro argumento da função à sua direita. Isso não impede que as funções utilizadas em uma sequência de pipes tenham outros argumentos.
<- function(x, n) { x + n }
mais_n
%>% mais_n(4) %>% sobre_dois()
x #> [1] 2.5 3.0 3.5
7.2 Vantagens
A grande vantagem do pipe não é só enxergar quais funções são aplicadas primeiro, mas sim nos ajudar a programar pipelines (“encanamento” em inglês) de tratamentos de dados.
library(dplyr)
%>%
starwars mutate(bmi = mass/((height/100)^2)) %>%
select(name, bmi, species) %>%
group_by(species) %>%
summarise(bmi = mean(bmi))
#> # A tibble: 38 x 2
#> species bmi
#> <chr> <dbl>
#> 1 Aleena 24.0
#> 2 Besalisk 26.0
#> 3 Cerean 20.9
#> 4 Chagrian NA
#> 5 Clawdite 19.5
#> 6 Droid NA
#> 7 Dug 31.9
#> 8 Ewok 25.8
#> 9 Geonosian 23.9
#> 10 Gungan NA
#> # ... with 28 more rows
Acima fica extremamente claro o que está acontecendo em cada passo da pipeline.
Partindo da base starwars
, primeiro transformamos, depois selecionamos,
agrupamos e resumimos: em cada linha temos uma operação e elas são executadas
em sequência.
Isso não melhora só a legibilidade do código, mas também a sua debugabilidade4. Se tivermos encontrado um bug na pipeline, basta executar o encadeamento linha a linha até que encontremos o comando problemático. Com o pipe é possível programar de forma mais compacta, legível e correta.
Todos os exemplos acima envolvem passar a entrada do pipe como o primeiro
argumento da função à direita, mas esta não é uma obrigatoriedade. Com o
operador placeholder .
podemos indicar exatamente onde deve ser colocado o
valor que chega no pipe:
<- function(x, y) { y - x }
y_menos_x
%>%
x mais_tres() %>%
::map2(4:6, ., y_menos_x)
purrr# [[1]]
# [1] 0
#
# [[2]]
# [1] 0
#
# [[3]]
# [1] 0
Outra funcionalidade interessante e pouco conhecida do pipe são as funções
unárias. Se você estiver familiarizado com o pacote
purrr
, esse é um jeito bastante
simples de criar funções descartáveis.
<- . %>%
m3_s2 mais_tres() %>%
sobre_dois()
m3_s2(x)
#> [1] 2.0 2.5 3.0
Usando novamente o .
definimos uma função que recebe apenas um argumento com
uma sequência de aplicações de outras funções. Mas não se preocupe se as funções
unárias tiverem parecido algo de outro mundo porque é realmente muito raro
encontrá-las nos código alheios.
Por fim, caso você queira utilizar o pipe dentro do seu próprio pacote, basta
executar uma simples função do usethis
: use_pipe()
. Ela adiciona o
magrittr
como uma dependência do seu pacote e faz com que o pipe seja
acessível dentro do mesmo, o que realmente facilita o trabalho de quem não quer entender o significado do código a seguir:
#' Pipe operator
#'
#' See \code{magrittr::\link[magrittr:pipe]{\%>\%}} for details.
#'
#' @name %>%
#' @rdname pipe
#' @keywords internal
#' @export
#' @importFrom magrittr %>%
#' @usage lhs \%>\% rhs
NULL
Se você não estiver trabalhando em um pacote, mas também não quer carregar o
pacote com library()
, há duas formas de trazer o pipe para os seus scripts:
puxando ele como uma função ou usando o argumento include.only
da library()
.
# Puxando como função
`%>%` <- magrittr::`%>%`
# Usando include.only
library(magrittr, include.only = "%>%")
Ainda não encontrei um termo melhor em português para esse conceito↩︎