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. |
mutate
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(
tbl "https://github.com/viniciusoike/restateinsight/raw/main/static/data/cities_brazil.csv"
)
A função mutate serve para criar novas colunas a partir de colunas pré-existentes. Ela segue a seguinte sintaxe:
mutate(coluna_nova = f(coluna_velha))
onde f()
designa algum transformação que é feita sobre os dados antigos. Em geral, esta transformação é alguma operação matemática (+
, -
, log
, etc.), transformação de classe (e.g. as.numeric
), ou função em geral. Abaixo mostra-se alguns exemplos de transformações simples. Como as saídas ocupam muito espaço, vou omiti-las.
A primeira linha cria uma coluna onde todas as entradas são iguais a 1. O segundo exemplo aplica a função log
sobre a variável pib
. O terceiro exemplo divide a coluna household
por um milhão. Por fim, o quarto exemplo mostra como dropar uma coluna usando a função mutate
.
#> Cria uma coluna onde todas as entradas são iguais a 1
mutate(tbl, id = 1)
#> Cria a coluna 'lpib' igual ao logaritmo natural do 'pib'
mutate(tbl, lpib = log(pib))
#> Cria a coluna hh igual a 'household' dividido por 1 milhão
mutate(tbl, hh = household / 1e6)
#> Dropa a coluna pib
mutate(tbl, pib = NULL)
Vale notar que, assim como a função filter
é mais eficiente juntar todas as operações dentro de um único mutate
:
|>
tbl mutate(
id = 1,
lpib = log(pib),
hh = household / 1e6,
pib = NULL
)
Um fato conveniente da função mutate
é que ela vai criando as colunas sequencialmente, assim é possível fazer diversas transformações numa mesma chamada à função. No caso abaixo, pode-se criar a variável lpibpc
a partir das colunas lpib
e lpop
; similarmente, pode-se criar a coluna lpibs
a partir de pibserv
, que é a soma de pib_services
e pib_govmt_services
|>
tbl mutate(
lpib = log(pib),
lpop = log(population),
#> Criando uma variável a partir de duas colunas criadas anteriormente
lpibpc = lpib - lpop,
pibserv = pib_services + pib_govmt_services,
#> Criando uma variável a partir de duas colunas criadas anteriormente
lpibs = log(pibserv)
)
Grupos
A expressão mutate
sempre é aplicada dentro de grupos. No caso em que não existe um grupo, a expressão é aplicada para todos os dados disponíveis. O código abaixo, por exemplo, encontra a participação percentual do PIB de cada município no PIB brasileiro.
|>
tbl select(name_muni, abbrev_state, pib) |>
mutate(pib_share = pib / sum(pib) * 100) |>
arrange(desc(pib_share))
# A tibble: 5,570 × 4
name_muni abbrev_state pib pib_share
<chr> <chr> <dbl> <dbl>
1 São Paulo SP 748759007 9.84
2 Rio de Janeiro RJ 331279902 4.35
3 Brasília DF 265847334 3.49
4 Belo Horizonte MG 97509893 1.28
5 Manaus AM 91768773 1.21
6 Curitiba PR 88308728 1.16
7 Osasco SP 76311814 1.00
8 Porto Alegre RS 76074563 1.00
9 Guarulhos SP 65849311 0.865
10 Campinas SP 65419717 0.860
# ℹ 5,560 more rows
Já este segundo código encontra a participação percentual do PIB de cada município dentro do seu respectivo estado.
|>
tbl select(name_muni, abbrev_state, pib) |>
mutate(pib_share = pib / sum(pib) * 100, .by = "abbrev_state") |>
arrange(desc(pib_share))
# A tibble: 5,570 × 4
name_muni abbrev_state pib pib_share
<chr> <chr> <dbl> <dbl>
1 Brasília DF 265847334 100
2 Manaus AM 91768773 79.1
3 Boa Vista RR 11826207 73.8
4 Macapá AP 11735557 63.5
5 Rio Branco AC 9579592 58.1
6 Rio de Janeiro RJ 331279902 43.9
7 Fortaleza CE 65160893 39.0
8 Teresina PI 21578875 38.3
9 Porto Velho RO 19448762 37.7
10 Aracaju SE 16447105 36.2
# ℹ 5,560 more rows
Vale notar que a sintaxe .by = "coluna"
é nova e ainda está em fase experimental. Ela substitui a sintaxe mais antiga do group_by
. O código acima é equivalente ao código abaixo.
|>
tbl select(name_muni, abbrev_state, pib) |>
group_by(abbre_state) |>
mutate(pib_share = pib / sum(pib) * 100) |>
ungroup()
Uma das vantagens de usar .by
é que não é necessário usar ungroup
já que os dados são desagrupados automaticamente.
Transformando múltiplas colunas
A função mutate
tem um par importante na função across
, que permite aplicar uma mesma função a múltiplas colunas com facilidade. Imagine o seguinte caso, onde quer-se aplicar a função scale
, que serve para “normalizar” vetores numéricos, em todas as colunas de uma base. Tipicamente, seria necessário escrever e nomear cada coluna
|>
tbl mutate(
scaled_pib = scale(pib),
scaled_pop = scale(population),
scaled_agriculture = scale(pib_agriculture),
scaled_industrial = scale(pib_industrial),
... )
Em linhas gerais, o resultado do código acima pode ser replicado simplesmente com:
|>
tbl mutate(
across(where(is.numeric), scale)
)
A função across
serve para aplicar uma função sobre um subconjunto de colunas seguindo: across(colunas, funcao)
. Ela funciona com os tidyselectors
1, facilitando a seleção de colunas a ser transformadas. Funções mais complexas podem ser utilizadas via função anônima usando o operador ~
2.
O primeiro exemplo abaixo mostra como aplicar a função log
em todas as colunas cujo nome começa com pib
. Já o segundo exemplo mostra como converter todas as colunas do tipo character
para factor
. O terceiro exemplo mostra como converter as colunas de factor
para numeric
utilizando o operador ~
. Os últimos dois exemplos mostram outras aplicações do mesmo operador.
#> Aplica uma transformação log em todas as colunas que começam com pib
mutate(tbl, across(starts_with("pib"), log))
#> Converte todas as colunas de strings para factors
mutate(tbl, across(where(is.character), as.factor))
#> Converte as colunas de factors para numeric
mutate(tbl, across(where(is.factor), ~ as.numeric(as.character(.x))))
#> Divide por pib e multiplica por 100 todas as colunas entre pib_taxes e
#> pib_govmt_services
mutate(tbl, across(pib_taxes:pib_govmt_services, ~.x / pib * 100))
#> Normaliza todas as colunas numéricas
mutate(tbl, across(where(is.numeric), ~ as.numeric(scale(.x))))
Por fim, existe um argumento opcional .names
que permite renomear as novas colunas usando uma sintaxe estilo glue
3. Esta sintaxe tem dois tipos especiais importantes: {.col}
, que faz referência ao nome original da coluna, e {.fn}
, que faz referência ao nome da função utilizada. O exemplo abaixo refina o primeiro caso que vimos acima. Agora aplica-se a função as.numeric(scale(x))
sobre cada uma das colunas numéricas. As novas colunas têm o nome "scaled_NomeOriginalDaColuna"
.
|>
tbl mutate(
across(
where(is.numeric),
~ as.numeric(scale(.x)),
.names = "scaled_{.col}"
) )
O tipo especial {.fn}
é bastante útil com a função summarise
, que permite aplicar uma lista de múltiplas funções simultaneamente. Ainda assim, é possível utilizá-lo com a função mutate
. A sintaxe tem de ser adaptada, pois {.fn}
espera que a função tenha sido passada como uma lista com nomes. No exemplo abaixo, aplica-se a função log
sobre todas as colunas númericas e as colunas resultantes são renomeadas. Vale notar que, na maioria dos casos, não vale a pena utilizar {.fn}
no contexto do mutate
.
|>
tbl mutate(
across(
where(is.numeric),
list("ln" = log),
.names = "{.fn}_{.col}"
) )
Outros argumentos
A função mutate
tem alguns outros argumentos, de uso diverso. Os argumentos .before
e .after
permitem selecionar a posição das novas colunas. O padrão da função é de sempre adicionar as novas colunas ao final do data.frame
. Estes argumentos aceitam o nome de alguma das colunas ou mesmo funções tidyselect. No caso abaixo, cria-se a coluna lpib
que é posta no início do data.frame
.
|>
tbl mutate(
lpib = log(pib),
.before = everything()
|>
) select(1:5)
# A tibble: 5,570 × 5
lpib code_muni name_muni code_state name_state
<dbl> <dbl> <chr> <dbl> <chr>
1 13.3 1100015 Alta Floresta D'Oeste 11 Rondônia
2 14.9 1100023 Ariquemes 11 Rondônia
3 12.0 1100031 Cabixi 11 Rondônia
4 14.7 1100049 Cacoal 11 Rondônia
5 13.3 1100056 Cerejeiras 11 Rondônia
6 12.8 1100064 Colorado do Oeste 11 Rondônia
7 12.5 1100072 Corumbiara 11 Rondônia
8 12.5 1100080 Costa Marques 11 Rondônia
9 13.4 1100098 Espigão D'Oeste 11 Rondônia
10 13.8 1100106 Guajará-Mirim 11 Rondônia
# ℹ 5,560 more rows
O outro argumento opcional é o .keep
que permite controlar quais colunas devem ser preservadas após a aplicação da função mutate
. O padrão da função, naturalmente, é de preservar todas as colunas, isto é, .keep = "all"
. Contudo, pode-se usar .keep = "used"
para manter somente as colunas que foram utilizadas.
|>
tbl mutate(
code_muni = as.character(code_muni),
lpib = log(pib),
.keep = "used"
)
# A tibble: 5,570 × 3
code_muni pib lpib
<chr> <dbl> <dbl>
1 1100015 570272 13.3
2 1100023 2818049 14.9
3 1100031 167190 12.0
4 1100049 2519353 14.7
5 1100056 600670 13.3
6 1100064 366931 12.8
7 1100072 268381 12.5
8 1100080 261978 12.5
9 1100098 666331 13.4
10 1100106 984586 13.8
# ℹ 5,560 more rows
Vale notar que .keep = "used"
sempre preserva as colunas “agrupadoras”.
|>
tbl mutate(
code_muni = as.character(code_muni),
lpib = log(pib),
.by = "code_state",
.keep = "used"
)
# A tibble: 5,570 × 4
code_muni code_state pib lpib
<chr> <dbl> <dbl> <dbl>
1 1100015 11 570272 13.3
2 1100023 11 2818049 14.9
3 1100031 11 167190 12.0
4 1100049 11 2519353 14.7
5 1100056 11 600670 13.3
6 1100064 11 366931 12.8
7 1100072 11 268381 12.5
8 1100080 11 261978 12.5
9 1100098 11 666331 13.4
10 1100106 11 984586 13.8
# ℹ 5,560 more rows
Funções úteis
Abaixo segue uma lista de funções úteis.
|>
tbl mutate(
#> Cria um ranking da variável
rank_pib = rank(pib),
#> Cria um rakning (em percentil) da variável
rank_perc_pib = percent_rank(pib),
#> Agrupa em decis
group_decile_pib = ntile(pib, 10),
#> Cria um id
id = row_number(),
#> Aplica uma transformação condicional a uma condição lógica
lpib = ifelse(pib > 0, log(pib), 1),
#> Aplica uma transformação condicional a múltiplas condições lógicas
type = case_when(
%in% c(11, 12, 13) ~ "grupo_1",
code_state %in% c(14, 15, 16) ~ "grupo_2",
code_state TRUE ~ "outros"
),#> Soma cumulativa
spib = cumsum(pib),
#> Diferença percentual usando o valor imediatamente anterior
diff_pib = pib / lag(pib) - 1,
#> Participação relativa da variável
share_pib = pib / sum(pib, na.rm = TRUE) * 100,
#> Normalizar variável
scaled_pib = as.numeric(scale(pib))
)
Outros posts da série
Footnotes
Para mais detalhes sobre os
tidyselectors
veja o post sobre a funçãoselect
.↩︎A função
across
foi uma mudança significativa de paradigma na evolução dodplyr
. Esta função tornou obsoletas diversas funções que eram distinguidas pelos sufixos (_at
,_all
,_if
). Para mais detalhes veja o post do blog do dplyr.↩︎Do pacote
glue
. Veja mais aqui.↩︎