library(dplyr)
library(readr)
Tidyverse
O tidyverse é uma coleção poderosa de pacotes, voltados para a manipulação e limpeza de dados. Num outro post, discuti alguns aspectos gerais da filosofia destes pacotes que incluem a sua consistência sintática e o uso de pipes. A filosofia geral do tidyverse toma muito emprestado da gramática. As funções têm nomes de verbos que costumam ser intuitivos e são encadeados via “pipes” que funcionam como conectivos numa frase. Em tese, isto torna o código mais legível e até mais didático.
O tidyverse está em constante expansão, novas funcionalidades são criadas para melhorar a performance e capabilidade de suas funções. Assim, é importante atualizar nosso conhecimento destes pacotes periodicamente. Nesta série de posts vou focar nas funções principais dos pacotes dplyr
e tidyr
, voltados para a limpeza de dados.
Alguns verbos
Essencialmente, o dplyr
gira em torno de quatro grandes funções: filter
, select
, mutate
e summarise
. Estas funções fazem o grosso do trabalho de limpeza de dados: filtram linhas, selecionam colunas e transformam os dados. A tabela abaixo resume as principais funções do pacote.
Nome da Função | Tradução | O que faz |
---|---|---|
rename |
Renomear | Modifica o nome das colunas. |
select |
Selecionar | Seleciona as colunas. |
filter |
Filtrar | Filtra/seleciona as linhas segundo alguma condição. |
arrange |
Arranjar/ordenar | Ordena as linhas (crescente/decrescente) segundo alguma variável. |
mutate |
Mutar/transformar | Cria uma nova coluna a partir de outras colunas ou dados. |
summarise |
Sumarizar/resumir | Aplica alguma função sobre as linhas. Cria uma tabela “resumo”. |
group_by |
Agrupar | Agrupa as linhas segundo alguma variável. |
filter
O básico
Os pacotes utilizados neste tutorial são listados abaixo.
Para praticar as funções vamos utilizar uma tabela que traz informações sobre as cidades do Brasil.
<- readr::read_csv(
dat "https://github.com/viniciusoike/restateinsight/raw/main/static/data/cities_brazil.csv"
)
# dat <- select(dat, 1:7, population, population_growth, pib)
A função filter
é talvez uma das que menos mudou ao longo do desenvolvimento do pacote dplyr
. A função serve para filtrar as linhas de um data.frame
segundo alguma condição lógica.
<- filter(dat, population_growth < 0)
filtered_dat
nrow(filtered_dat)
[1] 2399
Os principais operadores lógicos no R:
“Maior que”, “Menor que”:
>
,<
,>=
,<=
E/ou:
&
,|
“Negação”:
!
“Igual a”:
==
“Dentro de”:
%in%
As funções is_*
também são bastante importantes; em particular a função is.na()
é útil para encontrar ou remover observações ausentes.
O exemplo abaixo mostra como filtrar linhas baseado num string. Note que quando se usa múltiplos strings é preciso usar o %in%
.
filter(dat, name_muni == "São Paulo")
filter(dat, name_muni %in% c("São Paulo", "Rio de Janeiro"))
<- c("São Paulo", "Rio de Janeiro")
cities filter(dat, name_muni %in% cities)
filter(dat, name_muni %in% c("São Paulo", "Rio de Janeiro")) |>
print_table()
Para negar a igualdade, basta usar o operador !
. No caso do operador %in%
há duas maneiras válidas de negá-lo: pode-se colocar o ! no começo da expressão ou colocar a expressão inteira dentro de um parêntesis. Eu tendo a preferir a segunda sintaxe.
#> Remove todas as cidades da região Sudeste
filter(dat, name_region != "Sudeste")
#> Remove todas as cidades das regiões Sudeste e Norte
filter(dat, !name_region %in% c("Sudeste", "Norte"))
#> Remove todas as cidades das regiões Sudeste e Norte
filter(dat, !(name_region %in% c("Sudeste", "Norte")))
Em geral, pode-se omitir o operador E (&
), já que se pode concatenar várias condições lógicas dentro uma mesma chamada para a função filter
, separando as condições por vírgulas. Esta sintaxe costuma ser preferida pois ela é mais eficiente do que chamar a função a função filter
múltiplas vezes. Além disso, a escrita do código fica mais limpa, pois é fácil separar as condições em linhas distintas. As três versões do código abaixo geram o mesmo resultado.
# Mais eficiente e mais fácil de ler
<- dat |>
d1 filter(
== "Nordeste",
name_region !(name_state %in% c("Pernambuco", "Piauí")),
!(name_muni %in% c("Natal", "Fortaleza", "Maceió"))
)# Igualmente eficiente, leitura fica um pouco pior
<- dat |>
d2 filter(
== "Nordeste" &
name_region !(name_state %in% c("Pernambuco", "Piauí")) &
!(name_muni %in% c("Natal", "Fortaleza", "Maceió"))
)
# Menos eficiente
<- dat |>
d3 filter(name_region == "Nordeste") |>
filter(!(name_state %in% c("Pernambuco", "Piauí"))) |>
filter(!(name_muni %in% c("Natal", "Fortaleza", "Maceió")))
all.equal(d1, d2)
all.equal(d2, d3)
all.equal(d3, d1)
Relações de grandeza funcionam naturalmente com números. A tabela abaixo mostra todos os municípios com mais do que um milhão de habitantes.
filter(dat, population > 1e6)
name_muni | abbrev_state | population | population_density |
---|---|---|---|
São Paulo | SP | 11.451.245 | 7.528 |
Rio de Janeiro | RJ | 6.211.423 | 5.175 |
Brasília | DF | 2.817.068 | 489 |
Fortaleza | CE | 2.428.678 | 7.775 |
Salvador | BA | 2.418.005 | 3.487 |
Belo Horizonte | MG | 2.315.560 | 6.988 |
Manaus | AM | 2.063.547 | 181 |
Curitiba | PR | 1.773.733 | 4.079 |
Recife | PE | 1.488.920 | 6.804 |
Goiânia | GO | 1.437.237 | 1.971 |
Porto Alegre | RS | 1.332.570 | 2.690 |
Belém | PA | 1.303.389 | 1.230 |
Guarulhos | SP | 1.291.784 | 4.054 |
Campinas | SP | 1.138.309 | 1.433 |
São Luís | MA | 1.037.775 | 1.780 |
Também pode-se usar alguma função que retorne um valor numérico. Nos exemplos abaixo filtra-se apenas os municípios com PIB acima da média e os municípios no top 1% da distribuição do PIB.
filter(dat, pib > mean(pib))
filter(dat, pib > quantile(pib, probs = 0.99))
name_muni | abbrev_state | pib | pib_share_uf |
---|---|---|---|
São Paulo | SP | 748.759.007 | 31 |
Rio de Janeiro | RJ | 331.279.902 | 44 |
Brasília | DF | 265.847.334 | 100 |
Belo Horizonte | MG | 97.509.893 | 14 |
Manaus | AM | 91.768.773 | 79 |
Curitiba | PR | 88.308.728 | 18 |
Osasco | SP | 76.311.814 | 3 |
Porto Alegre | RS | 76.074.563 | 16 |
Guarulhos | SP | 65.849.311 | 3 |
Campinas | SP | 65.419.717 | 3 |
Fortaleza | CE | 65.160.893 | 39 |
Salvador | BA | 58.938.115 | 19 |
Goiânia | GO | 51.961.311 | 23 |
Barueri | SP | 51.254.572 | 2 |
Jundiaí | SP | 51.235.050 | 2 |
Recife | PE | 50.311.002 | 26 |
São Bernardo do Campo | SP | 48.614.342 | 2 |
Duque de Caxias | RJ | 47.153.673 | 6 |
Niterói | RJ | 40.949.495 | 5 |
São José dos Campos | SP | 39.148.012 | 2 |
Paulínia | SP | 38.572.766 | 2 |
Parauapebas | PA | 38.014.863 | 18 |
Uberlândia | MG | 37.631.537 | 6 |
Sorocaba | SP | 36.723.769 | 2 |
Joinville | SC | 36.391.912 | 10 |
Maricá | RJ | 35.618.327 | 5 |
Ribeirão Preto | SP | 35.218.869 | 1 |
Itajaí | SC | 33.084.145 | 9 |
São Luís | MA | 33.074.010 | 31 |
Belém | PA | 30.835.763 | 14 |
Campo Grande | MS | 30.121.789 | 25 |
Contagem | MG | 29.558.094 | 4 |
Santo André | SP | 29.440.477 | 1 |
Piracicaba | SP | 27.172.817 | 1 |
Cuiabá | MT | 26.528.839 | 15 |
Betim | MG | 26.185.005 | 4 |
Caxias do Sul | RS | 25.965.161 | 6 |
Camaçari | BA | 25.697.266 | 8 |
Vitória | ES | 25.473.898 | 18 |
Serra | ES | 25.079.657 | 18 |
Campos dos Goytacazes | RJ | 23.841.837 | 3 |
Maceió | AL | 22.872.756 | 36 |
Natal | RN | 22.729.773 | 32 |
Canaã dos Carajás | PA | 22.522.725 | 10 |
Santos | SP | 22.073.535 | 1 |
São José dos Pinhais | PR | 21.975.612 | 4 |
Londrina | PR | 21.729.852 | 4 |
Teresina | PI | 21.578.875 | 38 |
Florianópolis | SC | 21.312.447 | 6 |
Cajamar | SP | 20.798.646 | 1 |
João Pessoa | PB | 20.766.551 | 30 |
Maringá | PR | 20.005.630 | 4 |
Araucária | PR | 19.724.416 | 4 |
Porto Velho | RO | 19.448.762 | 38 |
São Gonçalo | RJ | 19.002.883 | 3 |
São José do Rio Preto | SP | 18.694.213 | 1 |
Grupos
A função de filtro segue uma regra lógica que é aplicada sobre a tabela como um todo. É possível filtrar dentro de grupos usando o argumento .by = "nome_do_grupo"
.
No código abaixo, novamente filtra-se os municípios com PIB acima da média. No segundo exemplo, contudo, este filtro é aplicado dentro de cada região, segundo a coluna/grupo name_region
. A regra lógica pib > mean(pib)
é aplicada dentro de cada região, isto é, filtra-se todos os municípios que têm PIB superior à média do PIB da sua região.
|> filter(pib > mean(pib))
dat |> filter(pib > mean(pib), .by = "name_region") dat
Vale notar que a a sintaxe .by = "grupo"
ainda está em fase experimental. Ela oferece um substituto mais sucinto à antiga sintaxe que usava a função group_by()
com a vantagem de sempre aplicar a função ungroup()
ao final do processo, isto é, o resultado final da função acima será uma tabela sem grupos. O código acima é equivalente ao código abaixo.
|>
dat group_by(name_region) |>
filter(pib > mean(pib)) |>
ungroup()
Este outro exemplo enfatiza como o resultado da função filter
muda quando é aplicada em diferentes grupos.
|> filter(pib == max(pib))
dat |> filter(pib == max(pib), .by = "name_state") dat
name_muni | abbrev_state | pib | pib_share_uf |
---|---|---|---|
Porto Velho | RO | 19.448.762 | 38 |
Rio Branco | AC | 9.579.592 | 58 |
Manaus | AM | 91.768.773 | 79 |
Boa Vista | RR | 11.826.207 | 74 |
Parauapebas | PA | 38.014.863 | 18 |
Macapá | AP | 11.735.557 | 64 |
Palmas | TO | 9.940.091 | 23 |
São Luís | MA | 33.074.010 | 31 |
Teresina | PI | 21.578.875 | 38 |
Fortaleza | CE | 65.160.893 | 39 |
Natal | RN | 22.729.773 | 32 |
João Pessoa | PB | 20.766.551 | 30 |
Recife | PE | 50.311.002 | 26 |
Maceió | AL | 22.872.756 | 36 |
Aracaju | SE | 16.447.105 | 36 |
Salvador | BA | 58.938.115 | 19 |
Belo Horizonte | MG | 97.509.893 | 14 |
Vitória | ES | 25.473.898 | 18 |
Rio de Janeiro | RJ | 331.279.902 | 44 |
São Paulo | SP | 748.759.007 | 31 |
Curitiba | PR | 88.308.728 | 18 |
Joinville | SC | 36.391.912 | 10 |
Porto Alegre | RS | 76.074.563 | 16 |
Campo Grande | MS | 30.121.789 | 25 |
Cuiabá | MT | 26.528.839 | 15 |
Goiânia | GO | 51.961.311 | 23 |
Brasília | DF | 265.847.334 | 100 |
if_any
e if_all
A função filter
não funciona em conjunção com a função across()
. Esta função foi desenvolvida para funcionar apenas com mutate
e summarise
e aplica uma mesma regra/função sobre múltiplas colunas.
Já a função filter
recebeu duas funções auxiliares: if_any
e if_all
. Elas seguem o mesmo padrão das funções base any
e all
. Estas funções servem para agregar condições lógicas. A função any
, por exemplo, testa múltiplas condições lógicas e retorna um único TRUE
se houver ao menos um TRUE
entre as condições lógicas. Já a função all
retorna um único TRUE
se absolutamente todas as condições lógicas testadas também retornaram TRUE
.
A função if_any
aplica uma mesma regra em múltiplas colunas e retorna todas as linhas que atendem esta regra. No exemplo abaixo
|> filter(if_any(starts_with("pib"), ~ . > 100000)) dat
O exemplo seguinte é mais interessante. Neste caso, todas as variáveis numéricas da tabela são normalizadas (por região) e retorna-se apenas os municípios onde o valor de cada coluna é superior a 1. Como as variáveis estão normalizadas isto é equivalente a retornar os municípios que estão 1 desvio-padrão acima da média da sua região em todos os atributos numéricos considerados.
|>
dat select(-contains("code")) |>
select(where(~all(.x > 0))) |>
mutate(across(where(is.numeric), ~as.numeric(scale(log(.x)))), .by = "name_region") |>
filter(if_all(everything(), ~ . > 1))
# A tibble: 2 × 15
name_muni name_state abbrev_state name_region population city_area
<chr> <chr> <chr> <chr> <dbl> <dbl>
1 Paranaguá Paraná PR Sul 2.32 1.13
2 São José dos Pinhais Paraná PR Sul 3.00 1.28
population_density households dwellers_per_household pib pib_taxes
<dbl> <dbl> <dbl> <dbl> <dbl>
1 1.52 2.24 2.25 2.80 2.81
2 2.10 2.97 1.04 3.27 3.31
pib_added_value pib_industrial pib_services pib_govmt_services
<dbl> <dbl> <dbl> <dbl>
1 2.76 2.36 2.71 2.57
2 3.18 2.76 2.92 3.17
O último exemplo é similar ao anterior. As variáveis numéricas novamente são normalizadas mas desta vez busca-se somente os municípios que estão 3 desvios-padrão, acima da média do seu estado, ou na população ou no PIB.
|>
dat select(-contains("code")) |>
select(where(~all(.x > 0))) |>
mutate(across(where(is.numeric), ~as.numeric(scale(log(.x)))), .by = "name_state") |>
filter(if_any(c(population, pib), ~ . > 3))
name_muni | abbrev_state | population | pib | population_density | pib_services | city_area |
---|---|---|---|---|---|---|
Porto Velho | RO | 3,249 | 3,508 | 1,129 | 2,954 | 2,622 |
Rio Branco | AC | 3,254 | 3,337 | 2,525 | 3,112 | 0,628 |
Manaus | AM | 5,214 | 5,439 | 3,420 | 5,136 | -0,239 |
Boa Vista | RR | 3,299 | 3,310 | 3,059 | 3,172 | -0,709 |
Belém | PA | 4,041 | 3,410 | 3,122 | 3,670 | -0,672 |
Canaã dos Carajás | PA | 0,950 | 3,151 | 0,511 | 2,260 | 0,069 |
Parauapebas | PA | 2,305 | 3,582 | 0,816 | 2,795 | 0,602 |
Araguaína | TO | 3,837 | 3,459 | 2,184 | 3,492 | 1,201 |
Gurupi | TO | 3,062 | 2,857 | 2,255 | 3,018 | 0,381 |
Palmas | TO | 4,467 | 4,166 | 3,269 | 4,073 | 0,580 |
Balsas | MA | 2,045 | 3,190 | -0,898 | 3,117 | 2,899 |
Imperatriz | MA | 3,236 | 3,599 | 2,353 | 3,654 | 0,373 |
São José de Ribamar | MA | 3,103 | 2,412 | 4,270 | 2,681 | -1,894 |
São Luís | MA | 4,845 | 5,096 | 4,541 | 4,903 | -0,581 |
Parnaíba | PI | 3,676 | 3,413 | 3,580 | 3,577 | -0,564 |
Picos | PI | 2,881 | 3,018 | 2,643 | 3,340 | -0,266 |
Teresina | PI | 5,667 | 5,526 | 4,092 | 5,417 | 0,676 |
Uruçuí | PI | 1,464 | 3,101 | -1,185 | 2,756 | 2,604 |
Caucaia | CE | 3,041 | 2,955 | 1,882 | 2,814 | 0,878 |
Fortaleza | CE | 5,212 | 4,963 | 5,137 | 4,922 | -0,641 |
Maracanaú | CE | 2,569 | 3,239 | 3,900 | 3,146 | -1,851 |
Parnamirim | RN | 3,428 | 3,265 | 4,138 | 3,371 | -0,674 |
Mossoró | RN | 3,475 | 3,434 | 1,213 | 3,512 | 2,658 |
Natal | RN | 4,538 | 4,421 | 4,968 | 4,488 | -0,323 |
Cabedelo | PB | 2,206 | 3,090 | 3,771 | 3,125 | -2,247 |
Campina Grande | PB | 4,163 | 4,287 | 2,668 | 4,093 | 1,407 |
João Pessoa | PB | 4,893 | 4,951 | 4,328 | 4,721 | 0,137 |
Santa Rita | PB | 3,070 | 3,029 | 1,490 | 2,768 | 1,645 |
Ipojuca | PE | 1,478 | 3,168 | 0,696 | 2,718 | 0,530 |
Jaboatão dos Guararapes | PE | 3,469 | 3,148 | 2,836 | 3,085 | -0,142 |
Recife | PE | 4,360 | 4,261 | 3,673 | 4,202 | -0,303 |
Arapiraca | AL | 3,021 | 2,913 | 3,084 | 3,187 | 0,645 |
Maceió | AL | 4,588 | 4,318 | 4,478 | 4,537 | 1,190 |
Aracaju | SE | 3,638 | 3,676 | 4,258 | 3,886 | -0,094 |
Camaçari | BA | 3,357 | 4,191 | 2,547 | 3,774 | 0,001 |
Feira de Santana | BA | 4,229 | 3,722 | 2,744 | 3,800 | 0,506 |
Juazeiro | BA | 3,068 | 2,623 | 0,357 | 2,778 | 2,135 |
Luís Eduardo Magalhães | BA | 2,123 | 3,040 | 0,108 | 3,046 | 1,628 |
Salvador | BA | 5,882 | 4,927 | 4,579 | 5,007 | -0,122 |
São Francisco do Conde | BA | 0,853 | 3,510 | 1,627 | 2,947 | -1,059 |
Vitória da Conquista | BA | 3,615 | 3,054 | 1,439 | 3,230 | 1,414 |
Belo Horizonte | MG | 5,103 | 4,613 | 5,077 | 4,620 | -0,186 |
Betim | MG | 3,489 | 3,638 | 3,490 | 3,365 | -0,148 |
Contagem | MG | 3,874 | 3,728 | 4,373 | 3,712 | -0,715 |
Extrema | MG | 1,581 | 3,027 | 1,961 | 3,055 | -0,487 |
Governador Valadares | MG | 3,049 | 2,630 | 1,342 | 2,768 | 1,768 |
Ipatinga | MG | 2,935 | 3,004 | 3,619 | 2,862 | -0,882 |
Juiz de Fora | MG | 3,744 | 3,312 | 2,450 | 3,412 | 1,280 |
Montes Claros | MG | 3,495 | 2,900 | 1,386 | 2,955 | 2,195 |
Nova Lima | MG | 2,270 | 3,072 | 2,118 | 2,792 | 0,073 |
Ribeirão das Neves | MG | 3,282 | 2,320 | 4,007 | 2,358 | -0,944 |
Uberaba | MG | 3,304 | 3,326 | 0,995 | 3,208 | 2,426 |
Uberlândia | MG | 4,003 | 3,907 | 1,752 | 3,795 | 2,332 |
Serra | ES | 3,040 | 2,923 | 2,726 | 2,891 | 0,284 |
Rio de Janeiro | RJ | 3,591 | 3,414 | 2,301 | 3,286 | 1,393 |
Guarulhos | SP | 3,055 | 2,881 | 2,875 | 2,802 | 0,155 |
São Paulo | SP | 4,578 | 4,338 | 3,294 | 4,267 | 2,022 |
Araucária | PR | 2,360 | 3,294 | 2,508 | 2,752 | 0,310 |
Cascavel | PR | 3,108 | 3,016 | 1,810 | 2,989 | 2,310 |
Curitiba | PR | 4,576 | 4,554 | 5,174 | 4,384 | 0,210 |
Foz do Iguaçu | PR | 2,930 | 3,212 | 2,899 | 2,666 | 0,660 |
Londrina | PR | 3,531 | 3,376 | 2,550 | 3,365 | 1,995 |
Maringá | PR | 3,255 | 3,306 | 3,514 | 3,330 | 0,361 |
Ponta Grossa | PR | 3,135 | 3,183 | 1,859 | 2,982 | 2,286 |
São José dos Pinhais | PR | 3,058 | 3,385 | 2,585 | 3,081 | 1,249 |
Florianópolis | SC | 3,322 | 3,046 | 2,472 | 3,052 | 1,339 |
Itajaí | SC | 2,731 | 3,372 | 2,587 | 3,097 | 0,268 |
Joinville | SC | 3,436 | 3,443 | 2,155 | 3,134 | 1,988 |
Canoas | RS | 3,136 | 3,231 | 3,730 | 2,942 | -0,689 |
Caxias do Sul | RS | 3,370 | 3,492 | 1,901 | 3,237 | 1,694 |
Pelotas | RS | 3,083 | 2,722 | 1,637 | 2,718 | 1,669 |
Porto Alegre | RS | 4,230 | 4,316 | 3,739 | 4,169 | 0,561 |
Campo Grande | MS | 4,131 | 3,470 | 2,785 | 3,555 | 1,112 |
Cuiabá | MT | 3,653 | 2,967 | 3,573 | 3,093 | 0,040 |
Anápolis | GO | 3,070 | 3,060 | 2,746 | 3,038 | 0,151 |
Aparecida de Goiânia | GO | 3,297 | 3,039 | 3,874 | 3,094 | -0,966 |
Goiânia | GO | 4,110 | 3,976 | 3,908 | 4,062 | -0,081 |