Visualização de dados com Seaborn
Tags: datascience, python, dataviz, seaborn
Published: 2019-10-28 01:14:58

Nesse artigo vamos entender um pouco melhor como fazer uma análise exploratória simples e como a biblioteca Seaborn pode ajudar nesse processo melhorando a visualização dos dados.

Na análise exploratória dos dados, também conhecida como EDA, é feita uma investigação para que se possa ter um resumo do que os dados podem nos dizer. Por exemplo, a média, o desvio padrão e a distribuição dos dados são informações importantes para entender o comportamento de um dataset portanto sempre estão presentes quando uma EDA é feita. Essa análise também é a base para que se possa criar hipóteses e posteriormente fazer inferências sobre os dados.

Vou usar um dataset criado pelo site PetFinder.my com diversas informações sobre a adoção de 150.000 cães e gatos. Esse dataset está  [disponível no Keaggle](https://www.kaggle.com/c/petfinder-adoption-prediction)  para uma competição. Abaixo mostro alguns dados do conjunto para entendermos melhor o que será analisado:

![](https://miro.medium.com/max/30/1*WJSYktW53tzDtDf7WtvL4Q.png?q=20)

![](https://miro.medium.com/max/722/1*WJSYktW53tzDtDf7WtvL4Q.png)

Aqui podemos ver que temos nome (Name), idade (Age), raça (Breed), cores (Color), entre outras variáveis no dataset e, apesar da maioria das colunas possuir apenas números, não se enganem, tirando a idade e ‘Fee’ que é a taxa paga pela adoção, todos os outros dados são categóricos. Nesse dataset também existem alguns arquivos com a correspondência desses números com a devida categoria, por exemplo, na variável ‘tipo’ (Type) 1 significa cão e 2 significa gato.

Aqui está o início do código que estou usando, com os imports. Aqui também usei o Jupyter notebook :)

```
import pandas as pd  
import seaborn as sns  
import matplotlib.pyplot as plt  
%matplotlib inline #usar esse comando para não precisar dar plt.show em cada gráfico  
df = pd.read_csv('train.csv') #esse é o nosso dataframe
```
Vou usar o comando  `df.describe()` para ver informações como a média, a quantidade de dados e o desvio padrão de cada coluna.

![](https://miro.medium.com/max/30/1*sv7JzEq3nEu1Kk_Nq1Sz4g.png?q=20)

![](https://miro.medium.com/max/255/1*sv7JzEq3nEu1Kk_Nq1Sz4g.png)

A gente já consegue ver que a média de idade (coluna ‘Age’) dos animais é de 10 meses entretanto nesse caso a média não nos dá uma visão da realidade já que a mediana (50%) é 3, ou seja, metade dos animais do dataset tinha até 3 meses na adoção. Também vemos que a idade máxima relatada são 255 meses, ou 21 anos (velhinhos❤). Como esse dataset possui muitos dados categóricos numéricos, algumas das descrições podem não fazer sentido mas podemos extrair algumas coisas já de cara como em ‘Type’ a média é 1,45 e com isso já sabemos que há mais animais do tipo 1 (cachorros) do que do tipo 2 (gatos).

Se usarmos o comando  ``df.describe(include=`all`)``  é possível ver informações sobre os nossos dados categóricos também, como quantidade de dados únicos.

É importante entender a distribuição dos dados e isso pode ser feito de diversas formas. Começando pela mais comum, o gráfico distplot mostra a combinação de um histograma com a linha do KDE. Um histograma é basicamente um gráfico de barras para variáveis quantitativas, que são divididas em intervalos, e nos mostra a frequência de dados que se tem em cada intervalo; o KDE é uma estimativa da densidade desses dados, calculando a probabilidade de se ter um ponto naquele local do gráfico.

```
sns.distplot(df['Age'])  
sns.distplot(df['Age'], kde=False) #Para não ver a linha do KDE no gráfico use o parâmetro 'kde' igual a 'False', que por padrão é igual a 'True' 
```

![](https://miro.medium.com/max/30/1*xVU6COvXgPETWoizHK1fbA.png?q=20)

![](https://miro.medium.com/max/381/1*xVU6COvXgPETWoizHK1fbA.png)

![](https://miro.medium.com/max/30/1*C09KlH-yQ2ZPKEw4GFp4iQ.png?q=20)

![](https://miro.medium.com/max/391/1*C09KlH-yQ2ZPKEw4GFp4iQ.png)

Nesses gráficos vemos que as idades dos animais, em meses, estão concentradas no começo do gráfico, ou seja, a maior parte dos animais adotados ainda é filhote (Como já mostrei usando a mediana no comando ‘describe’). Outra coisa para se notar é que o gráfico não segue uma distribuição normal, sua distribuição é enviesada para direita, essa é uma informação usada na escolha de outros métodos estatísticos a serem usados nesse dataset.

Um gráfico simples mas muito útil que podemos usar é o countplot, um gráfico de barras mostrando a contagem das variáveis. Vamos ver como estão distribuídos os valores de ‘MaturitySize’. Essa coluna representa o tamanho do animal quando atinge a maturidade e seus valores são de 1 à 4, sendo 1 pequeno e 4 grande.

`sns.countplot(x='MaturitySize', data=df) `

![](https://miro.medium.com/max/30/1*6LuLmvqBNTLkPMO1VjG39A.png?q=20)

![](https://miro.medium.com/max/405/1*6LuLmvqBNTLkPMO1VjG39A.png)

O gráfico mostra que a maior parte dos animais tinham porte médio-pequeno. Esse gráfico também pode ser feito utilizando a função catplot ao invés do countplot, interface para construção de vários tipos de gráficos categóricos, com o parâmetro  `kind=count`.

No próximo gráfico, um barplot, conseguimos ver a média de velocidade de adoção por tipo de animal. A linha preta vertical no meio das barras mostra o desvio padrão, no caso dos nossos dados o desvio padrão é bem pequeno então os dados não variam tanto dessa média.

`sns.barplot(x='Type', y='AdoptionSpeed', data=df,) `

![](https://miro.medium.com/max/30/1*XQk7ckBdldtNbTUBQduBTw.png?q=20)

![](https://miro.medium.com/max/389/1*XQk7ckBdldtNbTUBQduBTw.png)

Os próximos gráficos são boxplots, um tipo de gráfico com muita informação sobre os dados: Nele conseguimos ver o limite inferior e superior, os quartis, a mediana, como os dados estão distribuídos e os possíveis outliers.

-   Quartis: Os quartis dividem os dados em quatro partes. Ordenando os dados cada quartil vai possuir 25% das observações. Ou seja, os primeiros 25% das observação estarão até o corte do 1º quartil, os próximos 25% até o corte do 2º quartil, etc.
-   Limites: O limite inferior é calculado sendo, geralmente, 1,5 vezes o corte do 1º quartil e o limite superior sendo 1,5 vezes o corte do 3º quartil.
-   O corte do segundo quartil é igual a mediana.
-   Outliers são observações atípicas, muito afastadas da maioria dos dados. Num boxplot esses valores ficam abaixo do limite inferior ou acima do limite superior.

![](https://miro.medium.com/max/30/0*TmxBdUzCLOmZu6zB.png?q=20)

![](https://miro.medium.com/max/1024/0*TmxBdUzCLOmZu6zB.png)

Agora com os dados que estamos usando:
```
sns.boxplot(x='Type', y='AdoptionSpeed', data=df)  
sns.boxplot(x='Type', y='AdoptionSpeed', data=df, hue='Gender')  
sns.boxplot(x='Type', y='Fee', data=df) 
```

![](https://miro.medium.com/max/30/1*QyOeiqzrPV2xoxqD6k2LVA.png?q=20)

![](https://miro.medium.com/max/389/1*QyOeiqzrPV2xoxqD6k2LVA.png)

Nesse primeiro boxplot (Velocidade de adoção por tipo de animal) conseguimos ver que não há grande variação na velocidade de adoção dos cães mas que a maior parte deles demora um pouco mais que os gatos para serem adotados, comparando as medianas e as caixas que comportam metade dos dados.

![](https://miro.medium.com/max/30/1*Bexa8qI29fGDNvONbwQdfQ.png?q=20)

![](https://miro.medium.com/max/389/1*Bexa8qI29fGDNvONbwQdfQ.png)

Esse gráfico é o quase o mesmo que o anterior mas aqui foi feita uma divisão por gênero do animal ( 1=macho, 2=fêmea, 3=misturado, em casos de grupos adotados) com o parâmetro  `hue`  no comando.

![](https://miro.medium.com/max/30/1*wHVDvWCKamC8ITR7cnR8uQ.png?q=20)

![](https://miro.medium.com/max/398/1*wHVDvWCKamC8ITR7cnR8uQ.png)

Esse último, a taxa paga pela adoção por tipo de animal, é interessante. Não vemos as caixas e mal se vê a linha da mediana, apenas pontos, isso é porquê a maior parte das adoções não teve custo nenhum, então os quartis são 0 e a mediana também, os pontos do gráfico são todos outliers.

Alguns outros gráficos que mostram a distribuição dos dados são stripplot e o swarmplot. Os dois primeiros gráficos abaixo são stripplots de idade por gênero, o segundo ainda foi subdivido pelo tipo de animal através do parâmetro  `hue`.


```
sns.stripplot(x='Gender', y='Age', data=df)  
sns.stripplot(x='Gender', y='Age', data=df, hue='Type', split=True)  
sns.swarmplot(x='Gender', y='Age', data=df)
```


![](https://miro.medium.com/max/30/1*ZkJHMTmnHiwRKP3omH-hAQ.png?q=20)

![](https://miro.medium.com/max/392/1*ZkJHMTmnHiwRKP3omH-hAQ.png)

![](https://miro.medium.com/max/30/1*SSaDUWp9pkWs7CUUSYHaxw.png?q=20)

![](https://miro.medium.com/max/392/1*SSaDUWp9pkWs7CUUSYHaxw.png)

![](https://miro.medium.com/max/30/1*TIdCD5WDAyt7wMB-NBr7IQ.png?q=20)

![](https://miro.medium.com/max/392/1*TIdCD5WDAyt7wMB-NBr7IQ.png)

Esse último é o swarmplot, com as mesmas informações que os stripplots mas não há sobreposição dos dados.

Os próximos gráficos são sobre correlação, uma métrica do quão relacionadas duas variáveis são. Existem alguns jeitos de calcular a correlação mas vou usar a de Pearson, linear e mais comumente usada, onde os coeficientes da correlação vão de -1 a 1. Valores próximos a -1 significam que as variáveis são fortemente e inversamente correlacionadas: quando uma cresce a outra diminui; valores próximos a 1 são variáveis fortemente e positivamente correlacionadas: as duas crescem ou diminuem juntas, e 0 significa nenhuma correlação.

Um ponto importante de entender quando falamos de correlação é que uma variável ter correlação com a outra  **não implica que uma cause a outra**. Por exemplo, um aumento nas vendas de meias em julho pode estar correlacionado com um aumento nas vendas de chás no mesmo mês, mas isso não significa que as pessoas compraram chás porque compraram meias.

Além dos coeficientes também podemos ver uma correlação em um diagrama de dispersão ou, como é chamado no Seaborn, um scatterplot. Para identificar uma correlação no diagrama os dados devem estar parecidos com o primeiro e último gráfico da imagem abaixo (correlação negativa e positiva, respectivamente). O gráfico do meio representa dados não correlacionados.

![](https://miro.medium.com/max/30/1*XF1FxLmLJBjsmxBChBG-ow.png?q=20)

![](https://miro.medium.com/max/768/1*XF1FxLmLJBjsmxBChBG-ow.png)

E abaixo está o scatterplot da idade (Age) versus a taxa de adoção (Fee). No caso dessas duas variáves não há uma correlação aparente.

![](https://miro.medium.com/max/30/1*8k3qEmLFhLXU9C5aAVAtJw.png?q=20)

![](https://miro.medium.com/max/398/1*8k3qEmLFhLXU9C5aAVAtJw.png)

No pandas existe uma função que mostra os coeficientes de correlação de todas as variáveis, a tabela logo abaixo, e você pode usar esses valores para criar um heatmap, que mapeia os valores de uma matriz usando cores. Isso pode facilitar a identificação de variáveis correlacionadas quando se tem muitas variáveis.

`df.corr()`

![](https://miro.medium.com/max/30/1*PNF_Q9WxUWXhvlRl3JxKQQ.png?q=20)

![](https://miro.medium.com/max/629/1*PNF_Q9WxUWXhvlRl3JxKQQ.png)

Parte da matriz de correlação

`sns.heatmap(df.corr())`

![](https://miro.medium.com/max/30/1*5iDS1kjKfMce6SfkTVK44A.png?q=20)

![](https://miro.medium.com/max/907/1*5iDS1kjKfMce6SfkTVK44A.png)

Entretanto, o comando  `df.corr()`  usa por padrão a correlação de Pearson, que não é a mais indicada quando estamos tratando de dados categóricos como a maioria desse dataset, então criei um outro heatmap usando a correlação V de Crámer, cujo os coeficiente vão de 0 a 1, só para termos números mais reais.

`sns.heatmap(corr, annot=True) #annot mostra os coeficientes da matriz `

![](https://miro.medium.com/max/28/1*CuwqeEkP2TXgGN0KJn3Kuw.png?q=20)

![](https://miro.medium.com/max/1122/1*CuwqeEkP2TXgGN0KJn3Kuw.png)

Agora vendo a imagem e os coeficientes percebemos que há bem poucas correlações fortes no dataset, informação útil caso você vá criar modelos preditivos.

Um exemplo da importância da correlação é a Regressão. A Regressão Linear (existem outros tipos) é um modelo estatístico que tenta encontrar a melhor reta/plano que descreva a relação entre os dados. Assim é possível inferir valores sobre uma das variáveis. Mas para se usar uma regressão os dados (tirando a variável que se quer estimar) não devem estar correlacionados, e daí vem a importância do método. No Seaborn é possível ver graficamente a linha criada por uma regressão usando o comando lmplot ou regplot (o lmplot possui mais opções gráficas como a criação de grids).

![](https://miro.medium.com/max/30/0*ofh1EEo3JTTOn-H4.png?q=20)

![](https://miro.medium.com/max/300/0*ofh1EEo3JTTOn-H4.png)

Essa não é uma regressão do nosso dataset, mas trouxe aqui para mostrar como seria um boa regressão, capturando boa parte da relação entre os dados.

`sns.lmplot(x='Age', y='AdoptionSpeed', data=df, hue='Type' , col='Type') `

![](https://miro.medium.com/max/30/1*QXexCBBhcWC2d1EQgWH8dQ.png?q=20)

![](https://miro.medium.com/max/712/1*QXexCBBhcWC2d1EQgWH8dQ.png)

Esses gráficos mostram as retas das regressões entre as variáveis idade e velocidade de adoção, separado em duas colunas pelo tipo de animal, usando os parâmentos  `hue`  e  `col`. Junto da reta também é mostrada a dispersão dos dados. Só pelo gráfico já é possível ver que uma regressão talvez não se dê tão bem caso precisássemos estimar alguns valores, mas também é possível entender que cães (Type 1) não sofrem muita diferença na velocidade de adoção mesmo estando mais velhos, diferente dos gatos (Type 2) onde a velocidade de adoção aumenta quando idosos.

Também é possível juntar mais de um gráfico em um só. O jointplot é a junção de um gráfico univariado, nesse caso um histograma, e um bivariado, um scatterplot.

`sns.jointplot(x='Age', y='Fee', data=df) `

![](https://miro.medium.com/max/30/1*rgeto1n99iZ9_bAG8m9CaQ.png?q=20)

![](https://miro.medium.com/max/434/1*rgeto1n99iZ9_bAG8m9CaQ.png)

Usei novamente a relação idade versus taxa de adoção. A idade está no eixo horizontal, assim como seu histograma na parte superior do gráfico, na vertical está a taxa de adoção e o seu histograma. No meio está o scatterplot mostrando a relação entre as duas variáveis. Podemos ver que para animais mais novos não há muito consenso sobre as taxas, mas para os poucos animais mais velhos, nenhum deles possui taxa para adoção.

O jointplot pode ser usado com vários outros gráficos mudando seu parâmetro  `kind`e dentre as opções estão gráfico de kde ou gráfico de hexágonos que te dá um mapa de calor da relação das variáveis.

O FacetGrid cria uma matriz/grid em branco que podemos preencher com qualquer gráfico. Abaixo criei um FacetGrid em branco separando as linhas para o tipo de animal e as colunas para o gênero e na linha abaixo preenchi o grid usando o countplot do Seaborn (poderia ser de outra biblioteca) e usei também a velocidade de adoção como valor dos gráficos. Assim temos uma visão da velocidade de adoção separada por tipo e gênero dos animais.

```
g = sns.FacetGrid(df, row='Type', col='Gender') #Cria um grid vazio  
g.map(sns.countplot, 'AdoptionSpeed') #preenche com o gráfico
```


![](https://miro.medium.com/max/30/1*_gV9xWV8sNwz5V0573BN1g.png?q=20)

![](https://miro.medium.com/max/640/1*_gV9xWV8sNwz5V0573BN1g.png)

É possível mudar as cores e o tamanho do gráficos. Um exemplo é o heatmap que criei para a correlação. O gráfico que usei está estilizado com o mapa de cores (parâmetro  `cmap`  ) chamado ‘coolwarm’ . O Seaborn consegue criar gráficos com quaisquer mapas de cores da biblioteca matplotlib ([veja aqui](https://matplotlib.org/users/colormaps.html)) e também com listas de cores personalizadas. Em um heatmap ainda é possível mudar a cor e espessura das linhas da matriz com os parâmetros  `linecolor`  e  `linewidths`  .  
Outro ponto importante na visualização de dados é o tamanho da imagem. Alguns gráficos tem parâmetros para personalizar seu tamanho mas no geral podemos usar o matplotlib para criar uma figura em branco do tamanho necessário e criar o gráfico por cima. Abaixo é um exemplo do heatmap estilizado.

```
plt.subplots(figsize=(15, 8))  
sns.heatmap(corr, cmap='plasma', linecolor='gray', linewidths=1)
```


![](https://miro.medium.com/max/30/1*qos4XleVY1X1RevkjjS2uA.png?q=20)

![](https://miro.medium.com/max/853/1*qos4XleVY1X1RevkjjS2uA.png)

Para finalizar, vou mostrar também o set_style e o set_context do Seaborn. O set_style defini o estilo do gráfico: com ou sem grid, fundo claro ou escuro.

```
sns.set_style('darkgrid')  
sns.countplot(x='MaturitySize', data=df)
```

![](https://miro.medium.com/max/30/1*xY2EojED3mMOXvIuTykmiw.png?q=20)

![](https://miro.medium.com/max/405/1*xY2EojED3mMOXvIuTykmiw.png)

Já o set_context muda o gráfico de acordo com o contexto que ele vai ser apresentado, por exemplo, a visualização em um poster precisa ser diferente da visualização em relatório comum.

sns.set_style('whitegrid')  
sns.set_context('poster')  
sns.countplot(x='MaturitySize', data=df)

![](https://miro.medium.com/max/30/1*wPs927_ghOAhJfsf8tXyOw.png?q=20)

![](https://miro.medium.com/max/475/1*wPs927_ghOAhJfsf8tXyOw.png)

----------

Claro que muitas análises mostradas nesse artigo são redundantes, mas é importante perceber que há diversas formas de conseguir extrair algo do dados e mostrar, cabe a você decidir o que vai ser melhor para seus dados. Além disso, existem algumas técnicas para uma boa visualização de dados, como o Data Storytelling, mas isso é assunto para um outro artigo ;).

----------

# Referências e Recomendações

-   1º  [seaborn.pydata.org/](http://seaborn.pydata.org/): Deitem em cima da documentação do Seaborn, é uma biblioteca bem rica!
-   2º  [Python para Data Science e Machine Learning — COMPLETO](https://www.udemy.com/share/1001AGA0sfeFtSR3w=/?xref=E0ETdVdXRX4BSUQvCz0GJVUWTx4dChQ%2BVFE=): Fiz esse curso da Udemy e achei bem completo para quem tá começando na área.
-   3º e não menos importante:  **NÃO COMPREM, ADOTEM! ❤**