Open In Colab

7. Relações


Muitos conjuntos de dados contêm duas ou mais variáveis, e podemos estar interessados ​​em como essas variáveis ​​se relacionam entre si. De certo modo muitos dos gráficos que estudamos nos capítulos anteriores podem ser usados e ajudam nessa análise.

Gráficos de linha, ajudam a entender a evolução de uma variável com outra, e no mesmo capítulo vimos o uso dos gráficos de pontos ou dispersão. Gráficos de caixa também podem ser utilizados para representar a distribuição de uma variável com relação a diferentes valores de uma outra variável, e também podemos empregar gráficos de barra ou gráficos emparelhados para apresentar valores com relação a outras quantidades. A divisão entre gráficos para representar Evolução, Distribuições, Quantidades e Proporções e, agora, Relações, ajuda a organizar o conteúdo indicando os gráficos mais comuns para cada propósito, mas de fato os gráficos permitem uma análise muito mais ampla e em geral exibem múltiplos aspectos dos dados.

Os gráficos de dispersão, que vimos no capítulo de Evolução, são talvez o tipo mais empregado para análise de relações entre duas variáveis quantitativas, e você deve notar que muitos dos gráficos anteriores em que representamos relações dos dados tratam de relações entre uma variável quantitativa e categorias apenas. Aqui vamos explorar um pouco mais esse gráfico fazendo representações multidimensionais, empregando cores e diferentes tamanhos dos pontos de dados (gráfico de Bolhas) para representar novas dimensões (classes ou categorias de dados) em gráfico 2D.

O principal risco no uso de gráficos de dispersão é a criação de gráficos com overplotting. Um excesso de dados no gráfico em geral não permite identificarmos as relações e, no final, o objetivo da visualização é completamente prejudicado. Correlogramas são simplesmente gráficos de dispersão de diferentes pares de variáveis, e vão permitir separar uma visualização de dados com muitas relações e que são mais fáceis de analisar individualmente.

Por último ainda temos os mapas de calor que podem ser empregados para visualizar uma matriz de correlação dos dados e dados multivariados.

7.1. Gráficos de Dispersão, Scatter plots

Basicamente os gráficos de dispersão, gráfico de pontos ou diagramas de dispersão, representam duas variáveis quantitativas, uma para cada eixo, para exibir e podermos identificar alguma relação ou correlação entre esses dados.

A partir dos padrões exibidos você pode, por exemplo, identificar uma correlação positiva entre as variáveis (os valores aumentam em conjunto) ou negativa (um valor diminui à medida que os outros aumentam), pode identificar uma relação linear, ou ainda um padrão exponencial ou polinomial (por exemplo, a forma de U para polinômios de segundo grau). A força da correlação é expressa pela proximidade (concentração) dos pontos em torno do padrão e dados mais fora do agrupamento indicam a dispersão do padrão e possíveis outliers. É também comum, quando identificado esse padrão, adicionarmos uma linha de ajuste ou linha de tendência para inferência de valores, o que pode ser feito por métodos de aproximação, como regressão linear e outras formas de interpolação que empregamos antes no capítulo Evolução.

Um caso especial de relação e de muito interesse são as relações lineares. Correlação é o termo que deve ser empregado para relações lineares entre os dados. O coeficiente de correlação é um número entre \([-1,1]\) e é uma medida da relação linear entre duas variáveis onde \(1\) expressa uma relação linear positiva perfeita, \(-1\) uma relação linear negativa perfeita e \(0\) a ausência de uma relação linear. Identificada uma relação linear é comum querermos aproximar esses dados por uma regressão linear representando também essa aproximação no gráfico de dispersão como vimos antes.

Mas os dados podem exibir muitas outras relações e podemos encontrar relações que são aproximadas por funções exponenciais, polinomiais etc. e, é um erro bastante comum é considerar que um coeficiente de correlação 0 indica ausência de relações entre os dados. O coeficiente de correlação 0 indica apenas a ausência de relações lineares entre os dados!

\(\bigstar \text{ }\) Um erro bastante comum é considerar que um coeficiente de correlação \(0\) indica a ausência de relações entre os dados. O coeficiente de correlação \(0\) indica apenas a ausência de relações lineares entre os dados!

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sns
%matplotlib inline
plt.figure(figsize=(12,4))
np.random.seed(99)
x = np.linspace(-10, 10, 100)
y1 = 2*(x + np.random.rand(len(x))*0.5) + 1
y2 = (x + np.random.rand(len(x))*0.5)**2

plt.subplot(1,2,1)
plt.scatter(x,y1, color='lightblue', s=60, edgecolors='grey')
z = np.polyfit(x, y1, 1)
p = np.poly1d(z)
plt.plot(x,p(x),color='k', linestyle='dashed')
plt.title('Relação Linear', fontsize=14, weight='bold')
plt.text(-9,12.5,f'Correlation = {np.corrcoef(x,y1)[0,1]:.2f}',fontsize=14, color='grey')

plt.subplot(1,2,2)
plt.scatter(x,y2, color='lightblue', s=60, edgecolors='grey')
z = np.polyfit(x, y2, 2)
p = np.poly1d(z)
plt.plot(x,p(x),color='k', linestyle='dashed')
plt.title('Outras Relações', fontsize=14, weight='bold')
plt.text(-3,80,f'Correlation = {np.corrcoef(x,y2)[0,1]:.2f}',fontsize=14, color='grey')

plt.tight_layout()
plt.show()
_images/c6_parte_1_5_0.png

O primeiro conjunto de dados acima apresenta uma correlação positiva perfeita (valor \(1\)), enquanto o segundo uma correlação muito baixa (\(0.1\)) apesar de ter uma relação bastante direta dos valores (\(y = x^2\)).

Um outro alerta importante sobre relações refere-se a como interpretamos essas relações e dois outros erros são bastante comuns.

\(\bigstar \text{ }\) O primeiro erro é interpretarmos a presença de uma relação como uma relação de causa-efeito entre as variáveis. Duas variáveis podem estar relacionadas mas serem determinadas por outros fatores, por uma ou outras variáveis, que não estão representadas e que talvez você desconheça. Assim, mesmo havendo uma relação entre duas variáveis, uma variável não necessariamente determina a outra como numa relação causa-efeito.

