5 Limpando dados

No dia a dia de quem trabalha com dados, infelizmente, é muito comum se deparar com dados formatados de um jeito bastante complicado de se manipular. Isso acontece pois a forma de se trabalhar com dados é muito diferente da forma de se apresentar ou visualizar dados. Resumindo: “olhar” dados requer uma estrutura bem diferente de “mexer” com dados. Limpeza de dados também é considerada parte da manipulação de dados.

5.1 O formato “ideal” dos dados

É importante entender um pouco mais sobre como os dados podem ser estruturados antes de entrarmos nas funções de limpeza. O formato ideal para analisar dados, visualmente, é diferente do formato ideal para analisá-los de forma sistemática. Observe as duas tabelas a seguir:

Tabela wide

Figura 5.1: Tabela wide

Tabela long

Figura 5.2: Tabela long

A primeira tabela é mais intuitiva para análise visual, pois faz uso de cores e propõe uma leitura natural, da esquerda para a direita. Utiliza, ainda, elementos e estruturas que guiam seus olhos por uma análise de forma simples. Já a segunda tabela é um pouco árida para se interpretar “no olho”.

Há uma espécie de regra geral a qual diz que um dado bem estruturado deve conter uma única variável em uma coluna e uma única observação em uma linha.

Observando-se a primeira tabela, com essa regra em mente, podemos perceber que as observações de ano estão organizadas em colunas. Apesar de estar num formato ideal para análise visual, esse formato dificulta bastante certas análises sistemáticas. O melhor a se fazer é converter a primeira tabela a um modelo mais próximo o possível da segunda tabela.

Infelizmente, não temos como apresentar um passo a passo padrão para limpeza de dados, pois isso depende completamente do tipo de dado que você receber, da análise que você quer fazer e da sua criatividade em manipulação de dados. Mas conhecer os pacotes certos ajuda muito nessa tarefa.

Lembre-se: é muito mais fácil trabalhar no R com dados “bem estruturados”, onde cada coluna deve ser uma única variável e cada linha deve ser uma única observação.

Na contramão da limpeza de dados, você provavelmente terá o problema contrário ao final da sua análise. Supondo que você organizou seus dados perfeitamente, conseguiu executar os modelos que gostaria, gerou diversos gráficos interessantes e está satisfeito com o resultado, você ainda precisará entregar relatórios finais da sua análise em forma de tabelas sumarizadas e explicativas, de modo que os interessados possam entender facilmente, apenas com uma rápida análise visual. Neste caso, que tipo de tabela seria melhor produzir? Provavelmente, quem for ler seus relatórios entenderá mais rapidamente as tabelas mais próximas do primeiro exemplo mostrado.

É importante aprender a estruturar e desestruturar tabelas de todas as formas possíveis.

Para exemplificar, veja algumas tabelas disponíveis no pacote tidyverse, ilustrando os diferentes tipos de organização nos formatos wide e long. Todas as tabelas possuem os mesmos dados e informações:

library(tidyverse)
table1
## # A tibble: 6 x 4
##       country  year  cases population
##         <chr> <int>  <int>      <int>
## 1 Afghanistan  1999    745   19987071
## 2 Afghanistan  2000   2666   20595360
## 3      Brazil  1999  37737  172006362
## 4      Brazil  2000  80488  174504898
## 5       China  1999 212258 1272915272
## 6       China  2000 213766 1280428583
table2
## # A tibble: 12 x 4
##        country  year       type      count
##          <chr> <int>      <chr>      <int>
##  1 Afghanistan  1999      cases        745
##  2 Afghanistan  1999 population   19987071
##  3 Afghanistan  2000      cases       2666
##  4 Afghanistan  2000 population   20595360
##  5      Brazil  1999      cases      37737
##  6      Brazil  1999 population  172006362
##  7      Brazil  2000      cases      80488
##  8      Brazil  2000 population  174504898
##  9       China  1999      cases     212258
## 10       China  1999 population 1272915272
## 11       China  2000      cases     213766
## 12       China  2000 population 1280428583
table3
## # A tibble: 6 x 3
##       country  year              rate
## *       <chr> <int>             <chr>
## 1 Afghanistan  1999      745/19987071
## 2 Afghanistan  2000     2666/20595360
## 3      Brazil  1999   37737/172006362
## 4      Brazil  2000   80488/174504898
## 5       China  1999 212258/1272915272
## 6       China  2000 213766/1280428583
table4a
## # A tibble: 3 x 3
##       country `1999` `2000`
## *       <chr>  <int>  <int>
## 1 Afghanistan    745   2666
## 2      Brazil  37737  80488
## 3       China 212258 213766
table4b
## # A tibble: 3 x 3
##       country     `1999`     `2000`
## *       <chr>      <int>      <int>
## 1 Afghanistan   19987071   20595360
## 2      Brazil  172006362  174504898
## 3       China 1272915272 1280428583
table5
## # A tibble: 6 x 4
##       country century  year              rate
## *       <chr>   <chr> <chr>             <chr>
## 1 Afghanistan      19    99      745/19987071
## 2 Afghanistan      20    00     2666/20595360
## 3      Brazil      19    99   37737/172006362
## 4      Brazil      20    00   80488/174504898
## 5       China      19    99 212258/1272915272
## 6       China      20    00 213766/1280428583

