Manipulação

Introdução

“(…) The fact that data science exists as a field is a colossal failure of statistics. To me, what I do is what statistics is all about. It is gaining insight from data using modelling and visualization. Data munging and manipulation is hard and statistics has just said that’s not our domain.”

Hadley Wickham

Esta seção trata do tema transformação de dados. Trata-se de uma tarefa dolorosa e demorada, tomando muitas vezes a maior parte do tempo de uma análise estatística. Essa etapa é essencial em qualquer análise de dados e, apesar de negligenciada pela academia, é decisiva para o sucesso de estudos aplicados.

Usualmente, o cientista de dados parte de uma base “crua” e a transforma até obter uma base de dados analítica. A base crua pode ser não estruturada, semi-estruturada ou estruturada. Já a base analítica é necessariamente estruturada e, a menos de transformações simples, está preparada para passar por análises estatísticas.

A figura abaixo mostra a fase de “disputa” com os dados (data wrangling) para deixá-los no formato analítico.

Transformação no ciclo da ciência de dados.

Figure 1: Transformação no ciclo da ciência de dados.

Um conceito importante para obtenção de uma base analítica é o data tidying, ou arrumação de dados. Uma base é considerada tidy se

  1. Cada linha da base representa uma observação.
  2. Cada coluna da base representa uma variável.
  3. Cada tabela considera informações de uma unidade amostral.

A base de dados analítica é estruturada de tal forma que pode ser colocada diretamente em ambientes de modelagem estatística ou de visualização. Nem sempre uma base de dados analítica está no formato tidy, mas usualmente são necessários poucos passos para migrar de uma para outra. A filosofia tidy é a base do tidyverse.

Os principais pacotes encarregados da tarefa de estruturar os dados são o dplyr e o tidyr. Eles serão o tema desse tópico. Instale e carregue os pacotes utilizando:

install.packages("dplyr")
install.packages("tidyr")

library(dplyr)
library(tidyr)

Mas antes de apresentar as principais funções do dplyr e do tidyr, precisamos falar sobre o conceito de tibbles.

Trabalhando com tibbles

Uma tibble nada mais é do que um data.frame, mas com um método de impressão mais adequado.

As tibbles são parte do pacote tibble. Assim, para começar a usá-las, instale e carregue o pacote.

install.packages("tibble")
library(tibble)

Mais informações sobre tibbles podem ser encontradas neste link.

Nessa seção, vamos trabalhar com uma base simplificada do PNUD (Programa das Nações Unidas para o Desenvolvimento), contendo informações socioeconômicas de todos os municípios do país. Os resultados foram obtidos a partir dos Censos de 1991, 2000 e 2010.

A base contém 16686 linhas e 14 colunas, descritas abaixo:

  • ano - Ano do Censo utilizado como base para cálculo do IDH-Municipal e outras métricas.
  • muni - Nome do município. Cada município aparece três vezes, um para cada ano.
  • uf - Unidade Federativa.
  • regiao - Região brasileira.
  • idhm - IDH municipal, dividido em
    • idhm_e - IDH municipal - educação.
    • idhm_l - IDH municipal - longevidade.
    • idhm_r - IDH municipal - renda.
  • espvida - Expectativa de vida.
  • rdpc - Renda per capita.
  • gini - Coeficiente de gini municipal (mede desigualdade social).
  • pop - População residente do município.
  • lat, lon - Latitude e longitude do município (ponto médio).

Para acessar esta base, instale e carregue o pacote abjData da seguinte maneira:

devtools::install_github("abjur/abjData")
library(abjData)

Assim, utilizaremos o objeto pnud_min para acessar os dados.

pnud_min
## # A tibble: 16,686 x 14
##      ano muni  uf    regiao  idhm idhm_e idhm_l idhm_r espvida  rdpc  gini
##    <int> <chr> <chr> <chr>  <dbl>  <dbl>  <dbl>  <dbl>   <dbl> <dbl> <dbl>
##  1  1991 ALTA… RO    Norte  0.329  0.112  0.617  0.516    62.0 198.  0.63 
##  2  1991 ARIQ… RO    Norte  0.432  0.199  0.684  0.593    66.0 319.  0.570
##  3  1991 CABI… RO    Norte  0.309  0.108  0.636  0.43     63.2 116.  0.7  
##  4  1991 CACO… RO    Norte  0.407  0.171  0.667  0.593    65.0 320.  0.66 
##  5  1991 CERE… RO    Norte  0.386  0.167  0.629  0.547    62.7 240.  0.6  
##  6  1991 COLO… RO    Norte  0.376  0.151  0.658  0.536    64.5 225.  0.62 
##  7  1991 CORU… RO    Norte  0.203  0.039  0.572  0.373    59.3  81.4 0.59 
##  8  1991 COST… RO    Norte  0.425  0.22   0.629  0.553    62.8 250.  0.65 
##  9  1991 ESPI… RO    Norte  0.388  0.159  0.653  0.561    64.2 263.  0.63 
## 10  1991 GUAJ… RO    Norte  0.468  0.247  0.662  0.625    64.7 391.  0.6  
## # ... with 16,676 more rows, and 3 more variables: pop <int>, lat <dbl>,
## #   lon <dbl>