\(\bigstar \text{ }\) O segundo erro é considerarmos que se não encontramos relações na análise bivariada (duas variáveis) elas não tem então qualquer relação. Assumir isso, entretanto, é desconsiderar a existência de relações conjuntas (uma outra variável que, em conjunto, potencializa uma relação) que são mais difíceis de serem analisadas.

7.2. Um Gráfico de Dispersão Simples

Gráficos de dispersão podem ser facilmente produzidos com a função plt.scatter(x,y) do Matplotlib indicando-se apenas as variáveis de interesse xe y de interesse.

Vamos empregar aqui o data-set gapminder e explorar algumas relações entre os dados de desenvolvimento de diferentes países.

df = pd.read_csv('https://raw.githubusercontent.com/Rogerio-mack/Visualizacao-de-Dados-em-Python/main/data/gapminder_2015.csv')
df['cod_continent'] = df[['continent']].astype('category').apply(lambda x: x.cat.codes)
df.head()
continent country year demox_eiu income_per_person invest_%_gdp tax_%_gdp gini_index LifeExpect HappyIdx SchoolYears15_24 VacineBelieve ChildMortality Co2Emissions CPI Population cod_continent
0 Africa Botswana 2015 78.7 15700 32.1 24.7 60.5 66.9 0.376 8.40 NaN 40.7 2.560 63.0 2120000 0
1 Africa Burkina Faso 2015 47.0 1600 24.3 15.1 35.5 60.7 0.442 3.76 NaN 86.8 0.182 38.0 18100000 0
2 Africa Cote d'Ivoire 2015 33.1 3230 20.1 15.4 41.6 61.0 0.445 6.59 NaN 90.0 0.405 32.0 23200000 0
3 Africa Egypt 2015 31.8 10200 14.3 12.5 31.2 70.2 0.476 10.60 NaN 23.6 2.370 36.0 92400000 0
4 Africa Kenya 2015 53.3 2800 21.5 16.3 41.5 64.7 0.436 9.06 NaN 46.3 0.341 25.0 47900000 0

O gráfico abaixo mostra o gráfico de pontos para os valores de renda anual por habitante e expectativa de vida, variáveis para as quais esperamos alguma relação. O gráfico pode ser obtido diretamente apenas informando as variáveis envolvidas:

plt.scatter(df['income_per_person'],df['LifeExpect']))
plt.scatter(df['income_per_person'],df['LifeExpect'] , alpha=0.6)

plt.title('Income $\\times$ Life Expectancy', fontsize=14, weight='bold')
plt.xlabel('Anual Income per Person')
plt.ylabel('Life Expectancy (Years)')

plt.show()
_images/c6_parte_1_12_0.png

Essa é uma representação muito simples e, em geral, queremos explorar essa relação em outras dimensões adicionando à visualização dos dados diferentes categorias ou classes com seus valores. Para isso podemos empregar cores ou diferentes tamanhos dos pontos de dados para adicionar outras dimensões aos dados.

Isso pode ser feito com o Matplotlib e você pode, com ele, ter total controle sobre a representação dos dados. Para análises multidimensionais, entretanto, o pacote Seaborn em geral oferece uma interface mais simples.

7.2.1. Adicionando Dimensões com Cores

Vamos então entender melhor como podemos atribuir cores para adicionar novas dimensões a um gráfico 2D.

Já empregamos esquemas de cores antes mas eles tinham um propósito mais ornamental na construção dos gráficos. Aqui, entretanto, eles vão passar a representar valores dos dados permitindo criarmos representações multidimensionais mesmo com gráficos 2D. É essencial então entendermos claramente como atibuir esses valores.

Existem muitas formas de se fazer isso e deixo aqui uma forma que parece bastante útil e flexível, podendo ser empregada em muitas situações. A ideia é criar um dicionário que mapea os valores às cores desejadas. Para as diferentes cores podemos usar os rótulos já conhecidos ‘r’,’g,’b’ etc. ou ainda ‘red’, ‘lightblue’, ‘yellow’ etc., mas também podemos empregar os esquemas de cores do Matplotlib ou do Seaborn que fornecem espectros de cores mais sofisticadas.

Abaixo mostramos como empregar esses 3 esquemas para associar cores diferentes a cada continente do nosso dataset com dados do desenvolvimento dos páises e, assim, adicionar mais essa dimensão à representação dos dados.

7.2.1.1. Esquema de cores 1

Empregando cores básicas.

dict(zip(df.continent.unique(), ['r','g','b','c','m']))
{'Africa': 'r', 'Americas': 'g', 'Asia': 'b', 'Europe': 'c', 'Oceania': 'm'}

7.2.1.2. Esquema de cores 2

Empregando o esquema de cores do Matplotlib.

from matplotlib import cm
dict(zip(df.continent.unique(), cm.viridis(len( df.continent.unique() )) ) )
{'Africa': 0.273809, 'Americas': 0.031497, 'Asia': 0.358853, 'Europe': 1.0}

7.2.1.3. Esquema de cores 3

Empregando o esquema de cores do Seaborn.

sns.color_palette("Set2", len(df.continent.unique()))
dict(zip(df.continent.unique(), sns.color_palette("Set2", len(df.continent.unique()))))
{'Africa': (0.4, 0.7607843137254902, 0.6470588235294118),
 'Americas': (0.9882352941176471, 0.5529411764705883, 0.3843137254901961),
 'Asia': (0.5529411764705883, 0.6274509803921569, 0.796078431372549),
 'Europe': (0.9058823529411765, 0.5411764705882353, 0.7647058823529411),
 'Oceania': (0.6509803921568628, 0.8470588235294118, 0.32941176470588235)}

Esses dicionários podem ser então empregados para mapear cada valor de continent com o método map.

plt.figure(figsize=(12,4))

colors1 = dict(zip(df.continent.unique(), ['r','g','b','c','m']))
colors2 = dict(zip(df.continent.unique(), cm.viridis(len( df.continent.unique() )) ) )
colors3 = dict(zip(df.continent.unique(), sns.color_palette("Set2", len(df.continent.unique()))))

colors = [ colors1, colors2, colors3 ]

for i in range(len(colors)):
  plt.subplot(1,3,i+1)
  plt.scatter(df['income_per_person'], df['LifeExpect'], alpha=0.8, 
              c = df['continent'].map(colors[i]))

  plt.title('Income $\\times$ Life Expectancy', fontsize=14, weight='bold')
  plt.xlabel('Anual Income per Person')
  plt.ylabel('Life Expectancy (Years)')

