Manipulação de dados

Aula 2

Bruno Montezano

Grupo Alliance
Programa de Pós-Graduação em Psiquiatria e Ciências do Comportamento
Universidade Federal do Rio Grande do Sul

25 de abril de 2023

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

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:

install.packages("tidyverse")

Importando e exportando dados com readr

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():

getwd()
[1] "/home/bruno/dox/projects/r-workshop/slides/2_manipulacao"

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():

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).

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

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:

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):

library(dados)
library(haven)

write_sav(
  data = pixar_bilheteria,
  path = "/home/bruno/tmp/dados_bilheteria.sav"
)

Pacote dplyr

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…”.

x <- c(2, 7, 40, 11, 21)

sqrt(sum(x)) # Somar valores do vetor x e tirar a raíz quadrada da soma
[1] 9
x |> sum() |> sqrt() # Mesmo cálculo, agora com pipe!
[1] 9

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.

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.

raiz_quadrada_da_soma <- x |> 
  sum() |> 
  sqrt()

raiz_quadrada_da_soma
[1] 9

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.

# install.packages("dados")
library(dados)

questionario
# A tibble: 21,483 × 9
     ano estado_civil  idade raca   renda  partido religiao denominacao horas_tv
   <int> <fct>         <int> <fct>  <fct>  <fct>   <fct>    <fct>          <int>
 1  2000 Nunca casou      26 Branca US$ 8… Indepe… Protest… Batistas d…       12
 2  2000 Divorciado(a)    48 Branca US$ 8… Não fo… Protest… Batista, n…       NA
 3  2000 Viúvo(a)         67 Branca Não s… Indepe… Protest… Sem denomi…        2
 4  2000 Nunca casou      39 Branca Não s… Indepe… Cristã … Não se apl…        4
 5  2000 Divorciado(a)    25 Branca Não s… Não fo… Nenhuma  Não se apl…        1
 6  2000 Casado(a)        25 Branca US$ 2… Fortem… Protest… Batistas d…       NA
 7  2000 Nunca casou      36 Branca US$ 2… Não fo… Cristã   Não se apl…        3
 8  2000 Divorciado(a)    44 Branca US$ 7… Indepe… Protest… Sínodo lut…       NA
 9  2000 Casado(a)        44 Branca US$ 2… Não fo… Protest… Outra              0
10  2000 Casado(a)        47 Branca US$ 2… Fortem… Protest… Batistas d…        3
# ℹ 21,473 more rows

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.

vetor_com_missing <- c(25, NA, 13, 44, 12, NA)

mean(vetor_com_missing)
[1] NA

No entanto, existem maneiras de aplicar funções em vetores (ou colunas) com NAs.

mean(vetor_com_missing, na.rm = TRUE)
[1] 23.5
mean(vetor_com_missing[!is.na(vetor_com_missing)])
[1] 23.5

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.

sexo <- factor(c("Masculino", "Feminino", "Feminino", "Masculino", "Masculino"))

sexo
[1] Masculino Feminino  Feminino  Masculino Masculino
Levels: Feminino Masculino
questionario
# A tibble: 21,483 × 9
     ano estado_civil  idade raca   renda  partido religiao denominacao horas_tv
   <int> <fct>         <int> <fct>  <fct>  <fct>   <fct>    <fct>          <int>
 1  2000 Nunca casou      26 Branca US$ 8… Indepe… Protest… Batistas d…       12
 2  2000 Divorciado(a)    48 Branca US$ 8… Não fo… Protest… Batista, n…       NA
 3  2000 Viúvo(a)         67 Branca Não s… Indepe… Protest… Sem denomi…        2
 4  2000 Nunca casou      39 Branca Não s… Indepe… Cristã … Não se apl…        4
 5  2000 Divorciado(a)    25 Branca Não s… Não fo… Nenhuma  Não se apl…        1
 6  2000 Casado(a)        25 Branca US$ 2… Fortem… Protest… Batistas d…       NA
 7  2000 Nunca casou      36 Branca US$ 2… Não fo… Cristã   Não se apl…        3
 8  2000 Divorciado(a)    44 Branca US$ 7… Indepe… Protest… Sínodo lut…       NA
 9  2000 Casado(a)        44 Branca US$ 2… Não fo… Protest… Outra              0
10  2000 Casado(a)        47 Branca US$ 2… Fortem… Protest… Batistas d…        3
# ℹ 21,473 more rows

Filtrando linhas com filter()

Podemos usar a função filter() para filtrar as observações da base de dados.

library(dplyr)

divorciados <- questionario |> 
  filter(estado_civil == "Divorciado(a)")

