Cantinho do R

Temos que pegar! - parte 1, correlações

Pearson, eu escolho você! :D

Aprendendo um pouco sobre correlações no R, de um jeito que você não esperava

Por Marcos Vital, do LEQ-UFAL

Este material foi construído com a ajuda de muitas pessoas que acreditam no LEQ e em Ciência Livre. Muito obrigado!

Para mais material, visite o Cantinho do R


 

Tava demorando para aparecerem uns Pokemóns por aqui, não é? :D

Mas vamos, falando sério: este script vai falar sobre correlações e como calculá-las no R, e servirá de introdução para outros scripts, nos quais vou mostrar algumas maneiras de trabalhar com várias correlações de uma vez, incluindo um gráfico maneiro. ;) Os Pokémons entram só pela curiosidade e nerdice, mas você não precisa conhecê-los para entender o que vai rolar aqui, beleza? Então, se você for um fã desses bixinhos, siga adiante e divirta-se; se não for, relaxe e acompanhe assim mesmo, pois o conteúdo é de verdade. ;)

Ah sim, e antes que eu me esqueça: este script é pesadamente inspirado no trabalho feito por algumas outras pessoas. Particularmente, tem elementos desta postagem bem legal aqui, do blog do Joshua Kunst. E também desta postagem aqui do blog is.R. São desses blogs completamente abertos, com scripts disponíveis e muita coisa legal! Obrigado aos autores!


 

Os dados

Os dados que vamos usar se parecem com muitos dados multivariados por aí afora, e se você quiser fazer um rápido e fácil paralelo com a biologia, pode imaginar, por exemplo, um estudo comparativo com várias espécies, no qual alguém mediu características de cada uma e tacou tudo numa tabela. Mas uma tabela especial, aqui um pedaço dela:

##   N       Name  HP  Attack  Defense  Speed  Special  Total  Average
## 1 1  Bulbasaur  45      49       49     45       65    253     50.6
## 2 2    Ivysaur  60      62       63     60       80    325     65.0
## 3 3   Venusaur  80      82       83     80      100    425     85.0
## 4 4 Charmander  39      52       43     65       50    249     49.8
## 5 5 Charmeleon  58      64       58     80       65    325     65.0
## 6 6  Charizard  78      84       78    100       85    425     85.0

Cada linha é um Pokémon, e cada coluna uma de suas características. Você tem ali informações dos pontos de vida (HP), poder de ataque e de defesa, velocidade e poder especial. Finalmente, temos a soma e a média (essas duas podemos descartar mais adiante).

Se parar parar pensar, é muito, mas muito parecido com uma planilha de dados de um trabalho de morfometria ou de traços funcionais de espécies, por exemplo. E é, em essência, uma planilha de dados multivariados: pra cada linha (que equivale a uma unidade amostral) temos várias medidas (as variáveis). Isso faz com que os métodos que vamos aplicar aqui possam ser considerados bem genéricos, então sinta-se à vontade para aplicar nas mais diversas situações. ;)

 

Obtendo e organizando os dados

Uma das coisas legais do R é a flexibilidade que ele tem pra tudo, e a forma como vamos obter os dados dos Pokémons é mais um exemplo disso. As informações que precisamos estão em um site bem conhecido pelos fãs, o Bulbapedia. Mais especificamente, estamos falando desta tabela aqui.

A tabela tem o formatinho que queremos, mas está ali, em uma página de internet, e não em um arquivo bonitinho de planilha no meu computador… Então, como fazer? Será que eu deveria fazer gambiarra, copiando e colando os dados da página no meu gerenciador de planilhas? Ou será que dá pra fazer diferente? Ei, será que o R lê esses dados online, diretamente? Mas é claro que sim! :D

Vou adaptar o material lá do blog is.R, e vamos lá. Ah, você vai precisar do pacote XML, então é importante instalá-lo antes no seu R, beleza?

library(XML)

pagina <- readLines("http://bulbapedia.bulbagarden.net/wiki/List_of_Pok%C3%A9mon_by_base_stats_(Generation_I)")