plt.tight_layout()
plt.show()
_images/c6_parte_1_23_0.png

Independente do esquema que você escolher, será inútil empregar diferentes cores se você não puder associar para a cada cor os valores que elas representam. Você pode fazer isso a partir das legendas.

Com o Matplotlib a forma mais simples de fazer isso é adicionando camadas de plot dos gráficos para cada dimensão desejada. A cada plot pode ser associado um label que é, então, exibido na legenda ao final. Isso está primeiro gráfico abaixo onde o laço de programa (for) percorre cada um dos valores da dimensão continent para adicionar uma nova camada de pontos com essa dimensão dos dados no gráfico. Cada nova camada recebe uma cor e um rótulo diferente para representar essa nova dimensão que são exibidos ao final na legenda. Essa forma de construção é a mais simples.

O mesmo gráfico pode ser construído sem uso de iterações plotando todos os pontos e indicando um array de cores para cada ponto como fizemos antes. Essa opção, entretanto, torna a adição de legendas um pouco mais complexa sendo necessário editar a legenda em separado. A construção desse modo encontra-se no segundo gráfico abaixo.

plt.figure(figsize=(12, 4))
colors1 = dict(zip(df.continent.unique(), ['r','g','b','c','m']))

plt.subplot(1,2,1)
for continent in df.continent.unique():
  plt.scatter(df[ df['continent'] == continent ]['income_per_person'], 
              df[ df['continent'] == continent ]['LifeExpect'] , 
              c=df[ df['continent'] == continent ]['continent'].map(colors1), 
              label=continent,
              alpha=0.6)

plt.title('Income $\\times$ Life Expectancy\nCom iterações', fontsize=14, weight='bold')
plt.xlabel('Anual Income per Person')
plt.ylabel('Life Expectancy (Years)')

plt.legend()

plt.subplot(1,2,2)
plt.scatter(df['income_per_person'], 
            df['LifeExpect'] ,
            c=df['continent'].map(colors1),
            alpha=0.6)

plt.title('Income $\\times$  Life Expectancy\nSem iterações', fontsize=14, weight='bold')
plt.xlabel('Anual Income per Person')
plt.ylabel('Life Expectancy (Years)')

# Adicionando a legenda
from matplotlib.lines import Line2D
legends = []
for color in colors1.values():
  legends.append( Line2D([0], [0], markerfacecolor=color, marker='o', markersize=8, color='w', alpha=0.6) )

plt.legend(legends, df['continent'].unique())

plt.tight_layout()
plt.show()
_images/c6_parte_1_26_0.png

Uma alternativa prática pode ser empregar o gráfico sem iterações para obter a figura desejada e, sobre ela, produzir gráficos vazios por meio de iterações adicionando a legenda. Mas é esse um ‘truque’ de programação que não vamos apresentar embora empreguemos algo semelhante mais adiante para inibir as legendas automáticas do Seaborn.

7.2.2. Seaborn, hue

O Seaborn, mais voltado para análises multidimensionais, já fornece um parâmetro para adição de dimensões de classes de dados aos gráficos de pontos e pode ser uma boa alternativa. O parâmetro hue indica a classe de valores em que serão exibidos os dados e pode ser empregado na maior parte dos comandos do Seaborn.

plt.figure(figsize=(12, 4))

plt.subplot(1,2,1)
for continent in df.continent.unique():
  plt.scatter(df[ df['continent'] == continent ]['income_per_person'], 
              df[ df['continent'] == continent ]['LifeExpect'] , 
              c=df[ df['continent'] == continent ]['continent'].map(colors1), 
              label=continent,
              alpha=0.6)

plt.title('Income $\\times$ Life Expectancy\nCom Matplotlib', fontsize=14, weight='bold')
plt.xlabel('Anual Income per Person')
plt.ylabel('Life Expectancy (Years)')

plt.legend()

plt.subplot(1,2,2)
colors = sns.color_palette(['r','g','b','c','m'])

sns.scatterplot(x=df['income_per_person'], 
            y=df['LifeExpect'] ,
            hue=df['continent'],
            palette=colors,
            s=60,
            alpha=0.6)

plt.title('Income  $\\times$ Life Expectancy\nCom Seaborn', fontsize=14, weight='bold')
plt.xlabel('Anual Income per Person')
plt.ylabel('Life Expectancy (Years)')

plt.legend()

plt.tight_layout()
plt.show()
_images/c6_parte_1_29_0.png

7.2.3. Usando markers

A adição de novas dimensões (classes de dados) pode ser feita também empregando-se markers no lugar de cores, mas mesmo para poucas classes essa solução facilmente leva ao overplotting poluindo facilmente a figura, como você pode ver abaixo, e é necessário uma escolha cuidadosa da representação.

Empregamos abaixo um esquema semelhante ao que empregamos para cores para associar diferentes markers a cada valor de continent, empregando diretamente o dicionário no parâmetro marker.

dict(zip(df.continent.unique(), ['>','+','x','o','*']))
{'Africa': '>', 'Americas': '+', 'Asia': 'x', 'Europe': 'o', 'Oceania': '*'}
markers = dict(zip(df.continent.unique(), ['>','+','x','o','*']))

for continent in  df.continent.unique():
  plt.scatter(df[ df['continent'] == continent ]['income_per_person'], 
              df[ df['continent'] == continent ]['LifeExpect'] , 
              marker=markers[continent],
              s=90,
              c='red',
              label=continent,
              alpha=0.6)

plt.title('Income $\\times$ Life Expectancy', fontsize=14, weight='bold')
plt.xlabel('Anual Income per Person')
plt.ylabel('Life Expectancy (Years)')
plt.legend()

plt.show()
_images/c6_parte_1_32_0.png

7.3. Adicionando Dimensões Contínuas e Gráficos de Bolha

Diferentes tamanhos e cores dos pontos de dados também podem ser empregados para adicionarmos um dimensão de uma variável quantitativa contínua, e não discreta como fizemos até aqui adicionando a dimensão de continent aos gráficos.

Em geral, neste caso, empregamos círculos como marcadores e um gráfico de bolhas é um gráfico multivariável que adiciona uma dimensão de uma variável quantitativa a um gráfico de dispersão sendo a área do círculo proporcional a grandeza contínua que representa.

Podemos assim empregar os tamanhos ou cores para, por exemplo, para adicionar as dimensões SchoolYears15_24 ou HappyIdx, que são valores contínuos, ao gráfico de dados de renda e expectativa de vida e explorar conjuntamente essas relações.

