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. |
select
A função select
serve para selecionar colunas e reduzir a complexidade de uma tabela. Esta função mudou consideravelmente nos últimos anos após a criação de algumas funções auxiliares conhecidas como selection helpers, que fazem parte do tidyselect
. Estas funções poderosas permitem selecionar colunas usando regras lógicas e segundo padrões de texto; isto facilita consideravelmente a tarefa de limpeza de dados. Além disso, a lógica do tidyselect
foi extendida para outros pacotes como tidymodels
, gt
, entre outros.
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 select
serve para selecionar colunas. Adicionalmente, ela também pode renomear as colunas selecionadas. A sintaxe da função é a seguinte
select(dados, coluna1, coluna2, nome_coluna = coluna3)
O código abaixo seleciona três colunas: name_muni
, population
, pib
.
select(tbl, name_muni, population, pib)
# A tibble: 5,570 × 3
name_muni population pib
<chr> <dbl> <dbl>
1 Alta Floresta D'Oeste 21495 570272
2 Ariquemes 96833 2818049
3 Cabixi 5363 167190
4 Cacoal 86895 2519353
5 Cerejeiras 15890 600670
6 Colorado do Oeste 15663 366931
7 Corumbiara 7519 268381
8 Costa Marques 12627 261978
9 Espigão D'Oeste 29397 666331
10 Guajará-Mirim 39386 984586
# ℹ 5,560 more rows
Pode-se selecionar colunas de três maneiras gerais: (1) como expressões (escrevendo o nome delas como se elas fossem objetos); (2) strings; (3) índices (que indicam a sua posição na tabela).
# Selecionando colunas de modo geral
select(tbl, name_muni, population, pib)
# Selecionando colunas usando strings
select(tbl, "name_muni", "population", "pib")
# Selecionando colunas usando vetor de texto
<- c("name_muni", "population", "pib")
sel_cols select(tbl, sel_cols)
# Selecionando colunas usando índices
# Usando índices explicitamente
select(tbl, 2, 8, 15)
# Usando um vetor numérico
# Encontra a posição de todas as colunas que começam com 'pib'
<- grep("^pib_", names(tbl))
inds select(tbl, inds)
Para remover uma coluna usa-se o sinal de menos (-
) ou o operador lógico de negação (!
)1.
select(tbl, !name_muni)
select(tbl, -name_muni)
De modo geral, para facilitar a seleção de colunas, pode-se usar os operadores lógicos convencionais (&
, |
, !
). Por fim, existe também o operador :
que serve para selecionar colunas contíguas.
select(tbl, code_muni:name_region)
Tidyselectors
Básico
Existe um conjunto de funções auxiliares que facilita a seleção de colunas. Estas funções retornam índices a partir de alguma regra. Isto é, elas permitem selecionar colunas com base em algum padrão. O caso mais geral é da função matches
, que seleciona colunas com base em um regex.
# Seleciona as colunas que começam com 'pib_'
select(tbl, matches("^pib_"))
# Seleciona as colunas que terminam com 'muni'
select(tbl, matches("muni$"))
# Seleciona as colunas que contém o termo '_share'
select(tbl, matches("_share"))
A função matches
retorna colunas a partir de algum padrão de texto no nome da coluna. Na linha da filosofia do tidyverse, de transformar tarefas rotineiras em funções específicas e com nomes “intuitivos” há uma série de funções auxiliares que imitam a função matches
:
starts_with()
- seleciona colunas que começam com algum stringends_with()
- seleciona colunas que terminam com algum stringcontains()
- seleciona colunas que contêm algum string
Isto é, podemos reescrever os códigos acima da seguinte maneira
# Seleciona as colunas que começam com 'pib_'
select(tbl, starts_with("pib_"))
# Seleciona as colunas que terminam com 'muni'
select(tbl, ends_with("muni"))
# Seleciona as colunas que contém o termo '_share'
select(tbl, contains("_share"))
all_of
e any_of
Como visto acima, pode-se selecionar colunas com base em um vetor de texto. Nos casos em que é necessário maior controle sobre a seleção, há duas funções auxiliares: any_of
e all_of
. A primeira função faz o match entre o vetor de texto e o nome das colunas e retorna todos os casos positivos; já a segunda função faz o match entre o vetor de texto e o nome das colunas e retorna um resultado somente no caso de todos os matches terem sucesso.
Estas funções também são úteis para evitar potenciais ambiguidades entre o nome de objetos criados com o nome de colunas.
A diferença entre as funções fica mais evidente num exemplo. Considere o caso em que colocamos uma coluna adicional pib_per_capita
que não existe na base de dados. Quando se usa a função all_of
retorna-se todas as colunas onde o match teve sucesso.
<- c("code_muni", "name_muni", "population", "pib", "pib_per_capita")
sel_cols
select(tbl, any_of(sel_cols))
# A tibble: 5,570 × 4
code_muni name_muni population pib
<dbl> <chr> <dbl> <dbl>
1 1100015 Alta Floresta D'Oeste 21495 570272
2 1100023 Ariquemes 96833 2818049
3 1100031 Cabixi 5363 167190
4 1100049 Cacoal 86895 2519353
5 1100056 Cerejeiras 15890 600670
6 1100064 Colorado do Oeste 15663 366931
7 1100072 Corumbiara 7519 268381
8 1100080 Costa Marques 12627 261978
9 1100098 Espigão D'Oeste 29397 666331
10 1100106 Guajará-Mirim 39386 984586
# ℹ 5,560 more rows
A função all_of
é mais exigente e retorna um erro neste caso.
select(tbl, all_of(sel_cols))
Error in `all_of()`:
! Can't subset columns that don't exist.
✖ Column `pib_per_capita` doesn't exist.
Note que, neste caso, isto é equivalente a simplesmente usar o vetor. Esta sintaxe, contudo, não é recomendado e futuramente será descontinuada.
select(tbl, sel_cols)
Error in `select()`:
! Can't subset columns that don't exist.
✖ Column `pib_per_capita` doesn't exist.
Conflitos de nomes
A função select
faz um bom trabalho em resolver situações onde há alguma ambiguidade sobre qual o environment em que se deve avaliar uma expressão. O uso de all_of
e any_of
é recomendado justamente para evitar potenciais ambiguidades. Vale tirar um tempo para entender os exemplos abaixo.
Note que no primeiro caso, a função ncol
é aplicada sobre x
dentro da função select
. A função ncol
é avaliada no environment geral, isto é, ela considera x
como o data.frame
criado no espaço geral e não como uma coluna específica.
No segundo caso, a expressão y
dentro da função select
é interpretada como uma data-expression, isto é, como uma expressão que se refere ao nome de coluna do data.frame
x.
No último caso, a função all_of
indica que a expressão y
deve ser avaliada como uma env-expression, isto é, como uma variável no environment geral, como o vetor de texto criado anteriormente.
<- data.frame(x = 1, y = 2)
x <- c("x", "y")
y
# Retorna as duas colunas, 'x', 'y'
select(x, 1:ncol(x))
# Retorna a segunda coluna, 'y'
select(x, y)
# Retorna as duas colunas, 'x', 'y'
select(x, all_of(y))
Helpers de posição
Há também algumas funções auxilares mais gerais:
everything()
- seleciona todas as colunaslast_col()
- seleciona a última colunagroup_cols()
- seleciona todas as colunas que compõem ogroup
.
A função everything()
tem um comportamento particular quando combinada com outras colunas. A função seleciona todas as colunas, exceto as que foram explicitamente chamadas. Isto facilita bastante o trabalho de rearranjar as colunas dentro de uma mesma base de dados.
# Coloca as colunas pib e pib_industrial na frente das demais colunas
select(tbl, pib, pib_industrial, everything())
# Dropa todas as variáveis
select(tbl, -everything())
Helpers de tipo
Por fim, pode-se selecionar as colunas pela sua classe. Vale lembrar que num data.frame
cada coluna tem uma classe específica. Os exemplos abaixo mostram os casos de aplicação mais simples.
# Seleciona todas as colunas numéricas
select(tbl, where(is.numeric))
# Seleciona todas as colunas tipo character
select(tbl, where(is.character))
# Seleciona todas as colunas tipo factor
select(tbl, where(is.factor))
# Selciona todas as colunas lógicas (i.e. TRUE, FALSE, NA)
select(tbl, where(is.logical))
# Seleciona todas as colunas
select(tbl, where(is.Date))
Essencialmente, o que o código acima faz é aplicar a função selecionada em cada uma das colunas e retornar os casos positivos. É possível criar condições lógicas mais complexas com auxílio do operador ~
(tilde). Os exemplos abaixo mostram como dropar todas as colunas que contém somente NA
e selecionar as colunas com datas (Date
).
# eval: true
<- tibble(
dat lgl = NA,
missing = NA,
lglT = sample(c(TRUE, FALSE), size = 10, replace = TRUE),
dia_mes = seq(as.Date("2000-01-01"), by = "month", length.out = 10),
val = rnorm(10)
)
# "Remove" as colunas que contêm somente NA
select(dat, !where(~all(is.na(.x))))
# Seleciona apenas as colunas de data
select(dat, where(~all(inherits(.x, "Date"))))
Resumindo
Em resumo, temos quatro grupos gerais de tidyselectors
.
- Seleção com base num padrão de texto. (
matches
,starts_with
,ends_with
,contains
) - Seleção com base num vetor de texto. (
all_of
,any_of
) - Seleção com base na “posição”. (
last_col
,everything
,group_cols
) - Seleção com base na “classe”, i.e., numa função que retorna um valor lógico. (
where
)
Estas funções auxiliares são muito importantes pois elas funcionam não somente com o select
mas também com outras funções do pacote dplyr
como mutate
, rename
, summarise
e outras. Estas funções são relativamente recentes e marcam uma mudança considerável em relação às versões <1 do dplyr
, que utilizam sufixos (all
, if
, at
) para diferenciar as funções como select_if
, ou mutate_at
.
Outros posts da série
Veja também
Footnotes
Apesar de ambas as opções serem válidas, recomenda-se utilizar os operadores booleanos ao invés do opreadores de conjuntos. Isto é, deve-se usar
!
ao invés de-
.↩︎