tabela <- readHTMLTable(pagina)

poketabela <- tabela[[1]]

O que aconteceu aqui é um processo em algumas etapas:

  • primeiro, usamos a função readLines para ler a página e importar tudo para o objeto que chamamos ali de pagina. É um processo bruto mesmo, lendo tudo o que tem lá. É claro que isso gera um objeto cheio de informação, então temos que absorver apenas o que interessa, o que acontece na sequência.

  • agora usamos a função readHTMLTable para ler apenas a tabela que estava dentro da página lida. Aqui chamamos o objeto criado de tabela. Mas ainda não está legal, pois o objeto recém criado é, na verdade, um lista com quatro partes, da qual a primeira é a tabela…

  • então finalizamos finalmente criando um novo objeto, o poketabela. Aqui não precisamos usar função alguma, simplesmente usamos os colchetes para indicar o “pedaço” que precisamos

Quer entender melhor? Uma boa é ir fuçando os objetos criados, passo a passo, para tentar entender melhor o que está acontecendo. Você pode, por exemplo, tacar um str(tabela) para entender melhor o que foi lido. ;)

Mas olha, ainda não está pronto, pois a tabela não vem assim tããão bonitinha de uma vez. Veja só:

head(poketabela)
##    #\n \n  Pokémon\n  HP\n  Attack\n  Defense\n  Speed\n  Special\n
## 1  001      Bulbasaur    45        49         49       45         65
## 2  002        Ivysaur    60        62         63       60         80
## 3  003       Venusaur    80        82         83       80        100
## 4  004     Charmander    39        52         43       65         50
## 5  005     Charmeleon    58        64         58       80         65
## 6  006      Charizard    78        84         78      100         85
##    Total\n  Average\n
## 1      253       50.6
## 2      325         65
## 3      425         85
## 4      249       49.8
## 5      325         65
## 6      425         85

Pois é, este processo de importar uma tabela direto de um site nem sempre sai perfeito de primeira, então temos que lapidar o resultado. Vamos, então, fazer uma faxina geral:

poketabela.nova<-poketabela[,-2] #Aqui apenas retiramos a coluna n repetida e vazia
colnames(poketabela.nova)[1:2] <- c("N", "Name") #Aqui ajeitamos os nomes das duas primeiras colunas
colnames(poketabela.nova) <- gsub("\n", "", colnames(poketabela.nova)) #Aqui nós retiramos aqueles "\n" chatos que aparecem nos nomes das demais colunas

#Agora vamos ver como está ficando:
head(poketabela.nova)
##     N       Name  HP  Attack  Defense  Speed  Special  Total  Average
## 1 001  Bulbasaur  45      49       49     45       65    253     50.6
## 2 002    Ivysaur  60      62       63     60       80    325       65
## 3 003   Venusaur  80      82       83     80      100    425       85
## 4 004 Charmander  39      52       43     65       50    249     49.8
## 5 005 Charmeleon  58      64       58     80       65    325       65
## 6 006  Charizard  78      84       78    100       85    425       85
  • a função gsub é legal, não é? Ela é bem prática para se substituir coisas, como fizemos ali. E usamos a função colnames para fazer isso diretamente com os nomes das colunas.

Aêêêê, ficou bacana, não ficou? Mas ainda tem uma coisa! É que quando importamos dados que chegam assim meio bagunçados da internet, o R normalmente interpreta todas as colunas como se fossem variáveis categóricas (ou fatores, na linguagem do próprio R). Então a gente pode estar até olhando para números, mas na real o R está tratando tudo como nomes! Quer conferir? Veja só:

summary(poketabela.nova)
##        N               Name           HP         Attack       Defense 
##  001    :  1   Abra      :  1   65     :17   65     :11   65     :11  
##  002    :  1   Aerodactyl:  1   60     :15   80     :11   55     :10  
##  003    :  1   Alakazam  :  1   40     :13   45     :10   70     :10  
##  004    :  1   Arbok     :  1   50     :10   50     : 7   50     : 9  
##  005    :  1   Arcanine  :  1   80     :10   70     : 7   60     : 9  
##  006    :  1   Articuno  :  1   90     :10   85     : 7   80     : 9  
##  (Other):145   (Other)   :145   (Other):76   (Other):98   (Other):93  
##       Speed       Special       Total        Average  
##  70     :11   50     :14   345    :  8   69     :  8  
##  90     :11   100    :12   270    :  6   54     :  6  
##  100    : 9   40     :11   280    :  6   56     :  6  
##  45     : 9   65     :11   395    :  6   79     :  6  
##  55     : 9   70     :10   420    :  6   84     :  6  
##  60     : 8   80     : 9   425    :  6   85     :  6  
##  (Other):94   (Other):84   (Other):113   (Other):113

Viu só? O R, ao ler variáveis categóricas, mostra a frequência de cada classe no summary. Se ele tivesse lido as informações como números, teria dado um resultado com valores de média, máximo, mínimo, etc. Então temos que ajeitar este detalhe, certinho? Vamos lá, é fácil:

#Conferindo qual o tamanho da nossa tabela:
dim(poketabela.nova)
## [1] 151   9
#São 9 colunas. A primeira é o número do Pokémon no Pokédex, e a segunda é o nome. As demais são dados, então:

poketabela.nova[,3:9]<-apply(poketabela.nova[,3:9], 2, as.numeric)

#Vamos ver se deu certo? Ó só:
summary(poketabela.nova)
##        N               Name           HP             Attack      
##  001    :  1   Abra      :  1   Min.   : 10.00   Min.   :  5.00  
##  002    :  1   Aerodactyl:  1   1st Qu.: 45.00   1st Qu.: 51.00  
##  003    :  1   Alakazam  :  1   Median : 60.00   Median : 70.00  
##  004    :  1   Arbok     :  1   Mean   : 64.21   Mean   : 72.22  
##  005    :  1   Arcanine  :  1   3rd Qu.: 80.00   3rd Qu.: 90.00  
##  006    :  1   Articuno  :  1   Max.   :250.00   Max.   :134.00  
##  (Other):145   (Other)   :145                                    
##      Defense           Speed          Special           Total     
##  Min.   :  5.00   Min.   : 15.0   Min.   : 20.00   Min.   :175.0  
##  1st Qu.: 50.00   1st Qu.: 46.5   1st Qu.: 50.00   1st Qu.:275.0  
##  Median : 65.00   Median : 70.0   Median : 65.00   Median :345.0  
##  Mean   : 68.16   Mean   : 68.8   Mean   : 69.25   Mean   :342.6  
##  3rd Qu.: 84.00   3rd Qu.: 90.0   3rd Qu.: 90.00   3rd Qu.:410.0  
##  Max.   :180.00   Max.   :140.0   Max.   :154.00   Max.   :590.0  
##                                                                   
##      Average     
##  Min.   : 35.00  
##  1st Qu.: 55.00  
##  Median : 69.00  
##  Mean   : 68.53  
##  3rd Qu.: 82.00  
##  Max.   :118.00  
## 

Yes, funcionou!

Aqui usamos a função apply, ela é um quebra galho, pois de outra forma a gente teria que repetir a etapa do as.numeric para cada coluna desejada, o que não seria muito divertido… Ali dentro da função, o segundo argumento, que está apenas como o número 2, indica que queremos que a operação seja feita nas colunas. Qualquer coisa chame o help da função para espiar, ela é muito útil. ;)

Mas pera que ainda tem um probleminha!

Quando eu estava num ponto mais avançado deste script, percebi que os nomes das variáveis vieram com uns espaços antes, o que gerou alguns errinhos na hora de “chamar” uma coluna. Só consegui sacar isso quado no lugar do summary eu usei o str. Saca só:

str(poketabela.nova)
## 'data.frame':    151 obs. of  9 variables:
##  $ N       : Factor w/ 151 levels "001","002","003",..: 1 2 3 4 5 6 7 8 9 10 ...
##  $ Name    : Factor w/ 151 levels "Abra","Aerodactyl",..: 10 57 140 15 16 14 130 145 9 12 ...
##  $  HP     : num  45 60 80 39 58 78 44 59 79 45 ...
##  $  Attack : num  49 62 82 52 64 84 48 63 83 30 ...
##  $  Defense: num  49 63 83 43 58 78 65 80 100 35 ...
##  $  Speed  : num  45 60 80 65 80 100 43 58 78 45 ...
##  $  Special: num  65 80 100 50 65 85 50 65 85 20 ...
##  $  Total  : num  253 325 425 249 325 425 250 325 425 175 ...
##  $  Average: num  50.6 65 85 49.8 65 85 50 65 85 35 ...

Conseguiu ver? Ali antes dos nomes das colunas, exceto no caso das colunas que alteramos. Então podemos tentar substituir estes espaços por “nada”, de novo usando gsub. Vamos tentar?

colnames(poketabela.nova) <- gsub(" ", "", colnames(poketabela.nova))

Pra ver se resolveu:

str(poketabela.nova)
## 'data.frame':    151 obs. of  9 variables:
##  $ N      : Factor w/ 151 levels "001","002","003",..: 1 2 3 4 5 6 7 8 9 10 ...
##  $ Name   : Factor w/ 151 levels "Abra","Aerodactyl",..: 10 57 140 15 16 14 130 145 9 12 ...
##  $ HP     : num  45 60 80 39 58 78 44 59 79 45 ...
##  $ Attack : num  49 62 82 52 64 84 48 63 83 30 ...
##  $ Defense: num  49 63 83 43 58 78 65 80 100 35 ...
##  $ Speed  : num  45 60 80 65 80 100 43 58 78 45 ...
##  $ Special: num  65 80 100 50 65 85 50 65 85 20 ...
##  $ Total  : num  253 325 425 249 325 425 250 325 425 175 ...
##  $ Average: num  50.6 65 85 49.8 65 85 50 65 85 35 ...

Yes, deu certo!

Sabe, eu poderia ter alterado este script de forma a resolver isso lá em cima. Mas aí pensei que seria meio desonesto, saca? É que o fluxo de trabalho do R é assim mesmo, a gente vai trabalhando, descobre problemas, volta atrás e por aí vai. Neste caso, fica a dica de usar str de vez em quando, pois ele pode mostrar coisas que o summary não mostra - até porque são funções com propósitos diferentes. Beleza?

 

Entendendo as correlações

Bom, vamos lá? O objetivo deste script é brincar um pouco com correlações (e a importação de uma tabela da internet é um bônus maneiro, que pode ser muito útil também).

Uma medida correlação é algo bem fácil de interpretar, e mede o quanto duas variáveis “andam” juntas. Normalmente, as medidas de correlação tem valores que variam de -1 até +1, sendo que o sinal serve para você saber a “direção”, enquanto o valor serve para você medir a “força”.

Não tem mistério, veja os exemplos:

Exemplinhos de correlação

Exemplinhos de correlação

Sacou? Se a correlação é zero, então isto indica que as duas variáveis estudadas não possuem relação entre si. Por outro lado, se a correlação é 1 (em módulo), então as duas variáveis estão perfeitamente correlacionadas. Agora, é claro que podemos ter correlações, digamos assim, “no meio do caminho” - e na prática são elas que costumamos ver no dia à dia. Veja só:

Mais exemplinhos de correlação

Mais exemplinhos de correlação

Estas correlações que estamos olhando até agora são calculadas pelo Coeficiente de Correlação de Pearson. É o coeficiente mais comum de correlação, e vai funcionar na maioria das situações mais “comuns”. E o que seria uma situação incomum?! Bom, de vez em quando os nossos dados podem…

 

Dados na mão, vamos nos divertir!!! :D

Beleza, vamos começar pra valer! A função básica de correlação no R é a função cor. Por padrão, ela calcula a correlação de Pearson, a não ser que você queira que ela calcule outro tipo, alterando o argumento method. Se quiser saber mais você pode (e deve!) olhar o help da função, com ?cor.

Nós podemos começar com um uso mega simples a função, calculando a correlação entre os pontos de vida e o poder de ataque, por exemplo:

cor(poketabela.nova$HP, poketabela.nova$Attack, method="pearson")
## [1] 0.302534

Parabéns, você calculou a sua primeira correlação no R! :D

Então, o valor foi 0.3. Falar em alto ou baixo é um tanto subjetivo, mas é comum que ao estudarmos correlações a gente comece a pegar um feeling sobre isso, e a intuitivamente interpretá-las. No meu caso, por exemplo, eu diria que temos aqui uma correlação baixa, mas que não é desprezível. Como ela é positiva então isso quer dizer que existe uma ligeira tendência para pokemóns com mais pontos de vida terem um poder de ataque mais alto, e vice versa (sem relação de causa e efeito presumida aqui, pois a correlação não mede direção nem causa e efeito nem nada do tipo).

Será que entre estas variáveis há alguma correlação mais alta? A gente poderia verificar cada par, mas o R é um pouco mais espertinho do que você imagina, e pode calucular todas de uma vez. O que vamos fazer a seguir é calcular uma matriz de correlação, comparando todas as variáveis. É mó legal!

Pra calcular, devemos primeiro decidir quais variáveis usar. Como as duas últimas são apenas sumários (soma e média), vamos deixá-las de fora. E as duas primeiras são identificações, não medidas em si, então não entram também.

cor(poketabela.nova[,3:7], method="pearson") #Apenas colunas 3 a 7, de HP até Special
##                  HP    Attack     Defense       Speed   Special
## HP       1.00000000 0.3025340  0.12194102 -0.04246078 0.3147197
## Attack   0.30253397 1.0000000  0.48910402  0.19492229 0.1138314
## Defense  0.12194102 0.4891040  1.00000000 -0.05477581 0.1530025
## Speed   -0.04246078 0.1949223 -0.05477581  1.00000000 0.4195258
## Special  0.31471972 0.1138314  0.15300246  0.41952575 1.0000000

Uhu!!! Olha que coisa mais linducha!

Bom, se você não está acostumado a ler uma matriz, né difícil não. Basta escolher uma variável em uma linha e comparar com outra em uma coluna (ou co contrário, claro, dá no mesmo!). Por exemplo, se eu olhar para a Linha Attack e a coluna HP, vou ver de novo a correlação que encontramos antes, de 0.3.

Se você cruzar uma variável com ela mesma, vai esbarrar na famosa diagonal principal, e o valor vai ser sempre 1. Duh! Claro que sim! Uma variável correlacionada com ela mesma? Tem que dar 1, né?

Agora, se você olhar os numerinhos com calma, vai ver que eles se repetem. Isso acontece porque se você olhar pra correlação de Attack com HP, ela tem mesmo que ser a mesma que a de HP com Attack, pois a ordem das variáveis não faz diferença. Daí dizemos que a nossa matriz é simétrica, saca?

Então o que nossa matriz mostra? Bom, agora é uma questão de interpretação. De cara me chama a atenção a correlação positiva entre Ataque e Defesa, que deu 0.48. É uma correlação razoável, indica que é bem comum que pokemons com alto Ataque tenham alta Defesa também, e vice-versa. É a maior correlação que temos, seguida pela que vimos antes entre Ataque e HP; depois disso todas as demais tem valores marromenos baixos.

 


E agora

Bom, agora vocês podem olhar, brincar com as funções, namorar os dados e tal. Em uma próxima oportunidade eu quero continuar este script, explorando um gráfico maneirão para explorarmos as correlações. E depois disso, podemos começar a dar uns pulinhos na estatística multivariada, não é? Mas vamos por partes.

Um abração, e até a próxima!

Prof Marcos

 


Este documento faz parte do material que disponibilizo no meu blog, o “Cantinho do R”, e foi foi criado utilizando o software R, o programa RStudio e a linguagem Markdown. Para saber mais sobre eles, acesse:

http://www.r-project.org/

http://www.rstudio.com

http://rmarkdown.rstudio.com/

 

Quer comentar ou perguntar? Faça isso no blog, aqui