plt.figure(figsize=(12, 4))

plt.subplot(1,2,1)
plt.scatter(df['income_per_person'], 
              df['LifeExpect'] , 
              sizes=(40,250),
              s=df['SchoolYears15_24'],
              alpha=0.5)

plt.title('Income X Life Expectancy', fontsize=14, weight='bold')
plt.xlabel('Anual Income per Person')
plt.ylabel('Life Expectancy (Years)')

from matplotlib.lines import Line2D
legends = []
valores = np.round( df['SchoolYears15_24'].quantile([0,0.25,0.5,0.75,1]), 0).astype('int') 
for size in valores:
  legends.append( Line2D([0], [0], markerfacecolor='blue', marker='o', markersize=size, color='w', alpha=0.5) )

plt.legend( legends,  valores, title='$\\bf{School Years}$')

plt.subplot(1,2,2)
plt.scatter(df['income_per_person'], 
              df['LifeExpect'] , 
              sizes=(40,250),
              s=df['HappyIdx'],
              alpha=0.5,
              c='red')

plt.title('Income X Life Expectancy', fontsize=14, weight='bold')
plt.xlabel('Anual Income per Person')
plt.ylabel('Life Expectancy (Years)')

from matplotlib.lines import Line2D
legends = []
valores = np.round( df['HappyIdx'].quantile([0,0.25,0.5,0.75,1]), 1) 
for size, _ in enumerate(valores):
  legends.append( Line2D([0], [0], markerfacecolor='red', marker='o', markersize=size*4, color='w', alpha=0.5) )

plt.legend( legends,  valores, title='$\\bf{Happy Idx}$')

plt.tight_layout()
plt.show()
_images/c6_parte_1_34_0.png

Não é surpresa que você pode notar acima uma proximidade muito grande dos padrões de anos de escolaridade e o índice de felicidade dos países. E é exatamente esse o objetivo desses gráficos, fornecer padrões que nos permitem encontrar relações entre os dados. Você pode, em seguida, aprofundar essa exploração com outros gráficos sobre essas variáveis.

Sabendo da semelhança do padrão exibido pelas variáveis SchoolYears15_24 e HappyIdx podemos, por exemplo, produzir um gráfico de bolhas com escalas diferentes para dimensões SchoolYears15_24 e HappyIdx identificando as intersecções e confirmando a semelhança desses padrões ou, ainda, simplesmente construir um gráfico de pontos com as duas variáveis e observar a relação positiva que elas apresentam.

plt.figure(figsize=(12, 4))

plt.subplot(1,2,1)
plt.scatter(df['income_per_person'], 
              df['LifeExpect'] , 
              sizes=(80,800),
              s=df['SchoolYears15_24'],
              alpha=0.6,
              edgecolor='b')
plt.scatter(df['income_per_person'], 
              df['LifeExpect'] , 
              sizes=(20,250),
              s=df['HappyIdx'],
              alpha=0.6,
              edgecolor='r',
              c='red')

plt.title('Income $\\times$ Life Expectancy', fontsize=14, weight='bold')
plt.xlabel('Anual Income per Person')
plt.ylabel('Life Expectancy (Years)')

plt.subplot(1,2,2)
plt.scatter(df['SchoolYears15_24'], 
              df['HappyIdx'],
              s=60,
              c='r',
              alpha=0.5)

plt.title('School Years $\\times$ Happy Index', fontsize=14, weight='bold')
plt.xlabel('School Years')
plt.ylabel('Happy Index')


bbox_props = dict(boxstyle="rarrow", fc='lightblue', ec='grey', lw=1)
t = plt.text(6, 0.45, 'positive relation', ha="center", va="center", rotation=30,
            size=12, bbox=bbox_props)

bb = t.get_bbox_patch()
bb.set_boxstyle("rarrow", pad=0.6)

plt.tight_layout()
plt.show()
_images/c6_parte_1_37_0.png

7.4. Adicionando Dimensões Contínuas com Cores

Diferentes níveis de cores também podem ser empregados para representar a dimensão de variável quantitativa contínua.

Neste caso o mapeamento de cores pode ser feito diretamente com o parâmetro cmap associado a um esquema de cores de sua preferência. Como se tratam de valores contínuos é mais útil empregarmos uma escala de cores no lugar de uma legenda de valores. No Matplotlib isso pode ser feito com o comando colorbar().

plt.figure(figsize=(12, 4))

plt.subplot(1,2,1)
plt.scatter(df['income_per_person'],df['LifeExpect'], alpha=0.6, s=120, c=df['SchoolYears15_24'], cmap=cm.viridis )

plt.title('Income $\\times$ Life Expectancy', fontsize=14, weight='bold')
plt.xlabel('Anual Income per Person')
plt.ylabel('Life Expectancy (Years)')

cbar = plt.colorbar()
cbar.set_label('School Years', fontsize=12, weight='bold')

plt.subplot(1,2,2)
plt.scatter(df['income_per_person'],df['LifeExpect'], alpha=0.6, s=120, c=df['HappyIdx'], cmap=cm.gnuplot2_r)

plt.title('Income $\\times$ Life Expectancy', fontsize=14, weight='bold')
plt.xlabel('Anual Income per Person')
plt.ylabel('Life Expectancy (Years)')

cbar = plt.colorbar()
cbar.set_label('Happy Index', fontsize=12, weight='bold')

plt.tight_layout()
plt.show()
_images/c6_parte_1_39_0.png

7.5. Usando o Seaborn

De modo semelhante esses mesmos gráficos podem ser produzidos com o pacote Seaborn. Para criar gráficos de bolhas você pode empregar os parâmetros hue e size para associar a uma variável quantitativa ao tamanho dos círculos, o parâmetro sizes (com ‘s’ ao final!) indica os diâmetros mínimo e máximo dos círculos a que o valores serão associados proporcionalmente. Você pode verificar que o resultado não será tão bom se você empregar o size, sem empregar o parâmetro hue que define as classes de valores a serem exibidos.

Já ao empregar uma escala de cores para representar uma nova dimensão de valores contínuos é necessário empregar alguns artifícios para exibir a barra de escala de valores e excluir a legenda automática do Seaborn. O artifício consiste em criar um plot nulo do Matplotlib com a barra de cores e a legenda é excluída em um comando separadamente.

plt.figure(figsize=(12, 4))

