3º Avaliação: plotagem e aceleração computacional em Python

Autor

Wericky Barbosa de Melo

introdução

Este trabalho tem como objetivo explorar importantes ferramentas do ecossistema python voltadas para plotagem, em outras palavras, uma representação mais visualização dos dados.

Além disso será apresentado conceitos acerca de aceleração computacional, utilizando o Numba juntamente a um exemplo comparativo.

Dica

Para visualizar os códigos, basta clicar nos botão |>Código

Parte 1: plotagem em python

Plotagem em python é do criar uma representação visual dos dados, como por exemplo gráficos e tabelas, para isto podemos utilizar alguns pacotes como por exemplo Matplotlib, Seaborn, Plotly e Plotnine.

matplotlib

Exemplo de gráfico

Esta é uma biblioteca de baixo nível, altamente flexível e configurável, que serve de base para outras bibliotecas como o Seaborn por exemplo.

Exemplo de plotagem de gráficos utilizando matplotlib

gráfico de linha

Código
import matplotlib.pyplot as plt
import numpy as np

N = 21
x = np.linspace(0, 10, 11)
y = [3.9, 4.4, 10.8, 10.3, 11.2, 13.1, 14.1,  9.9, 13.9, 15.1, 12.5]

# fit a linear curve and estimate its y-values and their error.
a, b = np.polyfit(x, y, deg=1)
y_est = a * x + b
y_err = x.std() * np.sqrt(1/len(x) +
                          (x - x.mean())**2 / np.sum((x - x.mean())**2))

fig, ax = plt.subplots()
ax.plot(x, y_est, '-')
ax.fill_between(x, y_est - y_err, y_est + y_err, alpha=0.2)
ax.set_title('Gráfico de linha')
ax.plot(x, y, 'o', color='tab:brown')

Dica

Para visualizar os códigos, basta clicar nos botão |> Código

gráfico de colunas

Código
import matplotlib.pyplot as plt

fig, ax = plt.subplots()

frutas = ['maça', 'blueberry', 'cereja', 'laranja']
quantidade = [40, 100, 30, 55]
bar_labels = ['vermelho', 'azul', 'rosa', 'orange']
bar_colors = ['tab:red', 'tab:blue', 'tab:pink', 'tab:orange']

ax.bar(frutas, quantidade, label=bar_labels, color=bar_colors)

ax.set_ylabel('suprimento de frutas')
ax.set_title('Gráfico de colunas')
ax.legend(title='cor de frutas')

plt.show()

Seaborn

Exemplo de gráfico

O Seaborn utiliza o matplotlib para funcionar e simplifica o uso, a maioria de suas funções permitem que lide diretamente com dataframes e criam gráficos com poucas linhas.

gráfico de dispersão

Código
import seaborn as sns
import matplotlib.pyplot as plt
sns.set_theme(style="whitegrid")

# Load the example diamonds dataset
diamonds = sns.load_dataset("diamonds")

# Draw a scatter plot while assigning point colors and sizes to different
# variables in the dataset
f, ax = plt.subplots(figsize=(6.5, 6.5))
sns.despine(f, left=True, bottom=True)
clarity_ranking = ["I1", "SI2", "SI1", "VS2", "VS1", "VVS2", "VVS1", "IF"]
sns.scatterplot(x="carat", y="price",
                hue="clarity", size="depth",
                palette="ch:r=-.2,d=.3_r",
                hue_order=clarity_ranking,
                sizes=(1, 8), linewidth=0,
                data=diamonds, ax=ax)

Dica

Para visualizar os códigos, basta clicar nos botão |> Código

gráfico de boxplot

Código
import seaborn as sns
sns.set_theme(style="ticks", palette="pastel")

# Load the example tips dataset
tips = sns.load_dataset("tips")

# Draw a nested boxplot to show bills by day and time
sns.boxplot(x="day", y="total_bill",
            hue="smoker", palette=["m", "g"],
            data=tips)
sns.despine(offset=10, trim=True)

Plotly

Exemplo de gráfico

Possui um foco maior na interatividade. Como por exemplo para a construção de dashboards e gráficos interativos, permitindo zoom ou seleção por exemplo.

Aviso

Alguns gráficos podem demorar para renderizar dependendo do tamanho dos dados.

Nota

Note que ao posicionar o cursor sobre o gráfico, é possível obter detalhes, e clicando e arrastando, é possível dar um zoom.

tabela e gráfico vertical

Código
import plotly.graph_objects as go
import plotly.figure_factory as ff

# Add table data
table_data = [['Team', 'Wins', 'Losses', 'Ties'],
              ['Montréal<br>Canadiens', 18, 4, 0],
              ['Dallas Stars', 18, 5, 0],
              ['NY Rangers', 16, 5, 0],
              ['Boston<br>Bruins', 13, 8, 0],
              ['Chicago<br>Blackhawks', 13, 8, 0],
              ['Ottawa<br>Senators', 12, 5, 0]]