5.2 Pacote tidyr

Apesar de existirem diversas possibilidades de situações que necessitem de limpeza de dados, a conjugação de três pacotes consegue resolver a grande maioria dos casos: dplyr, tidyr, stringr.

O pacote tidyr é mais um dos pacotes criados por Hadley Wickham. Este fato, por si só, já traz algumas vantagens: ele se integra perfeitamente com o dplyr, usando o conector %>%, e tem a sintaxe de suas funções bastante intuitiva.

install.packages("tidyr")
library(tidyr)
?tidyr

O tidyr também tem suas funções organizadas em pequenos verbetes, onde cada um representa uma tarefa para organizar os dados. Os verbetes básicos que abordaremos são os seguintes:

  • gather()
  • separate()
  • spread()
  • unite()

Vale lembrar que tudo que for feito usando o tidyr é possível executar também usando o R base, mas de uma forma um pouco menos intuitiva. Caso queira entender como usar o R base pra isso, procure mais sobre as funções melt() e cast().

Tabela long

Figura 5.3: Tabela long

5.2.1 Gather

A função gather() serve para agrupar duas ou mais colunas e seus respectivos valores (conteúdos) em pares. Assim, o resultado após o agrupamento é sempre duas colunas. A primeira delas possui observações cujos valores chave eram as colunas antigas e a segunda possui os valores respectivos relacionados com as colunas antigas. Na prática, a função gather diminui o número de colunas e aumenta o número de linhas de nossa base de dados.

Usaremos dados disponíveis no R base para exemplificar:

data("USArrests")

str(USArrests)
## 'data.frame':    50 obs. of  4 variables:
##  $ Murder  : num  13.2 10 8.1 8.8 9 7.9 3.3 5.9 15.4 17.4 ...
##  $ Assault : int  236 263 294 190 276 204 110 238 335 211 ...
##  $ UrbanPop: int  58 48 80 50 91 78 77 72 80 60 ...
##  $ Rape    : num  21.2 44.5 31 19.5 40.6 38.7 11.1 15.8 31.9 25.8 ...
head(USArrests)
##            Murder Assault UrbanPop Rape
## Alabama      13.2     236       58 21.2
## Alaska       10.0     263       48 44.5
## Arizona       8.1     294       80 31.0
## Arkansas      8.8     190       50 19.5
## California    9.0     276       91 40.6
## Colorado      7.9     204       78 38.7
# Transformando o nome das linhas em colunas
USArrests$State <- rownames(USArrests)
head(USArrests)
##            Murder Assault UrbanPop Rape      State
## Alabama      13.2     236       58 21.2    Alabama
## Alaska       10.0     263       48 44.5     Alaska
## Arizona       8.1     294       80 31.0    Arizona
## Arkansas      8.8     190       50 19.5   Arkansas
## California    9.0     276       91 40.6 California
## Colorado      7.9     204       78 38.7   Colorado
usa.long <- USArrests %>% 
  gather(key = "tipo_crime", value = "valor", -State)

head(usa.long)
##        State tipo_crime valor
## 1    Alabama     Murder  13.2
## 2     Alaska     Murder  10.0
## 3    Arizona     Murder   8.1
## 4   Arkansas     Murder   8.8
## 5 California     Murder   9.0
## 6   Colorado     Murder   7.9
tail(usa.long)
##             State tipo_crime valor
## 195       Vermont       Rape  11.2
## 196      Virginia       Rape  20.7
## 197    Washington       Rape  26.2
## 198 West Virginia       Rape   9.3
## 199     Wisconsin       Rape  10.8
## 200       Wyoming       Rape  15.6

