Capítulo 39, Ecossistema
NumPy e Matplotlib
Um gráfico é a forma mais rápida de descobrir que os seus dados não são o que você pensava. O Matplotlib recebe arrays e desenha, e devolve a imagem, que por sua vez também é um array.
Código deste capítulo: ecossistema/cap39_numpy_matplotlib.py
Desenhar sem abrir janela
Em um script, em um servidor ou em um teste, não existe tela. O Matplotlib tem um backend sem janela, o Agg, que desenha em memória e salva em arquivo. Escolha-o antes de importar o pyplot. Em um notebook, os gráficos aparecem sozinhos abaixo da célula, e você não precisa dessa linha:
uv sync --group ecossistema
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 2 * np.pi, 200)
fig, ax = plt.subplots(figsize=(4, 3), dpi=100)
ax.plot(x, np.sin(x), label="seno")
ax.plot(x, np.cos(x), label="cosseno")
ax.legend()
fig.savefig("ondas.png")
plt.close(fig)
Eu uso sempre a interface orientada a objetos (fig, ax = plt.subplots() e depois ax.plot(...)), e não a que depende de um "gráfico atual" global (plt.plot(...)): fica claro em qual gráfico cada comando age, e o código funciona em funções e em vários gráficos ao mesmo tempo. E o plt.close(fig) libera a memória, o que importa quando você gera centenas de figuras em um laço.
O gráfico salvo é um tensor
Ler a imagem de volta devolve um array de forma (altura, largura, 4), com os canais vermelho, verde, azul e transparência. O tamanho é o da figura multiplicado pelos pontos por polegada (4 × 100 por 3 × 100):
imagem = plt.imread("ondas.png")
print(imagem.shape, imagem.dtype)
(300, 400, 4) float32
É o tensor do capítulo 17 em ação: uma imagem é um array, e tudo o que você aprendeu (fatiar, tirar a média de um canal, comparar duas imagens) funciona sobre ela.
O `hist` usa o `np.histogram`
O Matplotlib não reinventa as contas. O ax.hist chama o np.histogram do capítulo 22 e desenha o resultado, e ele devolve também as contagens, para você usar:
dados = np.random.default_rng(0).normal(size=1000)
fig, ax = plt.subplots()
contagens, limites, _ = ax.hist(dados, bins=10)
esperado, _ = np.histogram(dados, bins=10)
print(np.array_equal(contagens, esperado))
plt.close(fig)
True
Um array 2D como imagem
O imshow desenha uma matriz como uma imagem, com uma escala de cores. Combinado com o meshgrid do capítulo 27, ele mostra uma função de duas variáveis. Aqui, uma "montanha" gaussiana, com o pico no centro:
X, Y = np.meshgrid(np.linspace(-2, 2, 100), np.linspace(-2, 2, 100))
Z = np.exp(-(X ** 2 + Y ** 2))
fig, ax = plt.subplots()
imagem_z = ax.imshow(Z, extent=(-2, 2, -2, 2), origin="lower")
fig.colorbar(imagem_z)
fig.savefig("montanha.png")
plt.close(fig)
print(Z.shape, round(float(Z.max()), 2))
(100, 100) 1.0
O parâmetro origin="lower" põe a linha 0 embaixo, como em um gráfico, e não no topo, como em uma imagem. Esse é um detalhe que confunde muito: imagens indexam de cima para baixo, e gráficos de baixo para cima.
Gravar uma matriz direto como imagem
O plt.imsave converte um array em arquivo de imagem sem criar uma figura, com a escala de cores que você escolher:
plt.imsave("cinza.png", Z, cmap="gray")
print(plt.imread("cinza.png").shape)
(100, 100, 4)
Limpeza
from pathlib import Path
for nome in ("ondas.png", "montanha.png", "cinza.png"):
Path(nome).unlink()
print(Path("ondas.png").exists())
False
| Quero | Uso |
|---|---|
| Uma série ao longo do tempo | ax.plot(x, y) |
| A distribuição de valores | ax.hist(dados, bins=...) |
| Duas variáveis juntas | ax.scatter(x, y) |
| Uma matriz ou imagem | ax.imshow(matriz) |
| Uma função de duas variáveis | meshgrid + imshow ou contourf |
| Salvar sem janela | matplotlib.use("Agg") + fig.savefig(...) |
Exercício 1
Salvar um gráfico e conferir
Escreva salvar_grafico(x, y, caminho) que desenhe a linha, salve em caminho, feche a figura e devolva a forma da imagem salva. Com a figura padrão (6,4 por 4,8 polegadas e 100 pontos por polegada), a forma deve ser (480, 640, 4).