library(dplyr)
library(readr)O novo tidyverse: select
dplyr trouxe nos últimos anos como as funções tidyselectors, que ajudam a selecionar colunas com base em padrões lógicos.
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.
# inserir link aberto
tbl <- ...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
sel_cols <- c("name_muni", "population", "pib")
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'
inds <- grep("^pib_", names(tbl))
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.
sel_cols <- c("code_muni", "name_muni", "population", "pib", "pib_per_capita")
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 `select()`:
ℹ In argument: `all_of(sel_cols)`.
Caused by error in `all_of()`:
! Can't subset elements that don't exist.
✖ Element `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 select 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.
x <- data.frame(x = 1, y = 2)
y <- c("x", "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
dat <- tibble(
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-.↩︎