No primeiro parâmetro do gather(), nós informamos a “chave”, ou seja, a coluna que guardará o que antes era coluna. No segundo parâmetro, informamos o “value”, ou seja, a coluna que guardará os valores para cada uma das antigas colunas. Repare que agora você pode afirmar com certeza que cada linha é uma observação e que cada coluna é uma variável.

5.2.2 Spread

É a operação antagônica do gather(). Ela espalha os valores de duas colunas em diversos campos para cada registro: os valores de uma coluna viram o nome das novas colunas, e os valores de outra viram valores de cada registro nas novas colunas. Usaremos a table2 para exemplificar:

head(table2)
## # A tibble: 6 x 4
##       country  year       type     count
##         <chr> <int>      <chr>     <int>
## 1 Afghanistan  1999      cases       745
## 2 Afghanistan  1999 population  19987071
## 3 Afghanistan  2000      cases      2666
## 4 Afghanistan  2000 population  20595360
## 5      Brazil  1999      cases     37737
## 6      Brazil  1999 population 172006362
table2.wide <- table2 %>%
  spread(key = type, value = count)

head(table2.wide)
## # A tibble: 6 x 4
##       country  year  cases population
##         <chr> <int>  <int>      <int>
## 1 Afghanistan  1999    745   19987071
## 2 Afghanistan  2000   2666   20595360
## 3      Brazil  1999  37737  172006362
## 4      Brazil  2000  80488  174504898
## 5       China  1999 212258 1272915272
## 6       China  2000 213766 1280428583

5.2.3 Separate

O separate() é usado para separar duas variáveis que estão em uma mesma coluna. Lembre-se: cada coluna deve ser apenas uma única variável! É muito normal virem variáveis juntas em uma única coluna, mas nem sempre isso é prejudicial, cabe avaliar quando vale a pena separá-las.

Usaremos o exemplo da table3 para investigar:

table3.wide <- table3 %>% 
  separate(rate, into = c("cases", "population"), sep='/')

head(table3.wide)
## # A tibble: 6 x 4
##       country  year  cases population
##         <chr> <int>  <chr>      <chr>
## 1 Afghanistan  1999    745   19987071
## 2 Afghanistan  2000   2666   20595360
## 3      Brazil  1999  37737  172006362
## 4      Brazil  2000  80488  174504898
## 5       China  1999 212258 1272915272
## 6       China  2000 213766 1280428583

5.2.4 Unite

A operação unite() é o oposto da separate(), ela pega duas colunas (variáveis) e transforma em uma só. É muito utilizada para montar relatórios finais ou tabelas para análise visual. Aproveitemos o exemplo em table2 para montarmos uma tabela final comparando a “case” e “population” de cada país, em cada ano.

table2.relatorio <- table2 %>% 
  unite(type_year, type, year) %>% 
  spread(key = type_year, value = count, sep = '_')
  
table2.relatorio
## # A tibble: 3 x 5
##       country type_year_cases_1999 type_year_cases_2000
## *       <chr>                <int>                <int>
## 1 Afghanistan                  745                 2666
## 2      Brazil                37737                80488
## 3       China               212258               213766
## # ... with 2 more variables: type_year_population_1999 <int>,
## #   type_year_population_2000 <int>

O primeiro parâmetro é a coluna que desejamos criar, os próximos são as colunas que desejamos unir e, por fim, temos o sep, que representa algum símbolo opcional para ficar entre os dois valores na nova coluna.

5.3 Manipulação de texto

Manipulação de texto também é algo importante em ciência de dados, pois nem tudo são números, existem variáveis categóricas que são baseadas em texto. Mais uma vez, esse tipo de manipulação depende do tipo de arquivo que você receber.

a <- 'texto 1'
b <- 'texto 2'
c <- 'texto 3'
paste(a, b, c)
## [1] "texto 1 texto 2 texto 3"

O paste() é a função mais básica para manipulação de textos usando o R base. Ela concatena todas as variáveis textuais que você informar. Existe um parâmetro extra (sep) cujo valor padrão é espaço ` `.

paste(a, b, c, sep = '-')
## [1] "texto 1-texto 2-texto 3"
paste(a, b, c, sep = ';')
## [1] "texto 1;texto 2;texto 3"
paste(a, b, c, sep = '---%---')
## [1] "texto 1---%---texto 2---%---texto 3"

5.3.1 Pacote stringr

Texto no R é sempre do tipo character. No universo da computação, também se referem a texto como string. E é daí que vem o nome desse pacote, também criado por Hadley Wickham. Por acaso, este pacote não está incluído no tidyverse.

install.packages('stringr')
library(stringr)
?stringr

Começaremos pela função str_sub(), que extrai apenas parte de um texto.