plt.subplot(1,2,1)
scatter= sns.scatterplot(x=df['income_per_person'], 
            y=df['LifeExpect'] ,
            hue=df['SchoolYears15_24'],
            sizes=(40,300),
            size=df['SchoolYears15_24'],
            alpha=0.6)

plt.title('Income $\\times$ Life Expectancy', fontsize=14, weight='bold')
plt.xlabel('Anual Income per Person')
plt.ylabel('Life Expectancy (Years)')

plt.legend(title='$\\bf{School Years}$')

plt.subplot(1,2,2)
plt_colbar = plt.scatter([], [],
            s=60,
            alpha=0.6,
            cmap=cm.viridis)

scatter = sns.scatterplot(x=df['income_per_person'], 
            y=df['LifeExpect'] ,
            c=df['HappyIdx'],
            hue=df['HappyIdx'],
            palette=cm.viridis,
            s=60,
            alpha=0.6)

plt.title('Income $\\times$ Life Expectancy', fontsize=14, weight='bold')
plt.xlabel('Anual Income per Person')
plt.ylabel('Life Expectancy (Years)')

cbar = plt.colorbar(plt_colbar)
cbar.set_label('Happy Index', fontsize=12, weight='bold')
scatter.get_legend().remove()

plt.tight_layout()
plt.show()
_images/c6_parte_1_41_0.png

Ao final, o uso do Matplotlib ou do Seaborn para gráficos de dispersão não parecem apresentar muita diferença para construção de gráficos simples e, para dados multidimensionais, cada um apresenta uma dificuldade particular para o customização final dos gráficos (as legendas, por exemplo). De qualquer modo para gráficos multidimensionais mais exploratórios, nos quais você não terá necessidade de ter controle total dos elementos do gráfico, o Seaborn parece oferecer uma interface mais simples e ágil de usar.

7.6. Linha de Ajuste

Você pode retornar ao capítulo de Evolução para verificar detalhes como adicionar linhas de tendências em gráficos de pontos. Lá aplicamos o mesmo procedimento que empregaremos aqui empregando uma função de otimização para o ajuste de curvas do pacote SciPy.

O que adicionamos aqui é um artifício bastante comum aplicado a uma série de dados populacionais como renda, mas também aplicável a dados de preços de imóveis e veículos etc. que apresentam uma distribuição exponencial. Relações lineares são sempre mais fáceis de serem empregadas e podemos empregar o artíficio de transformar uma distribuição exponencial aplicando uma transformação de \(log( )\) para obter uma relação linear dos dados.

# Produzindo o ajuste da curva
from scipy.optimize import curve_fit

def func(x, a, b):
    return a*np.log(x) + b

coefs, covar = curve_fit(func, df['income_per_person'], df['LifeExpect'])

x = np.linspace(df['income_per_person'].min(), df['income_per_person'].max(), 100)
y = func(x,*coefs)
plt.figure(figsize=(12, 4))
colors1 = dict(zip(df.continent.unique(), ['r','g','b','c','m']))

plt.subplot(1,2,1)
for continent in df.continent.unique():
  plt.scatter(df[ df['continent'] == continent ]['income_per_person'], 
              df[ df['continent'] == continent ]['LifeExpect'] , 
              c=df[ df['continent'] == continent ]['continent'].map(colors1), 
              label=continent,
              alpha=0.5)

plt.plot(x,y,color='black', linestyle='dashed', lw=1.5)

plt.title('Income $\\times$ Life Expectancy', fontsize=14, weight='bold')
plt.xlabel('Anual Income per Person')
plt.ylabel('Life Expectancy (Years)')

plt.legend()

plt.subplot(1,2,2)
for continent in df.continent.unique():
  plt.scatter(df[ df['continent'] == continent ]['income_per_person'], 
              df[ df['continent'] == continent ]['LifeExpect'] , 
              c=df[ df['continent'] == continent ]['continent'].map(colors1), 
              label=continent,
              alpha=0.5)

plt.plot(x,y,color='black', linestyle='dashed', lw=1.5)

plt.xscale('log')

plt.title('Income $\\times$ Life Expectancy', fontsize=14, weight='bold')
plt.xlabel('Anual Income per Person')
plt.ylabel('Life Expectancy (Years)')

plt.legend(loc='lower right')


plt.tight_layout()
plt.show()
_images/c6_parte_1_45_0.png

Você pode notar como o primeiro gráfico se ajusta a uma função logarítmica e, alterando a escala de \(x\) para uma escala logarítmica, obtemos o ajuste linear. Veja os coeficientes de correlação obtidos em cada caso.

from IPython.display import display, Math, Latex 
Math('\\bf Corr( LifeExpect , Income ) =' +  f'{np.corrcoef(df["income_per_person"],df["LifeExpect"])[0,1]:.3f}')  
\[\displaystyle \bf Corr( LifeExpect , Income ) =0.711\]
Math('\\bf Corr( LifeExpect , Income ) =' +  f'{np.corrcoef(np.log( df["income_per_person"] ),df["LifeExpect"])[0,1]:.3f}')  
\[\displaystyle \bf Corr( LifeExpect , Income ) =0.851\]

7.7. Correlogramas

Correlogramas nada mais são do que vários gráficos de pontos de diferentes pares de variáveis para que você possa explorar relações entre elas para cada par. Esse é um modo de evitarmos o overplotting e dividirmos o problema em várias partes explorando individualmente cada par de variáveis.

Podemos fazer isso com o Matplotlib, mas havendo várias variáveis pode ser necessário fazer um laço de programa para produzir os diferentes gráficos de dispersão de cada par de variável (em geral empregamos somente as quantitativas, numéricas) e logo a seguir você verá um laço de programa como esse. Mas a tarefa pode ser um tando tediosa.

O Seaborn fornece uma função para exibir, em um único comando, os diferentes pares de variáveis e é, geralmente, a forma mais simples e empregada para isso. Apenas para fins de apresentação no texto limitamos as variáveis a serem exploradas. De qualquer modo, embora possível, é recomendável evitar execuções do tipo sns.pairs(df) e que vão exibir todas as relações. Elas levarão muito tempo para serem processadas e, em muitos casos, produzem gráficos desnecessários (variáveis categóricas também serão incluídas neste caso).

sns.pairplot(df[['income_per_person', 'LifeExpect', 'HappyIdx','SchoolYears15_24', 'ChildMortality', 'Co2Emissions',
            'CPI', 'Population']])
