Kit de sobrevivência em R - Parte 7: Avançando e Aprofundando

Chegamos ao fim do kit de sobrevivência em R. Nesse último post da série vamos retomar alguns pontos que merecem ser complementados e revisados, além de apresentar um pouco mais de transformações e operações usando apenas funções básicas do R.

Se você seguiu a sequência e chegou até aqui, parabéns! Você provavelmente conhece o básico de R e o suficiente para começar a aprofundar em aspectos mais interessantes sobre operações com massas de dados.

Breve revisão sobre pacotes

Mostramos que pacotes são conjuntos de funções específicas agrupadas para objetivos temáticos: carregar dados, gráficos, machine learning. É muito simples carregar e utilizar pacotes. Vamos relembrar os principais comandos envolvidos:

Carregando dados

Lembre-se que antes de carregar um arquivo de dados você precisa informar onde o R deve ler o arquivo. Para isso usamos o comando setwd().

A função mais básica para leitura de dados estruturados (csv, tabular, tamanho fixo, com separadores, etc.) é o famoso read.table(). Lembra dos principais parâmetros? Nome do arquivo, separador, se tem cabeçalho ou não, e, no caso de campos com tamanho fixo, o tamanho de cada campo.

Se quiser exercitar com diversos arquivos de dados diferentes, tente o Portal Brasileiro de Dados Abertos ou esse repositório de dados públicos (em inglês).

Comentamos sobre algumas funções básicas para começar a explorar seus dados carregados. Você lembra?

Dica: se estiver usando o RStudio, tente visualizar seu data.frame com a função View() (com V maiúsculo). Ela cria uma planilha para ver melhor os dados!

Tipos e estrutura de dados

Conhecer os tipos e estruturas de dados em R será fundamental daqui pra frente. Achamos importante revisar e apresentar alguns dos principais.

Tipos básicos

Tipo Descrição
logical Valor lógico, TRUE ou FALSE. Usado com os operadores lógicos &, |, ==, !=, >, <, >=, <=
integer Valores de números inteiros
numeric Valores de números decimais. Também representam números inteiros
character Valores textuais, também conhecidos como string


Conversões

Existem algumas operações de conversões entre os tipos. São bastante usadas em transformações de campos. Por exemplo:


[1] 20

[1] "20"

[1] 3

Estruturas básicas

Dominar as estruturas de dados do R será fundamental no desenvolvimento das suas análises. Inicialmente, a ideia de estrutura de dados pode parecer um pouco abstrata, mas conhecê-las e saber suas características será útil para você perceber quais são as possibilidades.

A tabela abaixo apresenta um resumo das estruturas básicas. Ela está baseada na explicação que está no livro do Advanced R do Hadley Wickham (leitura recomendada pra quem deseja aprofundar seus conhecimento em R).

Tipo Descrição Dimensões Homogêneo
vector Coleção de elementos simples. Todos os elementos precisam ser do mesmo tipo básico de dado 1 Sim
array Coleção que se parece com o vector, mas é multidimensional n Sim
matrix Tipo especial de array com duas dimensões 2 Sim
list Objeto complexo com elementos que podem ser de diferentes tipos 1 Não
data.frame Tipo especial de lista onde cada coluna é um vetor de apenas um tipo e todos as colunas têm o mesmo número de registros. É o tipo mais utilizado se trabalhar com dados 2 Não
factor Tipo especial de vector que só contém valores pré definidos (levels) e categóricos (characters). Não é possível adicionar novas categorias sem criação de novos levels 1 Não


Do que se trata o campo Dimensões na tabela? Na prática, isso afetará como você usará partes desse objeto. Por exemplo, um objeto com duas dimensões tem linhas e colunas. Assim, você usará [ , ] (com vírgula separando linha e coluna, respectivamente) para acessar a dimensão que você deseja selecionar. Já um objeto unidimensional terá seus elementos acessados usando apenas [ ]. A lista, por sua vez, tem seus elementos acessados com [[ ]].

E o campo Homogêneo da tabela? Trata-se de mais uma características das estruturas de dados. Diz respeito à variedade de tipos básicos que um objeto pode conter. Por exemplo, vetores só aceitam um tipo de dado. Assim, se você atribuir dois tipos diferentes, ele forçará para um único tipo. Listas e data frames aceitam diferentes tipos de dados.

