Eixo Secundário do Matplotlib

Beginner

This tutorial is from open-source community. Access the source code

Introdução

Matplotlib é uma biblioteca popular de visualização de dados em Python. Às vezes, precisamos plotar duas escalas diferentes de dados no mesmo gráfico, e é nesse momento que o conceito de um eixo secundário entra em jogo. Neste laboratório, aprenderemos como criar um eixo secundário em Matplotlib.

Dicas para a VM

Após a inicialização da VM, clique no canto superior esquerdo para mudar para a aba Notebook e acessar o Jupyter Notebook para praticar.

Às vezes, pode ser necessário aguardar alguns segundos para que o Jupyter Notebook termine de carregar. A validação das operações não pode ser automatizada devido às limitações do Jupyter Notebook.

Se você enfrentar problemas durante o aprendizado, sinta-se à vontade para perguntar ao Labby. Forneça feedback após a sessão, e resolveremos o problema prontamente para você.

Importar as bibliotecas necessárias

Começaremos importando as bibliotecas necessárias, que são matplotlib, numpy e datetime.

import datetime
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.dates as mdates
from matplotlib.ticker import AutoMinorLocator

Plotar os dados

Criaremos uma onda senoidal simples para demonstrar o uso de um eixo secundário. Plotaremos a onda senoidal usando graus como o eixo x.

fig, ax = plt.subplots(layout='constrained')
x = np.arange(0, 360, 1)
y = np.sin(2 * x * np.pi / 180)
ax.plot(x, y)
ax.set_xlabel('angle [degrees]')
ax.set_ylabel('signal')
ax.set_title('Sine wave')

Criar o Eixo Secundário

Agora criaremos o eixo secundário e converteremos o eixo x de graus para radianos. Usaremos deg2rad como a função direta (forward function) e rad2deg como a função inversa (inverse function).

def deg2rad(x):
    return x * np.pi / 180

def rad2deg(x):
    return x * 180 / np.pi

secax = ax.secondary_xaxis('top', functions=(deg2rad, rad2deg))
secax.set_xlabel('angle [rad]')

Plotar outro exemplo

Agora plotaremos outro exemplo de conversão de número de onda (wavenumber) para comprimento de onda (wavelength) em uma escala log-log. Usaremos um espectro aleatório para este exemplo.

fig, ax = plt.subplots(layout='constrained')
x = np.arange(0.02, 1, 0.02)
np.random.seed(19680801)
y = np.random.randn(len(x)) ** 2
ax.loglog(x, y)
ax.set_xlabel('f [Hz]')
ax.set_ylabel('PSD')
ax.set_title('Random spectrum')

Criar o Eixo X Secundário

Criaremos o eixo x secundário e converteremos de frequência para período. Usaremos one_over como a função direta (forward function) e inverse como a função inversa (inverse function).

def one_over(x):
    """Vectorized 1/x, treating x==0 manually"""
    x = np.array(x, float)
    near_zero = np.isclose(x, 0)
    x[near_zero] = np.inf
    x[~near_zero] = 1 / x[~near_zero]
    return x

## the function "1/x" is its own inverse
inverse = one_over

secax = ax.secondary_xaxis('top', functions=(one_over, inverse))
secax.set_xlabel('period [s]')

Criar o Eixo Y Secundário

Criaremos um terceiro exemplo de relacionar os eixos em uma transformação que é ad-hoc a partir dos dados, e é derivada empiricamente. Neste caso, definiremos as funções de transformação direta (forward) e inversa (inverse) para serem interpolações lineares de um conjunto de dados para o outro.

fig, ax = plt.subplots(layout='constrained')
xdata = np.arange(1, 11, 0.4)
ydata = np.random.randn(len(xdata))
ax.plot(xdata, ydata, label='Plotted data')

xold = np.arange(0, 11, 0.2)
## fake data set relating x coordinate to another data-derived coordinate.
## xnew must be monotonic, so we sort...
xnew = np.sort(10 * np.exp(-xold / 4) + np.random.randn(len(xold)) / 3)

ax.plot(xold[3:], xnew[3:], label='Transform data')
ax.set_xlabel('X [m]')
ax.legend()

def forward(x):
    return np.interp(x, xold, xnew)

def inverse(x):
    return np.interp(x, xnew, xold)

secax = ax.secondary_xaxis('top', functions=(forward, inverse))
secax.xaxis.set_minor_locator(AutoMinorLocator())
secax.set_xlabel('$X_{other}$')

Criar Múltiplos Eixos

Agora criaremos um exemplo final que traduz np.datetime64 para o dia do ano (yearday) no eixo x e de Celsius para Fahrenheit no eixo y. Também adicionaremos um terceiro eixo y e o posicionaremos usando um float para o argumento de localização.

dates = [datetime.datetime(2018, 1, 1) + datetime.timedelta(hours=k * 6)
         for k in range(240)]
temperature = np.random.randn(len(dates)) * 4 + 6.7
fig, ax = plt.subplots(layout='constrained')

ax.plot(dates, temperature)
ax.set_ylabel(r'$T\ [^oC]$')
plt.xticks(rotation=70)

def date2yday(x):
    """Convert matplotlib datenum to days since 2018-01-01."""
    y = x - mdates.date2num(datetime.datetime(2018, 1, 1))
    return y

def yday2date(x):
    """Return a matplotlib datenum for *x* days after 2018-01-01."""
    y = x + mdates.date2num(datetime.datetime(2018, 1, 1))
    return y

secax_x = ax.secondary_xaxis('top', functions=(date2yday, yday2date))
secax_x.set_xlabel('yday [2018]')

def celsius_to_fahrenheit(x):
    return x * 1.8 + 32

def fahrenheit_to_celsius(x):
    return (x - 32) / 1.8

secax_y = ax.secondary_yaxis(
    'right', functions=(celsius_to_fahrenheit, fahrenheit_to_celsius))
secax_y.set_ylabel(r'$T\ [^oF]$')

def celsius_to_anomaly(x):
    return (x - np.mean(temperature))

def anomaly_to_celsius(x):
    return (x + np.mean(temperature))

## use of a float for the position:
secax_y2 = ax.secondary_yaxis(
    1.2, functions=(celsius_to_anomaly, anomaly_to_celsius))
secax_y2.set_ylabel(r'$T - \overline{T}\ [^oC]$')

Resumo

Neste laboratório, aprendemos como criar um eixo secundário em Matplotlib. Usamos vários exemplos para demonstrar o conceito de um eixo secundário e como criá-lo.