plt.show()
_images/c6_parte_1_50_0.png

Observando o gráfico acima algumas relações se destacam por apresentar uma relação mais linear como LifeExpect \(\times\) ChildMortality e SchoolYears15_24 \(\times\) ChildMortality.

De fato, essas são relações que apresentam um coeficiente de correlação bastante alto e podemos identificar essas maiores correlações produzindo uma matriz de correlação desses pares de variáveis e identificando seus maiores valores. Fazemos isso a seguir.

pd.options.display.max_rows = 999
df[['income_per_person',
       'LifeExpect', 'HappyIdx',
       'SchoolYears15_24', 'ChildMortality', 'Co2Emissions',
       'CPI', 'Population']].corr()
income_per_person LifeExpect HappyIdx SchoolYears15_24 ChildMortality Co2Emissions CPI Population
income_per_person 1.000000 0.710835 0.727471 0.652686 -0.600019 0.796020 0.805391 -0.052656
LifeExpect 0.710835 1.000000 0.750452 0.772257 -0.878321 0.532751 0.651758 0.017118
HappyIdx 0.727471 0.750452 1.000000 0.661920 -0.656261 0.558667 0.729569 -0.030911
SchoolYears15_24 0.652686 0.772257 0.661920 1.000000 -0.851195 0.628866 0.583404 0.023106
ChildMortality -0.600019 -0.878321 -0.656261 -0.851195 1.000000 -0.542851 -0.527718 -0.039804
Co2Emissions 0.796020 0.532751 0.558667 0.628866 -0.542851 1.000000 0.558390 0.059660
CPI 0.805391 0.651758 0.729569 0.583404 -0.527718 0.558390 1.000000 -0.086626
Population -0.052656 0.017118 -0.030911 0.023106 -0.039804 0.059660 -0.086626 1.000000
df[['income_per_person',
       'LifeExpect', 'HappyIdx',
       'SchoolYears15_24', 'ChildMortality', 'Co2Emissions',
       'CPI', 'Population']].corr().abs().unstack().drop_duplicates().sort_values(ascending=False).nlargest(5)
income_per_person  income_per_person    1.000000
LifeExpect         ChildMortality       0.878321
SchoolYears15_24   ChildMortality       0.851195
income_per_person  CPI                  0.805391
                   Co2Emissions         0.796020
dtype: float64

Com esses maiores valores de correlação identificados os pares de interesse podemos aprofundar a análise, por exemplo, produzindo as linhas de tendência de dessas variáveis.

Implementamos abaixo um laço de programa de gráficos de pontos do Matplotlib que poderia ser facilmente adaptado para produzir correlogramas como o do Seaborn.

plt.figure(figsize=(12, 8)) 

def linear(x, a, b):
    return a*x + b

for i, pairs in enumerate( [ ['LifeExpect','ChildMortality',],
                ['SchoolYears15_24','ChildMortality'],
                ['CPI','income_per_person'],
                ['Co2Emissions','income_per_person'] ] ): 
 
  coefs, covar = curve_fit(linear, df[pairs[0]], df[pairs[1]])
  x = np.linspace(df[pairs[0]].min(), df[pairs[0]].max(), 100)
  y = linear(x,*coefs)

  plt.subplot(2,2,i+1)
  plt.scatter(df[pairs[0]], df[pairs[1]], alpha=0.5)

  plt.plot(x,y,color='black', linestyle='dashed', lw=1.5)

  texto = f'Correlation =  {df[[pairs[0],pairs[1]]].corr().unstack()[1]:.2f} '
  plt.text(( plt.gca().get_xlim()[1] + plt.gca().get_xlim()[0])/2,
           ( plt.gca().get_ylim()[1] + plt.gca().get_ylim()[0])/2,
           texto, ha='center', va='center', fontsize=18, color='red', alpha=0.8)

  plt.title(pairs[0] + ' $\\times$ ' + pairs[1], fontsize=14, weight='bold')
  plt.xlabel(pairs[0])
  plt.ylabel(pairs[1])

plt.tight_layout()
plt.show()
_images/c6_parte_1_55_0.png

É importante que você note que, embora tenhamos aqui identificado e trabalhado relações lineares dos dados (e que por isso apresentam alto coeficiente de correlação), o correlograma não se limita a apresentar somente relações lineares e poderia ajudar a identificar muitos outros padrões de relacionamento entre os dados.

7.8. Mapas de Calor

Mapas de Calor ou Heat Maps permitem visualizar dados multivariados exibindo variáveis ​​discretas (categóricas ou numéricas) nas linhas e colunas e uma dimensão quantitativa colorindo as células do conjunto retangular de linhas e colunas. Os mapas de calor são ótimas representações para mostrar variações de valores em várias variáveis e identificar grupos de dados que apresentam um mesmo padrão de valores.

Um cuidado a ser tomado aqui é na escolha do esquema de cores a ser empregado. Caso você não opte pelo esquema padrão tenha cuidado na escolha do esquema de cores dando preferência aos esquemas cíclicos ou simplesmente sequenciais que permitem mais facilmente associar uma escala de cores a uma escala de valores.

7.8.1. Mapa de Correlação

Um caso especial muito comum de empregarmos Mapas de Calor é para exibirmos uma matriz de correlação. Esse gráfico pode substituir o Correlograma para a identificação de correlações fortes entre os dados, isto é, relações lineares. Mas podem haver outros padrões de relação nos dados e, neste caso, elas não podem ser identificadas por um Mapa de Correlação sendo mesmo necessário o uso de um Correlograma ou outras análises.

O Matplotlib não possui nenhuma função específica para mapas de calor, mas podemos empregar a função de exibição de imagens imshow() para construir mapas de calor e basta passar para a função uma matriz de valores. Os rótulos nesse caso precisam ser inseridos manualmente.

O Seaborn já tem uma função específica para isso e a função heatmap espera receber um narray ou DataFrame Pandas que, neste caso, empregará as informações de índice e coluna para rotular o gráfico.

plt.figure(figsize=(14,8))

matriz = df[['income_per_person',
       'LifeExpect', 'HappyIdx',
       'SchoolYears15_24', 'ChildMortality', 'Co2Emissions',
       'CPI', 'Population']].corr()

plt.subplot(1,2,1)
plt.imshow(matriz, cmap='jet_r', alpha=0.6)

