library(dplyr)
library(stringr)
library(stringi)
library(janitor)
library(sidrar)
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. 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. |
Rename
O básico
Os pacotes utilizados neste tutorial são listados abaixo.
Antes de mais nada, vamos importar uma base de dados qualquer do IBGE usando o pacote sidrar
. A base importada mostra o custo médio m² de construção no Brasil da pesquisa SINAPI.
<- sidrar::get_sidra(2296, period = "202201-202301") dat
Nível Territorial (Código) | Nível Territorial | Unidade de Medida (Código) | Unidade de Medida | Valor |
---|---|---|---|---|
1 | Brasil | 38 | Reais | 1525.48 |
1 | Brasil | 38 | Reais | 915.79 |
1 | Brasil | 38 | Reais | 609.69 |
1 | Brasil | 30 | Número-índice | 763.46 |
1 | Brasil | 30 | Número-índice | 693.71 |
1 | Brasil | 30 | Número-índice | 962.09 |
A função rename
serve para trocar o nome de uma coluna seguindo a sintaxe:
rename(nome_novo = nome_velho)
A forma mais imediata de utilizar a função é simplesmente:
<- rename(dat, valor = Valor) dat_renamed
Nível Territorial (Código) | Nível Territorial | Unidade de Medida (Código) | Unidade de Medida | valor |
---|---|---|---|---|
1 | Brasil | 38 | Reais | 1525.48 |
1 | Brasil | 38 | Reais | 915.79 |
1 | Brasil | 38 | Reais | 609.69 |
1 | Brasil | 30 | Número-índice | 763.46 |
1 | Brasil | 30 | Número-índice | 693.71 |
1 | Brasil | 30 | Número-índice | 962.09 |
Para renomear múliplas colunas, segue-se uma lógica similar, separando os argumentos adicionais por vírgulas. Note que somos forçados a utilizar o sinal ``
pois o nome de algumas colunas contêm acentos.
<- rename(
dat_renamed
dat,valor = Valor,
nivel_territorial = `Nível Territorial`,
code_unit = `Unidade de Medida (Código)`
)
Nível Territorial (Código) | nivel_territorial | code_unit | Unidade de Medida | valor |
---|---|---|---|---|
1 | Brasil | 38 | Reais | 1525.48 |
1 | Brasil | 38 | Reais | 915.79 |
1 | Brasil | 38 | Reais | 609.69 |
1 | Brasil | 30 | Número-índice | 763.46 |
1 | Brasil | 30 | Número-índice | 693.71 |
1 | Brasil | 30 | Número-índice | 962.09 |
Alternativamente, pode-se escrever os nomes usando aspas como no código abaixo.
<- rename(
dat_renamed
dat,valor = "Valor",
nivel_territorial = "Nível Territorial",
code_unit = "Unidade de Medida (Código)"
)
Literalmente reescrever o nome das colunas pode ser bastante enfadonho. Há duas maneiras mais interessantes de renomear colunas: (1) utilizando uma função; e (2) utilizando um vetor de “swap”. A primeira abordagem, em geral, é mais simples e pode-se aproveitar funções úteis pré-existentes para manipulação de strings como as do pacote stringr
. A segunda abordagem exige mais esforço manual e é recomendada quando não existem regras simples para renomear as colunas.
Boas práticas
Os nomes das colunas de um data.frame
devem:
- Ser únicos (não-duplicados) e evitar caracteres maiúsculos.
- Não devem incluir caracteres especiais (e.g. !*&@%), nem começar com um número ou caractere especial. Também é recomendável evitar o uso de acentos (é, ç, à, etc.).
- Evitar espaços em branco, que devem ser substituídos por
_
ou omitidos (e.g.PIB Agro
deve ser reescrito comopibAgro
oupib_agro
. - Evitar nomes “reservados” como
for
,in
,TRUE
,if
, etc. Estas palavras tem tratamento especial dentro do R e não se deve utilizá-las como nome de coluna1.
Existe uma lógica bastante simples para seguir estas convenções: nomes sintaticamente válidos facilitam (e, em alguns casos, possibilitam) a seleção de colunas. É muito mais simples escrever dat$pib_agro
do que dat$`PIB Agrícola (R$)`
. Abaixo listo alguns exemplos de nomes e sugestões de como melhorá-los.
# Ruim
<- c("PIB Agrícola", "INFLAÇÃO %", "Dia do mês", "!Nome D@ Coluna#", "If Buyer")
nomes
# Correto
<- c("pib_agricola", "pib_agro")
n1 <- c("inflacao_percent", "inflacao_pct", "inflacaoPct")
n2 <- c("dia_do_mes", "DiaDoMes")
n3 <- c("nome_da_coluna", "NomeDaColuna")
n4 <- c("is_buyer", "IsBuyer") n5
Usando funções
A maneira mais simples de entender o uso de funções para renomear colunas é através de exemplos. Para tornar os exemplos mais sucintos vamos focar apenas no nome das colunas através da função names
. Esta abordagem torna mais claro o fato de que os nomes das colunas de um data.frame
são, essencialmente, um vetor de texto.
names(dat)
[1] "Nível Territorial (Código)" "Nível Territorial"
[3] "Unidade de Medida (Código)" "Unidade de Medida"
[5] "Valor" "Brasil (Código)"
[7] "Brasil" "Mês (Código)"
[9] "Mês" "Variável (Código)"
[11] "Variável"
Para aplicar uma função sobre o nome de todas as colunas da base de dados dat
usa-se rename_with
, uma variação da função rename
. A sintaxe da função é simples: rename_with(dados, fn)
. No caso abaixo converte-se o nome de todas as colunas para maiúculo.
<- rename_with(dat, toupper)
dat_renamed names(dat_renamed)
[1] "NÍVEL TERRITORIAL (CÓDIGO)" "NÍVEL TERRITORIAL"
[3] "UNIDADE DE MEDIDA (CÓDIGO)" "UNIDADE DE MEDIDA"
[5] "VALOR" "BRASIL (CÓDIGO)"
[7] "BRASIL" "MÊS (CÓDIGO)"
[9] "MÊS" "VARIÁVEL (CÓDIGO)"
[11] "VARIÁVEL"
Abaixo mostro algumas funções úteis para renomear colunas.
#> Converte para minúsculo
rename_with(dat, tolower) |> names()
[1] "nível territorial (código)" "nível territorial"
[3] "unidade de medida (código)" "unidade de medida"
[5] "valor" "brasil (código)"
[7] "brasil" "mês (código)"
[9] "mês" "variável (código)"
[11] "variável"
#> Converte texto para um formato de 'título'
rename_with(dat, stringr::str_to_title) |> names()
[1] "Nível Territorial (Código)" "Nível Territorial"
[3] "Unidade De Medida (Código)" "Unidade De Medida"
[5] "Valor" "Brasil (Código)"
[7] "Brasil" "Mês (Código)"
[9] "Mês" "Variável (Código)"
[11] "Variável"
#> Remove acentos
rename_with(dat, ~stringi::stri_trans_general(.x, id ="latin-ascii")) |> names()
[1] "Nivel Territorial (Codigo)" "Nivel Territorial"
[3] "Unidade de Medida (Codigo)" "Unidade de Medida"
[5] "Valor" "Brasil (Codigo)"
[7] "Brasil" "Mes (Codigo)"
[9] "Mes" "Variavel (Codigo)"
[11] "Variavel"
Pode-se criar uma função customizada mais completa para renomear colunas. A função abaixo costuma funcionar bem para colunas problemáticas escritas em português.
<- function(x) {
nice_col_names
#> Remove acentos em geral
<- stringi::stri_trans_general(x, id = "latin-ascii")
x #> Remove pontuação e caracteres especiais (%$#& etc.)
<- stringr::str_replace_all(x, "[[:punct:]]", "")
x #> Remove espaços em branco antes e/ou depois do texto
<- stringr::str_trim(x, side = "both")
x #> Substitui espaços em branco por _
<- stringr::str_replace_all(x, " ", "_")
x #> Converte tudo para minúsculo
<- stringr::str_to_lower(x)
x
#> Avisa se houver nomes duplicados
<- x[duplicated(x)]
check_dups
if (length(check_dups) > 0) {
warning("Duplicated names!")
}
return(x)
}
|>
dat rename_with(nice_col_names) |>
names()
[1] "nivel_territorial_codigo" "nivel_territorial"
[3] "unidade_de_medida_codigo" "unidade_de_medida"
[5] "valor" "brasil_codigo"
[7] "brasil" "mes_codigo"
[9] "mes" "variavel_codigo"
[11] "variavel"
As funções base make.names
e make.unique
também podem ser úteis para ajudar a criar nomes de colunas válidos sem causar grandes alterações.
|>
dat rename_with(make.names) |>
names()
[1] "Nível.Territorial..Código." "Nível.Territorial"
[3] "Unidade.de.Medida..Código." "Unidade.de.Medida"
[5] "Valor" "Brasil..Código."
[7] "Brasil" "Mês..Código."
[9] "Mês" "Variável..Código."
[11] "Variável"
Por fim, vale notar que a função jantior::make_clean_names()
é bastante eficiente em “limpar” o nome de colunas
|>
dat rename_with(janitor::make_clean_names) |>
names()
[1] "nivel_territorial_codigo" "nivel_territorial"
[3] "unidade_de_medida_codigo" "unidade_de_medida"
[5] "valor" "brasil_codigo"
[7] "brasil" "mes_codigo"
[9] "mes" "variavel_codigo"
[11] "variavel"
Renomeando apenas algumas colunas
Eventualmente, pode ser interessante renomear apenas algumas colunas. A função rename_with tem um terceiro argumento opcional que perimite selecionar um subconjunto de colunas que deve ser renomeada utilizando a função definida. A maneira mais simples de selecionar estas colunas é pela sua posição.
<- rename_with(dat, janitor::make_clean_names)
dat_renamed
<- rename_with(
dat_prefix
dat_renamed,~paste0("abs_", .x),
c(1, 2, 3)
)
names(dat_prefix)
[1] "abs_nivel_territorial_codigo" "abs_nivel_territorial"
[3] "abs_unidade_de_medida_codigo" "unidade_de_medida"
[5] "valor" "brasil_codigo"
[7] "brasil" "mes_codigo"
[9] "mes" "variavel_codigo"
[11] "variavel"
Como de praxe, pode-se usar as funções tidyselect
para facilitar a vida:
#> Adiciona um prefixo nas colunas numéricas
rename_with(
dat_renamed,~paste0("num_", .x),
where(is.numeric)
)
#> Adiciona um sufixo nas colunas numéricas
rename_with(
dat_renamed,~paste0(.x, "_num"),
where(is.numeric)
)
#> Converte para maiúsculo as colunas que contem 'codigo' no nome
rename_with(
dat_renamed,::str_to_upper,
stringrcontains("codigo")
)
Usando vetores
Um dos elementos básicos do R
são os vetores. Um vetor, em linhas gerais, é uma coleção de elementos básicos do mesmo tipo. Uma sequência de números é um exemplo de vetor numérico, como c(2, 4, 6, 8)
. Um vetor de texto, similarmente, tem a forma c("abacaxi", "abacate", "tomate")
.
Um tipo especial de vetor é um named vector, um vetor com nomes. Um named vector é um vetor onde cada elemento tem um nome, como c("a" = 2, "b" = 4, "c" = 6, "d" = 8)
. Tipicamente, seleciona-se um elemento específico de um vetor através da sua posição, como em x[2]
ou y[5:10]
. No caso de um named vector, é possível especificar um elemento através do seu nome.
Note que a sintaxe c("A" = "abacaxi", "B = "banana")
é muito similar à sintaxe da função rename
. De fato, pode-se usar um vetor deste tipo para renomear as colunas usando um “swap” vector. Este tipo de vetor funciona como c("nome_novo" = "Nome Velho")
. Antigamente, podia-se simplesmente inserir o vetor dentro da função rename.
<- c("valor" = "Valor", "geo_level" = "Nível Territorial")
swap <- rename(dat, swap) dat_renamed
Warning: Using an external vector in selections was deprecated in tidyselect 1.1.0.
ℹ Please use `all_of()` or `any_of()` instead.
# Was:
data %>% select(swap)
# Now:
data %>% select(all_of(swap))
See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
Como deixa claro o aviso, desde a versão 1.1.0
do tidyselect
este tipo de sintaxe foi descontinuada e agora é preciso usar mais uma função.
all_of
e any_of
Atualmente, é preciso colocar o vetor swap
em uma de duas funções auxiliares: any_of
e all_of
. A primeira função é a mais branda e substitui o nome de todas as colunas que for possível. A segunda função é mais exigente: se qualquer umas das variáveis estiver ausente retorna-se um erro.
Para tornar a distinção mais clara, vale criar um novo vetor com o nome de coluna que não consta na nossa base. Note como a função any_of
retorna o mesmo output, ignorando a coluna inexistente Ano
.
<- c("valor" = "Valor", "geo_level" = "Nível Territorial", "ano" = "Ano")
swap
|>
dat rename(any_of(swap)) |>
names()
[1] "Nível Territorial (Código)" "geo_level"
[3] "Unidade de Medida (Código)" "Unidade de Medida"
[5] "valor" "Brasil (Código)"
[7] "Brasil" "Mês (Código)"
[9] "Mês" "Variável (Código)"
[11] "Variável"
|>
dat rename(all_of(swap)) |>
names()
Error in `all_of()`:
! Can't rename columns that don't exist.
✖ Column `Ano` doesn't exist.
No caso em que se quer renomear exatamente todas as colunas, usa-se a função auxiliar all_of
da seguinte maneira.
<- c(
new_names "geo_code" = "Nível Territorial (Código)",
"geo_level" = "Nível Territorial",
"unit_code" = "Unidade de Medida (Código)",
"unit" = "Unidade de Medida",
"value" = "Valor",
"geo_name_code" = "Brasil (Código)",
"geo_name" = "Brasil",
"month_code" = "Mês (Código)",
"month" = "Mês",
"variable_code" = "Variável (Código)",
"variable" = "Variável"
)
|>
dat rename(all_of(new_names)) |>
names()
[1] "geo_code" "geo_level" "unit_code" "unit"
[5] "value" "geo_name_code" "geo_name" "month_code"
[9] "month" "variable_code" "variable"
Uma maneira mais sucinta de escrever o código acima é aproveitar os nomes pré-existentes da base.
<- c(
new_names "geo_code", "geo_level", "unit_code", "unit", "value", "geo_name_code",
"geo_name", "month_code", "month", "variable_code", "variable"
)
<- names(dat)
swap_names names(swap_names) <- new_names
|>
dat rename(all_of(swap_names)) |>
names()
[1] "geo_code" "geo_level" "unit_code" "unit"
[5] "value" "geo_name_code" "geo_name" "month_code"
[9] "month" "variable_code" "variable"
Na maior parte das aplicações, pode-se usar a função any_of
sem grandes preocupações. Apenas em casos quando maior controle sobre o output for necessário deve-se considerar usar a função all_of
.
Por fim, vale notar que um swap vector pode ter algumas “redundâncias”. Imagine, por exemplo, que temos várias bases de dados com pequenas inconsistências de ortografia. Em alguns casos temos “Nível Territorial (Código)”, mas em outras temos “nível territorial (Código)” e em outras “Nível Territorial (código)”, etc. etc. Pode-se construir um vetor que corrige isto sem grandes dificuldades, pois o nome do named vector não precisa ser único. Isto é muito conveniente quando se cria uma função genérica que limpa uma base de dados.
<- c(
new_names "geo_code" = "Nível Territorial (Código)",
"geo_code" = "Nível Territorial (código)",
"geo_code" = "nível territorial (Código)"
)
|>
dat rename(any_of(new_names)) |>
names()
[1] "geo_code" "Nível Territorial"
[3] "Unidade de Medida (Código)" "Unidade de Medida"
[5] "Valor" "Brasil (Código)"
[7] "Brasil" "Mês (Código)"
[9] "Mês" "Variável (Código)"
[11] "Variável"
Usando funções e vetores
Também é possível fazer um mix de vetores e funções. Aqui, infelizmente, a lógica da ordem do vetor se inverte, o que pode causar grande confusão. No exemplo abaixo, monto uma função que “traduz” alguns termos do português para o inglês. A mágica é feita usando a função stringr::str_replace_all
que troca um termo por outro seguindo a ordem nome_velho = nome_novo
.
<- function(x) {
translate_pt
<- c(
trans "Código" = "code",
"Valor" = "value",
"Nível" = "level",
"Mês" = "month"
)
::str_replace_all(x, trans)
stringr
}
|>
dat rename_with(translate_pt) |>
names()
[1] "level Territorial (code)" "level Territorial"
[3] "Unidade de Medida (code)" "Unidade de Medida"
[5] "value" "Brasil (code)"
[7] "Brasil" "month (code)"
[9] "month" "Variável (code)"
[11] "Variável"
Outros posts da série
Footnotes
Para verificar a lista completa de palavras reservadas consulte
?Reserved
. Vale notar que não se deve criar objetos com estes nomes também, i.e., não se deve fazerTRUE <- c(1, 2, 3)
.↩︎