---
title: "Manipulação de dados"
subtitle: "Aula 2"
author: "Bruno Montezano"
institute: "Grupo Alliance
Programa de Pós-Graduação em Psiquiatria e Ciências do Comportamento
Universidade Federal do Rio Grande do Sul"
date: last-modified
date-format: long
lang: pt-br
execute:
echo: true
format:
revealjs:
incremental: true
smaller: true
theme: [default, ../assets/custom.scss]
logo: "../assets/logo_ufrgs.png"
---
## Conteúdo de hoje
- Importação de dados
- Valores ausentes
- Fatores
- Manipulação de dados
- Selecionar colunas
- Criar e modificar colunas
- Filtragem de linhas
- Ordenação de linhas
- Sumarização de dados
- Agrupar e resumir dados
## `tidyverse`
![](../assets/logo_tidyverse.png){fig-align="center" width=45%}
## O que é o `tidyverse`?
O `tidyverse` é uma coleção de pacotes de R desenvolvidos para ciência de dados.
. . .
No curso, vamos usar alguns pacotes do `tidyverse`, como:
- `readr`, `readxl` e `haven`: importação de dados
- `dplyr`: manipulação de dados
- `ggplot2`: visualização de dados
. . .
Para instalar o conjunto de pacotes:
```{r instalar-tidyverse}
#| eval: false
install.packages("tidyverse")
```
## Importando e exportando dados com `readr`
![](../assets/logo_readr.png){fig-align="center" width=45%}
## Caminhos
Um passo essencial na importação de dados é saber onde está o arquivo a ser importado.
. . .
Toda função vai exigir um **caminho**, ou seja, uma **string** que representa
o endereço do arquivo no computador.
. . .
Há duas formas de passar o caminho ao R: através de um **caminho absoluto**
ou **caminho relativo**.
. . .
Antes, vamos entender o que é o **diretório de trabalho**.
## Diretório de trabalho
O diretório de trabalho (*working directory*) é a pasta na qual o R vai
buscar os arquivos ao lê-los ou salvá-los.
. . .
Podemos descobrir que pasta é essa através da função `getwd()`:
```{r getwd}
getwd()
```
A função retorna uma string com o caminho do seu diretório de trabalho.
. . .
O diretório de trabalho pode ser modificado através da função `setwd()`:
```{r setwd}
#| eval: false
setwd("/home/bruno/Documents/Projects/Thesis")
```
## Caminhos absolutos
Caminhos absolutos são aqueles que iniciam na pasta raíz do seu computador.
. . .
Este é o caminho absoluto onde os slides desta aula foram produzidos:
```
"/home/bruno/dox/projects/r-workshop/slides/2_manipulacao"
```
. . .
Em computadores com Windows pode ter uma cara parecida com esta:
```
"C:/Users/Bruno/Documents/Projects/Thesis"
```
. . .
Na maioria dos casos, caminhos absolutos são **má prática**, pois deixam
o código irreprodutível. Se você trocar de computador ou passar o script para
outra pessoa rodar, o código não vai funcionar, pois o caminho absoluto para o
arquivo muito provavelmente será diferente.
## Caminhos relativos
Caminhos relativos são aqueles que iniciam no diretório de trabalho da sua
sessão.
. . .
O diretório de trabalho da sessão usada para produzir os slides é a pasta
`r-workshop`. Olhem o caminho absoluto no slide anterior. Logo, o caminho
relativo para a pasta onde os slides foram produzidos seria apenas
`slides/2_manipulacao`.
. . .
Trabalhar com **projetos no RStudio** ajuda bastante o uso de caminhos
relativos, pois nos incentiva a colocar todos os arquivos da análise dentro da
pasta do projeto. Projetos podem ser criados em *Arquivo > Novo Projeto...*
. . .
Assim, se você usar apenas caminhos relativos e compartilhar a pasta do projeto
com alguém, todos os caminhos existentes nos códigos continuarão a funcionar em
qualquer computador!
## Arquivos de texto delimitados
Uma das formas mais comuns de armazenar dados é em um arquivo de texto delimitado,
como por exemplo: valores separados por vírgula (**.csv**) ou valores separados
por tabulação (**.tsv**).
:::: {.columns}
::: {.column width="50%"}
Aqui estão dados em **.csv**:
```
"id","diagnostico","sexo","idade"
001,"thb","masculino",34
002,"tdm","feminino",25
003,"tdm","masculino",19
004,"thb","feminino",31
005,"tdm","feminino",24
006,"thb","masculino",21
```
:::
::: {.column width="50%"}
E aqui estão dados em **.tsv**:
```
"id" "diagnostico" "sexo" "idade"
001 "thb" "masculino" 34
002 "tdm" "feminino" 25
003 "tdm" "masculino" 19
004 "thb" "feminino" 31
005 "tdm" "feminino" 24
006 "thb" "masculino" 21
```
:::
::::
. . .
Ou poderíamos ter um arquivo em **.txt** separado por barras, por exemplo:
```
"id"/"diagnostico"/"sexo"/"idade"
001/"thb"/"masculino"/34
002/"tdm"/"feminino"/25
003/"tdm"/"masculino"/19
004/"thb"/"feminino"/31
005/"tdm"/"feminino"/24
006/"thb"/"masculino"/21
```
## `readr`
O pacote `readr` possui várias funções para carregar arquivos de texto delimitados:
`read_csv()`, `read_csv2()`, `read_tsv()` e `read_delim()`.
. . .
`readr` possui algumas vantagens:
- É mais rápido!
- Trabalha melhor com data e hora
- Barra de progresso para arquivos grandes
. . .
Também podemos exportar dados em `.csv`, por exemplo:
```{r exportar-csv}
#| eval: false
library(dados)
library(readr)
write_csv(
x = pixar_bilheteria,
file = "/home/bruno/tmp/dados_bilheteria.csv"
)
```
## `haven` e `readxl`
Na saúde, é comum trabalhar com dados provenientes de outros softwares.
. . .
Podemos usar algumas funções dos pacotes `haven` e `readxl` para lê-los:
- Arquivos do SPSS: `read_spss()` do pacote `haven`
- Arquivos do SAS: `read_sas()` do pacote `haven`
- Arquivos do Stata: `read_stata()` do pacote `haven`
- Arquivos do Excel: `read_xlsx()` do pacote `readxl`
. . .
Da mesma forma, podemos exportar dados em `.sav` (formato do SPSS):
```{r exportar-sav}
#| eval: false
library(dados)
library(haven)
write_sav(
data = pixar_bilheteria,
path = "/home/bruno/tmp/dados_bilheteria.sav"
)
```
## Pacote `dplyr`
![](../assets/logo_dplyr.png){fig-align="center" width=45%}
## `dplyr`
O pacote `dplyr` provê um conjunto de funções que nos ajudam nos problemas
mais comuns em manipulação de dados.
- `mutate()`: Adicionar ou modificar variáveis
- `select()`: Selecionar variáveis (colunas)
- `filter()`: Selecionar observações (linhas)
- `summarise()`: Reduzir múltiplos valores a um único resumo
- `arrange()`: Reordenar as observações (linhas)
. . .
O `dplyr` possibilita o uso da função `group_by()` para performar as operações
de forma agrupada.
. . .
Vamos ver cada uma destas funções separadamente.
## Mas antes, *pipes* (`|>`)
A maior parte das funções do `tidyverse` são construídas com o uso do
**pipe** (`|>`) em mente.
. . .
Os atalhos para inserir o pipe são:
`Ctrl + Shift + M` ou `⌘ + Shift + M`.
. . .
Os pipes pegam o objeto da *esquerda* e aplicam a função da *direita*.
Lemos como: "e então...".
```{r exemplo-pipe}
x <- c(2, 7, 40, 11, 21)
sqrt(sum(x)) # Somar valores do vetor x e tirar a raíz quadrada da soma
x |> sum() |> sqrt() # Mesmo cálculo, agora com pipe!
```
. . .
Os pipes nos poupam tempo de digitação, tornam o código legível e permitem o
encadeamento de funções como acima, por isso os usamos o tempo todo quando
manipulamos data frames.
## Usando pipe
Os pipes são mais legíveis quando temos cada função em uma nova linha.
. . .
```{r estrutura-pipe}
#| eval: false
pegue_estes_dados |>
aplicar_primeira_funcao(com = este_valor) |>
aplicar_proxima_funcao(usando = esse_valor) |> ...
```
. . .
O que estiver à esquerda do pipe (ou no exemplo, o que estiver acima) é repassado como
*primeiro argumento* da
função na direita (ou abaixo). Outros argumentos seguem à direita.
## Atribuindo valores ao usar pipe
Ao criar objetos provenientes da saída de funções encadeadas com pipe,
coloque o operador de atribuição (`<-`) no início.
```{r atribuir-com-pipe}
raiz_quadrada_da_soma <- x |>
sum() |>
sqrt()
raiz_quadrada_da_soma
```
. . .
Não importa o tamanho da cadeia de funções, eu recomendo que vocês realizem a
atribuição sempre no topo.
## Vamos colocar a mão na massa no `questionario`
`questionario` é uma base de dados disponibilizada no pacote `dados`.
```{r carregar-questionario}
# install.packages("dados")
library(dados)
questionario
```
Trata-se de uma base de dados do General Social Survey (GSS). Dados de
2000 a 2014.
## Valores ausentes
Valores ausentes ocorrem quando nenhum valor é armazenado para uma variável em uma
observação.
. . .
O R identifica os valores ausentes através do `NA`. Vamos entender as implicações
do `NA`.
. . .
```{r criar-vetor-com-missing-e-media}
vetor_com_missing <- c(25, NA, 13, 44, 12, NA)
mean(vetor_com_missing)
```
. . .
No entanto, existem maneiras de aplicar funções em vetores (ou colunas) com
`NA`s.
```{r media-com-narm}
mean(vetor_com_missing, na.rm = TRUE)
```
. . .
```{r media-narm-na-mao}
mean(vetor_com_missing[!is.na(vetor_com_missing)])
```
## Fatores
**Fatores** são uma classe de dados para categorizar dados e armazenar como **níveis**.
. . .
Variáveis como sexo e diagnóstico psiquiátrico seriam bons exemplos
de fatores.
. . .
```{r vetor-com-fator}
sexo <- factor(c("Masculino", "Feminino", "Feminino", "Masculino", "Masculino"))
sexo
```
```{r printar-questionario}
questionario
```
## Filtrando linhas com `filter()`
Podemos usar a função `filter()` para filtrar as observações da base de dados.
. . .
::: {style="font-size: 0.68em"}
:::: {.columns}
::: {.column width="50%"}
```{r filtrar-divorciado}
library(dplyr)
divorciados <- questionario |>
filter(estado_civil == "Divorciado(a)")
divorciados
```
:::
::: {.column width="50%"}
```{r divorciados-sem-missing-horas-tv}
divorciados |>
filter(!is.na(horas_tv))
```
:::
::::
:::
. . .
No segundo exemplo, `!is.na(horas_tv)` mantém na base de dados apenas as
observações dos divorciados que **não** possuem valores ausentes na coluna
`horas_tv`.
## Ordenando linhas com `arrange()`
Com a função `arrange()`, nós podemos reordenar as observações da base de dados.
. . .
```{r reordenar-questionario}
questionario |>
arrange(ano, desc(idade))
```
. . .
Os dados foram ordenados pelo `ano` de forma ascendente e `idade`
de forma descendente.
## Selecionando colunas com `select()`
Não apenas podemos limitar as linhas, mas podemos incluir colunas específicas (e
colocá-las na ordem listada) usando `select()`.
. . .
```{r selecionar-variaveis-divorciados}
divorciados |>
select(ano, idade, horas_tv)
```
## Removendo colunas com `select()`
Ao invés de selecionar, podemos remover colunas específicas com `select()` usando `-`.
```{r removendo-com-select}
divorciados |>
select(-estado_civil, -renda, -denominacao)
```
## Funções auxiliares para o `select()`
`select()` tem uma série de funções auxiliares como `starts_with()`, `ends_with()`,
e `contains()`, ou pode ser dada uma gama de colunas em sequência `varinicial:varfinal`.
. . .
:::: {.columns}
::: {.column width="50%"}
```{r select-starts-with}
questionario |>
select(ends_with("ao"))
```
:::
::: {.column width="50%"}
```{r select-sequencia}
questionario |>
select(ano:raca)
```
:::
::::
. . .
`?select` para mais detalhes.
## Criando e modificando colunas com `mutate()`
No `dplyr`, nós podemos adicionar ou modificar colunas usando `mutate()`.
. . .
:::: {.columns}
::: {.column width="56%"}
```{r adicionar-colunas-mutate}
divorciados |>
filter(raca == "Branca") |>
select(ano, idade, horas_tv) |>
mutate(idade_em_decadas = idade / 10,
horas_tv_em_minutos = horas_tv * 60)
```
:::
::: {.column width="44%"}
```{r modificar-colunas-mutate}
divorciados |>
filter(religiao == "Protestante") |>
select(ano, idade, horas_tv) |>
mutate(horas_tv = horas_tv * 60)
```
:::
::::
## Agregando dados com `summarise()`
`summarise()` pega suas colunas de dados e computa algo usando todas as linhas.
- Contar a quantidade de linhas
- Calcular a média ou mediana
- Computar a soma
- Obter um valor mínimo ou máximo
. . .
Ou seja, qualquer função que agregue *múltiplos valores* em um *único valor*
(por exemplo, `sd()`, `mean()` ou `max()`) podem ser usadas com o
`summarise()`.
## Exemplo com `summarise()`
Para os divorciados do ano de 2000, vamos captar o número de observações,
a média da idade, a mediana das horas de TV assistidas diariamente e
a amplitude de horas de TV assistidas por dia.
. . .
```{r exemplo-summarise}
divorciados |>
filter(ano == 2000) |>
summarise(n_observacoes = n(),
media_idade = mean(idade, na.rm = TRUE),
mediana_horas_tv = median(horas_tv, na.rm = TRUE),
amplitude_horas_tv = max(horas_tv, na.rm = TRUE) - min(horas_tv, na.rm = TRUE))
```
. . .
Estas novas variáveis foram calculadas usando *todas as linhas* do conjunto de
dados `divorciados`.
## Evitando repetição com `summarise(across())`
Talvez vocês precisem calcular a média e o desvio padrão de um conjunto de
colunas. Com `across()`, coloquem as variáveis a serem calculadas primeiro (usando
`c()`) e coloque as funções a serem usadas em uma `list()`
depois.
. . .
```{r summarise-across}
divorciados |>
filter(ano == 2014) |>
summarise(across(c(idade, horas_tv),
list(media = \(x) mean(x, na.rm = TRUE),
desvio_padrao = \(x) sd(x, na.rm = TRUE))))
```
## Agrupando com `group_by()`
A função `group_by()` muda como as funções operam sobre os dados, em especial,
a `summarise()`.
. . .
Funções usadas após o `group_by()` são computadas dentro de cada grupo como
definido pelas variáveis dadas, em vez de sobre todas as linhas de uma só vez.
. . .
Normalmente, vamos agrupar por variáveis de valores:
- Inteiros
- Fatores
- Caracteres
. . .
E **não** por valores contínuos (números com vírgula).
## Exemplo de `group_by()`
Vamos supor que eu queira saber o número de religiões reportadas pelos
entrevistados, o número de observações (tamanho da amostra) e a média
de horas diárias assistidas de TV em cada ano, ou seja, *agrupado* pelo
ano.
. . .
```{r exemplo-group-by}
questionario |>
group_by(ano) |>
summarise(numero_de_religioes = n_distinct(religiao),
n_observacoes = n(),
media_horas_tv = mean(horas_tv, na.rm = TRUE))
```
## Tarefa de casa
- Escolha uma das bases de dados do pacote `dados` e utilize ao menos uma vez
as funções: `select()`, `mutate()`, `filter()`, `arrange()`, `summarise()` e
`group_by()`
- As bases disponíveis podem ser acessadas no [site de referência do pacote](https://cienciadedatos.github.io/dados/reference/index.html)
- Nos dados `questionario` do pacote `dados`, crie um subconjunto apenas com
as observações do ano de 2014 e armazene em um objeto chamado `dados_2014`
- Dica de leitura: Capítulo 4 de *Data transformation* do livro
[*R for Data Science (segunda edição)*](https://r4ds.hadley.nz/data-transform.html)