# Initialize a figure with ff.create_table(table_data)
fig = ff.create_table(table_data, height_constant=60)

# Add graph data
teams = ['Montréal Canadiens', 'Dallas Stars', 'NY Rangers',
         'Boston Bruins', 'Chicago Blackhawks', 'Ottawa Senators']
GFPG = [3.54, 3.48, 3.0, 3.27, 2.83, 3.18]
GAPG = [2.17, 2.57, 2.0, 2.91, 2.57, 2.77]

# Make traces for graph
trace1 = go.Bar(x=teams, y=GFPG, xaxis='x2', yaxis='y2',
                marker=dict(color='#0099ff'),
                name='Goals For<br>Per Game')
trace2 = go.Bar(x=teams, y=GAPG, xaxis='x2', yaxis='y2',
                marker=dict(color='#404040'),
                name='Goals Against<br>Per Game')

# Add trace data to figure
fig.add_traces([trace1, trace2])

# initialize xaxis2 and yaxis2
fig['layout']['xaxis2'] = {}
fig['layout']['yaxis2'] = {}

# Edit layout for subplots
fig.layout.yaxis.update({'domain': [0, .45]})
fig.layout.yaxis2.update({'domain': [.6, 1]})

# The graph's yaxis2 MUST BE anchored to the graph's xaxis2 and vice versa
fig.layout.yaxis2.update({'anchor': 'x2'})
fig.layout.xaxis2.update({'anchor': 'y2'})
fig.layout.yaxis2.update({'title': 'Goals'})

# Update the margins to add a title and see graph x-labels.
fig.layout.margin.update({'t':75, 'l':50})
fig.layout.update({'title': '2016 Hockey Stats'})

# Update the height because adding a graph vertically will interact with
# the plot height calculated for the table
fig.layout.update({'height':800})

# Plot!
fig.show()

mapa de bolhas

Código
import plotly.express as px
df = px.data.gapminder().query("year==2007")
fig = px.scatter_geo(df, locations="iso_alpha", color="continent",
                     hover_name="country", size="pop",
                     projection="natural earth")
fig.show()

plotnine

Exemplo de gráfico

Gramática de gráficos inspirada no ggplot2 do R

gráfico de barras empilhadas

Código
import pandas as pd
import numpy as np

from plotnine import (
    ggplot,
    aes,
    after_stat,
    stage,
    geom_bar,
    geom_text,
    geom_bin_2d,
    stat_bin_2d,
)
df = pd.DataFrame({
    "var1": list("abbcccddddeeeee"),
    "cat": list("RSRSRSRRRSRSSRS")
})

(
    ggplot(df, aes(x="var1", fill="cat"))
    + geom_bar()
    + geom_text(
        aes(label=after_stat("count"), y=stage(after_stat="count", after_scale="y+.1")),
        stat="count",
        position="stack",
    )
)

Dica

Para visualizar os códigos, basta clicar nos botão |> Código

gráfico de barras horizontais

Código
from plotnine import ggplot, aes, geom_bar, coord_flip, theme_classic
from plotnine.data import mpg
mpg.head()
(
    ggplot(mpg) 
    + geom_bar(aes(x="class", fill="drv"))
    + coord_flip()
    + theme_classic()
)

Parte 2: aceleração computacional com Numba

Exemplo de gráfico

Numba é um pacote que acelera a execução de funções númericas utilizando a compilação JIT (Just-In-Time). Isto é, traduzindo funções python em um código de máquina otimizado, aumentando o desempenho e diminuindo o tempo de processamento. Esta otimizização é especialmente útil para realizar simulação de dados em larga escala.

Vamos seguir com um exemplo de código não-otimizado que demanda um tempo considerável para funcionar:

Código sem otimização

Código
# 1) Imports e semente
import time
import random
import matplotlib.pyplot as plt
from matplotlib.patches import Arc

random.seed(123)


# 2) Função de aproximação de π (Python puro)
def aproximar_pi_calculo_py(num_pontos):
    dentro = 0
    for _ in range(num_pontos):
        x = random.uniform(0, 1)
        y = random.uniform(0, 1)
        if x*x + y*y <= 1:
            dentro += 1
    return 4 * dentro / num_pontos