Observações sobre listas

Lista pode causar um pouco de confusão no começo. Daremos alguns exemplos para explicar melhor.

Iremos criar listas com duas bases de dados que já são fornecidas como exemplos no próprio R. Primeiramente, vamos carregar as duas bases de dados:


[1] "data.frame"

[1] "data.frame"

Veja que temos dois data frames. Agora, vamos criar um objeto único que irá receber essas duas bases. Além disso, a fim de mostrar a heterogeneidade, iremos incluir um objeto que será um vetor.

Faça um teste e digite lista.teste no console para ver o resultado.

E como eu faço pra acessar partes específicas? Como dissemos a lista tem uma pequena diferença, será necessário usar o [[ ]]. Lembre-se que, como a lista é um objeto de dimensão 1, só precisaremos passar o índice que temos interesse. O vetor x é o terceiro elemento. Logo, para acessá-lo podemos fazer o seguinte:


 [1]  1  2  3  4  5  6  7  8  9 10
Para terminar essa breve explicação sobre listas, vamos mostrar que cada objeto de uma lista pode ter um nome:
Dessa forma, você também poderá acessar usando o nome com o auxílio do $:

 [1]  1  2  3  4  5  6  7  8  9 10


Observações sobre vectors

Vectors possuem algumas propriedades muito úteis como length(), typeof() e unique(). Você também poderá usar o length() para saber o tamanho de uma lista.

Observações sobre data.frames

As funções nrow() e ncol() podem ser usadas para saber, respectivamente, o número de linhas e colunas de um data.frame (ou de uma matriz).

Data.frames também podem ter o nome das colunas alterados. Veja o exemplo a seguir:


  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

[1] "Sepal.Length" "Sepal.Width"  "Petal.Length" "Petal.Width" 
[5] "Species"     

  campo1 campo2 campo3 campo4 campo5
1    5.1    3.5    1.4    0.2 setosa
2    4.9    3.0    1.4    0.2 setosa
3    4.7    3.2    1.3    0.2 setosa
4    4.6    3.1    1.5    0.2 setosa
5    5.0    3.6    1.4    0.2 setosa
6    5.4    3.9    1.7    0.4 setosa

Valores Faltantes (Missing)

O R atribui NA para valores faltantes. Ou seja, se por acaso uma determinada posição de um vetor ou de uma coluna de um data.frame não possui valor algum, o R mostrará NA.

É muito comum lidar com conjuntos de dados que tenham ocorrências de NA em alguns campos. É importante saber o que fazer em casos de NA, e nem sempre a solução será a mesma, vai variar de acordo com as suas necessidades.

Em algumas bases de dados, quem gera o dado atribui valores genéricos como 999 ou até mesmo um “texto vazio” ' '. Nesse caso, você provavelmente terá que substituir esses valores “omissos” por NA.

Vamos explicar as funções básicas para começar a lidar com NA no R.

Em primeiro lugar, criaremos um simples data.frame para exemplificar:


  col1 col2
1    a   10
2    b   20
3    c   30
4    d   NA
5    e   50
6    f   NA

 col1       col2     
 a:1   Min.   :10.0  
 b:1   1st Qu.:17.5  
 c:1   Median :25.0  
 d:1   Mean   :27.5  
 e:1   3rd Qu.:35.0  
 f:1   Max.   :50.0  
       NA's   :2     

Usamos o letters que é uma lista pré construída no R e que retorna as 26 letras do alfabeto. No caso, usamos só as seis primeiras. Na segunda coluna, colocamos alguns NA’s.

A função summary mostra que existem dois NA’s na col2. Nesse exemplo fica fácil para encontrar onde estão os NA’s e fazer alguma modificação caso deseje, mas considere um caso em que seu data.frame é grande. Você não iria conseguir identificar no olho. Assim, é necessário usar algumas funções. Vamos começar como o is.na():


[1] FALSE FALSE FALSE  TRUE FALSE  TRUE

[1] 4 6

