Skip to content

    Chapter 39, Ecosystem

    NumPy and Matplotlib

    A graph is the quickest way to find out that your data is not what you thought. Matplotlib takes arrays and draws, and returns the image, which in turn is also an array.

    Code for this chapter: ecossistema/cap39_numpy_matplotlib.py

    Draw without opening a window

    In a script, on a server, or in a test, there is no screen. Matplotlib has a backend without a window, the Agg, which draws in memory and saves to a file. Choose it before importing the pyplot. In a notebook, the graphs appear automatically below the cell, and you don't need this line:

    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)
    

    I always use the object-oriented (fig, ax = plt.subplots() interface and then ax.plot(...)), and not the one that depends on a global "current graph" (plt.plot(...)): it becomes clear in which graph each command acts, and the code works in functions and in multiple graphs at the same time. And the plt.close(fig) frees memory, which matters when you generate hundreds of figures in a loop.

    The saved graph is a tensor

    Reading the image back returns an array of shape (altura, largura, 4), with the red, green, blue, and transparency channels. The size is that of the figure multiplied by the points per inch (4 × 100 for 3 × 100):

    ecossistema/cap39_numpy_matplotlib.pylinhas 27 a 28
    imagem = plt.imread("ondas.png")
    print(imagem.shape, imagem.dtype)
    
    Output
    (300, 400, 4) float32
    

    It's the tensor from chapter 17 in action: an image is an array, and everything you learned (slicing, averaging a channel, comparing two images) works on it.

    The `hist` uses `np.histogram`

    Matplotlib does not reinvent the calculations. The ax.hist calls the np.histogram from chapter 22 and draws the result, and it also returns the counts for you to use:

    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)
    
    Output
    True
    

    A 2D array as an image

    The imshow draws a matrix as an image, with a color scale. Combined with the meshgrid from chapter 27, it shows a function of two variables. Here, a Gaussian "mountain", with the peak in the center:

    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))
    
    Output
    (100, 100) 1.0
    

    The parameter origin="lower" puts line 0 at the bottom, like in a graph, and not at the top, like in an image. This is a detail that confuses many: images index from top to bottom, and graphs from bottom to top.

    Save a matrix directly as an image

    The plt.imsave converts an array into an image file without creating a figure, with the color scale of your choice:

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

    Cleanup

    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())
    
    Output
    False
    
    I wantUse
    A series over timeax.plot(x, y)
    The distribution of valuesax.hist(dados, bins=...)
    Two variables togetherax.scatter(x, y)
    A matrix or imageax.imshow(matriz)
    A function of two variablesmeshgrid + imshow or contourf
    Save without windowmatplotlib.use("Agg") + fig.savefig(...)

    Exercise 1

    Save a graph and check

    Write salvar_grafico(x, y, caminho) that draws the line, saves in caminho, closes the figure and returns the shape of the saved image. With the default figure (6.4 by 4.8 inches and 100 dots per inch), the shape should be (480, 640, 4).