Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Caderno 3

Eixos, Escalas e Sistemas de Coordenadas

Definir escalas de posição é imprescindível para visualizar informações, especialmente quantitativas. Escalas de posição em conjunto com um arranjo geométrico formam um sistema de coordenadas.

O sistema de coordenadas mais comum que utilizamos é o Cartesiano, em que o eixo de abscissas (geralmente representado pela variável xx) corre horizontalmente e o de ordenadas (geralmente representado pela variável yy) corre verticalmente. Entretanto, a maneira de relacionar dados poderia ocorrer com yy correndo de maneira oblíqua em relação a yy, ou com yy mudando de maneira circular e xx de maneira radial. Neste último caso, teríamos um sistema de coordenadas polares.

Escalas ajudam a posicionar a informação e a criar representações visuais com razões de aspecto distintas. Figuras esticadas na direção vertical e estreitas na direção horizontal enfatizarão um sentido de “crescer para cima”, por exemplo. Por outro lado, figuras compridas na direção horizontal e encurtadas na direção vertical podem dar uma ideia de algo que se “prolonga para o lado”. Definir razões de aspecto é uma escolha totalmente dependente do projeto visual.

Sistemas cartesianos e razões de aspecto

Vejamos um exemplo de como criar representações visuais cartesianas com diferentes razões de aspecto.

Sinal estocástico

Um sinal estocástico (ou aleatório) é aquele em que nenhuma previsibilidade ou padrão são detectáveis. Comportamentos estocásticos estão presentes em eventos climáticos, ativos do mercado financeiro, sistemas eletrônicos e audiovisuais, atividades bioquímicas e em muitas outras áreas. Casos típicos buscam visualizar como uma dada grandeza varia em função do tempo.

Source
import matplotlib.pyplot as plt
import numpy as np
from string import ascii_lowercase as lc

def plot_stochastic_mosaic():
    # Configurações de dados
    n_points = 200
    x = np.arange(n_points)
    
    # Diferentes tipos de sinais estocásticos para a aula
    sinais = [
        np.random.normal(0, 1, n_points),                 # Ruído Branco
        np.cumsum(np.random.normal(0, 1, n_points)),      # Passeio Aleatório (Random Walk)
        np.random.standard_t(df=3, size=n_points),        # Caudas Pesadas (Outliers)
        np.random.normal(0, 1, n_points) * np.sin(x/10),  # Ruído com Modulação
        np.random.logistic(0, 1, n_points),               # Ruído Logístico
        np.cumsum(np.random.choice([-1, 1], n_points))    # Passo Binário
    ]

    # Definição das razões de aspecto (Ly / Lx) para o mosaico 2x3
    # Vamos variar de muito achatado a muito alto
    fatores = [0.2, 0.5, 1.0, 1.2, 1.5, 2.0]
    
    # Criando o mosaico 2x3
    # figsize total da imagem (largura, altura)
    fig, axes = plt.subplot_mosaic([['a', 'b', 'c'],
                                    ['d', 'e', 'f']], 
                                   figsize=(12, 8), constrained_layout=True)

    for i, (label, ax) in enumerate(axes.items()):
        fator = fatores[i]
        
        # Plotagem do sinal
        ax.plot(sinais[i], lw=1.0, color='#117029', alpha=0.8)
        
        # Aplicando a razão de aspecto no eixo (Physical Aspect Ratio)
        # O 'set_box_aspect' define a proporção do retângulo do gráfico
        ax.set_box_aspect(fator) 
        
        # Estética e Títulos
        ax.set_title(f'{label}) Razão de Aspecto: {fator}', fontsize=10, fontweight='bold')
        ax.set_xlabel('Tempo', fontsize=8)
        ax.set_ylabel('Amplitude', fontsize=8)
        ax.tick_params(labelsize=7)
        ax.grid(True, linestyle='--', alpha=0.5)

    plt.suptitle('Efeito da Razão de Aspecto em Sinais Estocásticos', fontsize=14, y=1.05)
    plt.show()

plot_stochastic_mosaic()
<Figure size 1200x800 with 6 Axes>

Dos casos anteriores, haja vista a natureza da informação plotada, percebe-se que nem todas possuem um visual agradável. Na sua opinião, alguma representação visual é a mais adequada? Para tomar a decisão de escolha, que critério(s) você usaria?

Série temporal

A visualização abaixo corresponde a dados reais de montantes repassados pela Petrobras ao Fundo Amazônia - BNDES entre 2012 e 2018 de acordo com o Ministério do Meio Ambiente.

Source
import pandas as pd
import seaborn as sns

# dados 
f_amaz = pd.read_csv('../data/petr-repasse-redd-amazonia.csv')
f_amaz.drop(index=0,inplace=True)
f_amaz
Loading...
Source
# visualização
sns.set_style("darkgrid")
fig, ax = plt.subplots(1,2,figsize=(10,3),constrained_layout=True)
fig.suptitle('Repasses para Fundo Amazônia - BNDES: Petrobras')

f = sns.lineplot(x='ano',y='repasse',data=f_amaz, marker='o',c='#117029',ax=ax[0])
f.set_title('Escala linear');
f.set_ylabel('repasse (R$)');

f = sns.lineplot(x='ano',y='repasse',data=f_amaz, marker='o',c='#117029',ax=ax[1])
f.set(yscale='log') # 'linear', 'log', 'symlog', 'logit', 'function', 'functionlog'
f.set_title('Escala logarítmica');
f.set_ylabel('');
<Figure size 1000x300 with 2 Axes>

Este exemplo lida com valores financeiros. No primeiro caso, a escala do eixo y é linear; no segundo, é logarítmica, isto é, não linear.

Há situações em que escalas não lineares para os eixos mostram-se mais adequadas do que escalas não lineares. Os exemplos a seguir fazem essas diferenciações.

<Figure size 1000x100 with 4 Axes>

Sistemas polares

A representação de quantidades em coordenadas polares é feita alterando o mecanismo de projeção dos eixos.

Source
r = np.arange(0, 300, 0.01)
theta = 0.5*r*np.sin(r)

# configuração do eixo
fig, ax = plt.subplots(figsize=(4,4),subplot_kw={'projection': 'polar'})
ax.plot(theta, r,c='#117029')
ax.set_rmax(10)
ax.set_title('Plotagem da função $\\theta(r) = \\frac{1}{2}r sen(r)$', va='center',fontsize=8);
<Figure size 400x400 with 1 Axes>

Exemplo: Temperatura oceânica ao longo do tempo

Sistemas polares são apropriados para visualizar a variação de temperatura oceânica ao longo do tempo em diferentes lâminas d’água. O exemplo a seguir mostra a dinâmica de temperatura ao longo de 10 anos para a temperatura de uma região do Mar Mediterrâneo. O código é uma adaptação de sua versão original, com dados obtidos do banco de dados ARGO, por meio da base de dados francesa ERDDAP.

Source
import numpy as np, pandas as pd, matplotlib, matplotlib.pyplot as plt
from matplotlib.lines import Line2D
from matplotlib.animation import FuncAnimation
from IPython.display import HTML, IFrame

plt.rcParams['xtick.major.pad']='17'
plt.rcParams["axes.axisbelow"] = False
matplotlib.rc('axes',edgecolor='w')

# Read data
ndf = pd.read_csv('../data/mediterranean-sea-temp-argo.csv')

# Range data
daterange = np.arange('2010-01-01','2020-01-03', dtype='datetime64[7D]')


# Settings
big_angle= 360/12  # polar division
date_angle=((360/365)*ndf['day_of_the_year'])*np.pi/180  # corresponding angle for each day
inner, outer = 10, 30 # inner, outer ring limit values
ocean_color = ["#008790","#004752"] # surface, depth colors 


# Utility functions
def dress_axes(ax):
    """Format polar axes for yearly variation."""
    
    ax.set_facecolor('w')
    ax.set_theta_zero_location("N")
    ax.set_theta_direction(-1)
    
    # Months labels
    middles=np.arange(big_angle/2 ,360, big_angle)*np.pi/180
    ax.set_xticks(middles)
    ax.set_xticklabels(['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'])
    ax.set_yticks([15,20,25])
    ax.set_yticklabels(['15 °C','20 °C','25 °C'])
    
    # Radial ticks angle
    ax.set_rlabel_position(359)
    ax.tick_params(axis='both',color='w')
    plt.grid(None,axis='x')
    plt.grid(axis='y',color='w', linestyle=':', linewidth=1)    
    
    # Bar plot as background

    ax.bar(middles, outer, width=big_angle*np.pi/180, bottom=inner, color='lightgray', edgecolor='w',zorder=0)
    plt.ylim([2,outer])
    
    # Custom legend
    legend_elements = [Line2D([0], [0], marker='o', color='w', label='Surface', markerfacecolor=ocean_color[0], markersize=15),
                       Line2D([0], [0], marker='o', color='w', label='1000m', markerfacecolor=ocean_color[1], markersize=15),
                       ]
    ax.legend(handles=legend_elements, loc='center', fontsize=13, frameon=False)
    
    # Main title
    plt.suptitle('Temperaturas no Mar Mediterrâneo (ARGO data)',fontsize=16,horizontalalignment='center')
    
    
# Open figure
fig = plt.figure(figsize=(8,8))
ax = fig.add_subplot(111, polar=True)
dress_axes(ax)

# For animation with blit=True, to render only changes
line_ts = ax.plot([], [], '-', color=ocean_color[0], alpha=1.0, linewidth=5)[0]
line_t1000 = ax.plot([], [], '-', color=ocean_color[1], alpha=1.0, linewidth=5)[0]
line_ts_fine = ax.plot([], [], '-', color=ocean_color[0], linewidth=0.7)[0]
line_t1000_fine = ax.plot([], [], '-', color=ocean_color[1], linewidth=0.7)[0]
line_ref = ax.plot([], [], 'k-', linewidth=0.5)[0]

# Update function for animation
def draw_data(i):
    
    # Limit between thin lines and thick line: current date minus 51 weeks
    i0 = np.max([i - 51, 0])

    # Update the data for the thick lines (full range)
    line_ts.set_data(date_angle[i0:i+1], ndf['tsurf'][i0:i+1])
    line_t1000.set_data(date_angle[i0:i+1], ndf['t1000'][i0:i+1])

    # Update the data for the fine lines (detailed)
    line_ts_fine.set_data(date_angle[0:i+1], ndf['tsurf'][0:i+1])
    line_t1000_fine.set_data(date_angle[0:i+1], ndf['t1000'][0:i+1])

    # Update the reference line (vertical line indicating current time)
    line_ref.set_data([date_angle[i], date_angle[i]], [inner, outer])

    # Update title with current year
    ax.set_title(str(ndf['active_year'][i]), fontsize=16, horizontalalignment='center')

    return line_ts, line_t1000, line_ts_fine, line_t1000_fine, line_ref
    
# Test it
# draw_data(322); plt.show()    
plt.close(fig)

# REMARK: for the animation, ffmpeg is required ('brew install ffmpeg')
# Aumente 'interval' para mais resolução
# anim = FuncAnimation(fig, draw_data, interval=20, frames=len(daterange)-1, repeat=False, blit=True)    
# video_html = anim.to_html5_video()
# HTML(f'<div style="width:400px; height:400px;">{video_html}</div>')
Loading...

Sistemas geoespaciais

Sistemas geoespaciais são úteis para plotagem de dados cartográficos, mapas e geolocalizações.

Source
import matplotlib.pyplot as plt
import cartopy.crs as ccrs

plt.figure(figsize=(3.5, 3))
ax = plt.axes(projection=ccrs.Mercator())
ax.coastlines()
ax.gridlines();
<Figure size 350x300 with 1 Axes>

Visualizações 3D

Visualizações bidimensionais são sempre preferíveis a tridimensionais devido à carga cognitiva exigida para interpretação da informação em 3D. Entretanto, nem sempre é possível atingir representações visuais satisfatórias com apenas 2 dimensões, especialmente quando a terceira dimensão possui variações. Este é o caso de uma superfície descrita como z=f(x,y)z = f(x,y) em que zz é uma elevação de terreno, por exemplo. Eixos 3D são particularmente excelentes quando se quer interagir com ou manipular o visual.

Visualização de funções matemáticas

O exemplo abaixo mostra como plotar a função dada por f(x,y)=4e [(xa+b2)2+ (yc+d2)2],f(x,y) = 4e^{ - \ \left[ \left( x - \frac{a+b}{2} \right)^2 + \ \left( y - \frac{c+d}{2} \right)^2 \right] }, para o domínio bidimensional [a,b]×[c,d][a,b] \times [c,d]. Esta função, em sentido genérico, poderia bem representar uma pequena montanha, um tipo de chapéu, ou mesmo uma protuberância, tal qual um puxão na pele ou uma ectasia corneana. Independentemente da aplicação, as representações visuais fornecidas forneceriam diferentes posto de vista para o fenômeno, ajudando o viewer a interpretá-lo melhor.

Source
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm

# limites do domínio: região do plano [a,b] x [c,d]
a, b = -2, 2
c, d = -1, 3

# no. de pontos em cada direção
nx, ny = 40, 40 

# distribuição dos pontos
x = np.linspace(a,b,nx)
y = np.linspace(c,d,ny)

# grade 2D
[X,Y] = np.meshgrid(x,y)

# superfíe
Z = 4*np.exp(-( ( X - (a+b)/2 )**2 + ( Y - (c+d)/2 )**2 ))

# plotagem
fig = plt.figure(figsize=(10,4),constrained_layout=True)
fig.suptitle('$f(x,y) = 4e^{ - \
     \\left[ \\left( x -  \\frac{a+b}{2} \\right)^2  + \
             \\left( y -  \\frac{c+d}{2} \\right)^2 \\right] }$')

gs = fig.add_gridspec(1,3) 
ax1 = fig.add_subplot(gs[0],projection='3d')
ax2 = fig.add_subplot(gs[1],projection='3d')
ax3 = fig.add_subplot(gs[2])

# superfície
ax1.set_zlim(0,5)
s = ax1.plot_surface(X,Y,Z,cmap=cm.jet)
fig.colorbar(s, shrink=0.5,ax=ax1)
ax1.set_title('Superfície',fontsize=8)

# aramado
ax2.set_zlim(0,5)
s = ax2.plot_wireframe(X,Y,Z,cmap=cm.jet,lw=0.4)
ax2.axis('off')
ax2.set_title('Superfície aramada',fontsize=8)

# curvas de nível com preenchimento
ax3.contourf(X,Y,Z,cmap=cm.jet)
ax3.axis([a,b,c,d])
ax3.set_title('Curvas de nível',fontsize=8);
<Figure size 1000x400 with 4 Axes>

Visualização de dados multidimensionais categóricos

A representação visual abaixo lida com os preços de combustíveis aplicados em diversos municípios da Paraíba por diferentes operadoras (bandeiras).

Loading...