O is.na() realiza um teste para saber se cada elemento da variável col2 é um missing. Além disso, se usarmos o is.na() dentro da função which() saberemos quais as posições que possuem o NA. Um detalhe importante sobre funções que retornam TRUE ou FALSE como o is.na() é que você pode usar a ! para fazer o teste ao contrário. Isto é, se quisermos saber quais não são NA, faremos o seguinte:


[1]  TRUE  TRUE  TRUE FALSE  TRUE FALSE

Notou que a função retornou o contrário de is.na(data.ex$col2)?

Agora iremos introduzir a função complete.cases(). Bastante utilizada, essa função retorna TRUE para as linhas em que todas as colunas possuem valores válidos e FALSE para as linhas em que em alguma coluna existe um NA. Ou seja, essa função diz quais são as linhas (amostras) completas em todas suas características (campos).


[1]  TRUE  TRUE  TRUE FALSE  TRUE FALSE

[1] FALSE FALSE FALSE  TRUE FALSE  TRUE

Podemos usar o retorno dessa função para selecionar linhas do nosso data.frame:


  col1 col2
4    d   NA
6    f   NA

  col1 col2
1    a   10
2    b   20
3    c   30
5    e   50

Você poderia usar a função na.omit() para obter o mesmo resultado da seleção de linhas com o complete.cases():


  col1 col2
1    a   10
2    b   20
3    c   30
5    e   50

Por fim, iremos imputar a média da col2 nas linhas em que há NA. Para isso, usaremos o ifelse() que tratamos na parte 6 e o is.na(), além da função mean().


[1] 27.5

  col1 col2
1    a 10.0
2    b 20.0
3    c 30.0
4    d 27.5
5    e 50.0
6    f 27.5

Note que na função mean() usamos o argumento na.rm. Ele significa “remover NA”, o que é necessário nesse cálculo, pois se os NA’s não forem retirados, a média será NA também.

Imputar dados em casos de NA é uma das várias estratégias para lidar com ocorrência de missing no conjunto dos dados.

Exemplo final: Titanic

Vamos dar um exemplo final de algumas transformações e manipulações de dados na tentativa de resumir todos os aspectos tratados no kit de sobrevivência em R.

Escolhemos a base de dados dos passageiros do Titanic! É uma base muito utilizada como tutorial de machine learning onde o objetivo é criar um modelo para prever os sobreviventes do acidente. Se você pretende aprender machine learning, certamente vai esbarrar (ou já esbarrou) com essa base de dados. Inclusive há uma série de tutoriais de machine learning com essa base no Kaggle.

Nosso objetivo não é criar nenhum modelo nem ensinar a fazer isso, vamos apenas explorar a base, manipular, transformar e criar algumas variáveis. Teremos muitos posts em breve sobre modelos preditivos e machine learning!

Primeiro criamos um novo script, lembre-se sempre de salvar o seu trabalho para não perder nada. Em seguida vamos limpar o ambiente de memória para começar.

Vamos instalar e carregar o pacote R que disponibiliza os dados.

O data frame que iremos usar já estará carregado na memória e se chama titanic_train. Trata-se da base de treinamento usada para treinar modelos. Vamos dar uma olhada. As bases de treinamento já vem com a resposta na variável que você quer descobrir na base de teste. Nesse caso os modelos que usam essa base são treinados para descobrir a variável Survived.


'data.frame':   891 obs. of  12 variables:
 $ PassengerId: int  1 2 3 4 5 6 7 8 9 10 ...
 $ Survived   : int  0 1 1 1 0 0 0 0 1 1 ...
 $ Pclass     : int  3 1 3 1 3 3 1 3 3 2 ...
 $ Name       : chr  "Braund, Mr. Owen Harris" "Cumings, Mrs. John Bradley (Florence Briggs Thayer)" "Heikkinen, Miss. Laina" "Futrelle, Mrs. Jacques Heath (Lily May Peel)" ...
 $ Sex        : chr  "male" "female" "female" "female" ...
 $ Age        : num  22 38 26 35 35 NA 54 2 27 14 ...
 $ SibSp      : int  1 1 0 1 0 0 0 3 0 1 ...
 $ Parch      : int  0 0 0 0 0 0 0 1 2 0 ...
 $ Ticket     : chr  "A/5 21171" "PC 17599" "STON/O2. 3101282" "113803" ...
 $ Fare       : num  7.25 71.28 7.92 53.1 8.05 ...
 $ Cabin      : chr  "" "C85" "" "C123" ...
 $ Embarked   : chr  "S" "C" "S" "S" ...

  PassengerId Survived Pclass