# 3) Função de visualização (Monte Carlo)
def visualizar_aproximacao(num_pontos_plot):
    dentro_x, dentro_y = [], []
    fora_x, fora_y = [], []

    for _ in range(num_pontos_plot):
        x, y = random.uniform(0, 1), random.uniform(0, 1)
        if x*x + y*y <= 1:
            dentro_x.append(x)
            dentro_y.append(y)
        else:
            fora_x.append(x)
            fora_y.append(y)

    pi_grafico = 4 * len(dentro_x) / num_pontos_plot

    fig, ax = plt.subplots(figsize=(6,6))
    ax.scatter(dentro_x, dentro_y, color='blue',  s=1, label='Dentro do círculo')
    ax.scatter(fora_x, fora_y, color='red',   s=1, label='Fora do círculo')
    circ = Arc((0, 0), 2, 2, theta1=0, theta2=90, color='green', linewidth=2)
    ax.add_patch(circ)
    ax.set_aspect('equal')
    ax.set_xlim(0,1)
    ax.set_ylim(0,1)
    ax.legend(loc="lower right")
    plt.title(f'Aproximação de π (Monte Carlo)\nπ ≈ {pi_grafico:.5f} com {num_pontos_plot} pontos')
    plt.show()


# 4) Execução e medição de tempos
if __name__ == "__main__":
    N = 100_000

    # chamando função
    t0 = time.time()
    pi1 = aproximar_pi_calculo_py(N)
    t1 = time.time()
    print(f"[Python puro]   π ≈ {pi1:.5f}   tempo: {t1-t0:.4f} segundos")

    # Visualização dos pontos
    visualizar_aproximacao(N)
[Python puro]   π ≈ 3.13860   tempo: 0.0554 segundos

Agora otimizado com Numba:

Código
import time
import random
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Arc
from numba import njit, prange

# 1) Função original em Python puro
def aproximar_pi_calculo_py(num_pontos):
    dentro = 0
    for _ in range(num_pontos):
        x = random.uniform(0, 1)
        y = random.uniform(0, 1)
        if x*x + y*y <= 1:
            dentro += 1
    return 4 * dentro / num_pontos

# 2) Função otimizada com Numba (parallel loop + PRNG do NumPy)
@njit(parallel=True)
def aproximar_pi_calculo_numba(num_pontos):
    dentro = 0
    for i in prange(num_pontos):
        x = np.random.random()
        y = np.random.random()
        if x*x + y*y <= 1:
            dentro += 1
    return 4 * dentro / num_pontos

# 3) Visualização (igual ao seu código original)
def visualizar_aproximacao(num_pontos_plot):
    dentro_x, dentro_y, fora_x, fora_y = [], [], [], []
    for _ in range(num_pontos_plot):
        x, y = random.uniform(0,1), random.uniform(0,1)
        if x*x + y*y <= 1:
            dentro_x.append(x); dentro_y.append(y)
        else:
            fora_x.append(x); fora_y.append(y)

    pi_grafico = 4 * len(dentro_x) / num_pontos_plot

    fig, ax = plt.subplots(figsize=(6,6))
    ax.scatter(dentro_x, dentro_y, color='blue', s=1, label='Dentro do círculo')
    ax.scatter(fora_x, fora_y, color='red',  s=1, label='Fora do círculo')
    circ = Arc((0,0), 2, 2, theta1=0, theta2=90, color='green', linewidth=2)
    ax.add_patch(circ)
    ax.set_aspect('equal')
    ax.set_xlim(0,1); ax.set_ylim(0,1)
    ax.legend(loc="lower right")
    plt.title(f'Aproximação de π (Monte Carlo)\nπ ≈ {pi_grafico:.5f} com {num_pontos_plot} pontos')
    plt.show()

# 4) Semente para Python + NumPy (garante reproducibilidade em NumPy)
random.seed(123)
np.random.seed(123)

# 5) Medindo tempos
N = 100_000

# 5.1 Python puro
t0 = time.time()
pi_py = aproximar_pi_calculo_py(N)
t1 = time.time()

print(f"[Python]    π ≈ {pi_py:.5f}   tempo: {t1-t0:.4f} segundos")

# 5.2 Numba: primeira chamada (compilação + execução)
t2 = time.time()
pi_nb_first = aproximar_pi_calculo_numba(N)
t3 = time.time()

print(f"[Numba] compilação+execução  π ≈ {pi_nb_first:.5f}   tempo: {t3-t2:.4f} s")

# 5.3 Numba: segunda chamada (só execução)
t4 = time.time()
pi_nb = aproximar_pi_calculo_numba(N)
t5 = time.time()

print(f"[Numba]    apenas execução  π ≈ {pi_nb:.5f}   tempo: {t5-t4:.4f} s")

# 6) Visualizar
visualizar_aproximacao(N)
[Python]    π ≈ 3.13860   tempo: 0.0553 segundos
[Numba] compilação+execução  π ≈ 3.15048   tempo: 1.1240 s
[Numba]    apenas execução  π ≈ 3.14440   tempo: 0.0006 s

Dica

Note que o tempo da versão otimizada do código é mais rápida, necessitando menos tempo para executar os cálculos.