cnae.texto <- c('10 Fabricação de produtos alimentícios', '11 Fabricação de bebidas', 
             '12 Fabricação de produtos do fumo', '13 Fabricação de produtos têxteis', 
             '14 Confecção de artigos do vestuário e acessórios',
             '15 Preparação de couros e fabricação de artefatos de couro, artigos para viagem e calçados',
             '16 Fabricação de produtos de madeira', 
             '17 Fabricação de celulose, papel e produtos de papel')
cnae <- str_sub(cnae.texto, 0, 2)
texto <- str_sub(cnae.texto, 4)

cnae
## [1] "10" "11" "12" "13" "14" "15" "16" "17"
texto
## [1] "Fabricação de produtos alimentícios"                                                    
## [2] "Fabricação de bebidas"                                                                  
## [3] "Fabricação de produtos do fumo"                                                         
## [4] "Fabricação de produtos têxteis"                                                         
## [5] "Confecção de artigos do vestuário e acessórios"                                         
## [6] "Preparação de couros e fabricação de artefatos de couro, artigos para viagem e calçados"
## [7] "Fabricação de produtos de madeira"                                                      
## [8] "Fabricação de celulose, papel e produtos de papel"

Temos também a função str_replace() e str_replace_all(), que substituem determinados caracteres por outros. Tal como no exemplo a seguir:

telefones <- c('9931-9512', '8591-5892', '8562-1923')
str_replace(telefones, '-', '')
## [1] "99319512" "85915892" "85621923"
cnpj <- c('19.702.231/9999-98', '19.498.482/9999-05', '19.499.583/9999-50', '19.500.999/9999-46', '19.501.139/9999-90')
str_replace_all(cnpj, '\\.|/|-', '')
## [1] "19702231999998" "19498482999905" "19499583999950" "19500999999946"
## [5] "19501139999990"

O que são esses símbolos no segundo exemplo? São símbolos especiais utilizados em funções textuais para reconhecimento de padrão. Esses símbolos são conhecidos como Expressões Regulares ou o famoso Regex.

5.3.2 Regex

Trata-se de um assunto bastante complexo e avançado. Não é fácil dominar regex e provavelmente você vai precisar sempre consultar e experimentar a montagem dos padrões de regex. Infelizmente não é possível aprender regex rápido e de um jeito fácil, só existe o jeito difícil: errando muito, com muita prática e experiências reais.

A seguir, uma lista dos principais mecanismos de regex:

regex correspondência
^ começa do string (ou uma negação)
. qualquer caractere
$ fim da linha
[maça] procura os caracteres m, a, ç
maça maça
[0-9] números
[A-Z] qualquer letra maiúscula
\\w uma palavra
\\W não é palavra
(pontuação, espaço etc.)
\\s um espaço (tab, newline, space)

A seguir, alguns bons sites para aprender mais sobre regex. É um assunto interessante e bastante utilizado para tratamento textual.

http://turing.com.br/material/regex/introducao.html

https://regexone.com/

5.4 Exercícios

  1. Utilizando senado.csv, monte uma tabela mostrando a quantidade de votos sim e não por coalisão, no formato wide (“sim” e “não” são linhas e “coalisão” ou “não coalisão” são colunas). Dica: mutate(tipo_coalisao = ifelse(GovCoalition, 'Coalisão', 'Não Coalisão'))
## # A tibble: 2 x 3
##   Tipo_voto Coalisão `Não Coalisão`
##       <chr>    <int>          <int>
## 1 Votos Não      702            657
## 2 Votos Sim     5002           2739
  1. Utilizando o dataframe abaixo, obtenha o resultado a seguir: Dica: separate(), str_replace_all(), str_trim(), str_sub()
cadastros <- data.frame(
  email = c('joaodasilva@gmail.com', 'rafael@hotmail.com', 'maria@uol.com.br', 'juliana.morais@outlook.com'),
  telefone = c('(61)99831-9482', '32 8976 2913', '62-9661-1234', '15-40192.5812')
)

cadastros
##                        email       telefone
## 1      joaodasilva@gmail.com (61)99831-9482
## 2         rafael@hotmail.com   32 8976 2913
## 3           maria@uol.com.br   62-9661-1234
## 4 juliana.morais@outlook.com  15-40192.5812
##            login dominio   telefone dd
## 1    joaodasilva   gmail 99831-9482 61
## 2         rafael hotmail  8976-2913 32
## 3          maria     uol  9661-1234 62
## 4 juliana.morais outlook 40192-5812 15