plt.xticks(np.arange(len( matriz.columns)), matriz.columns.to_list(), rotation=90)
plt.yticks(np.arange(len( matriz.columns)), matriz.columns.to_list())

cbar = plt.colorbar()
cbar.set_label('$\\bf{Correlação}$')

plt.title('Matriz de Correlação', fontsize=14, weight='bold')

plt.subplot(1,2,2)
sns.heatmap(matriz, cmap='RdBu', square=True, alpha=0.6) 

plt.title('Matriz de Correlação', fontsize=14, weight='bold')

plt.tight_layout()
plt.show()
_images/c6_parte_1_59_0.png

Em quaisquer dos gráficos acima você pode indentificar, por exemplo, a alta correlação de ChildMortality para diversos indicadores e baixa correlação de Population do mesmo modo que observamos antes no Correlograma.

7.8.2. Variáveis Categóricas e uma Dimensão Quantitativa

Um Mapa de Calor é uma representação de uma matriz retangular e, assim, as linhas e colunas correspondem a valores discretos dos dados enquanto uma terceira variável quantitativa corresponde ao valor na linha/coluna que será exibido com alguma cor representando suas quantidades.

Para exemplificar este caso vamos empregar o dataset mpg e representar a média dos valores de mpg ao longo dos anos por origem dos veículos. Neste exemplo vamos empregar apenas o Seaborn.

Boa parte do trabalho para construir um Mapa de Calor está em criar a matriz de dados e, portanto, começamos por ela.

mpg = sns.load_dataset("mpg")
mpg.head()
mpg cylinders displacement horsepower weight acceleration model_year origin name
0 18.0 8 307.0 130.0 3504 12.0 70 usa chevrolet chevelle malibu
1 15.0 8 350.0 165.0 3693 11.5 70 usa buick skylark 320
2 18.0 8 318.0 150.0 3436 11.0 70 usa plymouth satellite
3 16.0 8 304.0 150.0 3433 12.0 70 usa amc rebel sst
4 17.0 8 302.0 140.0 3449 10.5 70 usa ford torino

As operações abaixo permitem criar uma matriz onde os valores de média de mpg são únicos para cada par origin/model_year, e serão respectivamente as linhas e colunas da nossa matriz. Fazemos essas operações em separado para que você possa entender os dados produzidos e que empregaremos no mapa de calor diretamente.

mpg_group = pd.DataFrame(mpg.groupby(['origin', 'model_year']).mpg.mean()).reset_index()
mpg_group.pivot('origin',	'model_year',	'mpg' )
model_year 70 71 72 73 74 75 76 77 78 79 80 81 82
origin
europe 25.200000 28.75 22.000000 24.000000 27.000000 24.50 24.250000 29.250000 24.950000 30.450000 37.288889 31.575000 40.000000
japan 25.500000 29.50 24.200000 20.000000 29.333333 27.50 28.000000 27.416667 29.687500 32.950000 35.400000 32.958333 34.888889
usa 15.272727 18.10 16.277778 15.034483 18.333333 17.55 19.431818 20.722222 21.772727 23.478261 25.914286 27.530769 29.450000

Entendida essa construção dos valores podemos empregar a matriz de dados diretamente na função de mapa de calor do Seaborn.

plt.figure(figsize=(18,4))
          
sns.heatmap(mpg_group.pivot('origin',	'model_year',	'mpg' ), alpha=0.8)

plt.title('mean(MPG) - Origin $\\times$ Year Models', fontsize=14, weight='bold')
plt.show()
_images/c6_parte_1_67_0.png

A representação ajuda, por exemplo, a identificar que os veículos se tornam mais econômicos ao longo dos anos (maiores valores de mpg) e, em particular a Europa, no período, lidera a corrida por carros econômicos chegando a produzir veículos que fazem em média cerca de 40 mpg (parte superior direita do gráfico).

7.8.3. Mapa de Calor e Várias Medidas Quantitativas

É menos comum, mas também podemos apresentar váriaveis quantitativas em um Mapa de Calor e isso pode ser útil em alguns casos, como por exemplo ajudando a identificar atributos com muitos valores ausentes em um conjunto de milhares de dados.

O que podemos observar com esse tipo de gráfico é um pouco diferente dos gráficos anteriores. A primeira coisa que você precisa notar é que havendo várias variáveis quantitativas elas podem apresentar diferentes escalas de valores e, como só temos a dimensão cor para representar os valores, será necessário normalizar os dados e a representação dos valores será assim relativa.

Abaixo você encontra um exemplo e, no primeiro gráfico, empregamos os valores sem normalização para que você entenda o que ocorre. Como existem muitos dados separamos somente os dados dos top 10 e low 10 países com base na renda per capta para examinarmos simultâeamente todas as variáveis quantitativas de cada país.

dfTop10 = df.nlargest(10,columns='income_per_person')
dfLow10 = df.nsmallest(10,columns='income_per_person')

df10 = pd.concat([dfTop10,dfLow10])

matriz = df10.drop(columns=['continent', 'country', 'year', 'cod_continent'])
matriz_norm =(matriz-matriz.min())/(matriz.max()-matriz.min())
  
plt.figure(figsize=(14,8))

plt.subplot(1,2,1)
plt.imshow(matriz, cmap='jet_r',alpha=0.5)
plt.yticks(np.arange(len(df10['country'])),df10['country'])
plt.xticks(np.arange(len(matriz.columns)),matriz.columns, rotation=60)
cbar = plt.colorbar()
cbar.set_label('$\\bf{Gap Variables}$')

plt.title('Evite este gráfico', fontsize=14, weight='bold', color='r')

plt.subplot(1,2,2)
plt.imshow(matriz_norm, cmap='jet_r',alpha=0.75)
plt.yticks(np.arange(len(df10['country'])),df10['country'])
plt.xticks(np.arange(len(matriz_norm.columns)),matriz_norm.columns, rotation=60)
cbar = plt.colorbar()
cbar.set_label('$\\bf{Gap Variables}$')

plt.title('Dados Normalizados', fontsize=14, weight='bold')


plt.tight_layout()
plt.show()
_images/c6_parte_1_70_0.png