1           1        0      3
2           2        1      1
3           3        1      3
4           4        1      1
5           5        0      3
6           6        0      3
                                                 Name    Sex Age
1                             Braund, Mr. Owen Harris   male  22
2 Cumings, Mrs. John Bradley (Florence Briggs Thayer) female  38
3                              Heikkinen, Miss. Laina female  26
4        Futrelle, Mrs. Jacques Heath (Lily May Peel) female  35
5                            Allen, Mr. William Henry   male  35
6                                    Moran, Mr. James   male  NA
  SibSp Parch           Ticket    Fare Cabin Embarked
1     1     0        A/5 21171  7.2500              S
2     1     0         PC 17599 71.2833   C85        C
3     0     0 STON/O2. 3101282  7.9250              S
4     1     0           113803 53.1000  C123        S
5     0     0           373450  8.0500              S
6     0     0           330877  8.4583              Q

Repare que cada linha representa um passageiro e cada campo representa uma característica desse passageiro. As variáveis (campos) estão em inglês e talvez não sejam tão óbvias. Segue explicação de cada uma:

Nome do campo Descrição do campo
Survived Passageiro sobrevivente (1) ou morto (0)
Pclass Classe do passageiro
Name Nome do passageiro
Sex Gênero do passageiro (male ou female)
Age Idade do passageiro
SibSp Número de irmãos ou cônjuges a bordo
Parch Número de pais ou filhos a bordo
Ticket Número do tíquete
Fare Preço do tíquete
Cabin Cabine
Embarked Portão de embarque


Vamos traduzir os nomes dos campos para facilitar o entendimento. Para isso usaremos a função names()


 [1] "PassengerId" "Survived"    "Pclass"      "Name"       
 [5] "Sex"         "Age"         "SibSp"       "Parch"      
 [9] "Ticket"      "Fare"        "Cabin"       "Embarked"   

Como o objetivo dessa base de dados é treinar um modelo para descobrir se um passageiro vai sobreviver ou não, vamos manipular e criar variáveis para tentar ajudar a atingir esse objetivo. O ideal é fazer uma bela análise exploratória dos dados, com auxílio de gráficos e estatística básica, mas nosso foco agora é apenas na transformação de dados, portanto, tentaremos um pouco de intuição e criatividade para criar variáveis possivelmente úteis.

Vamos começar com a variável idade. Há um comportamento interessante nessa variável: missings!


 [1] 22.00 38.00 26.00 35.00    NA 54.00  2.00 27.00 14.00  4.00 58.00
[12] 20.00 39.00 55.00 31.00 34.00 15.00 28.00  8.00 19.00 40.00 66.00
[23] 42.00 21.00 18.00  3.00  7.00 49.00 29.00 65.00 28.50  5.00 11.00
[34] 45.00 17.00 32.00 16.00 25.00  0.83 30.00 33.00 23.00 24.00 46.00
[45] 59.00 71.00 37.00 47.00 14.50 70.50 32.50 12.00  9.00 36.50 51.00
[56] 55.50 40.50 44.00  1.00 61.00 56.00 50.00 36.00 45.50 20.50 62.00
[67] 41.00 52.00 63.00 23.50  0.92 43.00 60.00 10.00 64.00 13.00 48.00
[78]  0.75 53.00 57.00 80.00 70.00 24.50  6.00  0.67 30.50  0.42 34.50
[89] 74.00

[1] 177

Usando sum() junto com is.na() conseguimos contar a quantidade total de NA na variável.

Nesse nosso caso específico, vamos interpretar NA como se o passageiro tivesse a idade desconhecida, seja lá qual for o motivo. Dependendo do algoritmo de machine learning que será aplicado a esses dados, a presença de NA não é bem vinda. Portanto, precisamos lidar com os NAs dessa variável.

A título de exemplificação, vamos adicionar a média geral das idades quando não soubermos a idade do passageiro. (Veja, essa nem sempre é uma boa estratégia para imputação de dados. Vamos usá-la agora apenas por ser bem simples).


