Apostila Unidade 4 - Curso R

Fazer download em pdf ou txt
Fazer download em pdf ou txt
Você está na página 1de 32

Capítulo 4

Aula 4 ­ Transformando dados com dplyr

Pedro Duarte Faria


Fundação João Pinheiro

129
130

4.1 Transformando dados com dplyr


Depois de você importar os seus dados para dentro do R, vamos começar agora a trabalhar com
eles. Adicionando colunas, filtrando linhas, ordenando a base segundo uma variável, adicionando
novas colunas à uma tabela (que geralmente estão estruturadas no R, como um data.frame), etc.
Vamos ver várias funções do pacote dplyr nesta aula, que forem feitas para estas tarefas.

library(dplyr)
library(tidyverse)

Logo, você precisa chamar pelo pacote com a função library() para ter acesso a estas funções.
Dentre os vários pacotes tragos pelo tidyverse, está o dplyr. Portanto, você também pode acessar
as funções descritas aqui, ao chamar pelo pacote tidyverse. Mas antes de entrarmos nas funções
do pacote dplyr, vou introduzí­lo ao pipe.

4.2 Operador Pipe


O operador pipe (%>%) vem do pacote magrittr, e o seu objetivo é fazer com que você escreva um
código mais suscinto e claro, de forma que fique fácil de se ler e fácil de se entender a intenção de
seu código.
Isso é muito importante, especialmente no futuro em que você terá que voltar a esse código, seja
porque você encontrou um erro no resultado final e está procurando em qual local desse código este
erro foi gerado, ou porque você está compartilhando o código com um novo integrante do grupo de
trabalho, para que ele possa compreender qual foi o seu processo de análise, e o que vocês fizeram
com os dados.
Lembre­se que independentemente se você está ou não trabalhando com outras pessoas em um
projeto, você sempre está trabalhando com o seu “futuro eu”. Quase sempre você tem que voltar
ao trabalho que você realizou no passado. Por isso, usar ferramentas que facilitem a compreensão
do seu “futuro eu” sobre o que você fez no passado, quais foram as decisões e as etapas que você
tomou para chegar ao resultado desejado, é muito importante.

4.2.0.1 Como é trabalhar sem pipes ?

Quando você está aplicando várias etapas e transformações em sequência sobre os seus dados, caso
você não utilizar o operador pipe (%>%) você tem basicamente duas opções: salvar os resultados
intermediários de suas operações em objetos intermediários; ou agrupar as funções de cada etapa
em uma só.

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
131

Vamos começar com a primeira opção, ao salvar os resultados intermediários. Vamos supor que
você esteja pegando os dados da base mpg, que contém dados sobre o consumo de vários modelos
de carro. O seu objetivo é calcular a média de quilômetros por litro (hwy) de cada classe (class) de
carro.
Para isso, você tem que: primeiro, agrupar a base por cada classe de carro (com a função
group_by()), e depois, utilizar a função summarise() para calcular a média de cada grupo.
Suponha que você queira ainda ordenar (com a função arrange()) essa tabela gerada, para que
você veja rapidamente quais são as 10 maiores médias.
Temos então 3 etapas diferentes. Porém, sem o uso do pipe, para realizarmos cada uma delas temos
que salvar o resultado da etapa anterior em algum objeto, antes de passarmos para a próxima. Logo,
o código ficaria dessa maneira:

dados_mpg <- mpg


agrupamento <- group_by(.data = dados_mpg, class)
tabela_final <- summarise(
.data = agrupamento,
Media = mean(hwy)
)

## `summarise()` ungrouping output (override with `.groups` argument)

arrange(tabela_final, desc(Media))

## # A tibble: 7 x 2
## class Media
## <chr> <dbl>
## 1 compact 28.3
## 2 subcompact 28.1
## 3 midsize 27.3
## 4 2seater 24.8
## 5 minivan 22.4
## 6 suv 18.1
## 7 pickup 16.9

Essa alternativa, gera um código claro, onde as etapas estão separadas umas das outras, e com isso,
fica fácil de se entender o que cada uma delas faz. Porém, o código é “verboso” (ou seja, você
escreve mais do que o necessário para realizar uma mesma ação), além de que você está gastando
desnecessariamente a memória do seu computador. Com o uso do pipe você não precisa salvar os
resultados intermediários (logo, você está economizando memória), além de aumentar ainda mais
a clareza de seu código.

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
132

Agora, vamos para a segunda alternativa, que é agrupar as etapas em uma função só. Essa alternativa
é o seu pior cenário, pois veja o código abaixo, e tente entender o que exatamente ele faz.

arrange(
summarise(
.data = group_by(
.data = mpg,
class
),
Media = mean(hwy)
),
desc(Media)
)

Depois de uns minutos olhando para ele, você talvez tenha identificado que ele faz exatamente o
mesmo trabalho do código anterior em que utilizamos a primeira alternativa. Entretanto, caso você
não tenha entendido o que este código faz, você com certeza percebeu que ele está muito mais
confuso, e muito mais difícil de se ler.
Por isso essa segunda alternativa é ruim. Ela evita que você salve resultados intermediários, pois
nessas situações, o R irá se preocupar em calcular uma função de cada vez, começando pela função
mais “interna”, indo em direção a função mais “externa”. Mas essa alternativa gera um código
muito difícil de se ler, pois você tem que lê­lo de “dentro para fora”.
Ou seja, você primeiro deve encontrar a função mais “interna” dessa cadeia de código. No caso
acima, é a função group_by(), que está pegando a base mpg, agrupando pela variável class. O
resultado group_by() vai para dentro da função summarise(), que calcula a média de cada grupo
da variável class. Em seguida, o resultado de summarise() é ordenado por arrange().

4.2.0.2 O que o pipe faz ?

O operador %>% funciona como uma “ponte”. Ele pega o resultado da função que está antes dele, e
insere esse resultado como o primeiro argumento da próxima função. Veja o exemplo abaixo:

y %>% f() %>% g()

Tudo que o pipe está fazendo é pegando o valor do objeto y e inserindo como o primeiro argumento
da função f(), em seguida, ele pega o resultado de f() e insere­o como primeiro argumento da
função g(). Isso seria equivalente a escrevermos g(f(y)). Para ter acesso ao operador pipe, você
precisa do pacote magrittr, portanto, lembre­se de chamar por ele através da função library().
Caso você tenha o pacote tidyverse, ele já contém o pacote magrittr inserido, logo, você também
pode chamar por ele.

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
133

Tendo isso em mente, poderíamos reescrever o código anterior de forma mais suscinta e muito mais
clara. Para criar um pipe você pode escrevê­lo na mão (%>%), ou caso você esteja trabalhando no RS­
tudio, você pode utilizar o atalho Ctrl + Shift + M, que um pipe será gerado automaticamente.
Veja o código abaixo:

tabela_final <- mpg %>%


group_by(class) %>%
summarise(Media = mean(hwy)) %>%
arrange(desc(Media))

## `summarise()` ungrouping output (override with `.groups` argument)

tabela_final

## # A tibble: 7 x 2
## class Media
## <chr> <dbl>
## 1 compact 28.3
## 2 subcompact 28.1
## 3 midsize 27.3
## 4 2seater 24.8
## 5 minivan 22.4
## 6 suv 18.1
## 7 pickup 16.9

O primeiro argumento de todas as funções acima, se trata da base de dados utilizada pela função em
seus cálculos. Logo, no código acima, estamos pegando a base mpg, inserindo­a em group_by(),
para que ele agrupe essa base pela variável class; depois inserimos o resultado agrupado em
summarise(), para que ele calcule a coluna Media que contém a média de quilometragem (hwy)
de cada um destes grupos; e por último, o pipe insere o resultado em arrange(), para que a função
reordene a base de forma decrescente, se baseando nos valores da coluna Media.
Dessa forma, temos o melhor de ambas alternativas. Economizamos memória, pois não precisamos
salvar os resultados intermediários, e o código gerado, além de suscinto é muito claro quanto a sua
intenção. Apesar destes benefícios que o pipe traz ao seu trabalho, você nem sempre consegue
utilizá­lo. Principalmente porque muitas funções não possuem como primeiro argumento, a base
de dados utilizada pela função.
Um exemplo simples é a função de regressão linear lm(), onde o primeiro argumento é a fórmula,
ou a equação que o cálculo da regressão deve seguir. A base de dados sobre a qual a função irá
calcular, se encontra no segundo argumento da função (data). Neste caso, você pode utilizar o

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
134

ponto final “.” como uma forma de contornar esta situação. O que o ponto faz, é definir em qual
argumento o resultado transportado pelo pipe, deve ser inserido.
No exemplo abaixo, estou pegando a base flights que contém dados de diversos voôs realiza­
dos em um aeroporto de Nova York. A intenção, é calcularmos uma regressão linear onde temos
distância (coluna distance) como variável independente, e o tempo de atraso na chegada (coluna
arr_delay) como a variável dependente. Logo, utilizo o ponto “.” na função lm() para inserir
a base flights no argumento data da função, e levar o resultado dessa expressão para a função
summary() que fica responsável por nos mostrar as estatísticas geradas pelo modelo.

library(nycflights13)

flights %>%
lm(arr_delay ~ distance, data = .) %>%
summary()

##
## Call:
## lm(formula = arr_delay ~ distance, data = .)
##
## Residuals:
## Min 1Q Median 3Q Max
## -87.20 -24.03 -11.84 7.19 1279.87
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 10.8291976 0.1355211 79.91 <0.0000000000000002 ***
## distance -0.0037523 0.0001058 -35.47 <0.0000000000000002 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 44.55 on 327344 degrees of freedom
## (9430 observations deleted due to missingness)
## Multiple R-squared: 0.003828, Adjusted R-squared: 0.003825
## F-statistic: 1258 on 1 and 327344 DF, p-value: < 0.00000000000000022

4.3 Filtrando linhas com filter()


Agora que vimos como o pipe funciona, vamos começar a filtrar os nossos dados, através da fun­
ção filter(), que vêm do pacote dplyr. Esse pacote também está incluso dentre os pacotes do

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
135

tidyverse. Logo, para ter acesso a esta função, basta chamar por um destes dois pacotes com
library().
A função filter() possui dois argumentos principais. A base de dados a ser filtrada (.data), e as
condições que a função deve seguir ao filtrar a base (...). Para criar as condições de filtro, você
deve utilizar os operadores lógicos, e por isso, deixei abaixo uma tabela descrevendo cada um deles:
Operador Descrição
< menor que
<= menor ou igual a
> maior que
>= maior ou igual a
== exatamente igual
!= não é igual a
!x não se encaixa na condição definida em x
x|y x ou y
x&y xey
x %in% y tudo de x que está incluso em y
Para demostrar essa função, vamos utilizar uma base com várias transferências bancárias (transf).
A coluna Usuário nos mostra qual dos agentes do banco que criou a transferência. Temos também
a data e o horário (Data) da transferência, além de seu país de destino (País).

transf

## # A tibble: 20,006 x 6
## Data Usuario Valor TransferID Pais Descricao
## <dttm> <chr> <dbl> <dbl> <chr> <lgl>
## 1 2018-12-06 22:19:19 Eduardo 599. 116241629 Alemanha NA
## 2 2018-12-06 22:10:34 Júlio 4611. 115586504 Alemanha NA
## 3 2018-12-06 21:59:50 Nathália 4418. 115079280 Alemanha NA
## 4 2018-12-06 21:54:13 Júlio 2740. 114972398 Alemanha NA
## 5 2018-12-06 21:41:27 Ana 1408. 116262934 Alemanha NA
## 6 2018-12-06 21:18:40 Nathália 5052. 115710402 Alemanha NA
## 7 2018-12-06 20:54:32 Eduardo 5665. 114830203 Alemanha NA
## 8 2018-12-06 20:15:46 Sandra 1474. 116323455 Alemanha NA
## 9 2018-12-06 20:04:35 Armando 8906. 115304382 Alemanha NA
## 10 2018-12-22 20:00:56 Armando 18521. 114513684 Alemanha NA
## # ... with 19,996 more rows

O setor de Compliance do banco, é composto por 3 pessoas (Ana, Júlio Cesar e Armando). Por­
tanto, caso você quisesse saber todas as transações autorizadas por esse setor, você poderia filtrar
a base em relação a esses nomes na coluna Usuário. A melhor forma de criar este filtro, é uti­
lizando o operador %in%. Com ele, a função filter() irá procurar todas as linhas onde o valor

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
136

na coluna Usuário está incluso dentro do objeto compliance. Ao salvar os nomes da equipe do
Compliance em um objeto, você pode utilizá­lo para se referir novamente a equipe, caso precise em
outro momento de sua análise.

compliance <- c("Ana", "Júlio Cesar", "Armando")


transf %>%
filter(
Usuario %in% compliance
)

## # A tibble: 7,475 x 6
## Data Usuario Valor TransferID Pais Descricao
## <dttm> <chr> <dbl> <dbl> <chr> <lgl>
## 1 2018-12-06 21:41:27 Ana 1408. 116262934 Alemanha NA
## 2 2018-12-06 20:04:35 Armando 8906. 115304382 Alemanha NA
## 3 2018-12-22 20:00:56 Armando 18521. 114513684 Alemanha NA
## 4 2018-12-06 19:48:39 Ana 4589. 116281690 Alemanha NA
## 5 2018-12-06 18:48:29 Ana 8748. 114255039 Alemanha NA
## 6 2018-12-21 18:46:59 Júlio Cesar 16226. 116279014 Alemanha NA
## 7 2018-12-06 18:45:32 Ana 3285. 115459852 Alemanha NA
## 8 2018-12-06 18:09:15 Júlio Cesar 388. 114894102 Alemanha NA
## 9 2018-12-06 17:58:17 Ana 14890. 115036903 Alemanha NA
## 10 2018-12-06 17:34:49 Armando 5726. 114489509 Alemanha NA
## # ... with 7,465 more rows

Eu posso utilizar o símbolo “!” para localizar as transferências que não se encaixam em uma con­
dição lógica específica. Ou seja, o símbolo “!” muda o comportamento de filter(), pois com ele
a função irá pegar as linhas que possuem o valor FALSE para a condição lógica definida, ao invés
de TRUE.
Um exemplo clássico do uso do símbolo “!” nessas condições, é para retirar as linhas que possuem
valores não disponíveis ­ ou vazios (valores NA). A função is.na() aplica um teste lógico sobre
a coluna. Caso a linha dessa coluna possua um valor NA, a função nos retorna TRUE para aquela
linha, porém, caso a linha possua um valor qualquer definido, ela retorna FALSE. Ao colocarmos o
símbolo “!” antes dessa função, o filter() irá procurar pelas linhas que possuem valores FALSE.

tab <- data.frame(


grupo = c("a", "a", "b", "b", "c"),
valor = c(21, 14, 32, NA, NA)
)

tab %>%
filter(!is.na(valor))

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
137

## grupo valor
## 1 a 21
## 2 a 14
## 3 b 32

Tendo isso em mente, se eu quisesse saber as transferências que não são do Brasil, eu poderia utilizar
o símbolo “!” na frente da condição lógica que utilizaríamos para sabermos justamente o contrário
(quais são as transferências destinadas para o Brasil).

transf %>%
filter(!Pais == "Brasil")

## # A tibble: 17,822 x 6
## Data Usuario Valor TransferID Pais Descricao
## <dttm> <chr> <dbl> <dbl> <chr> <lgl>
## 1 2018-12-06 22:19:19 Eduardo 599. 116241629 Alemanha NA
## 2 2018-12-06 22:10:34 Júlio 4611. 115586504 Alemanha NA
## 3 2018-12-06 21:59:50 Nathália 4418. 115079280 Alemanha NA
## 4 2018-12-06 21:54:13 Júlio 2740. 114972398 Alemanha NA
## 5 2018-12-06 21:41:27 Ana 1408. 116262934 Alemanha NA
## 6 2018-12-06 21:18:40 Nathália 5052. 115710402 Alemanha NA
## 7 2018-12-06 20:54:32 Eduardo 5665. 114830203 Alemanha NA
## 8 2018-12-06 20:15:46 Sandra 1474. 116323455 Alemanha NA
## 9 2018-12-06 20:04:35 Armando 8906. 115304382 Alemanha NA
## 10 2018-12-22 20:00:56 Armando 18521. 114513684 Alemanha NA
## # ... with 17,812 more rows

Você poderia fazer o mesmo processo, através do símbolo “!=”, que realiza um trabalho diferente
do símbolo “!” no início de uma condição lógica. O símbolo “!=” significa “diferente de”, e ele não
vai alterar o comportamento da função filter(), que portanto, irá filtrar as linhas que possuem
valor TRUE para a condição. Logo, o filter() com este símbolo, irá filtrar todas as linhas que são
diferentes de um valor.

transf %>%
filter(Pais != "Brasil")

## # A tibble: 17,822 x 6
## Data Usuario Valor TransferID Pais Descricao
## <dttm> <chr> <dbl> <dbl> <chr> <lgl>
## 1 2018-12-06 22:19:19 Eduardo 599. 116241629 Alemanha NA
## 2 2018-12-06 22:10:34 Júlio 4611. 115586504 Alemanha NA

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
138

## 3 2018-12-06 21:59:50 Nathália 4418. 115079280 Alemanha NA


## 4 2018-12-06 21:54:13 Júlio 2740. 114972398 Alemanha NA
## 5 2018-12-06 21:41:27 Ana 1408. 116262934 Alemanha NA
## 6 2018-12-06 21:18:40 Nathália 5052. 115710402 Alemanha NA
## 7 2018-12-06 20:54:32 Eduardo 5665. 114830203 Alemanha NA
## 8 2018-12-06 20:15:46 Sandra 1474. 116323455 Alemanha NA
## 9 2018-12-06 20:04:35 Armando 8906. 115304382 Alemanha NA
## 10 2018-12-22 20:00:56 Armando 18521. 114513684 Alemanha NA
## # ... with 17,812 more rows

4.3.0.1 Ataque terrorista

Vamos dar um pouco de contexto a nossa análise. Suponha que houve um ataque terrorista em
Berlim no Natal (dia 24 de dezembro) de 2018. Você como parte do compliance de uma instituição
financeira que realiza transferências internacionais, deve ter certeza de que a sua instituição não
colaborou com o terrorismo internacional de alguma forma. Segundo a polícia, a munição usada no
ataque custa em média mais de $15.000, e foi comprada entre os dias 20 e 23.
O seu primeiro instinto, será provavelmente procurar por linhas em nossa base transf que possuem
características como essas. Perceba que são no mínimo três condições que a transferência deve
atender. Primeiro, possuir um valor maior do que 15 mil. Segundo, estar destinada para a Alemanha.
Terceiro, ter sido realizada entre os dias 20 e 23 de dezembro de 2018.
Ou seja, essas condições são dependentes, pois todas elas devem ser satisfeitas ao mesmo tempo.
Para dizermos isso à função filter(), conectamos essas condições lógicas pelo símbolo do laço
(“&”). Vale também destacar o uso da função as.Date() em between(). A coluna Data em
transf é uma coluna de date-time, e utilizo a função as.Date() para extrair apenas a data
da coluna. A função between() é uma forma rápida de criar uma condição lógica de intervalo.
No primeiro argumento, você dá a coluna onde a função deve procurar o intervalo; o segundo
argumento, se trata do limite inferior do intervalo; e o terceiro argumento, o limite superior do
intervalo. No caso de um intervalo de datas, você deve utilizar a função as.Date() sobre os limites
do intervalo, para que a função between() compreenda que se trata de um intervalo de datas.

transf %>%
filter(
Valor > 15000 &
Pais == "Alemanha" &
between(as.Date(Data), as.Date("2018-12-20"), as.Date("2018-12-23"))
)

## # A tibble: 132 x 6
## Data Usuario Valor TransferID Pais Descricao

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
139

## <dttm> <chr> <dbl> <dbl> <chr> <lgl>


## 1 2018-12-22 20:00:56 Armando 18521. 114513684 Alemanha NA
## 2 2018-12-21 18:46:59 Júlio Cesar 16226. 116279014 Alemanha NA
## 3 2018-12-21 17:41:48 Nathália 17583. 115748273 Alemanha NA
## 4 2018-12-23 09:46:23 Júlio 15396. 115272184 Alemanha NA
## 5 2018-12-21 06:38:20 Júlio Cesar 17555. 114983226 Alemanha NA
## 6 2018-12-23 18:11:27 Eduardo 17219. 115904797 Alemanha NA
## 7 2018-12-22 13:09:13 Eduardo 16255. 114520578 Alemanha NA
## 8 2018-12-23 10:59:50 Júlio Cesar 15093. 115919119 Alemanha NA
## 9 2018-12-23 10:29:34 Sandra 19241. 114665132 Alemanha NA
## 10 2018-12-21 06:04:49 Júlio Cesar 18938. 116281869 Alemanha NA
## # ... with 122 more rows

Bem, são 156 transferências suspeitas, e você terá que conferir os dados dos donos de cada uma
dessas transferências, um baita trabalho. Porém, você acaba lembrando que dentro das regras do
banco, não é exigido do cliente nenhum comprovante de endereço ou de origem dos fundos para
transferências de até $200, apenas a identidade (que pode ser falsificada).

transf %>%
filter(
Valor <= 200 &
Pais == "Alemanha" &
between(as.Date(Data), as.Date("2018-12-20"), as.Date("2018-12-23"))
)

## # A tibble: 5 x 6
## Data Usuario Valor TransferID Pais Descricao
## <dttm> <chr> <dbl> <dbl> <chr> <lgl>
## 1 2018-12-20 00:31:17 Júlio 193 115555598 Alemanha NA
## 2 2018-12-22 06:30:01 Sandra 100 116400001 Alemanha NA
## 3 2018-12-22 06:35:00 Sandra 200 116400002 Alemanha NA
## 4 2018-12-22 06:42:12 Eduardo 200 116400005 Alemanha NA
## 5 2018-12-22 06:55:54 Eduardo 150 116400009 Alemanha NA

Interessante, reduzimos a nossa amostra para cinco transferências. O dono da primeira transferência
é um senhor de 67 anos, que mora no centro de Berlim, e que estava comprando um remédio nada
barato de uma farmácia. Porém, as próximas quatro transferências levantam bastante suspeitas.
As transferências foram de clientes diferentes (mas com poucos minutos de diferença). Ao questio­
nar Sandra e Eduardo, eles indicam que todos os clientes apresentaram identidades francesas (será
que os clientes estavam testando quais eram as regras do banco para com identidades francesas?).
Ao pesquisar por mais clientes com identidades francesas você chega a uma estranha transferência

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
140

de $20.000 realizada 4 minutos depois da última, e com uma prova de fundos discutível. Talvez o
banco, quem autorizou essas transações, você e seus colegas também estejam em maus lençóis.

transf %>%
inner_join(
identidade,
by = "TransferID"
) %>%
filter(
Pais == "Alemanha" &
between(as.Date(Data), as.Date("2018-12-20"), as.Date("2018-12-23")) &
Identi_Nacion == "França"
)

## # A tibble: 5 x 7
## Data Usuario Valor TransferID Pais Descricao Identi_Nacion
## <dttm> <chr> <dbl> <dbl> <chr> <lgl> <chr>
## 1 2018-12-22 06:30:01 Sandra 100 116400001 Alemanha NA França
## 2 2018-12-22 06:35:00 Sandra 200 116400002 Alemanha NA França
## 3 2018-12-22 06:42:12 Eduardo 200 116400005 Alemanha NA França
## 4 2018-12-22 06:55:54 Eduardo 150 116400009 Alemanha NA França
## 5 2018-12-22 06:59:07 Eduardo 20000 116400010 Alemanha NA França

4.3.0.2 Condições dependentes (&) ou independentes (|) ?

Portanto, inicialmente no exemplo anterior, estávamos procurando por uma transferência de valor
alto (> $15.000) para a Alemanha e que fosse entre os dias 20 e 23 de dezembro de 2018. As condi­
ções dependiam uma da outra, e para que o filter() entenda isso, essas condições são conectadas
pelo operador “&”.
Porém, e se essas condições não dependessem uma da outra? Caso estivéssemos falando por exem­
plo, de uma transferência que era maior do que $15.000 OU que foi para a Alemanha OU que foi
feita entre os dias 20 e 23 de dezembro de 2018, o filter() deveria filtrar toda linha que atenda
pelo menos uma dessas condições. Neste caso, devemos separar as condições por um outro operador
lógico, uma barra vertical ( | ).

transf %>%
filter(
Valor > 15000 |
Pais == "Alemanha" |
between(as.Date(Data), as.Date("2018-12-20"), as.Date("2018-12-23"))
)

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
141

## # A tibble: 2,444 x 6
## Data Usuario Valor TransferID Pais Descricao
## <dttm> <chr> <dbl> <dbl> <chr> <lgl>
## 1 2018-12-06 22:19:19 Eduardo 599. 116241629 Alemanha NA
## 2 2018-12-06 22:10:34 Júlio 4611. 115586504 Alemanha NA
## 3 2018-12-06 21:59:50 Nathália 4418. 115079280 Alemanha NA
## 4 2018-12-06 21:54:13 Júlio 2740. 114972398 Alemanha NA
## 5 2018-12-06 21:41:27 Ana 1408. 116262934 Alemanha NA
## 6 2018-12-06 21:18:40 Nathália 5052. 115710402 Alemanha NA
## 7 2018-12-06 20:54:32 Eduardo 5665. 114830203 Alemanha NA
## 8 2018-12-06 20:15:46 Sandra 1474. 116323455 Alemanha NA
## 9 2018-12-06 20:04:35 Armando 8906. 115304382 Alemanha NA
## 10 2018-12-22 20:00:56 Armando 18521. 114513684 Alemanha NA
## # ... with 2,434 more rows

4.3.0.3 Filtrando através de caracteres (texto)

Vamos continuar usando o data.frame transf do banco como exemplo. Vamos supor que você
está a procura das transferências realizadas por Nathália do setor de crédito pessoal. Neste caso,
você pode usar o operador “==” que procura valores “iguais a”.

transf %>%
filter(
Usuario == "Nathália"
)

## # A tibble: 2,507 x 6
## Data Usuario Valor TransferID Pais Descricao
## <dttm> <chr> <dbl> <dbl> <chr> <lgl>
## 1 2018-12-06 21:59:50 Nathália 4418. 115079280 Alemanha NA
## 2 2018-12-06 21:18:40 Nathália 5052. 115710402 Alemanha NA
## 3 2018-12-21 17:41:48 Nathália 17583. 115748273 Alemanha NA
## 4 2018-12-06 17:41:45 Nathália 2112. 115975046 Alemanha NA
## 5 2018-12-06 14:55:06 Nathália 2469. 114816281 Alemanha NA
## 6 2018-12-06 13:40:48 Nathália 1213. 116063458 Alemanha NA
## 7 2018-12-06 12:57:36 Nathália 5819. 115237461 Alemanha NA
## 8 2018-12-06 09:37:04 Nathália 1740. 115549066 Alemanha NA
## 9 2018-12-06 09:00:57 Nathália 856. 114781229 Alemanha NA
## 10 2018-12-06 06:34:15 Nathália 7241. 116323536 Alemanha NA
## # ... with 2,497 more rows

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
142

Mas quando estamos falando de texto, devemos ter cuidado com este operador, pois ele irá procurar
valores que são “exatamente iguais a”. Qualquer detalhe diferente em um texto, o torna comple­
tamente diferente para este operador. Logo “nathália” é completamente diferente de “Nathália”,
mesmo que a primeira letra seja a única diferença. No código abaixo, estou listando todos os dife­
rentes nomes que aparecem na coluna Usuário. Veja que temos pelo menos seis formas diferentes
de Nathália ao longo da base de dados.

nomes <- transf %>%


group_by(Usuario) %>%
count() %>%
arrange(desc(Usuario)) %>%
print(n = 7)

## # A tibble: 8 x 2
## # Groups: Usuario [8]
## Usuario n
## <chr> <int>
## 1 Sandra 2487
## 2 Nathália 2507
## 3 nathalia 2552
## 4 Júlio Cesar 2529
## 5 Júlio 2533
## 6 Eduardo 2452
## 7 Armando 2468
## # ... with 1 more row

Nestes casos, você pode utilizar um REGEX (regular expression) para encontrar textos próximos
a um padrão. Essa é uma poderosa ferramenta, que está presente em quase todas as linguagens de
programação.

4.3.0.4 Expressões regulares com stringr

O pacote stringr traz ao R, funções importantes para manipulações de texto, como concatena­
ção ­ str_c(), e subset ­ str_sub(). Além de ferramentas para expressões regulares (REGEX),
sendo a função str_detect() a responsável por detectar esses padrões em textos. Esse pacote
provavelmente já lhe satisfaz, e para ter acesso as suas funções, podemos chamar tanto pelo pacote
diretamente, quanto pelo pacote tidyverse.
Talvez você precise de mais transformações e funções de texto. Nestes casos, o pacote stringi traz
um arsenal de funções muito maior do que o stringr. Como disse anteriormente, a principal função
que vamos utilizar dentro do filter(), é a str_detect(). A função possui dois argumentos

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
143

principais: string, que é a coluna onde estão os textos pelo qual você irá filtrar; e pattern, que é
o padrão que a função deve encontrar dentro dos textos que você deu a ela.
Ao identificarmos todos os nomes presentes na base, podemos ver melhor quais textos queremos
filtrar, e podemos identificar pelo menos seis versões diferentes de Nathália presentes. Vamos fazer
alguns testes antes:

transf %>%
filter(
str_detect(Usuario, "N")
) %>%
group_by(Usuario) %>%
count()

## # A tibble: 1 x 2
## # Groups: Usuario [1]
## Usuario n
## <chr> <int>
## 1 Nathália 2507

No exemplo acima, o str_detect() detectou todas as linhas, onde o valor na coluna Usuário
possui um “N” maiúsculo em algum lugar da palavra. Vemos acima, que há apenas dois nomes que
possuem essa letra, ao longo de toda a base. Nomes como “nathalia” foram descartados, por não
possuírem o “N” maiúsculo em algum lugar.
Podemos utilizar alguns caracteres para definir essa busca. Por exemplo, em expressões regulares
no R, o ponto (.) representa qualquer caractere. Portanto, se dermos o padrão “.a.” à função
str_detect(), ela irá encontrar nomes que possuem a letra “a” entre dois caracteres quaisquer.
No exemplo abaixo, o padrão detectado foi: Armando ou Eduardo.

transf %>%
filter(
str_detect(Usuario, ".a.")
) %>%
group_by(Usuario) %>%
count() %>%
print(n = 5)

## # A tibble: 6 x 2
## # Groups: Usuario [6]
## Usuario n
## <chr> <int>

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
144

## 1 Armando 2468
## 2 Eduardo 2452
## 3 Júlio Cesar 2529
## 4 nathalia 2552
## 5 Nathália 2507
## # ... with 1 more row

Por isso nomes como “Ana” foram ignorados pelo filter(). Pois mesmo possuindo a letra “a”,
ela não está entre dois caracteres. Um padrão que detectaria o nome “Ana” seria “.a”, ou seja, o
padrão detectado seria Ana. Dessa forma, o filter() pegaria todo nome que possui um caractere
qualquer à esquerda de uma letra “a”.
Outros caracteres que podemos utilizar para definirmos como a busca pelo padrão ocorre, são o
acento circunflexo (^) e o cifrão ($). Estes caracteres definem os extremos do texto, onde o ^ define
o início do texto, e o $ define o final do texto. Portanto, se dermos o padrão “s$” apenas o nome
“mnathalias” aparece, pois só ele contém um “s” ao final do texto. Logo, nomes como “Júlio Cesar”
não são detectados por esse padrão. Mas caso substituíssemos este padrão por “ˆA” por exemplo,
nomes como “Ana” e “Armando” seriam detectados, pois possuem uma letra “a” maiúscula em seu
ínicio, enquanto outros como “NATHÁLIA##NATAL_30%”, seriam descartados.

transf %>%
filter(
str_detect(Usuario, "s$")
) %>%
group_by(Usuario) %>%
count()

## # A tibble: 0 x 2
## # Groups: Usuario [0]
## # ... with 2 variables: Usuario <chr>, n <int>

E se quiséssemos filtrar todas as linhas com nomes de até três caracteres? Uma solução seria utilizar
a função str_legth() que retorna o número de caracteres presentes em um texto. Mas poderíamos
realizar o mesmo trabalho com REGEX, através de um padrão de três pontos (…). Porém, ainda
falta algo neste padrão, pois caso você adicione este padrão à str_detect(), você irá perceber que
ele retorna todos os nomes possíveis na base!
Pare e pense por um instante, no que o ponto significa. Como eu disse, ele representa qualquer
caractere, logo, estes três pontos estão representando três caracteres quaisquer. O motivo pelo qual
este padrão em str_detect() retorna todos os nomes possíveis na base, é que todos esses nomes
possuem pelo menos três caracteres quaisquer ao longo de seu textos. Então, o que precisamos é
definir o limite dessa pesquisa, para que ela encontre textos de apenas três caracteres quaisquer, e
este trabalho é feito pelos símbolos ^ e $.

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
145

transf %>%
filter(
str_length(Usuario) == 3
)
###### Ou então utilizando 3 pontos entre ^ e $ como o padrão.
transf %>%
filter(
str_detect(Usuario, "^...$")
)

Com tudo o que vimos até aqui, como podemos detectar todas as versões de Nathália de uma só vez?
Temos que levar em conta que em 4 versões temos Nathália com letras minúsculas, e em outras 2
versões temos o nome com pelo menos uma letra em maiúsculo, além de uma letra “a” com acento.

nomes[c(1, 2, 4:7),]

## # A tibble: 6 x 2
## # Groups: Usuario [6]
## Usuario n
## <chr> <int>
## 1 Sandra 2487
## 2 Nathália 2507
## 3 Júlio Cesar 2529
## 4 Júlio 2533
## 5 Eduardo 2452
## 6 Armando 2468

