Criando um Dashboard com a biblioteca Matplotlib

O processo de criação de dashboard

Daniel Souza — Medium

Creating a Dashboard with the Matplotlib Library | LinkedIn

O objetivo desse tutorial é que consigamos construir gráficos para auxiliar na aplicação do processo de ciência de dados. Podemos empregar visualizações durante a análise exploratória, antes ou depois de processar os dados, ou até mesmo um gráfico ou dashboard como entrega final. Portanto, saber criar uma visualização, independentemente de qual ferramenta seja, é de fundamental importância.

Acesse o Jupyter Notebook para consultar os conceitos que serão abordados sobre Visualização de Dados com Matplotlib. Obs: as funções, outputs e termos importantes estão em negrito para facilitar a compreensão — pelo menos a minha.

Matplotlib

Nessa primeira etapa vamos criar gráficos em matplotlib, manipular formatações, realizar ajustes necessários nos dados para que possibilitem sua plotagem de forma adequeda.

• Importar pacotes

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from IPython.display import Image
%matplotlib inline
mpl.__version__
'3.3.3'

• Estilos Matplotlib

Uma vez carregado o matplotlib, a biblioteca traz uma série de estilos — templates que podem ser usados para criar gráficos, sem a necessidade de construtir tudo a partir do zero.

print(plt.style.available)['seaborn-dark', 'seaborn-darkgrid', 'seaborn-ticks', 'fivethirtyeight', 'seaborn-whitegrid', 'classic', '_classic_test', 'fast', 'seaborn-talk', 'seaborn-dark-palette', 'seaborn-bright', 'seaborn-pastel', 'grayscale', 'seaborn-notebook', 'ggplot', 'seaborn-colorblind', 'seaborn-muted', 'seaborn', 'Solarize_Light2', 'seaborn-paper', 'bmh', 'tableau-colorblind10', 'seaborn-white', 'dark_background', 'seaborn-poster', 'seaborn-deep']

Cada estilo desse tem a configuração de cor, tamanho, posicionamento dos elementos etc.

• Criar função

Vamos criar uma função para criar um plot. Uma vez que tenhamos um códique passa a se repetir muitas e muitas vezes, é conveniente tornar essa repetição em uma função — criamos função para repetição.

Primeiramente, vamos definir alguns valores randômicos com a função randn do módulo random do pacote numpy.

Em seguida, criar subplots — vários pequenos plots dentro da área de plotagem. Como saída temos os objetos de figura e eixos.

Na sequência definimos todos os parâmetros do gráfico, os parâmetros de varíaxel x e y, tipo e densidade são aplicados aos eixos — axes e, em seguida, ainda especificamos em axes o título, labels, legendas. Por fim, usar a função show para que o gráfico seja exposto no notebook:

def plot_1():x = np.random.randn(5000, 6)(figure, axes) = plt.subplots(figsize = (16,10))(n, bins, patches) = axes.hist(
x,
12,
density = 1,
histtype = 'bar',
label = ['Color 1', 'Color 2', 'Color 3', 'Color 4', 'Color 5', 'Color 6'])
axes.set_title("Histogram\nFor\nNormal Distribution", fontsize = 25)axes.set_xlabel("Data", fontsize = 16)
axes.set_ylabel("Frequency", fontsize = 16)
axes.legend()
plt.

• Chamar função

Vamos chamar a função criada:

plot_1()

Componentes principais do gráfico

  • Os dados são prevenientes dos 5000 valores randômicos de randn.
  • As cores foram definidas com uma lista de cores passadas em label;
  • Tipo de gráfico foi passado através do parâmetro histtype;
  • Legendas do gráfico habilitadas em axes com legend();
  • Legendas dos eixos de x e y definidas set_label();
  • Título principal definido com set_title().

Customizar gráficos

Podemos criar nossos estilos, ou seja, podemos customizar completamente os gráficos. Vamo executar um comando diretamente no sistema operacional:

• Usuários Windows

Ao executar o código abaixo, listamos o conteúdo do diretório:

!dir estilos

• Usuários Mac e Linux

Ao executar o código abaixo, listamos o conteúdoo do diretório:

!ls -l estilos

Consultar estilos no diretório

Temos dois arquivos no diretório — mplstyle, são estilos do matplotlib. Vamos consultar um desses arquivos.

• Usuários Windows

Ao executar o código abaixo, carregamos o personalstyle-1 do diretório styles:

!type styles/personalstyle-1.mplstyle

• Usuários Mac e Linux

Ao executar o código abaixo, carregamos o personalstyle-1 do diretório styles:

!cat styles/personalstyle-1.mplstyle

• Como usar estilo customizado

Chamamos a função plt.style.use e apontamos para o diretório onde o arquivo texto de estilo está armazenado:

plt.style.use("styles/personalstyle-1.mplstyle")

• Chamar função de plotagem

Chamamos novamente a função de plotagem definida no início:

plot_1()

Veja que a priori o gráfico estava no formato padrão do matplotlib e mudamos o estilo para uma aparência mais profissional.

Para auxiliar no nosso trabalho, utilizaremos o dataset sobre automóveis do repositório de Machine Learning da UCI: UCI Automobile Data Set.

Carregaremos o arquivo csv e, a partir desse conjunto de dados, vamos construir nossos gráficos.

Modularização em Python

Vamos abrir o diretório e encontramos 3 arquivos .py — 3 módulos muito importantes: generatedata.py, generateplot.py, radar.py. — Podemos abrir esses arquivos através de um editor de texto e explorá-los um pouco mais.

À medida que construímos nosso processo de análise, usaremos códigos que se repetem diversas vezes em vários projetos. Por exemplo, para a grande maioria dos algoritmos de Machine Learning teremos que normalizar os dados, eles exigem receber dados normalizados antes de aplicar a modelagem preditiva. Ou seja, teremos que normalizar os dados em vários projetos diferentes em que trabalharmos.

Para não ter que repetir esse código, podemos criar um Módulo Python e usá-lo a cada projeto necessário. Automatizar o trabalho nos ajudará a ser mais produtivos e consequentemente obter melhores resultados. Portanto, tendo um módulo personalizado, bastará carregar o módulo e fazer a chamada à função especificada no módulo — Modularizar, é profissionalizar o trabalho de um Cientista de Dados.

Usando o Pandas para Carregar Dados

Primeiramente, vamos importar o pacote sys — pacote de gerenciamento de sistema operacional, chamar a função append do módulo path da biblioteca sys e fazer uma junção ao diretório lib, ou seja, vamos trazer o diretório lib para ser reconhecido por esse Jupyter Notebook que estamos trabalhando.

Na sequência importamos os módulos que estão alocados no diretório lib: generatedata.py, generateplot.py e radar.py.

import syssys.path.append("lib")
import generatedata, generateplot, radar

• Chamada à função do módulo

Faremos a chamada à função get_raw_data — ao verificar a função no módulo generatedata, podemos interpretar que essa função carrega o arquivo csv e chama read_csv do Pandas, ou seja, toda vez que quisermos carregar um arquivo csv, basta mudar o nome do arquivo em data_file da função no módulo:

data = generate.get_raw_data()""" Load full set
def get_raw_data():
data_file = "cars.csv"
return pd.read_csv(data_file) """
data.head()

• Outra função do módulo

Assim como chamamos a função get_raw_data, podemos chamar o get_limited_data — carregará apenas algumas variáveis do conjunto de dados.

data_subset = generatedata.get_limited_data()""" Check limited data
def get_limited_data(cols = None, lower_bound = None):
if not cols:
cols = limited_columns
data = get_raw_data()[cols]
if lower_bound:
(makes, _) = get_make_counts(data, lower_bound)
data = data[data["make"].isin(makes)]
return data """
data_subset.head()

A função resulta em um subset — apenas algumas variáveis foram retornadas para o nosso conjunto de dados. Dessa forma, podemos carregar o conjunto de dados completo ou apenas algumas variáveis, de acordo com o nosso objetivo final.

generatedata.get_all_auto_makes()""" Search only car manufacturers
def get_all_auto_makes():
return pd.Series(get_raw_data()["make"]).unique()
array(['audi', 'bmw', 'chevrolet', 'dodge', 'honda', 'jaguar', 'mazda', 'mercedes-benz', 'mitsubishi', 'nissan', 'peugot', 'plymouth','porsche', 'saab', 'subaru', 'toyota', 'volkswagen', 'volvo'],dtype=object)

Com a função get_all_auto_makes buscamos apenas os fabricantes de automóveis do conjunto, na Series de nome make.

As funções estão prontas, basta chamá-las. A função get_make_counts fará a contagem de fabricantes no conjunto de dados, retornando dois parâmetros (fabricantes, total) que retornarão as saídas da função.

(automakers, total) = generatedata.get_make_counts(data_subset)""" # get count
def get_make_counts(pddata, lower_bound=0):
counts = []
filtered_makes = []
for make in get_all_auto_makes():
data = get_make_data(make, pddata)
count = len(data.index)
if count >= lower_bound:
filtered_makes.append(make)
counts.append(count)
return (filtered_makes, list(zip(filtered_makes, counts))) """
total[('audi', 4),
('bmw', 4),
('chevrolet', 3),
('dodge', 8),
('honda', 13),
('jaguar', 1),
('mazda', 11),
('mercedes-benz', 5),
('mitsubishi', 10),
('nissan', 18),
('peugot', 7),
('plymouth', 6),
('porsche', 1),
('saab', 6),
('subaru', 12),
('toyota', 31),
('volkswagen', 8),
('volvo', 11)]

• Definir uma banda de seleção Dados

Ao definir o lower_bound, definimos um limite para que possamos trazer o conjunto de dados:

data = generate.get_limited_data(lower_bound = 6)""" def get_limited_data(cols = None, lower_bound = None):    
if not cols:
cols = limited_columns
data = get_raw_data()[cols]
if lower_bound:
(makes, _) = get_make_counts(data, lower_bound)
data = data[data["make"].isin(makes)]
return data """
dados.head()

• Quantidade de índices

Obter a quantidade de índices dentro do conjunto ou tabela:

len(data.index)141

Normalizando os Dados

Quando trabalhamos com um conjunto de dados, com escalas muito diferentes, pode ser necessário normalizar os dados, ou seja, colocar os dados na mesma escala — uma tarefa estatística.

Primeiramente, vamos criar uma cópia do nosso conjunto de dados com a função copy e passá-los para norm_data, uma mera cópia do conjunto — uma boa prática à medida que fizermos transformações nos dados.

norm_data = data.copy()

Em seguida, vamos renomear a coluna horsepower:

norm_data.rename(columns = {"horsepower" : "power"}, inplace = True)norm_data.head()

Para normalizar, chamaremos a função norm_column — essa função está no módulo generatedate.py.

A função norm_column opera os valorse mínimo e máximo, ou seja, (valor -valor min) / (max-min) — uma operação matemática elementar, para então normalizar os dados colocando-os numa mesma escala.

A função norm_column recebe como parâmetro o nome da coluna em col_name, pddata e inverted. Se inverted for igual a True, a função executará o bloco de código if inverted, ou seja, se o valor for True, executará a última linha de código dessa função:

def norm_column(col_name, pddata, inverted = False):           pddata[col_name] -= pddata[col_name].min()    
pddata[col_name] /= pddata[col_name].max()
if inverted:
pddata[col_name] = 1 - pddata[col_name]

• Normalizar colunas

Vamos normalizar algumas colunas, visto que não é necessário normalizar as strings ou categóricas, normalizar apenas as colunas numéricas.

# higher valuesgeneratedata.norm_columns(["city mpg", "highway mpg", "power"], norm_data)norm_data.head()

Ao comparar as duas tabelas, temos a tabela da esquerda com os dados originais e a tabela da direita com os dados normalizados, ou seja, numa mesma escala numérica. Não mudamos em nada a escala contida no dado, apenas mudamos a escala — o dado ainda representa a mesma coisa, apenas numa escala diferente. Isso é útil para construção de gráficos e para modelagem preditiva.

Contudo, algumas variáveis podem requerer uma forma diferente de se normalizar os dados. A normalização anterior foi feita para valores mais altos, agora faremos a normalização de valores mais baixos — aplicar a normalização invertida com a função do módulo generatedata.py, invert_norm_columns, que chama a função norm_column para valores maiores e passa desta vez o parâmetro invertido igual a True.

Para essa normalização invertida, serão passadas as variáveis de valores mais baixos:

# lower valuesgeneratedados.invert_norm_columns(["price", "weight", "riskiness", "losses"], norm_data)norm_data.head()

Tendo todas as variáveis agora normalizadas, estamos prontos para dar início à série de plots.

Plots

Primeiramente, chamamos o plt.figure, que criará uma figura — uma área de plotagem com as dimensões em figsize.

Em seguida, criamos GridSpec, uma espécie de área de desenho.

Depois chamamos a função make_autos_price_plot que pertence ao módulo generateplot, que define o título, o tipo de gráfico para scatter plot, define o tipo de label padrão e alguns parâmetros para deixar o gráfico exatamente na posição desejada e fazemos apenas uma simples chamada a essa função make_autos_price_plot:

figure = plt.figure(figsize = (15, 5))prices_gs = mpl.gridspec.GridSpec(1, 1)prices_axes = generateplot.make_autos_price_plot(figure, prices_gs, data)plt.show()

• Gráfico de Dispersão Vertical

figure = plt.figure(figsize = (15, 5))mpg_gs = mpl.gridspec.GridSpec(1, 1)mpg_axes = generateplot.make_autos_mpg_plot(figure, mpg_gs, data)plt.show()

• Gráfico Stacked bar

figure = plt.figure(figsize = (15, 5))risk_gs = mpl.gridspec.GridSpec(1, 1)risk_axes = generate.make_autos_riskiness_plot(figure, risk_gs, norm_data)plt.show()

• Gráfico Stacked bar invertido

figure = plt.figure(figsize = (15, 5))loss_gs = mpl.gridspec.GridSpec(1, 1)loss_axes = geraplot.make_autos_losses_plot(figure, loss_gs, norm_data)plt.show()

• Gráfico de barras normalizado

figure = plt.figure(figsize = (15, 5))risk_loss_gs = mpl.gridspec.GridSpec(1, 1)risk_loss_axes = generateplot.make_autos_loss_and_risk_plot(figure, risk_loss_gs, norm_data)plt.show()

Com tudo isso, pudemos criar vários gráficos diferentes através das chamadas de funções pertencentes ao módulo de plotagem, de forma organizada e eficaz.

• Gráfico de Radar

Por último, criaremos o gráfico de radar — tipo de gráfico muito complexo. Temos que usar um módulo exclusivo para radar por conta da sua complexidade, pode levar até um bom tempo para renderizar esse gráfico.

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from IPython.display import Image
import warnings
#warnings.filterwarnings('ignore')
import matplotlib
%matplotlib inline

import
sys
sys.path.append("lib")
import generatedata, generateplot, radar
#plt.style.use("styles/personalstyle-1.mplstyle")
data = generatedata.get_raw_data()
data.head()
data_subset = generatadata.get_limited_data()
data_subset.head()
data = generatedata.get_limited_data(lower_bound = 6)
data.head()
norm_data = data.copy()
norm_data.rename(columns = {"horsepower": "power"}, inplace = True)
figure = plt.figure(b= (15, 5))
radar_gs = mpl.gridspec.GridSpec(3, 7,
height_ratios = [1, 10, 10],
wspace = 0.50,
hspace = 0.60,
top = 0.95,
bottom = 0.25)
radar_axes = generateplot.make_autos_radar_plot(figure, gs=radar_gs, pddata=norm_data)plt.show()

Veja que abaixo temos o gráfico de radar com o título: Radar plot com 7 dimensões para 12 fabricantes — cada um nomeado no topo de cada radar. As dimensões referem-se às variáveis que estão ao redor de cada radar, indicando a relação de cada um dos fabricantes através de plots individuais.

• Plots Combinados — Dashboard

Vamos agora construir um dashboard através do matplotlib. O diagrama abaixo é conhecido como wireframe — termo muito comum em design, uma visao geral daquilo que será feito.

Esse wireframe mostra um modelo de dashboard — um dashboard é um conjunto de gráficos, ou seja, definimos a nossa área de plotagem e dentro dessa área organizamos os nossos gráficos que foram criados individualmente através de um plot combinado — dashboard.

--------------------------------------------
| overall title |
--------------------------------------------
| price ranges |
--------------------------------------------
| combined loss/risk | |
| | radar |
---------------------- plots |
| risk | loss | |
--------------------------------------------
| mpg |
--------------------------------------------

Abaixo temos a construção do wireframe de forma fragmentada, camada por camada: desenhar a figura com pyplot do matplotlib, desenhar o grid que é a área onde os gráficos serão colocados. Uma vez com a figura posta, colocaremos as camadas de título, o overall title — primeiria camada onde será definido e adicionado o subplot.

# building layers (without data)
figure = plt.figure(figsize=(10, 8))
gs_master = mpl.gridspec.GridSpec(4, 2, height_ratios=[1, 2, 8, 2])
## ----------------------------- ## -----------------------------# layer 1 - Title
gs_1 = mpl.gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec=gs_master[0, :])
title_axes = figure.add_subplot(gs_1[0])
## ----------------------------- ## -----------------------------# layer 2 - Price
gs_2 = mpl.gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec=gs_master[1, :])
price_axes = figure.add_subplot(gs_2[0])
## ----------------------------- ## -----------------------------# layer 3 - Risks and Radar
gs_31 = mpl.gridspec.GridSpecFromSubplotSpec(2, 2, height_ratios=[2, 1], subplot_spec=gs_master[2, :1])
risk_and_loss_axes = figure.add_subplot(gs_31[0, :])
risk_axes = figure.add_subplot(gs_31[1, :1])
loss_axes = figure.add_subplot(gs_31[1:, 1])
gs_32 = mpl.gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec=gs_master[2, 1])
radar_axes = figure.add_subplot(gs_32[0])
## ----------------------------- ## -----------------------------# layer 4 - MPG
gs_4 = mpl.gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec=gs_master[3, :])
mpg_axes = figure.add_subplot(gs_4[0])
## ----------------------------- ## -----------------------------# joins layers still without data
gs_master.tight_layout(figure)
plt.show()

A segunda camada são os gráficos com preço, terceira camada com subdisivisões com riscos e gráficos de radar, quarta camada de MPG e por último chamamos o titght_layout() para juntar todas essas camadas em uma mesma área.

Uma vez criado o wireframe, podemos plotar os gráficos nas áreas reservadas dentro desse grid. A partir daqui, chamamos os gráficos para cada área:

# building layers with data
figure = plt.figure(figsize = (15, 15))
gs_master = mpl.gridspec.GridSpec(4, 2,
height_ratios = [1, 24, 128, 32],
hspace = 0,
wspace = 0)
## ----------------------------- ## -----------------------------# layer 1 - title
gs_1 = mpl.gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec = gs_master[0, :])
title_axes = figure.add_subplot(gs_1[0])
title_axes.set_title("Plots", fontsize = 30, color = "#cdced1")
geraplot.hide_axes(title_axes)
## ----------------------------- ## -----------------------------# layer 2 - price
gs_2 = mpl.gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec = gs_master[1, :])
price_axes = figure.add_subplot(gs_2[0])
geraplot.make_autos_price_plot(figure,
pddata = dados,
axes = price_axes)
## ----------------------------- ## -----------------------------
# layer 3 - risks
gs_31 = mpl.gridspec.GridSpecFromSubplotSpec(2, 2,
height_ratios = [2, 1],
hspace = 0.4,
subplot_spec = gs_master[2, :1])
risk_and_loss_axes = figure.add_subplot(gs_31[0, :])
geraplot.make_autos_loss_and_risk_plot(figure,
pddata = dados_normalizados,
axes = risk_and_loss_axes,
x_label = False,
rotate_ticks = True)
risk_axes = figure.add_subplot(gs_31[1, :1])
geraplot.make_autos_riskiness_plot(figure,
pddata = dados_normalizados,
axes = risk_axes,
legend = False,
labels = False)
loss_axes = figure.add_subplot(gs_31[1:, 1])
geraplot.make_autos_losses_plot(figure,
pddata = dados_normalizados,
axes = loss_axes,
legend = False,
labels = False)
## ----------------------------- ## -----------------------------# layer 3 - radar
gs_32 = mpl.gridspec.GridSpecFromSubplotSpec(5, 3,
height_ratios = [1, 20, 20, 20, 20],
hspace = 0.6,
wspace = 0,
subplot_spec = gs_master[2, 1])
(rows, cols) = geometry = gs_32.get_geometry()
title_axes = figure.add_subplot(gs_32[0, :])
inner_axes = []
projection = radar.RadarAxes(spoke_count = len(dados_normalizados.groupby("make").mean().columns))
[inner_axes.append(figure.add_subplot(m, projection = projection)) for m in [n for n in gs_32][cols:]]
geraplot.make_autos_radar_plot(figure,
pddata = dados_normalizados,
title_axes = title_axes,
inner_axes = inner_axes,
legend_axes = False,
geometry = geometry)
## ----------------------------- ## -----------------------------# layer 4 - MPG
gs_4 = mpl.gridspec.GridSpecFromSubplotSpec(1, 1, subplot_spec = gs_master[3, :])
mpg_axes = figure.add_subplot(gs_4[0])
geraplot.make_autos_mpg_plot(figure,
pddata = dados,
axes = mpg_axes)
## ----------------------------- ## -----------------------------# joining layersgs_master.tight_layout(figure)
plt.show()

Ao unirmos todas essas camadas, temos como saída um dashboard completo:

Podemos compreender então, que um dashboard é um conjunto de gráficos em uma mesma área de plotagem. Podemos construir nosso próprio dashboarda a partir do 0, sem ter que pagar por ferramentas proprietárias. Em contrapartida, há a questão da programação que gera uma complexidade adicional.

Esse dashboard podería ser usada como resultado final do nosso trabalho. Podería ser um dashboard para monitoramento de dados em tempo real, para previsão de vendas ou para análise de dados históricos — depende sempre do nosso objetivo.

Obrigado.

Creating a Dashboard with the Matplotlib Library | LinkedIn

Composing a repository of books (i bought), authors (i follow) & blogs (direct ones) for my own understanding.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store