Os resultados talvez não supreendam você, mas o gráfico permite que você observe bastante claramente que o grupo dos países de maior renda apresenta valores bastante superiores de vários índices de desenvolvimento como o índice de felicidade, de expectativa de vida e de anos de escolaridade (valores em azul na parte superior do mapa). Permitem também ver que o grupo de menor renda apresenta níveis baixos de emissões de CO2 e que, este sim um dado que parece surpreendente, os países ricos tem o maior nível de CPI (Corruption Perceptions Index). A linha branca que você encontra no gráfico também é uma informação útil e indica valores ausentes para VacineBelieve para uma série de países e, muitas vezes mapas de calor são uma forma rápida de mapearmos valores ausentes quando temos centenas de atributos de dados e milhares de dados.

7.9. Análise de Componentes Principais

Criado por Karl Pearson (o mesmo estatístico do coeficiente de Pearson) o PCA é uma técnica estatística para redução de dimensionalidade dos dados. O PCA converte um conjunto de variáveis correlacionadas num conjunto de valores linearmente não correlacionadas chamados de componentes principais. A ideia desse procedimento é que os componentes principais (de 1 até no máximo o número de variáveis consideradas) representam a variância dos dados em ordem decrescente e, assim, podemos empregar dois componentes principais, isto é, duas novas variáveis criadas a partir das variáveis originais, para exibir os dados no plano representando uma grande parte da variância dos dados.

Para construir isso vamos empregar a função PCA do Scikit-Learn. Como o PCA é sensível a normalização precisaremos primeiramente preparar os dados eliminando valores ausentes e normalizando os dados. Como vimos na seção anterior temos um grande número de ocorrências com ausência de valores para VacineBelieve, vamos então eliminar esse atributo e, sem prejuízo, excluir as demais linhas com valores nulos para gerar os componentes principais a partir das mesmas variáveis que empregamos no Correlograma mais atrás.

print('Dados com Valores ausentes:', df.isnull().values.any())
df = df.drop(columns=['VacineBelieve'])
df = df.dropna().reset_index(drop=True)
print('Dados com Valores ausentes:', df.isnull().values.any())
Dados com Valores ausentes: True
Dados com Valores ausentes: False
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

X = df[['income_per_person', 'LifeExpect', 'HappyIdx','SchoolYears15_24', 'ChildMortality', 'Co2Emissions', 'CPI', 'Population']]
features = X.columns
X = StandardScaler().fit_transform(X)
X = pd.DataFrame(X, columns = features)
X.head()
income_per_person LifeExpect HappyIdx SchoolYears15_24 ChildMortality Co2Emissions CPI Population
0 -0.378566 -1.239440 -1.850791 -0.980125 0.945894 -0.574424 0.642906 -0.308834
1 -1.104401 -2.172864 -1.226559 -2.817651 2.984586 -1.061338 -0.533444 -0.202322
2 -1.020492 -2.127698 -1.198185 -1.696919 3.126100 -1.015677 -0.815768 -0.168329
3 -0.661693 -0.742618 -0.904986 -0.108884 0.189676 -0.613328 -0.627552 0.292911
4 -1.042628 -1.570655 -1.283308 -0.718753 1.193544 -1.028782 -1.145146 -0.003695

Empregamos então a função PCA do Scikit-Learn para gerar os componentes principais.

pca = PCA(n_components=2)
PC2 = pca.fit_transform(X)

PC2 = pd.DataFrame(PC2, columns = ['PComp1','PComp2']) 
df = pd.concat([df,PC2],axis=1)
df.head()
continent country year demox_eiu income_per_person invest_%_gdp tax_%_gdp gini_index LifeExpect HappyIdx SchoolYears15_24 ChildMortality Co2Emissions CPI Population cod_continent PComp1 PComp2
0 Africa Botswana 2015 78.7 15700 32.1 24.7 60.5 66.9 0.376 8.40 40.7 2.560 63.0 2120000 0 2.044587 -0.613808
1 Africa Burkina Faso 2015 47.0 1600 24.3 15.1 35.5 60.7 0.442 3.76 86.8 0.182 38.0 18100000 0 4.537997 -0.822175
2 Africa Cote d'Ivoire 2015 33.1 3230 20.1 15.4 41.6 61.0 0.445 6.59 90.0 0.405 32.0 23200000 0 4.185610 -0.644831
3 Africa Egypt 2015 31.8 10200 14.3 12.5 31.2 70.2 0.476 10.60 23.6 2.370 36.0 92400000 0 1.455138 0.401299
4 Africa Kenya 2015 53.3 2800 21.5 16.3 41.5 64.7 0.436 9.06 46.3 0.341 25.0 47900000 0 3.022230 -0.005218

Esses dois componentes compreendem respectivamente 0.63 e 0.12 da variância dos dados e, portanto, podemos contar que uma representação com apenas essas duas dimensões estará representando 0.75 (75%) da variância dos dados, um valor bastante bom para termos uma representação de dados em duas dimensões uma vez que não podemos representar visualmente as 8 dimensões dos dados.

pca.explained_variance_ratio_
array([0.63095752, 0.12962208])

Podemos agora exibir esses dados em um gráfico de pontos e rotular os dados com cores, por exemplo, com os respectivos continentes dos pontos.

plt.figure(figsize=(12,4))
colors1 = dict(zip(df.continent.unique(), ['r','g','b','c','m']))

for continent in df.continent.unique():
  plt.scatter(df[ df['continent'] == continent ]['PComp1'], 
              df[ df['continent'] == continent ]['PComp2'] , 
              c=df[ df['continent'] == continent ]['continent'].map(colors1), 
              label=continent,
              s = 80,
              edgecolor='k',
              alpha=0.6)

plt.title('Componentes Principais', fontsize=14, weight='bold')
plt.xlabel('PComp1')
plt.ylabel('PComp2')

plt.legend()
plt.show()
_images/c6_parte_1_81_0.png

O gráfico, então, exibe algumas semelhanças e diferenças importantes dos continentes. A Europa e América, quando comparadas com a África estão claramente em posições opostas do gráfico representando as diferenças dos 8 indicadores (as variáveis originais) de desenvolvimento que empregamos. A Ásia, como sabemos, apresenta países em várias condições diferentes, indo do mais alto aos níveis mais baixos de desenvolvimento e isso também aparece representado no gráfico.

A análise de componentes principais, e a análise de fatores, são técnicas estatísticas bastante elaboradas e que envolvem muito mais elementos do que vimos aqui, mas para o aspecto de reduzir dimensões para a visualização de dados esta introdução parece bastante suficiente, e com essas ferramentas e as que vimos nos demais capítulos você estará bastante apto a encontrar e analisar várias possíveis relações entre dados.