Manipulação de dados com o dplyr
Introdução
Em aulas anteriores, trabalhamos com dois pacotes do tidyverse: readr e readxl. Com eles, aprendemos a importar dados de arquivos .csv e planilhas do Excel para dentro do R. Também vimos que esses dados são armazenados, em geral, no formato de tibbles, que são uma versão moderna dos data frames tradicionais do R.
Na última aula, aprendemos a escrever funções e a usar estruturas condicionais para descrever regras e automatizar operações. Em análises reais, essas regras raramente são aplicadas a um único valor. Normalmente, precisamos aplicá-las a colunas inteiras e a tabelas completas.
Para isso, utilizaremos o pacote dplyr, também da coleção tidyverse. Ele foi desenvolvido para facilitar a manipulação de dados tabulares, permitindo selecionar colunas, filtrar observações, criar novas variáveis e aplicar funções de forma clara e consistente.
Importando dados
Nesta aula, vamos trabalhar com um compilado global de dados de química mineral de clinopiroxênios do manto (Qin et al. 2022). Esses dados estão disponíveis no banco de dados GEOROC (Lehnert et al. 2000) e podem ser baixados através deste link.
A planilha .xlsx possui três abas, e as três primeiras linhas de cada aba servem como cabeçalho. Desse modo, para importar os dados de elementos maiores adequadamente, devemos executar o seguinte código:
Vamos carregar também o pacote dplyr, que vamos usar durante esta aula. Quando carregamos esse pacote, temos acesso à função glimpse(), que desempenha um papel semelhante ao da função str(), porém mais adequada para trabalhar com tibbles:
Verbos e Pipe
As funções do dplyr são conhecidas como verbos porque representam ações sobre os dados. No geral, os verbos desse pacote seguem a seguinte lógica:
O primeiro argumento da função é sempre um conjunto de dados (um tibble ou data frame).
Os argumentos seguintes indicam o que queremos fazer com esse conjunto de dados.
A função retorna outro tibble como resultado.
O objeto original não é modificado automaticamente.
Vamos entender essas etapas na prática usando o verbo select(), que seleciona colunas de uma tabela. Digamos que não estejamos interessados em todas as variáveis de cpx_majors, mas apenas no nome das amostras e dos valores dos óxidos. Podemos selecionar essas colunas dessa maneira:
Nosso objeto original continua intacto:
Costuma-se usar o operador pipe (|>) com bastante frequência com os verbos do dplyr. Ele pega o objeto da etapa anterior e o usa como primeiro argumento da função seguinte. Por exemplo:
O pipe é especialmente útil quando queremos aplicar mais de uma função em sequência (em cadeia):
Você também poderá encontrar o pipe representado pelo operador %>%. Esse operador foi criado como parte do pacote magrittr do tidyverse, antes do R incluir o pipe nativo (|>) a partir da versão 4.1.
Nesta disciplina, utilizaremos |>, mas é importante saber que %>% ainda é amplamente usado:
Select
Muitos verbos do dplyr têm funções auxiliares. Vamos ver algumas funções que podemos usar em combinação com select().
starts_with() seleciona todas as colunas que começam com o prefixo que especificarmos. Por exemplo, o códgo abaixo seleciona todas as colunas que começam com a letra “T”:
ends_with() seleciona todas as colunas que terminam com o sufixo que especificarmos. Por exemplo, o código abaixo seleciona todas as colunas que terminam com “(WT%)”:
contains() seleciona todas as colunas que contenham o termo que especificarmos. Por exemplo, o código abaixo seleciona todas as colunas que têm um espaço em brano no seu nome:
all_of() seleciona todas as colunas com base em um vetor externo:
Agora, o que acontece quando rodamos o código abaixo?
all_of() é para uma seleção exata. Se quaisquer variáveis no nosso vetor externo estiverem ausentes na nossa tabela, um erro será mostrado.
Para ignorar as variáveis ausentes, podemos usar any_of():
where() aplica uma função à todas as colunas e seleciona apenas aquelas que retornam TRUE.
Por exemplo, se quisermos trabalhar apenas com as colunas que tenham um valor númerico, podemos utilizar a função base is.numeric() dentro de where():
Perceba que usamos funções anônimas dentro de where(), do mesmo modo que fizemos com sapply() na aula passada.
Outras funções bases do R que são importantes conhecermos são all() e any(). Vejamos como essas funções funcionam na prática:
Elas também podem ser usadas dentro de where(). Por exemplo, digamos que queremos selecionar as colunas que tenham pelo menos um dado ausente. Podemos usar o código abaixo:
Filter
Agora que entendemos como a lógica geral dos verbos do pacote dplyr funciona, usando o exemplo do verbo select(), e entendemos como podemos encadeá-los como o operador |>, vamos explorar outros verbos e ampliar as possibilidades de manipulação dos nossos dados.
Digamos que estamos interessados apenas nas análises de clinopiroxênio que foram feitas no núcleo. Podemos selecionar essas observações com o verbo filter():
Assim como fizemos em aula passadas, também podemos combinar condições. Vamos selecionar todas as análises de núcleo que são provenientes de rochas de ambiente intraplaca:
Usando |>, podemos combinar filter() e select(). Por exemplo:
Mutate
O verbo mutate() nos permite criar novas colunas. Vamos usá-lo para criar uma nova coluna SI(WT%) que corresponderá aos valores de SIO2(WT%) multiplicados pelo fator 0.4674:
E se quisermos criar uma nova coluna TOTAL(WT%) que será igual a soma de todos os óxidos? Uma maneira de fazer isso é somando os óxidos, linha por linha, com a função base rowSums().
Mas para isso, rowSums() não pode atuar na tabela toda, visto que nem toda nossas colunas são óxidos.
Uma função auxiliar do dplyr que podemos usar com mutate é pick(), que nos deixa selecionar apenas parte da nossa tabela para ser usada dentro de uma função. Por exemplo:
Agora, digamos que queremos classificar os clinopiroxênios como “Alto Ti” quando apresentarem teor de TiO2 acima de 0.8 wt% e como “Baixo Ti” caso contrário. Para isso, podemos usar a função auxiliar if_else() em combinação com mutate():
if_else() só funciona quando estamos avaliando uma única condição. Se queremos avaliar múltiplas condições, temos que usar case_when().
case_when(
condição1 ~ valor1,
condição2 ~ valor2,
condição3 ~ valor3,
.default = valor_padrão
)Por exemplo, podemos adotar outro sistema de classificação no qual clinopiroxênios com TiO2 acima de 1.5 wt% são classificados como “Alto Ti”, aqueles com TiO2 abaixo de 0.5 wt% como “Baixo Ti”, e valores entre esses limites são considerados “Normal”:
Rename
Como vimos acima, nossa tabela tem nomes de colunas com espaços em branco e símbolos. Por causa disso, para acessá-las com o dplyr temos que escrevê-las dentro de ``.
Para facilitar as etapas seguintes, podemos renomear as colunas para algo que seja mais fácil de trabalhar em R. Por exemplo, vamos renomear os óxidos e algumas outras colunas com rename():
Perceba que assim como os outros verbos do dplyr, rename() não altera o objeto original a menos que salvemos o resultado com o operador <-.
Arrange
O verbo arrange() reordena a tabela de acordo com uma variável de sua escolha. No exemplo abaixo, selecionamos as amostras e os óxidos e reordenamos a tabela de acordo com a coluna SiO2:
Repare que por padrão esse verbo reordena a tabela em ordem crescente da variável escolhida. Para reordenar em ordem decrescente, podemos utilizar a função auxiliar desc():
Summarize
O verbo summarize() é geralmente usado para obter informações estatísticas (por exemplo, média, mediana e desvio padrão) de uma coluna.
O R base tem funções para realizar esses cálculos: mean(), median() e sd(), respectivamente.
Por exemplo, vamos calcular a média da coluna SiO2:
No exemplo acima, calculamos a média de uma coluna. Mas e se quisermos calcular a média de todos os óxidos? Para isso, podemos usar a função auxiliar across().
across() deixa você aplicar uma função para cada coluna que você selecionar. Então em vez de aplicar mean() apenas para coluna SiO2, podemos aplicar mean() para todas as colunas que selecionarmos.
No primeiro argumento de across() selecionamos as colunas, e no segundo argumento, escrevemos uma função anônima:
Note que
across()é diferente depick().pick()apenas seleciona colunas.across(), por outro lado, aplica uma função para cada coluna individualmente. Nós não estamos interessados em aplicar uma função para todos os óxidos como um conjunto. Nós queremos a média de SiO2, a média de TiO2, a média de Al2O3 e assim por diante…
Outra função auxiliar importante que pode ser usada com summarize() é n().
n() nos diz quantas observações temos na nossa tabela. Digamos que queremos saber quantas análises de borda (“rim”) temos no nosso dataset. Podemos combinar filter() e summarize() dessa maneira:
Group by
Outro verbo bastante importante é group_by(). Ele divide a tabela em grupos com base em uma ou mais variáveis. Depois disso, verbos como summarize(), mutate() e filter() passam a agir dentro de cada grupo, não mais na tabela inteira.
O código abaixo mostra a mediana da concentração (wt%) de SiO2 em clinopiroxênios para cada um dos ambientes tectônicos:
Se quisermos saber quantas análises temos de cada ambiente tectônico, podemos usar o seguinte código:
Para deixar o resultado do código acima ainda mais claro, podemos usar arrange() para reordená-lo em ordem decrescente:
Exercício
Para este exercício, use o objeto cpx_majors_renamed que nós criamos anteriormente.
- Nós já mudamos os nomes de algumas colunas, agora mude o nome das outras colunas usando
rename()para que não incluam espaços em branco e símbolos.
Você pode usar _ se necessário. Tente seguir o estilo que usamos anteriormente com Setting, Sample e Type.
- Atualize a coluna
Location(ou o nome que você usou para renomeá-la) usando o verbomutate()para que o nome das localidades sejam apresentados em letra minúscula. Observação: você terá que usar uma função que foi ensinada na aula de tipos e estruturas de dados.
- Usando uma combinação de
filter()esummarize(), qual a mediana do teor de SiO2 de piroxênios de borda (“rim”) de ambientes convergentes (“CONVERGENT MARGIN”)?
- Em qual ambiente tectônico encontramos clinopiroxênios com maior concentração média de TiO2? Dica:
group_by()emean()são úteis para resolver essa questão. Se usarmean(), não esqueça de usar o parâmetrona.rmpara remover dados ausentes.
- Qual o tipo de rocha mais presente na nossa tabela? Use uma combinação de verbos para achar a resposta.
Considerações finais
Na aula de hoje aprendemos a manipular dados tabulares e vimos que a preparação dos dados é uma etapa essencial antes de qualquer análise. Trabalhamos diferentes formas de selecionar, transformar, resumir e organizar informações, entendendo como essas operações fazem parte do fluxo natural de trabalho com dados. Na próxima aula, começaremos a visualizar dados com o ggplot2, dando início a uma nova etapa do curso voltada à exploração e interpretação dos dados por meio de gráficos.