divorciados
# A tibble: 3,383 × 9
     ano estado_civil  idade raca   renda  partido religiao denominacao horas_tv
   <int> <fct>         <int> <fct>  <fct>  <fct>   <fct>    <fct>          <int>
 1  2000 Divorciado(a)    48 Branca US$ 8… Não fo… Protest… Batista, n…       NA
 2  2000 Divorciado(a)    25 Branca Não s… Não fo… Nenhuma  Não se apl…        1
 3  2000 Divorciado(a)    44 Branca US$ 7… Indepe… Protest… Sínodo lut…       NA
 4  2000 Divorciado(a)    52 Branca US$ 2… Indepe… Nenhuma  Não se apl…        1
 5  2000 Divorciado(a)    72 Branca Não s… Fortem… Protest… Batistas d…        7
 6  2000 Divorciado(a)    36 Negra  US$ 1… Fortem… Nenhuma  Não se apl…       NA
 7  2000 Divorciado(a)    39 Negra  Não s… Indepe… Nenhuma  Não se apl…       NA
 8  2000 Divorciado(a)    51 Branca US$ 2… Indepe… Protest… Batista, n…        2
 9  2000 Divorciado(a)    45 Branca US$ 2… Indepe… Protest… Batistas d…        2
10  2000 Divorciado(a)    78 Branca Não s… Indepe… Protest… Batistas d…        4
# ℹ 3,373 more rows
divorciados |> 
  filter(!is.na(horas_tv))
# A tibble: 1,768 × 9
     ano estado_civil  idade raca   renda  partido religiao denominacao horas_tv
   <int> <fct>         <int> <fct>  <fct>  <fct>   <fct>    <fct>          <int>
 1  2000 Divorciado(a)    25 Branca Não s… Não fo… Nenhuma  Não se apl…        1
 2  2000 Divorciado(a)    52 Branca US$ 2… Indepe… Nenhuma  Não se apl…        1
 3  2000 Divorciado(a)    72 Branca Não s… Fortem… Protest… Batistas d…        7
 4  2000 Divorciado(a)    51 Branca US$ 2… Indepe… Protest… Batista, n…        2
 5  2000 Divorciado(a)    45 Branca US$ 2… Indepe… Protest… Batistas d…        2
 6  2000 Divorciado(a)    78 Branca Não s… Indepe… Protest… Batistas d…        4
 7  2000 Divorciado(a)    75 Branca Não s… Fortem… Protest… Associação…        7
 8  2000 Divorciado(a)    46 Branca US$ 2… Não fo… Inter n… Não se apl…        2
 9  2000 Divorciado(a)    39 Branca Se ne… Indepe… Nenhuma  Não se apl…        0
10  2000 Divorciado(a)    56 Branca Se ne… Não fo… Judaísmo Não se apl…        3
# ℹ 1,758 more rows

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.

questionario |> 
  arrange(ano, desc(idade))
# A tibble: 21,483 × 9
     ano estado_civil  idade raca   renda  partido religiao denominacao horas_tv
   <int> <fct>         <int> <fct>  <fct>  <fct>   <fct>    <fct>          <int>
 1  2000 Viúvo(a)         89 Branca Não s… Não fo… Protest… Outras lut…        4
 2  2000 Viúvo(a)         89 Branca Não s… Indepe… Protest… Outras met…        3
 3  2000 Nunca casou      89 Branca Não s… Indepe… Protest… Igreja lut…        2
 4  2000 Viúvo(a)         89 Negra  Não s… Indepe… Protest… Batista, n…        8
 5  2000 Viúvo(a)         89 Branca Não s… Não fo… Protest… Evangélica…        3
 6  2000 Divorciado(a)    89 Branca Não s… Não fo… Protest… Igreja met…        3
 7  2000 Casado(a)        89 Branca Não s… Fortem… Protest… União pres…        5
 8  2000 Viúvo(a)         89 Negra  Não s… Não fo… Protest… Batista, n…       10
 9  2000 Casado(a)        89 Negra  Não s… Fortem… Protest… Convenção …        3
10  2000 Viúvo(a)         89 Negra  Não s… Fortem… Protest… Outras bat…        8
# ℹ 21,473 more rows

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().

divorciados |> 
  select(ano, idade, horas_tv)
# A tibble: 3,383 × 3
     ano idade horas_tv
   <int> <int>    <int>
 1  2000    48       NA
 2  2000    25        1
 3  2000    44       NA
 4  2000    52        1
 5  2000    72        7
 6  2000    36       NA
 7  2000    39       NA
 8  2000    51        2
 9  2000    45        2
10  2000    78        4
# ℹ 3,373 more rows

Removendo colunas com select()

Ao invés de selecionar, podemos remover colunas específicas com select() usando -.

divorciados |> 
  select(-estado_civil, -renda, -denominacao)
