A.3 O operador %>%
e o encadeamento de ações
Notem que nada do que vimos até aqui parece ser muito relevante se comparamos com o que pode ser feito com o pacote base
do R. Vejamos:
# Queremos selecionar colunas? Operadores `$` e `[[` dao conta
head(
which(names(iris) == "Species")],
iris[, 10
)
## [1] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
## Levels: setosa versicolor virginica
head(
"Sepal.Length"],
iris[, 10
)
## [1] 5.1 4.9 4.7 4.6 5.0 5.4 4.6 5.0 4.4 4.9
# Filtrar linhas? Vetores lógicos em conjunto com o operador `[[` em um data.frame resolvem o problema
head(
$Species == "virginica" & iris$Sepal.Length > 7, ],
iris[iris10
)
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species | |
---|---|---|---|---|---|
103 | 7.1 | 3.0 | 5.9 | 2.1 | virginica |
106 | 7.6 | 3.0 | 6.6 | 2.1 | virginica |
108 | 7.3 | 2.9 | 6.3 | 1.8 | virginica |
110 | 7.2 | 3.6 | 6.1 | 2.5 | virginica |
118 | 7.7 | 3.8 | 6.7 | 2.2 | virginica |
119 | 7.7 | 2.6 | 6.9 | 2.3 | virginica |
123 | 7.7 | 2.8 | 6.7 | 2.0 | virginica |
126 | 7.2 | 3.2 | 6.0 | 1.8 | virginica |
130 | 7.2 | 3.0 | 5.8 | 1.6 | virginica |
131 | 7.4 | 2.8 | 6.1 | 1.9 | virginica |
# Ou podemos filtrar também usando a função `subset`:
head(
subset(iris, Species == "virginica" & iris$Sepal.Length > 7),
10
)
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species | |
---|---|---|---|---|---|
103 | 7.1 | 3.0 | 5.9 | 2.1 | virginica |
106 | 7.6 | 3.0 | 6.6 | 2.1 | virginica |
108 | 7.3 | 2.9 | 6.3 | 1.8 | virginica |
110 | 7.2 | 3.6 | 6.1 | 2.5 | virginica |
118 | 7.7 | 3.8 | 6.7 | 2.2 | virginica |
119 | 7.7 | 2.6 | 6.9 | 2.3 | virginica |
123 | 7.7 | 2.8 | 6.7 | 2.0 | virginica |
126 | 7.2 | 3.2 | 6.0 | 1.8 | virginica |
130 | 7.2 | 3.0 | 5.8 | 1.6 | virginica |
131 | 7.4 | 2.8 | 6.1 | 1.9 | virginica |
# Criar novas colunas? Podemos atribuir novas colunas a qualquer data.frame existente usando o operador `$` para criar uma nova coluna qualquer
<- iris
iris_novo $razaopetsep <- iris_novo$Petal.Length / iris_novo$Sepal.Length
iris_novohead(
iris_novo,10
)
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species | razaopetsep |
---|---|---|---|---|---|
5.1 | 3.5 | 1.4 | 0.2 | setosa | 0.2745098 |
4.9 | 3.0 | 1.4 | 0.2 | setosa | 0.2857143 |
4.7 | 3.2 | 1.3 | 0.2 | setosa | 0.2765957 |
4.6 | 3.1 | 1.5 | 0.2 | setosa | 0.3260870 |
5.0 | 3.6 | 1.4 | 0.2 | setosa | 0.2800000 |
5.4 | 3.9 | 1.7 | 0.4 | setosa | 0.3148148 |
4.6 | 3.4 | 1.4 | 0.3 | setosa | 0.3043478 |
5.0 | 3.4 | 1.5 | 0.2 | setosa | 0.3000000 |
4.4 | 2.9 | 1.4 | 0.2 | setosa | 0.3181818 |
4.9 | 3.1 | 1.5 | 0.1 | setosa | 0.3061224 |
# Sumariar resultados - Aqui temos um pouco mais de trabalho, porem nada muito complexo
<- as.data.frame(table(iris$Species))
iris_count names(iris_count) <- c("Species", "N")
<- cbind(iris_count, sepala_c_media = tapply(iris$Sepal.Length, iris$Species, "mean"), sepala_l_media = tapply(iris$Sepal.Width, iris$Species, "mean"), petala_c_media = tapply(iris$Petal.Length, iris$Species, "mean"), petala_l_media = tapply(iris$Petal.Width, iris$Species, "mean"))
iris_sumario2 head(
iris_sumario2,10
# comparem o resultado do objeto `iris_sumario2` com os de `iris_sumario` criado com as funcoes do pacote `dplyr` )
Species | N | sepala_c_media | sepala_l_media | petala_c_media | petala_l_media | |
---|---|---|---|---|---|---|
setosa | setosa | 50 | 5.006 | 3.428 | 1.462 | 0.246 |
versicolor | versicolor | 50 | 5.936 | 2.770 | 4.260 | 1.326 |
virginica | virginica | 50 | 6.588 | 2.974 | 5.552 | 2.026 |
O operador %>%
foi introduzido no R por meio do pacote magrittr
, de autoria de Stefan Milton Bache, com o intuito de encadear ações na manipulação de data.frames e facilitar a leitura do código.
Segundo palavras do próprio autor, o operador %>%
modifica semanticamente o código em R e o torna mais intuitivo tanto na escrita quanto na leitura.
Será? Vamos tentar entender isso na prática.
Vamos retomar os exemplos acima com a introdução do operador %>%
e usá-lo para efetuar dois conjuntos de comandos, expostos abaixo:
A.3.1 Conjunto de comandos 1
# Chamar o data.frame `iris`, então...
# Selecionar as colunas `Species`, `Petal.Length`, e `Sepal.Length`, então ...
# Agrupar os dados em função de `Species`, então ...
# Sumariar os dados para obter o número de observações por grupo, nomeando esta variável como `N`; obter o comprimento médio de pétalas, nomeando esta variável como `petala_l_media`, e o comprimento médio de sépalas, nomeando esta variável como `sepala_l_media`, então ...
# Atribui o resultado dessa operação a um objeto chamado `res1`
A.3.2 Conjunto de comandos 2
# Chamar o data.frame `iris`, então...
# Selecionar as colunas `Species`, `Petal.Length`, e `Sepal.Length`, então ...
# Filtrar os dados para conter apenas a espécie `virginica` e espécimes com comprimento de sépala maior que 7 cm, então ...
# Criar uma nova coluna chamada `razaopetsep` que contenha a razão entre os comprimentos de pétala e sépala, então ...
# Sumariar os dados para obter o número total de observações, nomeando esta variável como `N`; obter o comprimento médio de pétalas, nomeando esta variável como `petala_l_media`, o comprimento médio de sépalas, nomeando esta variável como `sepala_l_media`, e a média do índice da razão entre o comprimento de pétalas e o comprimento de sépalas, nomeando-a como `media_razaopetsep`, então ...
# Atribui o resultado dessa operação a um objeto chamado `res2`
Primeiramente, carreguemos o pacote magrittr
:
library("magrittr")
Executando o conjunto de comandos 1, temos:
# Chamar o data.frame `iris`, então...
<-
res1 %>%
iris # Selecionar as colunas `Species`, `Petal.Length`, e `Sepal.Length`, então ...
select(Species, Petal.Length, Sepal.Length) %>%
# Agrupar os dados em função de `Species`, então ...
group_by(Species) %>%
# Sumariar os dados para obter o número de observações por grupo, nomeando esta variável como `N`; obter o comprimento médio de pétalas, nomeando esta variável como `petala_l_media`, e o comprimento médio de sépalas, nomeando esta variável como `sepala_l_media`
summarise(
N = n(),
petala_l_media = mean(Petal.Length, na.rm = TRUE),
sepala_l_media = mean(Sepal.Length, na.rm = TRUE)
) res1
Species | N | petala_l_media | sepala_l_media |
---|---|---|---|
setosa | 50 | 1.462 | 5.006 |
versicolor | 50 | 4.260 | 5.936 |
virginica | 50 | 5.552 | 6.588 |
Fazendo o mesmo com o conjunto de comandos 2, temos:
# Chamar o data.frame `iris`, então...
<-
res2 %>%
iris # Selecionar as colunas `Species`, `Petal.Length`, e `Sepal.Length`, então ...
select(Species, Petal.Length, Sepal.Length) %>%
# Filtrar os dados para conter apenas a espécie `virginica` e espécimes com comprimento de sépala maior que 7 cm, então ...
filter(Species == "virginica" & Sepal.Length > 7) %>%
# Criar uma nova coluna chamada `razaopetsep` que contenha a razão entre os comprimentos de pétala e sépala, então ...
mutate(
razaopetsep = Petal.Length / Sepal.Length
%>%
) # Sumariar os dados para obter o número total de observações, nomeando esta variável como `N`; obter o comprimento médio de pétalas, nomeando esta variável como `petala_l_media`, o comprimento médio de sépalas, nomeando esta variável como `sepala_l_media`, e a média do índice da razão entre o comprimento de pétalas e o comprimento de sépalas, nomeando-a como `media_razaopetsep`
summarise(
N = n(),
petala_l_media = mean(Petal.Length, na.rm = TRUE),
sepala_l_media = mean(Sepal.Length, na.rm = TRUE)
) res2
N | petala_l_media | sepala_l_media |
---|---|---|
12 | 6.3 | 7.475 |
Notem que o código fica formatado da maneira que funciona nosso pensamento sobre as ações a serem executadas: pegamos os dados, efetuamos transformações, e agregamos os resultados, praticamente da mesma maneira que o código é executado.
Como diz o autor na vinheta de introdução ao operador %>%
, é como uma receita, fácil de ler, fácil de seguir (It’s like a recipe – easy to read, easy to follow!
).
Em conformidade com este entendimento, sugere-se que leiamos o operador %>%
como ENTÃO, implicando em uma passagem do resultado da ação à esquerda para a função à direita.
Por isso, eu fiz questão de incluir em ambos os conjuntos de comandos, 1 e 2, a palavra então...
ao fim de cada sentença.
Um ponto importante que deve ser levado em consideração é que o uso do operador %>%
permite que escondamos o data.frame
de entrada nas funções.
Vejamos na prática para entender.
Suponha que nós queiramos selecionar apenas as colunas Species
e Petal.Length
de iris
. Podemos executar isso de duas maneiras, todas com o mesmo resultado:
# podemos representar iris de três maneiras utilizando o operador `%>%`
%>% select(Species, Petal.Length) # como temos feito ate aqui
iris %>% select(., Species, Petal.Length) # explicitamos que `iris` esta dentro de select por meio do `.` iris
Isso pode ficar mais fácil de entender com outro exemplo. Suponha que tenhamos o vetor meuvetor <- c(1:20)
e queiramos obter o somatório deste vetor. Podemos executar isso de três maneiras utilizando o operador %>%
:
<- c(1:20)
meuvetor %>% sum(.) # representando o vetor na forma de um `.`
meuvetor %>% sum() # deixando a funcao vazia
meuvetor %>% sum() # sem parenteses e sem o `.`. O que????? meuvetor
Todas as maneiras acima executam e geram o mesmo resultado, 210. Essa multiplicidade de maneiras de expor o data.frame (ou o vetor no exemplo acima) é alvo de críticas por parte de alguns estudiosos, devido ao pacote magrittr
não exigir que o argumento seja explícito quando usamos o operador %>%
(vejam uma boa argumentação nesta postagem de John Mount).
Vale ressaltar que poderíamos muito bem encadear todas as ações executadas acima sem o operador %>%
, porém perderíamos a chance de ler o código da esquerda para a direita, oportunidade ofertada pelo uso do operador. Vejamos, usando o conjunto de comandos 2:
summarise(
mutate(
filter(
select(iris, Species, Petal.Length, Sepal.Length),
== "virginica" & Sepal.Length > 7
Species
),razaopetsep = Petal.Length / Sepal.Length
),N = n(),
petala_l_media = mean(Petal.Length, na.rm = TRUE),
sepala_l_media = mean(Sepal.Length, na.rm = TRUE)
)
Reparem que o código fica mais difícil de ser lido, pois temos de identificar primeiro quem é o data.frame
que serve de entrada para a função summarise
.
Depois, há outros desafios, como entender o que cada função faz, e em qual ordem.
Por fim, o código é lido de dentro para fora, um sentido nada intuitivo.
Foi pensando em tornar a leitura do código mais fácil que o autor decidiu criar este operador na linguagem R, uma vez que essa lógica já é extensivamente utilizada em algumas outras linguagens de programação, como F#
(representada como |>
e o bash
(e similares) (representada como |
).