Não podemos filtrar todos os nomes ignorando esse fato. Se colocássemos o padrão “nathalia”
apenas as 4 versões com letras minúsculas seriam detectados, enquanto as de letras maiúsculas
não. Neste caso, temos duas alternativas: criarmos dois padrões (um para maiúsculo e outro para
minúsculo), ou então transfromar todos os caracteres dos nomes para minúsculo (ou para maíusculo)
com a função str_to_lower() (ou str_to_upper()).
Perceba que podemos filtrar as versões de Nathália em maiúsculo, apenas com o padrão “N”, sim­
plesmente pelo fato de que não há outro nome na base com “n” maiúsculo. Caso não fosse esse o
caso, poderíamos utilizar o padrão mais geral abaixo. Utilizamos o padrão com um ponto (.) jus­
tamente onde temos o “a” com acento nas versões em maíusculo. Dessa forma o padrão detectaria
tanto “nathalia” como “nathália”.

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
146

transf %>%
filter(
str_detect(Usuario, "nathalia") |
str_detect(Usuario, "N")
)
###### Ou transformando todos os caracteres em minúsculo
transf %>%
filter(
str_detect(
str_to_lower(Usuario),
"nath.lia"
)
)

4.4 Selecionando colunas com select()


Você talvez já saiba como selecionar colunas no R, através da função de subsetting da linguagem ([).
Porém ,caso não conheça esse sistema, o pacote dplyr também oferece uma função para seleção
de colunas, chamada select(). A função é bem simples, e possui dois argumentos. O primeiro
(.data) é o data.frame onde estão as colunas que deseja selecionar, e o segundo (...) é a lista
de colunas que deseja selecionar.
Você tem quatro alternativas ao fornecer as colunas que deseja à função. 1) Você pode dar o índice,
ou uma sequência de índices das colunas (1 ­ primeira coluna, 2 ­ segunda coluna, 3 ­ terceira
coluna, e assim por diante); 2) Você pode dar diretamente o nome da coluna; 3) Você também
pode fornecer um vetor de texto, com os nomes das colunas. 4) Utilizar as funções de seleção
disponibilizadas pelo pacote dplyr (ends_with() e starts_with()). 5) Você pode selecionar
colunas, de acordo com o tipo de dado contido nela, através das funções do pacote base do R
(is.numeric(), is.character(), is.factor(), etc.).
Essa função não é mais rápida, ou particularmente melhor do que [. Porém, você não precisa se
prender a um dos sistemas que mencionei acima. Quando estiver utilizando o select(), você pode
utilizar todos estes sistemas ao mesmo tempo (veja o exemplo abaixo, onde utilizo os métodos 1,
2 e 4), sendo que o primeiro e o quarto método são muito úteis quando você tem várias colunas a
selecionar, e você está sem tempo para digitar o nome de todas elas.

iris %>%
select(Sepal.Length, 3, ends_with("Width")) %>%
head(n = 8)

## Sepal.Length Petal.Length Sepal.Width Petal.Width

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
147

## 1 5.1 1.4 3.5 0.2


## 2 4.9 1.4 3.0 0.2
## 3 4.7 1.3 3.2 0.2
## 4 4.6 1.5 3.1 0.2
## 5 5.0 1.4 3.6 0.2
## 6 5.4 1.7 3.9 0.4
## 7 4.6 1.4 3.4 0.3
## 8 5.0 1.5 3.4 0.2

O select() irá selecionar as colunas na ordem em que você as fornece à função. No exemplo
acima, ele alocou a terceira coluna (Petal.Length ­ representada pelo índice 3 em select())
como a segunda coluna do data.frame resultante. A função ends_with() irá procurar por todas
as colunas nodata.frame, em que o nome termina com o padrão “Width”. A sua função irmã
(starts_with()) realiza exatamente o mesmo trabalho, porém ela procura o padrão que você dá
a ela, no início do nome da coluna.
O primeiro método de seleção que mencionei, funciona exatamente da mesma forma que em [, e fica
particularmente útil, quando você utiliza sequências, produzidas pela função :. No exemplo abaixo,
estou selecionando todas as colunas entre a primeira e a quarta coluna. Outra opção interessante
quando você possui uma base com várias colunas que deseja jogar fora, é utilizar str_detect() em
colnames(), para encontrar o nome das colunas que possuem um padrão específico, e em seguida,
fornecer este vetor para select() dentro de all_of().

iris %>%
select(1:4) %>%
head(n = 8)

## Sepal.Length Sepal.Width Petal.Length Petal.Width


## 1 5.1 3.5 1.4 0.2
## 2 4.9 3.0 1.4 0.2
## 3 4.7 3.2 1.3 0.2
## 4 4.6 3.1 1.5 0.2
## 5 5.0 3.6 1.4 0.2
## 6 5.4 3.9 1.7 0.4
## 7 4.6 3.4 1.4 0.3
## 8 5.0 3.4 1.5 0.2

col_select <- colnames(iris)[str_detect(colnames(iris), "Petal")]

iris %>%
select(all_of(col_select)) %>%
head(n = 8)

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
148

## Petal.Length Petal.Width
## 1 1.4 0.2
## 2 1.4 0.2
## 3 1.3 0.2
## 4 1.5 0.2
## 5 1.4 0.2
## 6 1.7 0.4
## 7 1.4 0.3
## 8 1.5 0.2

O quinto método que citei também é bem útil, mas há alguns cuidados que são interessantes de
se tomar ao utilizá­lo. Caso você quisesse selecionar todas as colunas em iris, que contenham
fatores (factor), você pode inserir apenas o nome da função de teste correspondente a este tipo
(is.factor()). Dessa forma, o código ficaria: select(is.factor). Porém, este código pode ser
um pouco confuso para aqueles que não estão acostumados com essa funcionalidade de select().
Por isso, é recomendado que você envolva o nome da função com where(), deixando assim a
intenção de seu código mais claro para o leitor.

iris %>%
select(where(is.factor)) %>%
head(n = 8)

## Species
## 1 setosa
## 2 setosa
## 3 setosa
## 4 setosa
## 5 setosa
## 6 setosa
## 7 setosa
## 8 setosa

4.5 Ordenando linhas com arrange()


Uma tarefa muito comum em nossa análise, é a ordenação das linhas. O pacote base do R oferece
as funções order() e sort() para essa tarefa. Já o pacote dplyr, oferece a função arrange()
que gera um código menos verboso do que as funções do pacote base, e realiza a mesma tarefa.
A função possui dois argumentos. O primeiro (.data) é o data.frame que deseja ordernar, e o
segundo (…) o nome da coluna, ou a lista de nomes das colunas pelas quais você deseja ordenar a

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
149

base. Por padrão, a função ordena de forma crescente a coluna. Logo, caso a coluna seja numérica
(double ou integer), a função organiza a coluna a partir dos menores valores, indo até os maiores
valores. Caso a coluna seja de texto (character), a função utiliza ordenação alfabética (de A a Z).
Mas se a coluna for composta de fatores (factor), a função irá ordenar os valores, de acordo com
a ordem definida no atributo levels() destes fatores.

iris %>%
arrange(Sepal.Length) %>%
head(n = 10)

## Sepal.Length Sepal.Width Petal.Length Petal.Width Species


## 1 4.3 3.0 1.1 0.1 setosa
## 2 4.4 2.9 1.4 0.2 setosa
## 3 4.4 3.0 1.3 0.2 setosa
## 4 4.4 3.2 1.3 0.2 setosa
## 5 4.5 2.3 1.3 0.3 setosa
## 6 4.6 3.1 1.5 0.2 setosa
## 7 4.6 3.4 1.4 0.3 setosa
## 8 4.6 3.6 1.0 0.2 setosa
## 9 4.6 3.2 1.4 0.2 setosa
## 10 4.7 3.2 1.3 0.2 setosa

Caso você deseje ordenar a coluna segundo uma ordem decrescente (ou uma ordem alfabética de Z
a A), você deve utilizar a função desc().

iris %>%
arrange(desc(Sepal.Length))%>%
head(n = 10)

## Sepal.Length Sepal.Width Petal.Length Petal.Width Species


## 1 7.9 3.8 6.4 2.0 virginica
## 2 7.7 3.8 6.7 2.2 virginica
## 3 7.7 2.6 6.9 2.3 virginica
## 4 7.7 2.8 6.7 2.0 virginica
## 5 7.7 3.0 6.1 2.3 virginica
## 6 7.6 3.0 6.6 2.1 virginica
## 7 7.4 2.8 6.1 1.9 virginica
## 8 7.3 2.9 6.3 1.8 virginica
## 9 7.2 3.6 6.1 2.5 virginica
## 10 7.2 3.2 6.0 1.8 virginica

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
150

Um outro detalhe, é que ao dar mais de uma coluna para arrange(), ele vai aplicar a ordenação
sobre as colunas, de acordo com a ordem em que você dá elas à função. No exemplo abaixo, o
arrange() primeiro ordena de forma decrescente a coluna Sepal.Length, e em seguida, ele or­
dena a coluna Sepal.Width em ordem crescente dentro de cada faixa, ou grupo de Sepal.Length.
Ou seja, na hora de ordenar a próxima coluna, o arrange() tem de levar em conta os valores da
coluna que a função ordenou anteriormente.

iris %>%
arrange(desc(Sepal.Length), Sepal.Width) %>%
head(n = 10)

## Sepal.Length Sepal.Width Petal.Length Petal.Width Species


## 1 7.9 3.8 6.4 2.0 virginica
## 2 7.7 2.6 6.9 2.3 virginica
## 3 7.7 2.8 6.7 2.0 virginica
## 4 7.7 3.0 6.1 2.3 virginica
## 5 7.7 3.8 6.7 2.2 virginica
## 6 7.6 3.0 6.6 2.1 virginica
## 7 7.4 2.8 6.1 1.9 virginica
## 8 7.3 2.9 6.3 1.8 virginica
## 9 7.2 3.0 5.8 1.6 virginica
## 10 7.2 3.2 6.0 1.8 virginica

Você deve ter percebido que arrange() não possui toda a flexibilidade de select() na hora de
definir quais colunas serão utilizadas para ordenar a base. Mas nas novas versões do pacote dplyr,
essa possibilidade foi introduzida. Para ter acesso aos métodos utilizados em select(), basta
utilizar a função across() para definir quais colunas serão utilizadas.

iris %>%
arrange(across(starts_with("Sepal"))) %>%
head(n = 10)

## Sepal.Length Sepal.Width Petal.Length Petal.Width Species


## 1 4.3 3.0 1.1 0.1 setosa
## 2 4.4 2.9 1.4 0.2 setosa
## 3 4.4 3.0 1.3 0.2 setosa
## 4 4.4 3.2 1.3 0.2 setosa
## 5 4.5 2.3 1.3 0.3 setosa
## 6 4.6 3.1 1.5 0.2 setosa
## 7 4.6 3.2 1.4 0.2 setosa
## 8 4.6 3.4 1.4 0.3 setosa
## 9 4.6 3.6 1.0 0.2 setosa
## 10 4.7 3.2 1.3 0.2 setosa

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
151

Mais a frente, veremos como podemos agrupar, ou definir grupos em nossos dados com a função
group_by(). Essa é uma operação muito comum em análises de dados, e é importante frisar que
o arrange(), por padrão, não respeita estes grupos, caso você os defina. Por isso, se você quer
que a função passe a respeitá­los, ordenando as linhas dentro de cada grupo, você deve adicionar o
argumento .by_group = TRUE à função.

4.6 Adicionando variáveis à nossa tabela com mutate()


Uma atividade essencial em nossa análise, é a de adicionar novas colunas que são calculadas com
base em colunas já existentes em nossa tabela. Para esta tarefa, temos a função mutate().
A função mutate() portanto cria novas variáveis e as adiciona em novas colunas ao fim de seu
data.frame, e possui dois argumentos principais: o primeiro (.data) corresponde ao data.frame
onde deseja adicionar a nova coluna; e o segundo (…), a lista com as fórmulas de suas novas colunas.
Logo abaixo, estou listando algumas funções úteis que você pode utilizar com mutate(). A maioria
delas, são funções vetorizadas, ou dito de outra forma, que aplicam os seus cálculos sobre a coluna,
e não sobre a linha.
Todas as funções que fazem operações por linha (exemplo: rowSums() e rowMeans()), precisam
de alguns cuidados a mais para que elas funcionem com o mutate()1 . Caso você esteja somando
por linha, mas ao longo de poucas colunas, talvez você fique mais a vontade com os operadores
aritméticos.

1. Somatórios: somatório acumulado ­ cumsum(); soma total de uma coluna ­ sum(); soma­
tório por linha, ao longo de algumas colunas ­ operador +; somatório por linha, ao longo de
várias colunas ­ rowSums().

2. Medidas de posição: média de uma coluna ­ mean(); mediana de uma coluna ­ median();
média por linha, ao longo de várias colunas ­ rowMeans().

3. Medidas de dispersão: desvio padrão de uma coluna ­ sd(); variância de uma coluna ­
var(); intervalo interquartil ­ IQR(); desvio absoluto da mediana ­ mad().

4. Operadores aritméticos: soma (+); subtração (-); divisão (/); multiplicação (*); potência,
ou elevar um número a x (^); restante da divisão (%%); apenas o número inteiro resultante da
divisão (%/%).
1
São cuidados simples, você deve utilizar algum pronome (o ponto “.”, da mesma forma em que o utilizamos na seção do
pipe), ou inserir um select() dentro da função (como a rowSums()) que esteja utilizando, para que ela funcione com
o mutate(). Para exemplos, veja este post do StackOverflow: https://stackoverflow.com/questions/27354734/dplyr­
mutate­rowsums­calculations­or­custom­functions

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
152

5. Operadores lógicos: aplique um teste lógico por linha, e preencha essa linha com x caso o
teste resulte em TRUE, ou preencha com y caso o teste resulte em FALSE ­ if_else(); quando
você quer aplicar uma operação parecida com if_else(), mas que há vários casos possíveis,
um exemplo típico seria criar uma coluna de faixas etárias ­ case_when(); você pode utilizar
normalmente todos os operadores que vimos na seção de de filter(), para criar um teste
lógico sobre cada linha ­ <, <=, >, >=, ==, !=, !, &, |.

6. Funções para discretizar variáveis contínuas: calcula intervalos de forma a encaixar o


mesmo número número de observações em cada intervalo (comumente chamados de quan­
tis) ­ cut_number(); calcula intervalos com o mesmo alcance ­ cut_interval(); calcula
intervalos de largura definida no argumento width ­ cut_width().

7. Funções de defasagem e liderança: quando você precisa em algum cálculo naquela linha,
utilizar o valor da linha anterior ­ lag(); ou ao invés do valor da linha anterior, você precisa
do valor da linha posterior ­ lead().

Há várias outras funções que são oferecidas pelo pacote dplyr, mas a lista acima que inclue também
algumas funções do pacote base do R, é provavelmente representativa para a maioria dos problemas
e operações. Como eu disse, algumas funções terão comportamentos diferentes, e aquelas que
operam sobre linhas em diversas colunas (por exemplo rowSums) precisam de alguns cuidados
simples para funcionar com o mutate().
A função sum() por exemplo, calcula o total de uma coluna, e portanto, retorna um único valor
como resultado. Isso a torna extremamente útil e eficiente para calcular proporções. Eu poderia
por exemplo, pegar os dados de população dos municípios do estado de Minas Gerais, e calcular a
proporção de cada município sobre a população total do estado. A função mean(), da mesma forma
ao retornar a média de toda a coluna, sendo particularmente útil para calcular desvios em relação a
média do estado.

populacao %>%
mutate(
prop = `População` * 100 / sum(`População`),
desvio = `População` - mean(`População`)
)

## # A tibble: 853 x 9
## IBGE2 IBGE Munic População Ano PIB Intermediaria prop desvio
## <dbl> <dbl> <chr> <dbl> <dbl> <dbl> <chr> <dbl> <dbl>
## 1 10 310010 Abadia dos~ 6972 2017 3.34e7 Uberlândia 0.0331 -17695.
## 2 20 310020 Abaeté 23223 2017 9.62e7 Divinópolis 0.110 -1444.
## 3 30 310030 Abre Campo 13465 2017 2.91e7 Juíz de Fora 0.0640 -11202.
## 4 40 310040 Acaiaca 3994 2017 2.52e6 Juíz de Fora 0.0190 -20673.

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
153

## 5 50 310050 Açucena 9575 2017 1.53e7 Ipatinga 0.0455 -15092.


## 6 60 310060 Água Boa 13600 2017 3.00e7 Teófilo Otoni 0.0646 -11067.
## 7 70 310070 Água Compr~ 2005 2017 7.48e7 Uberaba 0.00953 -22662.
## 8 80 310080 Aguanil 4448 2017 1.54e7 Varginha 0.0211 -20219.
## 9 90 310090 Águas Form~ 19166 2017 1.12e7 Teófilo Otoni 0.0911 -5501.
## 10 100 310100 Águas Verm~ 13477 2017 4.81e7 Teófilo Otoni 0.0641 -11190.
## # ... with 843 more rows

Outras funções trabalham sobre vetores sem tentar reduzí­los a um único valor. Ou seja, essas
funções pegam vetores (ou colunas) como entrada (input) e retornam um vetor como resultado
(output), ao invés de retornarem um único valor. Todos os operadores aritméticos são vetorizados
dessa forma. Eu poderia por exemplo, usar a função left_join()2 para trazer rapidamente à nossa
tabela de população, o PIB de cada município no ano de 2017, e calcular o PIB per capita destes
municípios.
Outro exemplo, seria se eu usasse if_else() para aplicar uma operação sobre um grupo específico
de municípios, e aplicar uma outra operação sobre os municípios restantes. No exemplo logo abaixo,
eu crio um teste onde if_else() vai conferir se o valor daquela linha na coluna IBGE, pode ser
encontrado dentre os códigos dos cinquenta municípios mais populosos do estado (definidos na
coluna IBGE de munic_50_mais_populosos). Quando o if_else() encontrar um município que
está entre esses cinquenta mais populosos, ele aplica a operação definida no segundo argumento
da função (true), que simplesmente nos retorna o PIB per capita (valor da coluna PIB_per_cap)
do município. Caso o município não esteja entre os cinquenta mais populosos, ele preenche aquela
linha com um valor “não disponível”, mais especificamente um NA do tipo double (NA_real_).

munic_50_mais_populosos <- populacao %>%


arrange(desc(`População`)) %>%
head(n = 50)

populacao %>%
left_join(
PIB_municipal %>% filter(Ano == 2017),
by = "IBGE"
) %>%
mutate(
PIB_per_cap = PIB / `População`,
PIB_50_populosos = if_else(
IBGE %in% munic_50_mais_populosos$IBGE,
PIB_per_cap,
NA_real_
2
Vou descrever essa função em maiores detalhes na aula sobre dados relacionais. Por enquanto, pense ela como uma
irmã do PROCV() no Excel, que realiza basicamente o mesmo trabalho dela.

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
154

)
)

## # A tibble: 853 x 10
## IBGE2 IBGE Munic População Ano.x Intermediaria Ano.y PIB PIB_per_cap
## <dbl> <dbl> <chr> <dbl> <dbl> <chr> <chr> <dbl> <dbl>
## 1 10 310010 Abad~ 6972 2017 Uberlândia 2017 3.34e7 4789.
## 2 20 310020 Abae~ 23223 2017 Divinópolis 2017 9.62e7 4142.
## 3 30 310030 Abre~ 13465 2017 Juíz de Fora 2017 2.91e7 2165.
## 4 40 310040 Acai~ 3994 2017 Juíz de Fora 2017 2.52e6 631.
## 5 50 310050 Açuc~ 9575 2017 Ipatinga 2017 1.53e7 1593.
## 6 60 310060 Água~ 13600 2017 Teófilo Otoni 2017 3.00e7 2205.
## 7 70 310070 Água~ 2005 2017 Uberaba 2017 7.48e7 37292.
## 8 80 310080 Agua~ 4448 2017 Varginha 2017 1.54e7 3472.
## 9 90 310090 Água~ 19166 2017 Teófilo Otoni 2017 1.12e7 586.
## 10 100 310100 Água~ 13477 2017 Teófilo Otoni 2017 4.81e7 3568.
## # ... with 843 more rows, and 1 more variable: PIB_50_populosos <dbl>

populacao <- read_csv2("populacao.csv")

## Using ',' as decimal and '.' as grouping mark. Use read_delim() for more control.

## Parsed with column specification:


## cols(
## IBGE2 = col_double(),
## IBGE = col_double(),
## Munic = col_character(),
## População = col_double(),
## Ano = col_double(),
## PIB = col_double(),
## Intermediaria = col_character()
## )

Perceba que em if_else(), eu estou me referindo a uma coluna que acaba de ser criada
(PIB_per_cap) no mesmo mutate(). Ou seja, podemos em um mesmo mutate(), construir uma
coluna e utilizá­la como base de cálculo para outra coluna. Apesar dessa liberdade, eu recomendo
que você crie no máximo quatro colunas em um mesmo mutate(), para evitar erros de memória
(seu computador pode fazer muita coisa ao mesmo tempo, mas tudo tem um limite). Além disso,
um bloco muito grande dentro de mutate() pode se tornar confuso de se ler. Por isso, caso você
tenha que criar mais colunas, eu recomendo abrir um pipe e adicionar um novo mutate() em
seguida.

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
155

Agora, pode ser que você não queira necessariamente criar uma nova coluna em sua tabela. Talvez
você apenas queira utilizar as colunas de seu data.frame para calcular um vetor, ou um conjunto
de colunas, e não está interessado em manter o restante de seu data.frame. Se este é o seu caso,
você pode utilizar a função transmute(), que realiza exatamente o mesmo trabalho da mutate(),
mas que lhe retorna apenas a coluna calculada (ou colunas calculadas).

4.6.0.1 Uma dica rápida sobre valores não disponíveis

Na linguagem R, possuímos alguns valores especiais. Por exemplo, se você calcular no console a
divisão entre 1 e 0, ao invés de retornar um erro, o console irá lhe retornar o valor Inf, que se refere
a infinito. Mas se você tentar dividir 0 por ele mesmo, o console vai lhe retornar NaN, que significa
“not a number”, ou seja, o valor resultante da divisão não é um número.
Um outro valor especial, seria o NA, que significa not avaliable. Este valor é geralmente resultado de
observações não disponíveis em sua base de dados, ou então, quando você realiza alguma conversão,
por exemplo, converter valores de texto para números com as.double(). Se você está importando
um arquivo do Excel, onde em sua tabela há células vazias, essas células vazias serão preenchidas
em um data.frame do R, com valores NA.
Porque estou falando desses valores? Porque caso você, por exemplo, calcule a soma de uma coluna
que contém um NA, o resultado da operação será um NA. Da mesma forma, se a coluna possui um
valor NaN, o resultado será um valor NaN.

sum(c(1, 2, 3, NA, 4))

## [1] NA

Por isso, é importante que você saiba se a sua coluna contém algum destes valores especiais, para
que você possa tratar apropriadamente deles. Várias funções que operam sobre a coluna e retornam
um único valor (como sum() e mean()), possuem um argumento na.rm, que define se a função
deve ignorar esses valores especiais em seus cálculos. Portanto, caso a sua coluna possua esses
valores especiais, e você precisa ignorá­los, sete este argumento para TRUE.

sum(c(1, 2, 3, NA, 4), na.rm = TRUE)

## [1] 10

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
156

4.7 Agrupando dados e gerando estatísticas sumárias com


group_by() e summarise()
Essa é uma funcionalidade importante, pois muitas vezes possuímos grupos (macrorregião do mu­
nicípio, países, faixas etárias, cores de pele, setores e atividades econômicas, …) em nossos dados, e
queremos que as diversas operações que aplicarmos sobre os dados respeitem esses grupos. Dito de
outra forma, várias de nossas análises se encaixam em uma estratégia split­apply­combine (dividir­
aplicar­combinar), onde separamos os nossos dados em diferentes grupos, aplicamos uma operação
sobre cada uma dessas partes separadamente, e em seguida reunimos os resultados novamente em
um mesmo lugar.
O pacote dplyr oferece uma função chamada group_by(), que torna essa estratégia extremamente
simples de ser aplicada. Há várias outras alternativas, como as funções do pacote data.table (que
são mais rápidas do que as funções de dplyr), ou a função agreggate() do pacote base do R.
Apesar de muitas opções, a função group_by() é certamente a mais intuitiva e fácil de se aplicar.
Por exemplo, o IBGE (Instituto Brasileiro de Geografia e Estatística) possui diversas divisões terri­
toriais do Brasil, e uma delas são as regiões intermediárias. Podemos voltar a nossa tabela contendo
a população de cada município do estado de Minas Gerais, e incluir nesses dados, uma coluna que
defina qual região intermediária de Minas Gerais aquele município pertence. Para isso, utilizo no­
vamente a função left_join() para trazer essa informação da tabela regioes_intermed.

populacao_intermed <- populacao %>%


left_join(regioes_intermed, by = "IBGE2")

populacao_intermed

## # A tibble: 853 x 7
## IBGE2 IBGE Munic População Ano PIB Intermediaria
## <dbl> <dbl> <chr> <dbl> <dbl> <dbl> <chr>
## 1 10 310010 Abadia dos Dourados 6972 2017 33389769 Uberlândia
## 2 20 310020 Abaeté 23223 2017 96201158 Divinópolis
## 3 30 310030 Abre Campo 13465 2017 29149429 Juíz de Fora
## 4 40 310040 Acaiaca 3994 2017 2521892 Juíz de Fora
## 5 50 310050 Açucena 9575 2017 15250077 Ipatinga
## 6 60 310060 Água Boa 13600 2017 29988906 Teófilo Otoni
## 7 70 310070 Água Comprida 2005 2017 74771408 Uberaba
## 8 80 310080 Aguanil 4448 2017 15444038 Varginha
## 9 90 310090 Águas Formosas 19166 2017 11236696 Teófilo Otoni
## 10 100 310100 Águas Vermelhas 13477 2017 48088397 Teófilo Otoni
## # ... with 843 more rows

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
157

Portanto, ao definir no exemplo abaixo que esses municípios estão agrupados por região interme­
diária, através da função group_by(), eu estou mudando a unidade de análise dos meus dados.
Dessa forma, todas as ações que eu aplicar sobre esses dados, serão aplicados “por grupo”, ou sobre
cada grupo separadamente. No exemplo abaixo, eu calculo novamente a proporção da população
de cada município, sobre o total da população. Porém agora, o cálculo não será sobre o total da
população do estado inteiro, mas sim, sobre o total da população da região intermediária a qual o
município pertence. Da mesma forma, o desvio da população em relação a média, será calculado
em relação a média da região intermediária correspondente.

populacao_intermed %>%
group_by(Intermediaria) %>%
mutate(
prop = `População` * 100 / sum(`População`),
desvio = `População` - mean(`População`)
)

## # A tibble: 853 x 9
## # Groups: Intermediaria [13]
## IBGE2 IBGE Munic População Ano PIB Intermediaria prop desvio
## <dbl> <dbl> <chr> <dbl> <dbl> <dbl> <chr> <dbl> <dbl>
## 1 10 310010 Abadia dos ~ 6972 2017 3.34e7 Uberlândia 0.600 -41424.
## 2 20 310020 Abaeté 23223 2017 9.62e7 Divinópolis 1.79 1901.
## 3 30 310030 Abre Campo 13465 2017 2.91e7 Juíz de Fora 0.577 -2525.
## 4 40 310040 Acaiaca 3994 2017 2.52e6 Juíz de Fora 0.171 -11996.
## 5 50 310050 Açucena 9575 2017 1.53e7 Ipatinga 0.937 -13661
## 6 60 310060 Água Boa 13600 2017 3.00e7 Teófilo Otoni 1.11 -610.
## 7 70 310070 Água Compri~ 2005 2017 7.48e7 Uberaba 0.250 -25595.
## 8 80 310080 Aguanil 4448 2017 1.54e7 Varginha 0.272 -15487.
## 9 90 310090 Águas Formo~ 19166 2017 1.12e7 Teófilo Otoni 1.57 4956.
## 10 100 310100 Águas Verme~ 13477 2017 4.81e7 Teófilo Otoni 1.10 -733.
## # ... with 843 more rows

Você pode identificar se os seus dados estão ou não agrupados, ao olhar para o cabeçalho da tabela,
onde estão dispostas algumas informações como a sua dimensão (no nosso caso, é uma tabela de
853x7, ou 853 linhas e 7 colunas), e os seus grupos. Vemos que os grupos desses dados se encontram
na coluna Intermediaria, e que há 13 grupos diferentes definidos nessa coluna.
Agora, para dar um contexto atual a nossa análise, vou usar o trabalho que eu (Pedro) e outros
pesquisadores com os quais trabalho na Fundação João Piheiro (FJP), temos desempenhado sobre
as estatísticas da COVID­19. Resumindo, a FJP tem oferecido parte do seu corpo técnico para
dar suporte a Secretaria Estadual de Saúde no monitoramento das estatísticas de contaminação e
impacto do vírus.

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
158

Uma atividade muito comum com os dados de COVID­19 seria gerar uma variável contendo a vari­
ação diária do número de casos e mortes, já que esses números chegam muitas vezes já acumulados.
Na tabela abaixo, temos os números acumulados de mortes e de casos confirmados pelo vírus em
cada um dos 27 estados brasileiros, e em cada dia desde o início da pandemia.

covid

## # A tibble: 3,625 x 4
## data estado casos mortes
## <date> <chr> <dbl> <dbl>
## 1 2020-03-17 AC 3 0
## 2 2020-03-18 AC 3 0
## 3 2020-03-19 AC 4 0
## 4 2020-03-20 AC 7 0
## 5 2020-03-21 AC 11 0
## 6 2020-03-22 AC 11 0
## 7 2020-03-23 AC 17 0
## 8 2020-03-24 AC 21 0
## 9 2020-03-25 AC 23 0
## 10 2020-03-26 AC 23 0
## # ... with 3,615 more rows

Como eu disse, a intenção é criarmos uma coluna com a variação diária desses números que estão
acumulados. Como a base está ordenada em ordem crescente dos dias em cada estado, podemos
utilizar a função lag() para pegar em cada linha, o valor da linha anterior e simplesmente subtrair­
mos um valor do outro. Porém, temos que fazer essa operação separada por cada estado, pois se
não, na linha do primeiro dia de cada estado, será utilizado o valor de casos/mortes do último dia
do estado anterior. É como se no dia 01 de abril do estado de São Paulo, eu estivesse usando no
cálculo dessa variação, o número de casos do dia 31 de junho do estado que vem antes dele na base
(no caso, o estado de Sergipe). Por isso, temos que dar a coluna estado para o group_by(), para
que mutate() realize essa operação para cada estado separadamente.

covid %>%
group_by(estado) %>%
mutate(
casos_var = casos - lag(casos),
mortes_var = mortes - lag(mortes)
)

## # A tibble: 3,625 x 6
## # Groups: estado [27]

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
159

## data estado casos mortes casos_var mortes_var


## <date> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 2020-03-17 AC 3 0 NA NA
## 2 2020-03-18 AC 3 0 0 0
## 3 2020-03-19 AC 4 0 1 0
## 4 2020-03-20 AC 7 0 3 0
## 5 2020-03-21 AC 11 0 4 0
## 6 2020-03-22 AC 11 0 0 0
## 7 2020-03-23 AC 17 0 6 0
## 8 2020-03-24 AC 21 0 4 0
## 9 2020-03-25 AC 23 0 2 0
## 10 2020-03-26 AC 23 0 0 0
## # ... with 3,615 more rows

Agora que você viu como o group_by() funciona, vamos partir para a função summarise(). Essa
função reduz o seu data.frame em poucas linhas, ou dito de outra forma, essa função produz um
novo data.frame onde teremos uma linha para cada combinação possível dentro das variáveis
de grupo (as variáveis que você definiu em group_by()). Você provavelmente está processando
essa descrição, e pensando porque diabos você gostaria de reduzir o seu data.frame por grupo. O
principal motivo pelo qual você usaria a função summarise(), seria para produzir tabelas com as
estatísticas sumárias ou descritivas destes grupos.
Ou seja, você irá utilizar o group_by() para definir os grupos de seus dados, e a função
summarise() para gerar um novo data.frame com uma linha para cada combinação possível
dentro da variável de grupo, onde você pode alocar o valor da estatística sumária de cada um desses
grupos. Você poderia utilizar este conjunto para saber, por exemplo, quantos carros na tabela mpg,
existem em cada grupo da variável cyl. Ou você pode voltar a base populacao_intermed, e
descobrir qual é a população total, a população média, e o número de municípios em cada um das
treze regiões intermediárias de Minas Gerais.

mpg %>%
group_by(cyl) %>%
summarise(contagem = n())

## `summarise()` ungrouping output (override with `.groups` argument)

## # A tibble: 4 x 2
## cyl contagem
## <int> <int>
## 1 4 81
## 2 5 4
## 3 6 79
## 4 8 70

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br
160

populacao_intermed %>%
group_by(Intermediaria) %>%
summarise(
Total = sum(`População`),
`Média` = mean(`População`),
`Número de municípios` = n()
)

## `summarise()` ungrouping output (override with `.groups` argument)

## # A tibble: 13 x 4
## Intermediaria Total Média `Número de municípios`
## <chr> <dbl> <dbl> <int>
## 1 Barbacena 772694 15769. 49
## 2 Belo Horizonte 6237890 84296. 74
## 3 Divinópolis 1300658 21322. 61
## 4 Governador Valadares 771775 13306. 58
## 5 Ipatinga 1022384 23236 44
## 6 Juíz de Fora 2334530 15990. 146
## 7 Montes Claros 1673263 19457. 86
## 8 Patos de Minas 819435 24101. 34
## 9 Pouso Alegre 1289415 16118. 80
## 10 Teófilo Otoni 1222050 14210. 86
## 11 Uberaba 800412 27600. 29
## 12 Uberlândia 1161513 48396. 24
## 13 Varginha 1634643 19935. 82

Fundação João Pinheiro


Alameda das Acácias, 70, São Luiz, Belo Horizonte – MG
www.fjp.mg.gov.br

Você também pode gostar