# A tibble: 3,383 × 6
     ano idade raca   partido                              religiao    horas_tv
   <int> <int> <fct>  <fct>                                <fct>          <int>
 1  2000    48 Branca Não fortemente repubicano            Protestante       NA
 2  2000    25 Branca Não fortemente democrata             Nenhuma            1
 3  2000    44 Branca Independente, inclinação democrata   Protestante       NA
 4  2000    52 Branca Independente, inclinação democrata   Nenhuma            1
 5  2000    72 Branca Fortemente democrata                 Protestante        7
 6  2000    36 Negra  Fortemente democrata                 Nenhuma           NA
 7  2000    39 Negra  Independente                         Nenhuma           NA
 8  2000    51 Branca Independente                         Protestante        2
 9  2000    45 Branca Independente, inclinação republicana Protestante        2
10  2000    78 Branca Independente, inclinação democrata   Protestante        4
# ℹ 3,373 more rows

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.

questionario |>
  select(ends_with("ao"))
# A tibble: 21,483 × 2
   religiao        denominacao           
   <fct>           <fct>                 
 1 Protestante     Batistas do sul       
 2 Protestante     Batista, não sabe qual
 3 Protestante     Sem denominação       
 4 Cristã ortodoxa Não se aplica         
 5 Nenhuma         Não se aplica         
 6 Protestante     Batistas do sul       
 7 Cristã          Não se aplica         
 8 Protestante     Sínodo luterano       
 9 Protestante     Outra                 
10 Protestante     Batistas do sul       
# ℹ 21,473 more rows
questionario |> 
  select(ano:raca)
# A tibble: 21,483 × 4
     ano estado_civil  idade raca  
   <int> <fct>         <int> <fct> 
 1  2000 Nunca casou      26 Branca
 2  2000 Divorciado(a)    48 Branca
 3  2000 Viúvo(a)         67 Branca
 4  2000 Nunca casou      39 Branca
 5  2000 Divorciado(a)    25 Branca
 6  2000 Casado(a)        25 Branca
 7  2000 Nunca casou      36 Branca
 8  2000 Divorciado(a)    44 Branca
 9  2000 Casado(a)        44 Branca
10  2000 Casado(a)        47 Branca
# ℹ 21,473 more rows

?select para mais detalhes.

Criando e modificando colunas com mutate()

No dplyr, nós podemos adicionar ou modificar colunas usando mutate().

divorciados |> 
  filter(raca == "Branca") |> 
  select(ano, idade, horas_tv) |> 
  mutate(idade_em_decadas = idade / 10,
         horas_tv_em_minutos = horas_tv * 60)
# A tibble: 2,676 × 5
     ano idade horas_tv idade_em_decadas horas_tv_em_minutos
   <int> <int>    <int>            <dbl>               <dbl>
 1  2000    48       NA              4.8                  NA
 2  2000    25        1              2.5                  60
 3  2000    44       NA              4.4                  NA
 4  2000    52        1              5.2                  60
 5  2000    72        7              7.2                 420
 6  2000    51        2              5.1                 120
 7  2000    45        2              4.5                 120
 8  2000    78        4              7.8                 240
 9  2000    61       NA              6.1                  NA
10  2000    75        7              7.5                 420
# ℹ 2,666 more rows
divorciados |> 
  filter(religiao == "Protestante") |> 
  select(ano, idade, horas_tv) |> 
  mutate(horas_tv = horas_tv * 60)
# A tibble: 1,792 × 3
     ano idade horas_tv
   <int> <int>    <dbl>
 1  2000    48       NA
 2  2000    44       NA
 3  2000    72      420
 4  2000    51      120
 5  2000    45      120
 6  2000    78      240
 7  2000    75      420
 8  2000    62       NA
 9  2000    40       NA
10  2000    56      120
# ℹ 1,782 more rows

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.

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))
# A tibble: 1 × 4
  n_observacoes media_idade mediana_horas_tv amplitude_horas_tv
          <int>       <dbl>            <dbl>              <int>
1           441        48.6                2                 15

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.

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))))
# A tibble: 1 × 4
  idade_media idade_desvio_padrao horas_tv_media horas_tv_desvio_padrao
        <dbl>               <dbl>          <dbl>                  <dbl>
1        54.2                13.1           2.85                   2.36

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.

questionario |> 
  group_by(ano) |> 
  summarise(numero_de_religioes = n_distinct(religiao),
            n_observacoes = n(),
            media_horas_tv = mean(horas_tv, na.rm = TRUE))
# A tibble: 8 × 4
    ano numero_de_religioes n_observacoes media_horas_tv
  <int>               <int>         <int>          <dbl>
1  2000                  15          2817           2.97
2  2002                  15          2765           2.98
3  2004                  14          2812           2.87
4  2006                  14          4510           2.94
5  2008                  15          2023           2.98
6  2010                  15          2044           3.03
7  2012                  15          1974           3.09
8  2014                  15          2538           2.98

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()

  • 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)