Veja que, por padrão, apenas as dez primeiras linhas da tibble são impressas na tela. Além disso, as colunas que não couberem na largura do console serão omitidas. Também são apresentadas a dimensão da tabela e as classes de cada coluna.

O pacote dplyr

O dplyr é o pacote mais útil para realizar transformação de dados, aliando simplicidade e eficiência de uma forma elegante. Os scripts em R que fazem uso inteligente dos verbos dplyr e as facilidades do operador pipe tendem a ficar mais legíveis e organizados sem perder velocidade de execução.

As principais funções do dplyr são:

  • filter() - filtra linhas
  • select() - seleciona colunas
  • mutate() - cria/modifica colunas
  • arrange() - ordena a base
  • summarise() - sumariza a base

Todas essas funções seguem as mesmas características:

  • O input é sempre uma tibble e o output é sempre um tibble.
  • Colocamos o tibble no primeiro argumento e o que queremos fazer nos outros argumentos.
  • A utilização é facilitada com o emprego do operador %>%.
  • O pacote faz uso extensivo de NSE (non standard evaluation).

As principais vantagens de se usar o dplyr em detrimento das funções do R base são:

  • Manipular dados se torna uma tarefa muito mais simples.
  • O código fica mais intuitivo de ser escrito e mais simples de ser lido.
  • O pacote dplyr utiliza C e C++ por trás da maioria das funções, o que geralmente torna o código mais eficiente.
  • É possível trabalhar com diferentes fontes de dados, como bases relacionais (SQL) e data.table.

Agora, vamos avaliar com mais detalhes os principais verbos do pacote dplyr.


select()

A função select() seleciona colunas (variáveis). É possível utilizar nomes, índices, intervalos de variáveis ou utilizar as funções starts_with(x), contains(x), matches(x), one_of(x) para selecionar as variáveis.

pnud_min %>% 
  select(ano, regiao, muni)
## # A tibble: 16,686 x 3
##      ano regiao muni                 
##    <int> <chr>  <chr>                
##  1  1991 Norte  ALTA FLORESTA D'OESTE
##  2  1991 Norte  ARIQUEMES            
##  3  1991 Norte  CABIXI               
##  4  1991 Norte  CACOAL               
##  5  1991 Norte  CEREJEIRAS           
##  6  1991 Norte  COLORADO DO OESTE    
##  7  1991 Norte  CORUMBIARA           
##  8  1991 Norte  COSTA MARQUES        
##  9  1991 Norte  ESPIGÃO D'OESTE      
## 10  1991 Norte  GUAJARÁ-MIRIM        
## # ... with 16,676 more rows
pnud_min %>% 
  select(ano:regiao, rdpc)
## # A tibble: 16,686 x 5
##      ano muni                  uf    regiao  rdpc
##    <int> <chr>                 <chr> <chr>  <dbl>
##  1  1991 ALTA FLORESTA D'OESTE RO    Norte  198. 
##  2  1991 ARIQUEMES             RO    Norte  319. 
##  3  1991 CABIXI                RO    Norte  116. 
##  4  1991 CACOAL                RO    Norte  320. 
##  5  1991 CEREJEIRAS            RO    Norte  240. 
##  6  1991 COLORADO DO OESTE     RO    Norte  225. 
##  7  1991 CORUMBIARA            RO    Norte   81.4
##  8  1991 COSTA MARQUES         RO    Norte  250. 
##  9  1991 ESPIGÃO D'OESTE       RO    Norte  263. 
## 10  1991 GUAJARÁ-MIRIM         RO    Norte  391. 
## # ... with 16,676 more rows
pnud_min %>% 
  select(ano, starts_with('idhm'))
