import os
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sbs
import plotly as ply9 Visualização
9.1 Introdução
9.1.1 Pré-requisitos
Para trabalhar com funções e todas as suas funcionalidades, utilizaremos dois pacotes externos:
ospandasmatplotlibseabornplotly
9.2 O que é o Matplotlib?
Matplotlib é uma excelente biblioteca de ferramentas gráficas, projetada para computação científica, que nos permite um grau bastante alto de personalização sobre todos os aspectos da apresentação. Nos permite construir visualizações estáticas em 2D e 3D e exportá-las nos mais diferentes formatos. Embora existam outras bibliotecas (e.g., altair, bokeh, plotly) que nos permitam construir visualizações dinâmicas e mais complexas, o Matplotlib é uma excelente primeira abordagem no campo de visualização de dados dentro do Python. Para a esmagadora maioria de nossas aplicações, é essa biblioteca que iremos buscar primeiro.
9.3 Construindo nosso 1º gráfico
9.3.1 Base de dados
Em relação aos dados, utilizaremos mais uma vez a base com informações relacionadas a covid-19 nos 10 países com o maior número de casos acumulados. Dessa vez, no entanto, nos concentraremos em algumas poucas colunas e na agregação mensal.
# substituir esse caminho de diretório pelo caminho que você estiver utilizando no seu computador
os.chdir('D:/Dropbox/Projetos e trabalhos/FEAUSP/Cursos/EAE1106 - Metodos Computacionais para Economistas/Aulas/2023_2')
columns_to_read = ['location',
'date',
'total_cases',
'new_cases',
'total_deaths',
'new_deaths',
'population',
'people_fully_vaccinated',
'median_age',
'aged_65_older',
'cardiovasc_death_rate',
'diabetes_prevalence']
df_covid = pd.read_csv('owid-covid-data_top10.csv', sep=',', encoding='utf8',usecols=columns_to_read)
df_covid['ano_mes'] = df_covid['date'].str[0:7]
df_covid_mensal = df_covid.groupby(by=['location','ano_mes'], as_index=False).agg(total_cases=('total_cases', 'max'),
new_cases=('new_cases', 'sum'),
total_deaths=('total_deaths', 'max'),
new_deaths=('new_deaths', 'sum'),
population=('population', 'mean'),
people_vaccinated=('people_fully_vaccinated', 'max'),
median_age=('median_age', 'mean'),
aged_65_older=('aged_65_older', 'mean'),
cardiovasc_death_rate=('cardiovasc_death_rate', 'mean'),
diabetes_prevalence=('diabetes_prevalence', 'mean'))
df_covid_mensal.info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 269 entries, 0 to 268
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 location 269 non-null object
1 ano_mes 269 non-null object
2 total_cases 269 non-null float64
3 new_cases 269 non-null float64
4 total_deaths 255 non-null float64
5 new_deaths 269 non-null float64
6 population 269 non-null float64
7 people_vaccinated 145 non-null float64
8 median_age 269 non-null float64
9 aged_65_older 269 non-null float64
10 cardiovasc_death_rate 269 non-null float64
11 diabetes_prevalence 269 non-null float64
dtypes: float64(10), object(2)
memory usage: 25.3+ KB
df_covid_mensal| location | ano_mes | total_cases | new_cases | total_deaths | new_deaths | population | people_vaccinated | median_age | aged_65_older | cardiovasc_death_rate | diabetes_prevalence | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Brazil | 2020-02 | 2.0 | 2.0 | NaN | 0.0 | 213993441.0 | NaN | 33.5 | 8.552 | 177.961 | 8.11 |
| 1 | Brazil | 2020-03 | 5717.0 | 5715.0 | 201.0 | 201.0 | 213993441.0 | NaN | 33.5 | 8.552 | 177.961 | 8.11 |
| 2 | Brazil | 2020-04 | 87187.0 | 81470.0 | 6006.0 | 5805.0 | 213993441.0 | NaN | 33.5 | 8.552 | 177.961 | 8.11 |
| 3 | Brazil | 2020-05 | 516137.0 | 428950.0 | 29367.0 | 23361.0 | 213993441.0 | NaN | 33.5 | 8.552 | 177.961 | 8.11 |
| 4 | Brazil | 2020-06 | 1412669.0 | 916328.0 | 59792.0 | 30425.0 | 213993441.0 | NaN | 33.5 | 8.552 | 177.961 | 8.11 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 264 | United States | 2021-11 | 48582403.0 | 2547313.0 | 781652.0 | 34249.0 | 332915074.0 | 199916776.0 | 38.3 | 15.413 | 151.089 | 10.79 |
| 265 | United States | 2021-12 | 54810020.0 | 6227617.0 | 827893.0 | 46241.0 | 332915074.0 | 207510289.0 | 38.3 | 15.413 | 151.089 | 10.79 |
| 266 | United States | 2022-01 | 74984166.0 | 20174146.0 | 889490.0 | 61597.0 | 332915074.0 | 212184168.0 | 38.3 | 15.413 | 151.089 | 10.79 |
| 267 | United States | 2022-02 | 79044330.0 | 4060164.0 | 950732.0 | 61242.0 | 332915074.0 | 215103151.0 | 38.3 | 15.413 | 151.089 | 10.79 |
| 268 | United States | 2022-03 | 79265726.0 | 221396.0 | 958437.0 | 7705.0 | 332915074.0 | 215274110.0 | 38.3 | 15.413 | 151.089 | 10.79 |
269 rows × 12 columns
Note que temos alguns problemas com valores missing, vamos nesse casos substituí-los por zero.
df_covid_mensal.fillna(0,inplace=True)
df_covid_mensal| location | ano_mes | total_cases | new_cases | total_deaths | new_deaths | population | people_vaccinated | median_age | aged_65_older | cardiovasc_death_rate | diabetes_prevalence | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Brazil | 2020-02 | 2.0 | 2.0 | 0.0 | 0.0 | 213993441.0 | 0.0 | 33.5 | 8.552 | 177.961 | 8.11 |
| 1 | Brazil | 2020-03 | 5717.0 | 5715.0 | 201.0 | 201.0 | 213993441.0 | 0.0 | 33.5 | 8.552 | 177.961 | 8.11 |
| 2 | Brazil | 2020-04 | 87187.0 | 81470.0 | 6006.0 | 5805.0 | 213993441.0 | 0.0 | 33.5 | 8.552 | 177.961 | 8.11 |
| 3 | Brazil | 2020-05 | 516137.0 | 428950.0 | 29367.0 | 23361.0 | 213993441.0 | 0.0 | 33.5 | 8.552 | 177.961 | 8.11 |
| 4 | Brazil | 2020-06 | 1412669.0 | 916328.0 | 59792.0 | 30425.0 | 213993441.0 | 0.0 | 33.5 | 8.552 | 177.961 | 8.11 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 264 | United States | 2021-11 | 48582403.0 | 2547313.0 | 781652.0 | 34249.0 | 332915074.0 | 199916776.0 | 38.3 | 15.413 | 151.089 | 10.79 |
| 265 | United States | 2021-12 | 54810020.0 | 6227617.0 | 827893.0 | 46241.0 | 332915074.0 | 207510289.0 | 38.3 | 15.413 | 151.089 | 10.79 |
| 266 | United States | 2022-01 | 74984166.0 | 20174146.0 | 889490.0 | 61597.0 | 332915074.0 | 212184168.0 | 38.3 | 15.413 | 151.089 | 10.79 |
| 267 | United States | 2022-02 | 79044330.0 | 4060164.0 | 950732.0 | 61242.0 | 332915074.0 | 215103151.0 | 38.3 | 15.413 | 151.089 | 10.79 |
| 268 | United States | 2022-03 | 79265726.0 | 221396.0 | 958437.0 | 7705.0 | 332915074.0 | 215274110.0 | 38.3 | 15.413 | 151.089 | 10.79 |
269 rows × 12 columns
9.3.2 Função plot
Com o dataframe criado, podemos plotar o número total de novos casos no Brasil ao longo do tempo usando uma única função: plot. Essa função recebe como argumento os valores do eixo x (no nosso caso a data) e do eixo y (número de novos casos) e nos devolve um gráfico de linha relacionando esses dois conjuntos de dados. Por fim é preciso chamar a função show() para mostrar o gráfico que criamos.
df_covid_br = df_covid_mensal[df_covid_mensal['location'].isin(['Brazil'])].reset_index(drop=True)
x = df_covid_br['ano_mes']
y = df_covid_br['new_cases']
# construção do gráfico
plt.plot(x, y, 'b-', linewidth=2)
plt.show()
O gráfico tá feito, mas ficou um pouco feio. Como podemos melhorar?
- Ajustar o que mostramos no eixo X.
- Ajustar a escala do eixo Y.
- Mudar a cor da linha para algo mais agradável.
- Colocar um marcador em cada ponto de dado.
- Adicionar título ao gráfico.
Sabemos que nossos dados começam em fevereiro de 2020 e vão até março de 2022. Para ajustar o eixo y, porém, é importante conhecer os limites numéricos do número de casos.
df_covid_br.describe()| total_cases | new_cases | total_deaths | new_deaths | population | people_vaccinated | median_age | aged_65_older | cardiovasc_death_rate | diabetes_prevalence | |
|---|---|---|---|---|---|---|---|---|---|---|
| count | 2.600000e+01 | 2.600000e+01 | 26.000000 | 26.000000 | 26.0 | 2.600000e+01 | 26.0 | 2.600000e+01 | 2.600000e+01 | 2.600000e+01 |
| mean | 1.257560e+07 | 1.112912e+06 | 333718.576923 | 25085.230769 | 213993441.0 | 4.313415e+07 | 33.5 | 8.552000e+00 | 1.779610e+02 | 8.110000e+00 |
| std | 9.654155e+06 | 9.019196e+05 | 244101.785272 | 21076.256589 | 0.0 | 5.995921e+07 | 0.0 | 5.434607e-15 | 2.411662e-14 | 5.434607e-15 |
| min | 2.000000e+00 | 2.000000e+00 | 0.000000 | 0.000000 | 213993441.0 | 0.000000e+00 | 33.5 | 8.552000e+00 | 1.779610e+02 | 8.110000e+00 |
| 25% | 4.140463e+06 | 3.939348e+05 | 127233.000000 | 8953.750000 | 213993441.0 | 0.000000e+00 | 33.5 | 8.552000e+00 | 1.779610e+02 | 8.110000e+00 |
| 50% | 1.166074e+07 | 9.094320e+05 | 288658.500000 | 22273.500000 | 213993441.0 | 3.508376e+06 | 33.5 | 8.552000e+00 | 1.779610e+02 | 8.110000e+00 |
| 75% | 2.127057e+07 | 1.483579e+06 | 592965.000000 | 30504.500000 | 213993441.0 | 8.424734e+07 | 33.5 | 8.552000e+00 | 1.779610e+02 | 8.110000e+00 |
| max | 2.904080e+07 | 3.333041e+06 | 652216.000000 | 82392.000000 | 213993441.0 | 1.557116e+08 | 33.5 | 8.552000e+00 | 1.779610e+02 | 8.110000e+00 |
Agora podemos usar algumas opções e métodos sobre o plot que criamos dentro da célula para aplicar todas essas alterações sugeridas.
x_ticks = [0,6,12,18,24]
x_labels = ['fev/20','ago/20','fev/21','ago/21','fev/22']
y_ticks = [0,1000000,2000000,3000000,4000000]
y_labels = ['0','1,000,000','2,000,000','3,000,000','4,000,000']
plt.plot(x, y, color='#1f77b4', linewidth=1, marker='o') # Saiba que o código "#1f77b4" é um código hexadecimal de cores. Para entender qual cor esse código representa acesse: https://htmlcolorcodes.com/
plt.xticks(x_ticks, x_labels)
plt.yticks(y_ticks, y_labels)
plt.ylim([-200000, 4300000])
plt.title('Número de novos casos mensais de Covid-19 no Brasil')
plt.show()
Bem melhor né? Mas ainda dá para melhorar muito!
Uma das belezas do Python é sua lógica de programação multiparadigma, o que nos permite trabalhar também com a lógica orientada a objetos. Isso não é diferente com gráficos.
No método que acabamos de utilizar, muitos dos objetos que compõe o gráfico como um todo (eixos, fundo, bordas, etc.) são criados e transmitidos sem se tornarem conhecidos pelo programador. Em geral é preferível um estilo de programação mais explícito, sob o qual teríamos mais controle sobre as várias dimensões do gráfico. E é assim que trabalharemos com gráficos daqui para frente.
9.4 Personalização
Vamos começar refazendo o primeiro gráfico, antes das alterações na escala e cores.
fig, ax = plt.subplots()
ax.plot(x, y, 'b-', linewidth=2)
plt.show()
Legal, mas o que significam todas essas coisas?
Começamos utilizando a função subplots() do matplotlib.pyplot, que nos retorna dois objetos aos quais damos os nomes de fig e ax. Sem entrar nos detalhes de classes e instâncias da lógica de programação orientada a objetos no Python:
figé como se fosse um fundo branco.- Pense em
axcomo sendo uma forma de iniciar um quadro sobre o qual construiremos a figura.
fig, ax = plt.subplots()
fig.set_facecolor("red")
ax.set_facecolor("blue")
plt.show()
Em essência, a função plot() é na verdade um método de ax.
Embora fazer esse gráfico simples dessa forma exija um pouco mais de código, o uso explícito de objetos nos dá melhor controle sobre a aparência do objeto e de todas as personalizações possíveis. Isso ficará mais claro à medida que avançarmos.
Como replicar o 2º gráfico da seção anterior usando essa lógica orientada a objetos?
x_ticks = [0,6,12,18,24]
x_labels = ['fev/20','ago/20','fev/21','ago/21','fev/22']
y_ticks = [0,1000000,2000000,3000000,4000000]
y_labels = ['0','1,000,000','2,000,000','3,000,000','4,000,000']
fig, ax = plt.subplots()
ax.plot(x, y, color='#1f77b4', linewidth=1, marker='o')
ax.set_xticks(x_ticks)
ax.set_xticklabels(x_labels)
ax.set_yticks(y_ticks)
ax.set_yticklabels(y_labels)
ax.set_ylim(-200000, 4300000)
plt.title('Número de novos casos mensais de Covid-19 no Brasil')
plt.show()
Foram várias linhas de código a mais com essa abordagem mais orientada a objeto, mas que no fim nos permite muito mais personalizações. Vamos tentar deixá-lo ainda melhor?
A ideia por trás de uma figura é sempre a mesma: transmitir alguma informação de forma clara e direta. Seguindo essa ideia e partindo do gráfico inicial, vamos fazer mais algumas alterações uma a uma e ir comparando os resultados.
- Mudar a proporção do gráfico e manter a borda apenas do eixo X
height = 6
fig, ax = plt.subplots(1,1, figsize=(1.50*height, height))
# Tipo de gráfico a ser plotado
ax.plot(x, y, color='#1f77b4', linewidth=1, marker='o', markersize=4)
# Visibilidade das bordas
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(True)
ax.spines['bottom'].set_alpha(0.3)
# Mostrar o resultado
plt.show()
- Colocar linhas de grid para os valores de Y
height = 6
fig, ax = plt.subplots(1,1, figsize=(1.50*height, height))
# Tipo de gráfico a ser plotado
ax.plot(x, y, color='#1f77b4', linewidth=1, marker='o', markersize=4)
# Visibilidade das bordas
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(True)
ax.spines['bottom'].set_alpha(0.3)
# Linhas de grid
ax.grid(visible=True, which='major',axis='y', ls='-',lw=0.5,c='k',alpha=0.1)
# Mostrar o resultado
plt.show()
- Mudar os marcadores do eixo x e do eixo y como já havíamos feito.
height = 6
fig, ax = plt.subplots(1,1, figsize=(1.50*height, height))
# Tipo de gráfico a ser plotado
ax.plot(x, y, color='#1f77b4', linewidth=1, marker='o', markersize=4)
# Visibilidade das bordas
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(True)
ax.spines['bottom'].set_alpha(0.3)
# Linhas de grid
ax.grid(visible=True, which='major',axis='y', ls='-',lw=0.5,c='k',alpha=0.1)
# Marcadores dos eixos
ax.set_xticks(x_ticks)
ax.set_xticklabels(x_labels, fontsize=10, fontweight='light')
ax.set_yticks(y_ticks)
ax.set_yticklabels(y_labels, fontsize=10, fontweight='light')
# Limites dos eixos
ax.set_xlim(-1, 26)
ax.set_ylim(-200000, 4300000)
# Mostrar o resultado
plt.show()
- Adicionar uma linha de referência que representa a média do número de casos ao longo do tempo.
height = 6
fig, ax = plt.subplots(1,1, figsize=(1.50*height, height))
# Tipo de gráfico a ser plotado
ax.plot(x, y, color='#1f77b4', linewidth=1, marker='o', markersize=4)
# Visibilidade das bordas
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(True)
ax.spines['bottom'].set_alpha(0.3)
# Linhas de grid
ax.grid(visible=True, which='major',axis='y', ls='-',lw=0.5,c='k',alpha=0.1)
# Marcadores dos eixos
ax.set_xticks(x_ticks)
ax.set_xticklabels(x_labels, fontsize=10, fontweight='light')
ax.set_yticks(y_ticks)
ax.set_yticklabels(y_labels, fontsize=10, fontweight='light')
# Limites dos eixos
ax.set_xlim(-1, 26)
ax.set_ylim(-200000, 4300000)
# Linha de referência
ax.plot([-1, 26], [y.mean(), y.mean()], 'r--', lw=1.0)
ax.annotate('Média de novos casos\nmensais: {:,.0f}'.format(y.mean()),xy=(-1, 1.10*y.mean()),fontsize=9.5,color='r',fontweight='normal',style='italic')
# Mostrar o resultado
plt.show()
- Deixar o título maior e um pouco afastado do início da figura, juntamente com a fonte dos dados logo abaixo.
- Salvar a figura em formato .png
height = 6
fig, ax = plt.subplots(1,1, figsize=(1.50*height, height))
# Tipo de gráfico a ser plotado
ax.plot(x, y, color='#1f77b4', linewidth=1, marker='o', markersize=4)
# Visibilidade das bordas
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(True)
ax.spines['bottom'].set_alpha(0.3)
# Linhas de grid
ax.grid(visible=True, which='major',axis='y', ls='-',lw=0.5,c='k',alpha=0.1)
# Marcadores dos eixos
ax.set_xticks(x_ticks)
ax.set_xticklabels(x_labels, fontsize=10, fontweight='light')
ax.set_yticks(y_ticks)
ax.set_yticklabels(y_labels, fontsize=10, fontweight='light')
# Limites dos eixos
ax.set_xlim(-1, 26)
ax.set_ylim(-200000, 4300000)
# Linha de referência
ax.plot([-1, 26], [y.mean(), y.mean()], 'r--', lw=1.0)
ax.annotate('Média de novos casos\nmensais: {:,.0f}'.format(y.mean()),xy=(-1, 1.10*y.mean()),fontsize=9.5,color='r',fontweight='normal',style='italic')
# Título e fonte
plt.suptitle('Número de novos casos mensais de COVID19 - Brasil',fontsize=15,fontweight='normal')
plt.title('Fonte: Our World in Data',fontsize=12,fontweight='normal',pad=15)
# Salvar a figura e mostrar o resultado
plt.savefig('casos_covid_br.png', bbox_inches='tight')
plt.show()
Uau, que diferença! Foram muitas linhas de código, mas esse é o preço de um bom gráfico e de uma boa visualização. O controle sobre todos os elementos que compõe uma figura é essencial para que possamos cumprir com o objetivo de tornar visualizações cada vez mais informativas e democráticas.
9.5 Gráficos múltiplos
Uma vez aprendida a forma de construção de um gráfico através da função subplot() é fácil construir figuras que contenham várias séries em um mesmo plot ou vários plots na mesma figura. Vamos começar pelo caso mais simples.
9.5.1 Várias séries em um mesmo plot
Uma vez em posse dos dados de casos de covid-19 por país, uma pergunta que surge naturalmente é: como o número de casos reportados diferiu entre os diferentes países? Um aluno bem atento teria notado que no último gráfico da sessão anterior já havíamos juntado duas séries em um mesmo plot, sendo uma dessas séries a reta horizontal que representa a média de novos casos. Mas então basta adicionar mais uma linha de código com um segundo ax.plot()? É isso aí!
Para essa análise vamos primeiro criar a coluna new_cases_per_million, já que comparar o número absoluto de casos entre países com populações muito distintas pode não ser a melhor saída.
df_covid_mensal['new_cases_per_million'] = (df_covid_mensal['new_cases'] / df_covid_mensal['population']) * 1e6
df_covid_mensal['total_cases_per_million'] = (df_covid_mensal['total_cases'] / df_covid_mensal['population']) * 1e6
df_covid_mensal['total_deaths_per_million'] = (df_covid_mensal['total_deaths'] / df_covid_mensal['population']) * 1e6
df_covid_br = df_covid_mensal[df_covid_mensal['location'].isin(['Brazil'])].reset_index(drop=True)
df_covid_kr = df_covid_mensal[df_covid_mensal['location'].isin(['South Korea'])].reset_index(drop=True)
x = df_covid_br['ano_mes'][-26:]
y1 = df_covid_br['new_cases_per_million'][-26:]
y2 = df_covid_kr['new_cases_per_million'][-26:]x_ticks = [0,6,12,18,24]
x_labels = ['fev/20','ago/20','fev/21','ago/21','fev/22']
y_ticks = [0,10000,20000,30000,40000,50000]
y_labels = ['0','10,000','20,000','30,000','40,000','50,000']height = 6
fig, ax = plt.subplots(1,1, figsize=(1.50*height, height))
# Séries a serem plotdas
ax.plot(x, y1, color='#1f77b4',linewidth=2,marker='o',markersize=4)
ax.plot(x, y2, color='#d62728',linewidth=2,marker='s',markersize=4)
# Visibilidade das bordas
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(True)
ax.spines['bottom'].set_alpha(0.3)
# Linhas de grid
ax.grid(visible=True, which='major',axis='y', ls='-',lw=0.5,c='k',alpha=0.1)
# Marcadores dos eixos
ax.set_xticks(x_ticks)
ax.set_xticklabels(x_labels, fontsize=10, fontweight='light')
ax.set_yticks(y_ticks)
ax.set_yticklabels(y_labels, fontsize=10, fontweight='light')
# Limites dos eixos
ax.set_xlim(-1, 26)
ax.set_ylim(-1000, 55000)
# Título e fonte
plt.suptitle('Novos casos por 100 habitantes - Brasil e Coréia do Sul',fontsize=15,fontweight='normal')
plt.title('Fonte: Our World in Data',fontsize=12,fontweight='normal',pad=15)
# Salvar a figura e mostrar o resultado
plt.savefig('cm_covid_br.png', bbox_inches='tight')
plt.show()
Super direto! Mas ainda falta a legenda. Para isso basta adicionar mais uma única linha de código.
height = 6
fig, ax = plt.subplots(1,1, figsize=(1.50*height, height))
# Séries a serem plotdas
ax.plot(x, y1, color='#1f77b4',linewidth=2,marker='o',markersize=4,label='Brasil')
ax.plot(x, y2, color='#d62728',linewidth=2,marker='s',markersize=4,label='Coréia do Sul')
# Legenda
ax.legend(loc='center',bbox_to_anchor=(0.3,0.8),framealpha=0,ncol=2,prop={'size': 12})
# Visibilidade das bordas
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(True)
ax.spines['bottom'].set_alpha(0.3)
# Linhas de grid
ax.grid(visible=True, which='major',axis='y', ls='-',lw=0.5,c='k',alpha=0.1)
# Marcadores dos eixos
ax.set_xticks(x_ticks)
ax.set_xticklabels(x_labels, fontsize=10, fontweight='light')
ax.set_yticks(y_ticks)
ax.set_yticklabels(y_labels, fontsize=10, fontweight='light')
# Limites dos eixos
ax.set_xlim(-1, 26)
ax.set_ylim(-1000, 55000)
# Título e fonte
plt.suptitle('Novos casos por 1,000,000 de habitantes',fontsize=15,fontweight='normal')
plt.title('Fonte: Our World in Data',fontsize=12,fontweight='normal',pad=15)
# Salvar a figura e mostrar o resultado
plt.savefig('casos_covid_brkr.png', bbox_inches='tight')
plt.show()
Perfeito!
Vamos tentar agora vários plots dentro da mesma figura?
9.5.2 Vários plots dentro da mesma figura
Uma outra alternativa ao gráfico acima seria colocar cada série em um plot separado. Para tal, basta alterar os dois primeiros argumentos da função subplots(), que representam o número de linhas e o número de colunas da figura final. Nesse caso, porém, o objeto ax deixará de ser um simples objeto do matplotlib e se torna um numpy.ndarray com vários objetos do matplotlib. Aqui funciona a mesma lógica de indexação de sempre.
num_rows = 1
num_cols = 1
height = 6
fig, ax = plt.subplots(num_rows,num_cols, figsize=(1.50*height, height))
type(ax)matplotlib.axes._axes.Axes