[1] 29.69912

Calculamos a média desconsiderando ocorrências de NA, em seguida atribuímos a média (arredondada) às ocorrências de NA.

Agora todos os passageiros tem idade, alguns a idade correta, outros uma idade atribuída. Vamos criar agora uma classificação de jovem, adulto ou idoso para essa variável.

Pode ser que isso ajude algum algoritmo a prever melhor quem vive ou quem morre no acidente, pois, intuitivamente, talvez jovens sejam imaturos fiquem mais assustados, talvez idosos tenham menos habilidade de fuga e adultos talvez lidem melhor com situações de emergência.

Sendo assim, até 20 anos chamaremos de jovem, de 21 a 54 chamaremos de adulto, e acima de 55 chamaremos de idoso. Vamos chamar essa variável de faixa_etaria.


   idade faixa_etaria
1     22       adulto
2     38       adulto
3     26       adulto
4     35       adulto
5     35       adulto
6     30       adulto
7     54       adulto
8      2        jovem
9     27       adulto
10    14        jovem
11     4        jovem
12    58        idoso
13    20        jovem
14    39       adulto
15    14        jovem

Uma outra variável que pode ser interessante para ajudar modelos preditivos pode ser o total de parentes. Será que quanto mais parentes o passageiro tiver, mais ele se preocupe em salvar a vida dos seus entes queridos, botando a sua vida em risco? Ou será que a prioridade “mulheres e crianças primeiro” ajudou quem tinha família a sobreviver?

Há uma variável para irmãos e cônjuges, e outra para crianças ou pais. Vamos somá-las e criar o total_parentes.

Para complementar essa ideia, vamos tentar distinguir quem tinha família e quem não tinha, criando uma variável categórica simplesmente indicando se o passageiro tem família ou não:

Seguindo com as transformações, o título do passageiro pode dizer algo sobre ele. Vamos tentar isolar o título em uma variável para explicitar isso aos possíveis algoritmos:


                                                  nome  titulo
1                              Braund, Mr. Owen Harris      Mr
2  Cumings, Mrs. John Bradley (Florence Briggs Thayer)     Mrs
3                               Heikkinen, Miss. Laina    Miss
4         Futrelle, Mrs. Jacques Heath (Lily May Peel)     Mrs
5                             Allen, Mr. William Henry      Mr
6                                     Moran, Mr. James      Mr
7                              McCarthy, Mr. Timothy J      Mr
8                       Palsson, Master. Gosta Leonard  Master
9    Johnson, Mrs. Oscar W (Elisabeth Vilhelmina Berg)     Mrs
10                 Nasser, Mrs. Nicholas (Adele Achem)     Mrs
11                     Sandstrom, Miss. Marguerite Rut    Miss
12                            Bonnell, Miss. Elizabeth    Miss
13                      Saundercock, Mr. William Henry      Mr
14                         Andersson, Mr. Anders Johan      Mr
15                Vestrom, Miss. Hulda Amanda Adolfina    Miss

O comando usado talvez seja um pouco avançado, mas vamos tentar explicar por partes. Primeiramente usamos o strsplit(), uma função que lida com caracteres e divide uma string baseado numa marcação. Nesse caso, estamos dividindo o nome do passageiro em dois pontos: vírgula , e ponto ., que é justamente o padrão textual que separa o título no nome.

O resultado do strsplit() é uma lista com as partes da separação. Para acessar exatamente o segundo elemento da lista, que é onde está o título, usamos o lapply(), uma função da família apply, que executa um comando repetidamente ao longo de uma estrutura (coluna, array, listas, matrizes, etc…). O efeito prático das funções da família apply se assemelha muito à loops.

Dominar a família apply pode ser muito interessante para se tornar um bom analista de dados. Certamente faremos uma sequência de posts explicando detalhadamente todas as funções da família apply, aguarde!

Conclusão

E é isso pessoal. Chegamos ao fim da sequência. Esperamos que tenha gostado e aprendido o kit básico de sobrevivência em R. Daqui em diante os posts serão intermediários e avançados, tratando de questões mais profundas como junção de dados, visualização de dados, análise exploratória, estatística, machine learning.

Referências

Demais posts da sequência: