Pular para o conteúdo

    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:

    Terminal
    uv sync --group ecossistema
    
    ecossistema/cap39_numpy_matplotlib.pylinhas 10 a 22
    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):

    ecossistema/cap39_numpy_matplotlib.pylinhas 27 a 28
    imagem = plt.imread("ondas.png")
    print(imagem.shape, imagem.dtype)
    
    Saída
    (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:

    ecossistema/cap39_numpy_matplotlib.pylinhas 33 a 38
    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)
    
    Saída
    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:

    ecossistema/cap39_numpy_matplotlib.pylinhas 43 a 51
    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))
    
    Saída
    (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:

    ecossistema/cap39_numpy_matplotlib.pylinhas 56 a 57
    plt.imsave("cinza.png", Z, cmap="gray")
    print(plt.imread("cinza.png").shape)
    
    Saída
    (100, 100, 4)
    

    Limpeza

    ecossistema/cap39_numpy_matplotlib.pylinhas 62 a 66
    from pathlib import Path
    
    for nome in ("ondas.png", "montanha.png", "cinza.png"):
        Path(nome).unlink()
    print(Path("ondas.png").exists())
    
    Saída
    False
    
    QueroUso
    Uma série ao longo do tempoax.plot(x, y)
    A distribuição de valoresax.hist(dados, bins=...)
    Duas variáveis juntasax.scatter(x, y)
    Uma matriz ou imagemax.imshow(matriz)
    Uma função de duas variáveismeshgrid + imshow ou contourf
    Salvar sem janelamatplotlib.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).