num_rows = 2
num_cols = 1
height = 6
fig, ax = plt.subplots(num_rows,num_cols, figsize=(1.50*height, height))
type(ax)numpy.ndarray

Teremos que usar, portanto, algum tipo de loop justamente para poder lidar com as personalizações de cada plot de forma individual.
num_rows = 2
num_cols = 1
height = 6
fig, ax = plt.subplots(num_rows,num_cols, figsize=(1.50*height, height))
# Séries a serem plotdas
ax[0].plot(x, y1, color='#1f77b4',linewidth=2,marker='o',markersize=4,label='Brasil')
ax[1].plot(x, y2, color='#d62728',linewidth=2,marker='s',markersize=4,label='Coréia do Sul')
for i in range(0,2):
# Legenda
ax[i].legend(loc='upper left',bbox_to_anchor=(0.05,0.9),framealpha=0,ncol=2,prop={'size': 10})
# Visibilidade das bordas
ax[i].spines['top'].set_visible(False)
ax[i].spines['right'].set_visible(False)
ax[i].spines['left'].set_visible(False)
ax[i].spines['bottom'].set_visible(True)
ax[i].spines['bottom'].set_alpha(0.3)
# Linhas de grid
ax[i].grid(visible=True, which='major',axis='y', ls='-',lw=0.5,c='k',alpha=0.1)
# Marcadores dos eixos
ax[i].set_xticks(x_ticks)
ax[i].set_xticklabels(x_labels, fontsize=10, fontweight='light')
ax[i].set_yticks(y_ticks)
ax[i].set_yticklabels(y_labels, fontsize=10, fontweight='light')
# Limites dos eixos
ax[i].set_xlim(-1, 26)
ax[i].set_ylim(-1000, 55000)
# Título e fonte
plt.suptitle('Novos casos por 100 habitantes',fontsize=15,fontweight='normal')
plt.title('Fonte: Our World in Data',fontsize=12,fontweight='normal',pad=195)
# Salvar a figura e mostrar o resultado
plt.savefig('casos_covid_brkr2.png', bbox_inches='tight')
plt.show()
Como sempre, o céu é o limite!
9.6 Outros tipos de gráficos
9.6.1 Gráfico de dispersão: scatter
No entanto, gráficos de linhas não são os mais indicados quando estamos interessados em avaliar a relação entre duas variáveis e não de uma única variável ao longo do tempo, por exemplo. Para isso temos o gráfico de dispersão (scatterplot, em inglês), que nada mais é do que uma forma de representar pares ordenados de pontos ao longo de dois eixos.
Vamos construir um exemplo de scatterplot utilizando as novas variáveis total_cases_per_million e total_deaths_per_million. É de se esperar que um número maior de casos por habitantes esteja relacionado diretamente com um número maior de mortes por habitantes, o que implicaria em uma relação positiva entre essas duas variáveis. Como ficaria a representação gráfica dessa relação para a média anual por país?
df_covid_mensal['ano'] = df_covid_mensal['ano_mes'].str[0:4]
df_covid_anual = df_covid_mensal[['location','ano','total_cases_per_million','total_deaths_per_million']]
df_covid_anual = df_covid_anual.groupby(by=['location','ano'], as_index=False).mean()
x = df_covid_anual['total_cases_per_million']
y = df_covid_anual['total_deaths_per_million']
x_ticks = [0,100000,200000,300000]
x_labels = ['0','100,000','200,000','300,000']
y_ticks = [0,1000,2000,3000]
y_labels = ['0','1,000','2,000','3,000']height = 6
fig, ax = plt.subplots(1,1, figsize=(1.50*height, height))
# Séries a serem plotdas
ax.scatter(x,y,color='#1f77b4',marker='o',s=50)
# Visibilidade das bordas
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(True)
ax.spines['bottom'].set_alpha(0.3)
# Linhas de grid
ax.grid(visible=True, which='major',axis='y', ls='-',lw=0.5,c='k',alpha=0.1)
# Marcadores dos eixos
ax.set_xticks(x_ticks)
ax.set_xticklabels(x_labels, fontsize=10, fontweight='light')
ax.set_yticks(y_ticks)
ax.set_yticklabels(y_labels, fontsize=10, fontweight='light')
# Limites e título dos eixos
ax.set_xlim(-50000, 400000)
ax.set_ylim(-100, 3500)
ax.set_xlabel('Número de casos por milhão de habitantes',fontsize=12, fontweight='light', labelpad=20)
ax.set_ylabel('Número de mortes por milhão de habitantes',fontsize=12, fontweight='light', labelpad=20)
# Título e fonte
plt.suptitle('Casos VS Mortes - Top 10 países em casos',fontsize=15,fontweight='normal')
plt.title('Fonte: Our World in Data',fontsize=12,fontweight='normal',pad=15)
# Salvar a figura e mostrar o resultado
plt.savefig('casos_mortes_scatter.png', bbox_inches='tight')
plt.show()
Parece que tem uma relação positiva de fato. Para bater o martelo sobre a relação para todos os países, no entanto, seria necessário ir atrás de mais dados. Que tal refazer a análise buscando a amostra completa de países e não apenas os 10 países com o maior número absoluto de casos?
9.6.2 Gráfico de barra: bar e barh
Um outro tipo de gráfico bastante interessante é o gráfico de barra, que serve muito ao propósito, por exemplo, de mostrar diferenças em uma variável de interesse, para um dado instante do tempo, entre regiões ou grupos diferentes. Dentre os 10 países com o maior número de casos, quais países tem a maior proporção da população com 65 anos ou mais?
Aqui vamos usar apenas as colunas iso_code e aged_65_older para ordenar os países de acordo com a proporção de idosos em sua população. Como já vimos, os valores de aged_65_older são constantes ao longo do tempo no nível país. Podemos então partir da criação de um dataframe que contenha apenas os dados referentes ao último mês de dado e ficar com uma única observação por país.
df_covid_top10 = df_covid_mensal[df_covid_mensal['ano_mes']=='2022-03']
df_covid_top10 = df_covid_top10[['location','aged_65_older']].reset_index(drop=True)
df_covid_top10.sort_values(['aged_65_older'],ascending=False,ignore_index=True,inplace=True)
df_covid_top10| location | aged_65_older | |
|---|---|---|
| 0 | Japan | 27.049 |
| 1 | Italy | 23.021 |
| 2 | Germany | 21.453 |
| 3 | France | 19.718 |
| 4 | United Kingdom | 18.517 |
| 5 | United States | 15.413 |
| 6 | Russia | 14.178 |
| 7 | South Korea | 13.914 |
| 8 | Brazil | 8.552 |
| 9 | India | 5.989 |
# Para não dar problema com os labels do eixo x serem muito grandes, vamos substituir aqueles que tem nomes compostos
df_covid_top10.loc[4,'location'] = 'United\nKingdom'
df_covid_top10.loc[5,'location'] = 'United\nStates'
df_covid_top10.loc[7,'location'] = 'South\nKorea'À partir desses dados, podemos construir 2 tipos de gráfico de barras:
- Barra vertical
x = df_covid_top10['location']
y = df_covid_top10['aged_65_older']
height = 6
fig, ax = plt.subplots(1,1, figsize=(1.50*height, height))
# Séries a serem plotdas
ax.bar(x,y,align='center',color='#1f77b4')
# Visibilidade das bordas
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(True)
ax.spines['bottom'].set_alpha(0.3)
# Linhas de grid
ax.grid(visible=True, which='major',axis='y', ls='-',lw=0.5,c='k',alpha=0.1)
# Título e fonte
plt.suptitle('Fração da população 65+ - Top 10 países em casos',fontsize=15,fontweight='normal')
plt.title('Fonte: Our World in Data',fontsize=12,fontweight='normal',pad=15)
# Salvar a figura e mostrar o resultado
plt.savefig('frac65plus_bar.png', bbox_inches='tight')
plt.show()
- Barra horizontal
height = 6
fig, ax = plt.subplots(1,1, figsize=(1.50*height, height))
# Séries a serem plotdas
ax.barh(x,y,align='center',color='#1f77b4')
# Visibilidade das bordas
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['bottom'].set_visible(True)
ax.spines['bottom'].set_alpha(0.3)
# Linhas de grid
ax.grid(visible=True, which='major',axis='y', ls='-',lw=0.5,c='k',alpha=0.1)
# Título e fonte
plt.suptitle('Fração da população 65+ - Top 10 países em casos',fontsize=15,fontweight='normal')
plt.title('Fonte: Our World in Data',fontsize=12,fontweight='normal',pad=15)
# Salvar a figura e mostrar o resultado
plt.savefig('frac65plus_barh.png', bbox_inches='tight')
plt.show()
9.6.3 Histograma: hist
O Matplotlib também nos oferece um conjunto bastante grande de gráficos estatísticos, como por exemplo o boxplot e o histograma. Vamos utilizar esse último, através da função hist, para plotar em uma mesma figura histogramas de 4 variáveis diferentes: median_age, aged_65_older, cardiovasc_death_rate e diabetes_prevalence.
df_covid_top10 = df_covid_mensal[df_covid_mensal['ano_mes']=='2022-03']
df_covid_top10 = df_covid_top10[['location',
'median_age',
'aged_65_older',
'cardiovasc_death_rate',
'diabetes_prevalence']].reset_index(drop=True)
df_covid_top10| location | median_age | aged_65_older | cardiovasc_death_rate | diabetes_prevalence | |
|---|---|---|---|---|---|
| 0 | Brazil | 33.5 | 8.552 | 177.961 | 8.11 |
| 1 | France | 42.0 | 19.718 | 86.060 | 4.77 |
| 2 | Germany | 46.6 | 21.453 | 156.139 | 8.31 |
| 3 | India | 28.2 | 5.989 | 282.280 | 10.39 |
| 4 | Italy | 47.9 | 23.021 | 113.151 | 4.78 |
| 5 | Japan | 48.2 | 27.049 | 79.370 | 5.72 |
| 6 | Russia | 39.6 | 14.178 | 431.297 | 6.18 |
| 7 | South Korea | 43.4 | 13.914 | 85.998 | 6.80 |
| 8 | United Kingdom | 40.8 | 18.517 | 122.137 | 4.28 |
| 9 | United States | 38.3 | 15.413 | 151.089 | 10.79 |
num_rows = 2
num_cols = 2
height = 6
fig, ax = plt.subplots(num_rows,num_cols, figsize=(1.50*height, height))
# Séries a serem plotdas
ax[0,0].hist(df_covid_top10['median_age'],color='#1f77b4',bins=10)
ax[0,1].hist(df_covid_top10['aged_65_older'],color='#1f77b4',bins=10)
ax[1,0].hist(df_covid_top10['cardiovasc_death_rate'],color='#1f77b4',bins=10)
ax[1,1].hist(df_covid_top10['diabetes_prevalence'],color='#1f77b4',bins=10)
ax[0,0].set_title('Idade mediana', fontsize=8, fontweight='light')
ax[0,1].set_title('Fração pop. 65+', fontsize=8, fontweight='light')
ax[1,0].set_title('Taxa de mort. cardiovascular', fontsize=8, fontweight='light')
ax[1,1].set_title('Prevalência de diabetes', fontsize=8, fontweight='light')
for r in range(0,2):
for c in range(0,2):
# Visibilidade das bordas
ax[r,c].spines['top'].set_visible(False)
ax[r,c].spines['right'].set_visible(False)
ax[r,c].spines['left'].set_visible(False)
ax[r,c].spines['bottom'].set_visible(True)
ax[r,c].spines['bottom'].set_alpha(0.3)
# Linhas de grid
ax[r,c].grid(visible=True, which='major',axis='y', ls='-',lw=0.5,c='k',alpha=0.1)
# Opções do eixo y
ax[r,c].set_yticks([0,1,2,3,4])
ax[r,c].set_yticklabels(['0','1','2','3','4'], fontsize=10, fontweight='light')
ax[r,c].set_ylim(0,4.5)
# Ajuste de espaço entre os vários plots
fig.subplots_adjust(hspace = 0.6, wspace = 0.3)
# Título e fonte
plt.suptitle('Características dos países com o maior número de casos de covid19',y=1.05,fontsize=15,fontweight='normal')
plt.text(0, 13.3,'Fonte: Our World in Data',fontsize=12,fontweight='normal')
# Salvar a figura e mostrar o resultado
plt.savefig('carac_covid_all.png', bbox_inches='tight')
plt.show()
9.7 Seaborn
9.8 Plotly
9.9 Visualização e storytelling
É parte importante do dia-a-dia de um economista - e também de grande parte do profissionais pertencentes ao grupo das ciências sociais aplicadas - ser capaz de comunicar os resultados de uma análise ou pesquisa. E como há muito tempo já se sabe, para comunicar e informar, uma imagem vale mais do que mil palavras.
No artigo An Economist’s Guide to Visualizing Data, publicado no prestigiado Journal of Economic Perspectives em 2014, o autor Jonathan Schwabish fala sobre a importância de construir visualizações e gráficos que de fato chamem a atenção para aquilo que se deseja comunicar. Ele cita três princípios básicos na construção de uma boa visualização gráfica:
Mostre os dados. O conjunto de dados é o elemento básico por trás da história que se quer contar. Isso não quer dizer que é preciso mostrar todos os dados disponíveis, alguns gráficos mostram demais e acabam sendo pouco informativos. Mostre os dados, mas o faça da forma mais clara possível.
Reduza a sujeira e a desordem gráfica. O uso de elementos visuais desnecessários ou que distraem, tenderá a reduzir a eficácia do gráfico em comunicar uma mensagem. Pense duas vezes antes de utilizar bordas, linhas de referências muito grossas e escuras, pontos de referência desnecessários nos eixos, cores muito próximas em conjuntos de dados distintos, texturas, etc.
Elementos visuais e textuais devem funcionar de forma integrada. O gráfico não pode ser poluído de texto e se transformar naquilo que não é, mas elementos textuais podem ajudar bastante na tarefa de chamar a atenção para aquele que é o ponto principal da mensagem. O gráfico deve funcionar como um complemento ao texto, mas ao mesmo tempo deve se sustentar sem ele.
O artigo vai muito além desses 3 princípios básicos e mostra exemplos de visualizações ruins e de como é possível “consertá-las”. Há, no entanto, outras várias fontes de informação sobre visualização (esse link é uma ótima referência) e de como construir gráficos e visualizações a partir de uma ideia de storytelling, de utilizar dados e ferramentas gráficas para contar uma história e comunicar uma mensagem (o livro Storytelling com dados é outra ótima referência). Em resumo, construir um bom gráfico, uma visualização eficiente, é um trabalho de criatividade e de muita tentativa e erro. Uma determinada visualização pode funcionar muito bem para um certo tipo de dado e não funcionar para outros vários tipos. Personalização é a palavra chave nessa árdua tarefa de construir um bom gráfico.
9.10 Exercícios
X
X