## # A tibble: 16,686 x 5
##      ano  idhm idhm_e idhm_l idhm_r
##    <int> <dbl>  <dbl>  <dbl>  <dbl>
##  1  1991 0.329  0.112  0.617  0.516
##  2  1991 0.432  0.199  0.684  0.593
##  3  1991 0.309  0.108  0.636  0.43 
##  4  1991 0.407  0.171  0.667  0.593
##  5  1991 0.386  0.167  0.629  0.547
##  6  1991 0.376  0.151  0.658  0.536
##  7  1991 0.203  0.039  0.572  0.373
##  8  1991 0.425  0.22   0.629  0.553
##  9  1991 0.388  0.159  0.653  0.561
## 10  1991 0.468  0.247  0.662  0.625
## # ... with 16,676 more rows

filter()

A função filter() filtra linhas. Ela é semelhante à função subset(), do R base.

pnud_min %>% 
  select(ano, muni, uf) %>% 
  filter(uf == 'AC')
## Warning: package 'bindrcpp' was built under R version 3.4.4
## # A tibble: 66 x 3
##      ano muni            uf   
##    <int> <chr>           <chr>
##  1  1991 ACRELÂNDIA      AC   
##  2  1991 ASSIS BRASIL    AC   
##  3  1991 BRASILÉIA       AC   
##  4  1991 BUJARI          AC   
##  5  1991 CAPIXABA        AC   
##  6  1991 CRUZEIRO DO SUL AC   
##  7  1991 EPITACIOLÂNDIA  AC   
##  8  1991 FEIJÓ           AC   
##  9  1991 JORDÃO          AC   
## 10  1991 MÂNCIO LIMA     AC   
## # ... with 56 more rows

Para fazer várias condições, use os operadores lógicos & e | ou separe filtros entre vírgulas.

pnud_min %>% 
  select(ano, regiao, uf, idhm) %>% 
  filter(uf %in% c('SP', 'MG') | idhm > .5, ano == 2010)
## # A tibble: 5,527 x 4
##      ano regiao uf     idhm
##    <int> <chr>  <chr> <dbl>
##  1  2010 Norte  RO    0.641
##  2  2010 Norte  RO    0.702
##  3  2010 Norte  RO    0.65 
##  4  2010 Norte  RO    0.718
##  5  2010 Norte  RO    0.692
##  6  2010 Norte  RO    0.685
##  7  2010 Norte  RO    0.613
##  8  2010 Norte  RO    0.611
##  9  2010 Norte  RO    0.672
## 10  2010 Norte  RO    0.657
## # ... with 5,517 more rows

Repare que o operador %in% é muito útil para trabalhar com vetores. O resultado da operação é um vetor lógico o tamanho do vetor do elemento da esquerda, identificando quais elementos da esquerda batem com algum elemento da direita.

Também podemos usar funções que retornam valores lógicos, como a str_detect().

library(stringr)
## Warning: package 'stringr' was built under R version 3.4.4

pnud_min %>% 
  select(muni, ano, uf) %>% 
  filter(str_detect(muni, '^[HG]|S$'), 
         ano == 1991)
## # A tibble: 970 x 3
##    muni                        ano uf   
##    <chr>                     <int> <chr>
##  1 ARIQUEMES                  1991 RO   
##  2 CEREJEIRAS                 1991 RO   
##  3 COSTA MARQUES              1991 RO   
##  4 GUAJARÁ-MIRIM              1991 RO   
##  5 ALTO ALEGRE DOS PARECIS    1991 RO   
##  6 BURITIS                    1991 RO   
##  7 CASTANHEIRAS               1991 RO   
##  8 GOVERNADOR JORGE TEIXEIRA  1991 RO   
##  9 PARECIS                    1991 RO   
## 10 SERINGUEIRAS               1991 RO   
## # ... with 960 more rows

mutate()

A função mutate() cria ou modifica colunas. Ela é equivalente à função transform(), mas aceita várias novas colunas iterativamente. Novas variáveis devem ter o mesmo número de linhas da base original (ou terem comprimento 1).

pnud_min %>% 
  select(muni, rdpc, pop, idhm_l, espvida) %>% 
  mutate(renda = rdpc * pop, 
         razao = idhm_l / espvida)
## # A tibble: 16,686 x 7
##    muni                   rdpc   pop idhm_l espvida     renda   razao
##    <chr>                 <dbl> <int>  <dbl>   <dbl>     <dbl>   <dbl>
##  1 ALTA FLORESTA D'OESTE 198.  22835  0.617    62.0  4531834. 0.00995
##  2 ARIQUEMES             319.  55018  0.684    66.0 17576600. 0.0104 
##  3 CABIXI                116.   5846  0.636    63.2   680357. 0.0101 
##  4 CACOAL                320.  66534  0.667    65.0 21306848. 0.0103 
##  5 CEREJEIRAS            240.  19030  0.629    62.7  4569103  0.0100 
##  6 COLORADO DO OESTE     225.  25070  0.658    64.5  5636237. 0.0102 
##  7 CORUMBIARA             81.4 10737  0.572    59.3   873777. 0.00964
##  8 COSTA MARQUES         250.   6902  0.629    62.8  1726052. 0.0100 
##  9 ESPIGÃO D'OESTE       263.  22505  0.653    64.2  5919490. 0.0102 
## 10 GUAJARÁ-MIRIM         391.  31240  0.662    64.7 12226399. 0.0102 
## # ... with 16,676 more rows

arrange()

A função arrange() ordena a base. O argumento desc= pode ser utilizado para gerar uma ordem decrescente.

pnud_min %>% 
  filter(ano == 2010) %>% 
  arrange(desc(espvida))
## # A tibble: 5,562 x 14
##      ano muni  uf    regiao  idhm idhm_e idhm_l idhm_r espvida  rdpc  gini
##    <int> <chr> <chr> <chr>  <dbl>  <dbl>  <dbl>  <dbl>   <dbl> <dbl> <dbl>
##  1  2010 BLUM… SC    Sul    0.806  0.722  0.894  0.812    78.6 1253.  0.46
##  2  2010 BRUS… SC    Sul    0.795  0.707  0.894  0.794    78.6 1117.  0.4 
##  3  2010 BALN… SC    Sul    0.845  0.789  0.894  0.854    78.6 1626.  0.52
##  4  2010 RIO … SC    Sul    0.802  0.727  0.894  0.793    78.6 1114.  0.45
##  5  2010 RANC… SC    Sul    0.753  0.644  0.893  0.743    78.6  814.  0.42
##  6  2010 RIO … SC    Sul    0.754  0.625  0.892  0.769    78.5  957.  0.47
##  7  2010 IOME… SC    Sul    0.795  0.749  0.891  0.754    78.4  874.  0.33
##  8  2010 JOAÇ… SC    Sul    0.827  0.771  0.891  0.823    78.4 1338.  0.54
##  9  2010 NOVA… SC    Sul    0.748  0.628  0.891  0.749    78.4  848.  0.35
## 10  2010 PORT… SC    Sul    0.786  0.724  0.891  0.752    78.4  864.  0.53
## # ... with 5,552 more rows, and 3 more variables: pop <int>, lat <dbl>,
## #   lon <dbl>

summarise

A função summarise() sumariza a base. Ela aplica uma função às variáveis, retornando um vetor de tamanho 1. Geralmente ela é utilizada em conjunto da função group_by().

pnud_min %>% 
  group_by(regiao, uf) %>% 
  summarise(n = n(), espvida = mean(espvida)) %>% 
  arrange(regiao, desc(espvida))
## # A tibble: 27 x 4
## # Groups:   regiao [5]
##    regiao       uf        n espvida
##    <chr>        <chr> <int>   <dbl>
##  1 Centro-Oeste DF        3    73.4
##  2 Centro-Oeste GO      735    70.0
##  3 Centro-Oeste MS      234    69.9
##  4 Centro-Oeste MT      423    69.4
##  5 Nordeste     CE      552    65.6
##  6 Nordeste     RN      501    65.1
##  7 Nordeste     PE      555    64.9
##  8 Nordeste     BA     1251    64.6
##  9 Nordeste     SE      225    64.3
## 10 Nordeste     PI      672    64.0
## # ... with 17 more rows

# A função n() costuma ser bastante utilizada com a função summarise().

A função count() também pode ser usada para sumarizar em relação à frequência.

pnud_min %>% 
  filter(ano == 2010) %>% 
  count(regiao, sort = TRUE) %>% 
  mutate(prop = n / sum(n), prop = scales::percent(prop))
## # A tibble: 5 x 3
##   regiao           n prop 
##   <chr>        <int> <chr>
## 1 Nordeste      1794 32.3%
## 2 Sudeste       1667 30.0%
## 3 Sul           1187 21.3%
## 4 Centro-Oeste   465 8.4% 
## 5 Norte          449 8.1%

Outras funções do dplyr

  • Para retirar duplicatas, utilizar distinct().
  • Para realizar operações mais gerais, usar do.
  • Para juntar tabelas, usar inner_join(), left_join(), anti_join() etc.

Exercício: estudar o help() dessas funções e criar exemplos com elas utilizando a base pnud_min.

O pacote tidyr

O pacote tidyr dispõe de funções úteis para deixar os seus dados no formato que você precisa para a análise. Na maioria das vezes, utilizamos para deixá-los tidy. Outras, precisamos “bagunçá-los” um pouco para poder aplicar alguma função.

Nesse sentido, as principais funções são a gather() e a spread()


gather()

A função gather() “empilha” o banco de dados. Ela é utilizada principalmente quando as colunas da base não representam nomes de variáveis, mas sim seus valores.

pnud_min %>% 
  select(uf, muni, ano, starts_with('idhm_')) %>% 
  gather(tipo_idhm, idhm, starts_with('idhm_')) %>% 
  arrange(desc(idhm))
## # A tibble: 50,058 x 5
##    uf    muni                 ano tipo_idhm  idhm
##    <chr> <chr>              <int> <chr>     <dbl>
##  1 SC    BALNEÁRIO CAMBORIÚ  2010 idhm_l    0.894
##  2 SC    BLUMENAU            2010 idhm_l    0.894
##  3 SC    BRUSQUE             2010 idhm_l    0.894
##  4 SC    RIO DO SUL          2010 idhm_l    0.894
##  5 SC    RANCHO QUEIMADO     2010 idhm_l    0.893
##  6 SC    RIO DO OESTE        2010 idhm_l    0.892
##  7 SC    IOMERÊ              2010 idhm_l    0.891
##  8 SC    JOAÇABA             2010 idhm_l    0.891
##  9 SC    NOVA TRENTO         2010 idhm_l    0.891
## 10 SC    PORTO UNIÃO         2010 idhm_l    0.891
## # ... with 50,048 more rows

spread()

A função spread() é essencialmente o inverso da gather(). Ela espalha uma variável nas colunas.

pnud_min %>% 
  select(muni, uf, ano, starts_with('idhm_')) %>% 
  gather(tipo_idhm, idhm, starts_with('idhm_')) %>% 
  spread(ano, idhm)
## # A tibble: 16,686 x 6
##    muni                uf    tipo_idhm `1991` `2000` `2010`
##    <chr>               <chr> <chr>      <dbl>  <dbl>  <dbl>
##  1 ABADIA DE GOIÁS     GO    idhm_e     0.183  0.386  0.622
##  2 ABADIA DE GOIÁS     GO    idhm_l     0.658  0.765  0.83 
##  3 ABADIA DE GOIÁS     GO    idhm_r     0.563  0.623  0.687
##  4 Abadia dos Dourados MG    idhm_e     0.225  0.387  0.563
##  5 Abadia dos Dourados MG    idhm_l     0.728  0.799  0.839
##  6 Abadia dos Dourados MG    idhm_r     0.551  0.616  0.693
##  7 ABADIÂNIA           GO    idhm_e     0.188  0.292  0.579
##  8 ABADIÂNIA           GO    idhm_l     0.656  0.73   0.841
##  9 ABADIÂNIA           GO    idhm_r     0.56   0.598  0.671
## 10 Abaeté              MG    idhm_e     0.18   0.385  0.556
## # ... with 16,676 more rows

Outras funções do tidyr

  • A função unite() junta duas ou mais colunas usando algum separador (_, por exemplo).
  • A função separate() faz o inverso de unite(): transforma uma coluna em várias usando um separador.
pnud_min %>% 
  select(muni, uf, ano, starts_with('idhm_')) %>% 
  gather(tipo_idhm, idhm, starts_with('idhm_')) %>% 
  separate(tipo_idhm, c('idhm_nm', 'tipo'), sep = '_') %>% 
  select(-idhm_nm) %>% 
  filter(ano == 2010) %>% 
  group_by(tipo) %>% 
  summarise(maior = muni[which.max(idhm)], idhm = max(idhm)) %>% 
  arrange(tipo, desc(idhm))
## # A tibble: 3 x 3
##   tipo  maior               idhm
##   <chr> <chr>              <dbl>
## 1 e     ÁGUAS DE SÃO PEDRO 0.825
## 2 l     BALNEÁRIO CAMBORIÚ 0.894
## 3 r     SÃO CAETANO DO SUL 0.891

Nossa Newsletter

Uma vez por semana enviamos um e-mail para você não perder nenhum post da Curso-R. Avisamos também sempre que abrimos uma nova turma.