Análise de Dados

Matplotlib na prática: como criar gráficos com Pandas e dados reais

Na primeira parte desta série, usamos a biblioteca Requests para consultar uma API. Na segunda, transformamos a resposta em um DataFrame e aprendemos a selecionar, filtrar e resumir os dados com Pandas.

Visualizações de dados de qualidade do ar criadas com Pandas e Matplotlib

Na primeira parte desta série, usamos a biblioteca Requests para consultar uma API. Na segunda, transformamos a resposta em um DataFrame e aprendemos a selecionar, filtrar e resumir os dados com Pandas.

Como consumir uma API com Python usando Requests: guia prático

Pandas na prática: como criar e manipular um DataFrame com dados reais

Agora, vamos usar esse mesmo DataFrame para criar visualizações com Matplotlib.

O foco deste artigo será exclusivamente a construção de gráficos. Partiremos de dados já organizados e aprenderemos a criar:

  • gráficos de linha;
  • gráficos de barras;
  • histogramas;
  • gráficos de dispersão;
  • figuras com mais de um gráfico.

Também veremos como adicionar títulos, rótulos, legendas e grades, formatar datas e salvar as figuras como imagens.

Preparando o ambiente#

No projeto criado nas partes anteriores, adicione o Matplotlib:

BASH
uv add matplotlib

Para executar o projeto:

BASH
uv run main.py

O uv run executa o programa no ambiente do projeto e verifica previamente se as dependências e o arquivo de bloqueio estão sincronizados. ([Astral Docs][1])

No Google Colab, use uma célula de instalação:

PYTHON
%pip install -q matplotlib pandas requests

Em seguida, importe as bibliotecas:

PYTHON
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
import pandas as pd
import requests

O módulo matplotlib.pyplot será usado para criar as figuras. O módulo matplotlib.dates ajudará a controlar a apresentação das datas no eixo horizontal.

Preparando os dados#

O código abaixo repete apenas a preparação necessária para reconstruir o DataFrame utilizado na parte anterior:

PYTHON
API_URL = "https://air-quality-api.open-meteo.com/v1/air-quality"

PARAMETROS = {
    "latitude": -23.5505,
    "longitude": -46.6333,
    "hourly": (
        "european_aqi,"
        "pm10,"
        "pm2_5,"
        "nitrogen_dioxide,"
        "ozone"
    ),
    "forecast_days": 3,
    "timezone": "America/Sao_Paulo",
}

resposta = requests.get(
    API_URL,
    params=PARAMETROS,
    timeout=30,
)

resposta.raise_for_status()

dados = resposta.json()

df = pd.DataFrame(dados["hourly"])

df["time"] = pd.to_datetime(
    df["time"]
)

Confira as primeiras linhas:

PYTHON
df.head()

Cada linha representa um horário da previsão. As colunas contêm o horário, o índice europeu de qualidade do ar e as concentrações dos poluentes solicitados.

A Air Quality API permite solicitar séries horárias e até sete dias de previsão. Os dados de PM10, PM2.5, dióxido de nitrogênio e ozônio são expressos em microgramas por metro cúbico. ([Open Meteo][2])

Os valores são provenientes de modelos atmosféricos do CAMS. Para uma localização global como São Paulo, a fonte possui resolução espacial aproximada de 45 quilômetros e resolução temporal nativa de três horas. Portanto, os dados não representam necessariamente medições feitas por uma estação instalada exatamente nas coordenadas informadas. ([Open Meteo][2])

A partir daqui, todos os exemplos utilizarão o DataFrame armazenado em df.

Figure e Axes#

Um gráfico criado com Matplotlib normalmente envolve dois objetos principais:

PYTHON
fig, ax = plt.subplots()

A variável fig recebe um objeto Figure, que representa a figura completa.

A variável ax recebe um objeto Axes, que representa a área em que os dados serão desenhados. É por meio do Axes que adicionamos linhas, barras, títulos, nomes dos eixos, legendas e grades. ([Matplotlib][3])

Podemos verificar os tipos:

PYTHON
print(type(fig))
print(type(ax))

A saída será semelhante a:

TEXT
<class 'matplotlib.figure.Figure'>
<class 'matplotlib.axes._axes.Axes'>

Para exibir a figura:

PYTHON
plt.show()

O exemplo completo fica assim:

PYTHON
fig, ax = plt.subplots()

plt.show()

Nesse momento, será exibida apenas uma área vazia. Ainda precisamos adicionar os dados ao objeto ax.

Criando um gráfico de linha#

O gráfico de linha é adequado para representar valores ordenados no tempo.

Vamos visualizar a evolução do PM2.5 durante o período consultado:

PYTHON
fig, ax = plt.subplots(
    figsize=(11, 5),
    layout="constrained",
)

ax.plot(
    df["time"],
    df["pm2_5"],
)

plt.show()

No método ax.plot(), o primeiro argumento contém os valores do eixo horizontal e o segundo contém os valores do eixo vertical.

PYTHON
ax.plot(
    valores_x,
    valores_y,
)

A documentação do Matplotlib define Axes.plot() como o método usado para representar valores de y em função de x usando linhas e, opcionalmente, marcadores. ([Matplotlib][4])

O argumento figsize define o tamanho da figura em polegadas:

PYTHON
figsize=(11, 5)

O primeiro número representa a largura e o segundo, a altura.

O argumento:

PYTHON
layout="constrained"

solicita que o Matplotlib ajuste o espaço ocupado por títulos, rótulos e outros elementos para reduzir sobreposições.

Adicionando título e rótulos#

Um gráfico deve informar claramente o que está sendo representado.

Primeiro, recuperamos a unidade do PM2.5 diretamente da resposta da API:

PYTHON
unidade_pm25 = (
    dados["hourly_units"]["pm2_5"]
)

Agora podemos adicionar o título e os nomes dos eixos:

PYTHON
fig, ax = plt.subplots(
    figsize=(11, 5),
    layout="constrained",
)

ax.plot(
    df["time"],
    df["pm2_5"],
)

ax.set_title(
    "Previsão horária de PM2.5 em São Paulo"
)

ax.set_xlabel("Data e hora")

ax.set_ylabel(
    f"PM2.5 ({unidade_pm25})"
)

plt.show()

Os métodos utilizados foram:

PYTHON
ax.set_title()
ax.set_xlabel()
ax.set_ylabel()

O título descreve o conteúdo da figura. Os rótulos informam o significado e, quando necessário, a unidade de cada eixo.

Formatando as datas#

O Matplotlib consegue trabalhar com a coluna time porque ela foi convertida para um tipo de data e hora do Pandas.

Entretanto, a exibição automática pode produzir muitos rótulos ou datas difíceis de ler. Podemos controlar onde os marcadores aparecem e como são formatados:

PYTHON
fig, ax = plt.subplots(
    figsize=(11, 5),
    layout="constrained",
)

ax.plot(
    df["time"],
    df["pm2_5"],
)

ax.set_title(
    "Previsão horária de PM2.5 em São Paulo"
)

ax.set_xlabel("Data e hora")

ax.set_ylabel(
    f"PM2.5 ({unidade_pm25})"
)

ax.xaxis.set_major_locator(
    mdates.HourLocator(interval=12)
)

ax.xaxis.set_major_formatter(
    mdates.DateFormatter(
        "%d/%m\n%H:%M"
    )
)

plt.show()

O localizador determina onde os marcadores principais serão colocados:

PYTHON
mdates.HourLocator(interval=12)

Nesse caso, exibimos um marcador a cada 12 horas.

O formatador controla a aparência dos rótulos:

PYTHON
mdates.DateFormatter(
    "%d/%m\n%H:%M"
)

Os códigos utilizados representam:

TEXT
%d    dia
%m    mês
%H    hora
%M    minuto

O caractere \n quebra o rótulo em duas linhas. Assim, a data aparece acima do horário.

Adicionando marcadores e grade#

Os pontos individuais da série podem ser destacados com marcadores:

PYTHON
fig, ax = plt.subplots(
    figsize=(11, 5),
    layout="constrained",
)

ax.plot(
    df["time"],
    df["pm2_5"],
    marker="o",
    markersize=3,
    linewidth=1.5,
)

ax.set_title(
    "Previsão horária de PM2.5 em São Paulo"
)

ax.set_xlabel("Data e hora")

ax.set_ylabel(
    f"PM2.5 ({unidade_pm25})"
)

ax.grid(
    visible=True,
    alpha=0.3,
)

ax.xaxis.set_major_locator(
    mdates.HourLocator(interval=12)
)

ax.xaxis.set_major_formatter(
    mdates.DateFormatter(
        "%d/%m\n%H:%M"
    )
)

plt.show()

O argumento:

PYTHON
marker="o"

adiciona um marcador circular em cada observação.

O tamanho dos marcadores é controlado por:

PYTHON
markersize=3

A espessura da linha é definida por:

PYTHON
linewidth=1.5

A grade é adicionada com:

PYTHON
ax.grid()

O parâmetro alpha controla a transparência. Valores próximos de zero deixam o elemento mais transparente, enquanto valores próximos de um o deixam mais opaco.

Comparando duas séries#

É possível adicionar várias linhas ao mesmo Axes.

PM10 e PM2.5 possuem a mesma unidade, portanto podem ser comparados em um único eixo vertical:

PYTHON
unidade_pm = (
    dados["hourly_units"]["pm10"]
)
PYTHON
fig, ax = plt.subplots(
    figsize=(11, 5),
    layout="constrained",
)

ax.plot(
    df["time"],
    df["pm10"],
    label="PM10",
)

ax.plot(
    df["time"],
    df["pm2_5"],
    label="PM2.5",
)

ax.set_title(
    "Previsão horária de material particulado"
)

ax.set_xlabel("Data e hora")

ax.set_ylabel(
    f"Concentração ({unidade_pm})"
)

ax.legend()
ax.grid(alpha=0.3)

ax.xaxis.set_major_locator(
    mdates.HourLocator(interval=12)
)

ax.xaxis.set_major_formatter(
    mdates.DateFormatter(
        "%d/%m\n%H:%M"
    )
)

plt.show()

Cada chamada a ax.plot() adiciona uma nova série ao gráfico.

O argumento label define o nome da série:

PYTHON
label="PM10"

A legenda é exibida com:

PYTHON
ax.legend()

A documentação do Matplotlib também recomenda chamadas separadas a plot() como uma forma direta de representar vários conjuntos de dados. ([Matplotlib][4])

Adicionar várias séries ao mesmo gráfico só é adequado quando a comparação faz sentido. Variáveis com unidades ou escalas muito diferentes podem produzir uma visualização confusa.

Destacando a média#

Na parte anterior, usamos o Pandas para calcular a média. Agora, podemos representar esse valor visualmente.

PYTHON
media_pm25 = df["pm2_5"].mean()

O método ax.axhline() adiciona uma linha horizontal:

PYTHON
fig, ax = plt.subplots(
    figsize=(11, 5),
    layout="constrained",
)

ax.plot(
    df["time"],
    df["pm2_5"],
    label="PM2.5",
)

ax.axhline(
    y=media_pm25,
    linestyle="--",
    label=f"Média: {media_pm25:.2f}",
)

ax.set_title(
    "PM2.5 e média do período"
)

ax.set_xlabel("Data e hora")

ax.set_ylabel(
    f"PM2.5 ({unidade_pm25})"
)

ax.legend()
ax.grid(alpha=0.3)

ax.xaxis.set_major_locator(
    mdates.HourLocator(interval=12)
)

ax.xaxis.set_major_formatter(
    mdates.DateFormatter(
        "%d/%m\n%H:%M"
    )
)

plt.show()

O argumento:

PYTHON
y=media_pm25

define a posição vertical da linha.

O estilo tracejado é definido com:

PYTHON
linestyle="--"

Na legenda:

PYTHON
f"Média: {media_pm25:.2f}"

o especificador :.2f exibe o valor com duas casas decimais.

Criando um gráfico de barras#

Gráficos de barras são úteis para comparar categorias ou períodos discretos.

Como o DataFrame possui muitas observações horárias, vamos calcular a média diária do PM2.5:

PYTHON
df["data"] = df["time"].dt.date

media_diaria = (
    df.groupby(
        "data",
        as_index=False,
    )["pm2_5"]
    .mean()
)

Confira o resultado:

PYTHON
media_diaria

Agora, crie o gráfico:

PYTHON
fig, ax = plt.subplots(
    figsize=(9, 5),
    layout="constrained",
)

ax.bar(
    media_diaria["data"].astype(str),
    media_diaria["pm2_5"],
)

ax.set_title(
    "Média diária de PM2.5"
)

ax.set_xlabel("Data")

ax.set_ylabel(
    f"PM2.5 médio ({unidade_pm25})"
)

ax.grid(
    axis="y",
    alpha=0.3,
)

plt.show()

O método:

PYTHON
ax.bar()

recebe as categorias no eixo horizontal e os respectivos valores no eixo vertical.

Neste exemplo, as datas foram convertidas para texto:

PYTHON
media_diaria["data"].astype(str)

A grade foi aplicada apenas ao eixo vertical:

PYTHON
ax.grid(
    axis="y",
    alpha=0.3,
)

Essa configuração ajuda a comparar as alturas das barras sem adicionar linhas verticais desnecessárias.

Criando um histograma#

Um histograma representa a distribuição de uma variável numérica. Em vez de mostrar cada observação em ordem temporal, ele agrupa os valores em intervalos.

PYTHON
fig, ax = plt.subplots(
    figsize=(9, 5),
    layout="constrained",
)

ax.hist(
    df["pm2_5"].dropna(),
    bins=10,
)

ax.set_title(
    "Distribuição dos valores de PM2.5"
)

ax.set_xlabel(
    f"PM2.5 ({unidade_pm25})"
)

ax.set_ylabel("Frequência")

ax.grid(
    axis="y",
    alpha=0.3,
)

plt.show()

O método:

PYTHON
ax.hist()

cria o histograma.

O argumento:

PYTHON
bins=10

divide os dados em dez intervalos.

O eixo horizontal representa os valores de PM2.5. O eixo vertical mostra quantas observações foram encontradas em cada intervalo.

Usamos:

PYTHON
df["pm2_5"].dropna()

para evitar o envio de valores ausentes ao gráfico.

A quantidade de intervalos influencia a aparência da distribuição. Poucos intervalos produzem uma visão mais resumida; muitos intervalos mostram mais detalhes, mas podem tornar o gráfico fragmentado.

Criando um gráfico de dispersão#

O gráfico de dispersão é usado para observar a relação entre duas variáveis numéricas.

Vamos comparar PM2.5 e PM10:

PYTHON
fig, ax = plt.subplots(
    figsize=(8, 6),
    layout="constrained",
)

ax.scatter(
    df["pm2_5"],
    df["pm10"],
    alpha=0.7,
)

ax.set_title(
    "Relação entre PM2.5 e PM10"
)

ax.set_xlabel(
    f"PM2.5 ({unidade_pm25})"
)

ax.set_ylabel(
    f"PM10 ({unidade_pm})"
)

ax.grid(alpha=0.3)

plt.show()

Cada ponto representa uma linha do DataFrame.

A posição horizontal é determinada pelo PM2.5:

PYTHON
df["pm2_5"]

A posição vertical é determinada pelo PM10:

PYTHON
df["pm10"]

O gráfico permite observar se valores maiores de uma variável tendem a aparecer junto de valores maiores ou menores da outra.

Entretanto, a aparência de uma relação no gráfico não comprova causalidade. Uma análise estatística mais rigorosa seria necessária para quantificar a associação entre as variáveis.

Usando uma terceira variável#

O tamanho de cada ponto pode representar uma terceira variável.

Vamos utilizar o índice europeu de qualidade do ar:

PYTHON
fig, ax = plt.subplots(
    figsize=(8, 6),
    layout="constrained",
)

ax.scatter(
    df["pm2_5"],
    df["pm10"],
    s=df["european_aqi"] * 2,
    alpha=0.5,
)

ax.set_title(
    "PM2.5, PM10 e índice europeu"
)

ax.set_xlabel(
    f"PM2.5 ({unidade_pm25})"
)

ax.set_ylabel(
    f"PM10 ({unidade_pm})"
)

ax.grid(alpha=0.3)

plt.show()

O argumento:

PYTHON
s=df["european_aqi"] * 2

define o tamanho dos pontos.

A multiplicação por dois é apenas um ajuste visual. Ela aumenta os marcadores sem modificar os dados armazenados no DataFrame.

Quando o tamanho representa uma variável, essa informação deve ser explicada no título, na legenda ou no texto que acompanha o gráfico.

Criando mais de um gráfico na mesma figura#

Até agora, cada figura continha apenas um Axes.

Também podemos criar uma figura com vários gráficos:

PYTHON
fig, axes = plt.subplots(
    nrows=2,
    ncols=1,
    figsize=(11, 8),
    sharex=True,
    layout="constrained",
)

Nesse caso, axes contém dois objetos:

PYTHON
axes[0]
axes[1]

Vamos representar PM2.5 e ozônio separadamente:

PYTHON
unidade_ozonio = (
    dados["hourly_units"]["ozone"]
)
PYTHON
fig, axes = plt.subplots(
    nrows=2,
    ncols=1,
    figsize=(11, 8),
    sharex=True,
    layout="constrained",
)

axes[0].plot(
    df["time"],
    df["pm2_5"],
)

axes[0].set_title("PM2.5")

axes[0].set_ylabel(
    unidade_pm25
)

axes[0].grid(alpha=0.3)

axes[1].plot(
    df["time"],
    df["ozone"],
)

axes[1].set_title("Ozônio")

axes[1].set_xlabel("Data e hora")

axes[1].set_ylabel(
    unidade_ozonio
)

axes[1].grid(alpha=0.3)

axes[1].xaxis.set_major_locator(
    mdates.HourLocator(interval=12)
)

axes[1].xaxis.set_major_formatter(
    mdates.DateFormatter(
        "%d/%m\n%H:%M"
    )
)

plt.show()

Os argumentos:

PYTHON
nrows=2
ncols=1

criam duas áreas organizadas verticalmente.

O argumento:

PYTHON
sharex=True

faz com que os dois gráficos compartilhem o mesmo eixo horizontal.

Esse formato é útil quando queremos comparar variáveis no mesmo período sem colocá-las na mesma escala vertical.

Salvando uma figura#

Além de exibir um gráfico, podemos salvá-lo como arquivo.

PYTHON
fig, ax = plt.subplots(
    figsize=(11, 5),
    layout="constrained",
)

ax.plot(
    df["time"],
    df["pm2_5"],
)

ax.set_title(
    "Previsão horária de PM2.5 em São Paulo"
)

ax.set_xlabel("Data e hora")

ax.set_ylabel(
    f"PM2.5 ({unidade_pm25})"
)

ax.xaxis.set_major_locator(
    mdates.HourLocator(interval=12)
)

ax.xaxis.set_major_formatter(
    mdates.DateFormatter(
        "%d/%m\n%H:%M"
    )
)

fig.savefig(
    "previsao-pm25-sao-paulo.png",
    dpi=150,
    bbox_inches="tight",
)

plt.show()

O nome do arquivo determina o formato:

PYTHON
"previsao-pm25-sao-paulo.png"

Também seria possível utilizar extensões como .jpg, .svg ou .pdf, de acordo com os formatos suportados pelo ambiente.

O argumento:

PYTHON
dpi=150

define a resolução da imagem.

O argumento:

PYTHON
bbox_inches="tight"

reduz margens externas desnecessárias.

Para salvar uma imagem com fundo transparente:

PYTHON
fig.savefig(
    "previsao-pm25-transparente.png",
    dpi=150,
    bbox_inches="tight",
    transparent=True,
)

Em scripts que geram muitas figuras, feche cada uma depois de salvá-la:

PYTHON
plt.close(fig)

Isso libera os recursos associados à figura.

Código completo para o projeto com uv#

O arquivo main.py abaixo consulta os dados e salva três gráficos em uma pasta chamada graficos.

PYTHON
from pathlib import Path

import matplotlib.dates as mdates
import matplotlib.pyplot as plt
import pandas as pd
import requests

API_URL = (
    "https://air-quality-api.open-meteo.com"
    "/v1/air-quality"
)

PARAMETROS = {
    "latitude": -23.5505,
    "longitude": -46.6333,
    "hourly": (
        "european_aqi,"
        "pm10,"
        "pm2_5,"
        "nitrogen_dioxide,"
        "ozone"
    ),
    "forecast_days": 3,
    "timezone": "America/Sao_Paulo",
}

DIRETORIO_GRAFICOS = Path("graficos")

def consultar_dados() -> dict:
    """Consulta dados horários de qualidade do ar."""

    resposta = requests.get(
        API_URL,
        params=PARAMETROS,
        timeout=30,
    )

    resposta.raise_for_status()

    return resposta.json()

def criar_dataframe(
    dados: dict,
) -> pd.DataFrame:
    """Converte os dados horários em DataFrame."""

    if "hourly" not in dados:
        raise KeyError(
            "A resposta não contém a chave 'hourly'."
        )

    df = pd.DataFrame(
        dados["hourly"]
    )

    df["time"] = pd.to_datetime(
        df["time"]
    )

    return df

def formatar_eixo_temporal(ax) -> None:
    """Configura a apresentação das datas."""

    ax.xaxis.set_major_locator(
        mdates.HourLocator(interval=12)
    )

    ax.xaxis.set_major_formatter(
        mdates.DateFormatter(
            "%d/%m\n%H:%M"
        )
    )

def salvar_grafico_linha(
    df: pd.DataFrame,
    unidade: str,
) -> None:
    """Salva um gráfico temporal de PM2.5."""

    media_pm25 = df["pm2_5"].mean()

    fig, ax = plt.subplots(
        figsize=(11, 5),
        layout="constrained",
    )

    ax.plot(
        df["time"],
        df["pm2_5"],
        label="PM2.5",
    )

    ax.axhline(
        y=media_pm25,
        linestyle="--",
        label=f"Média: {media_pm25:.2f}",
    )

    ax.set_title(
        "Previsão horária de PM2.5 "
        "em São Paulo"
    )

    ax.set_xlabel("Data e hora")

    ax.set_ylabel(
        f"PM2.5 ({unidade})"
    )

    ax.legend()
    ax.grid(alpha=0.3)

    formatar_eixo_temporal(ax)

    fig.savefig(
        DIRETORIO_GRAFICOS
        / "linha-pm25.png",
        dpi=150,
        bbox_inches="tight",
    )

    plt.close(fig)

def salvar_histograma(
    df: pd.DataFrame,
    unidade: str,
) -> None:
    """Salva a distribuição do PM2.5."""

    fig, ax = plt.subplots(
        figsize=(9, 5),
        layout="constrained",
    )

    ax.hist(
        df["pm2_5"].dropna(),
        bins=10,
    )

    ax.set_title(
        "Distribuição dos valores de PM2.5"
    )

    ax.set_xlabel(
        f"PM2.5 ({unidade})"
    )

    ax.set_ylabel("Frequência")

    ax.grid(
        axis="y",
        alpha=0.3,
    )

    fig.savefig(
        DIRETORIO_GRAFICOS
        / "histograma-pm25.png",
        dpi=150,
        bbox_inches="tight",
    )

    plt.close(fig)

def salvar_dispersao(
    df: pd.DataFrame,
    unidade: str,
) -> None:
    """Salva um gráfico de PM2.5 por PM10."""

    fig, ax = plt.subplots(
        figsize=(8, 6),
        layout="constrained",
    )

    ax.scatter(
        df["pm2_5"],
        df["pm10"],
        alpha=0.7,
    )

    ax.set_title(
        "Relação entre PM2.5 e PM10"
    )

    ax.set_xlabel(
        f"PM2.5 ({unidade})"
    )

    ax.set_ylabel(
        f"PM10 ({unidade})"
    )

    ax.grid(alpha=0.3)

    fig.savefig(
        DIRETORIO_GRAFICOS
        / "dispersao-pm.png",
        dpi=150,
        bbox_inches="tight",
    )

    plt.close(fig)

def main() -> None:
    """Executa a consulta e cria os gráficos."""

    DIRETORIO_GRAFICOS.mkdir(
        parents=True,
        exist_ok=True,
    )

    try:
        dados = consultar_dados()

        df = criar_dataframe(dados)

    except requests.exceptions.Timeout:
        print(
            "A API demorou muito para responder."
        )
        return

    except requests.exceptions.HTTPError as erro:
        print(
            f"A API devolveu um erro HTTP: {erro}"
        )
        return

    except requests.exceptions.JSONDecodeError:
        print(
            "A resposta não contém um JSON válido."
        )
        return

    except requests.exceptions.RequestException as erro:
        print(
            f"Não foi possível consultar a API: "
            f"{erro}"
        )
        return

    except (KeyError, ValueError) as erro:
        print(
            f"Não foi possível preparar os dados: "
            f"{erro}"
        )
        return

    unidade_pm = (
        dados["hourly_units"]["pm2_5"]
    )

    salvar_grafico_linha(
        df=df,
        unidade=unidade_pm,
    )

    salvar_histograma(
        df=df,
        unidade=unidade_pm,
    )

    salvar_dispersao(
        df=df,
        unidade=unidade_pm,
    )

    print(
        "Gráficos salvos em:"
    )

    print(
        DIRETORIO_GRAFICOS.resolve()
    )

if __name__ == "__main__":
    main()

Execute o programa:

BASH
uv run main.py

Ao final, a estrutura será semelhante a:

TEXT
graficos/
├── dispersao-pm.png
├── histograma-pm25.png
└── linha-pm25.png

Exercícios#

Alterar a variável do gráfico de linha#

Substitua:

PYTHON
df["pm2_5"]

por:

PYTHON
df["ozone"]

Atualize também o título, o rótulo e a unidade do eixo vertical.

Criar um gráfico com o maior PM2.5 de cada dia#

Calcule o máximo diário:

PYTHON
df["data"] = df["time"].dt.date

maximo_diario = (
    df.groupby(
        "data",
        as_index=False,
    )["pm2_5"]
    .max()
)

Depois, represente maximo_diario usando ax.bar().

Comparar dióxido de nitrogênio e ozônio#

Adicione as duas séries ao mesmo gráfico:

PYTHON
ax.plot(
    df["time"],
    df["nitrogen_dioxide"],
    label="Dióxido de nitrogênio",
)

ax.plot(
    df["time"],
    df["ozone"],
    label="Ozônio",
)

Finalize com:

PYTHON
ax.legend()

Alterar os intervalos do histograma#

Compare os resultados produzidos por:

PYTHON
bins=5
PYTHON
bins=10
PYTHON
bins=20

Observe como a escolha muda a apresentação da distribuição.

Representar apenas valores acima da média#

Crie um subconjunto:

PYTHON
media_pm25 = df["pm2_5"].mean()

acima_media = df.loc[
    df["pm2_5"] > media_pm25
]

Em seguida, use acima_media em um gráfico de linha ou dispersão.

O que aprendemos#

Nesta terceira parte da série, usamos um DataFrame do Pandas para criar visualizações com Matplotlib.

Aprendemos a trabalhar com Figure e Axes, criar gráficos de linha, barras, histogramas e dispersão, comparar séries, destacar médias, formatar datas e organizar mais de um gráfico na mesma figura.

Também aprendemos a salvar os resultados como arquivos de imagem.

A escolha do gráfico deve acompanhar a pergunta feita aos dados:

  • gráficos de linha representam evolução ao longo do tempo;
  • gráficos de barras comparam categorias ou períodos;
  • histogramas mostram distribuições;
  • gráficos de dispersão ajudam a observar relações entre variáveis.

Uma visualização não deve apenas tornar os dados mais atraentes. Seu objetivo principal é comunicar uma informação com clareza.

Os valores usados neste artigo continuam sendo previsões produzidas por modelos atmosféricos. Ao publicar os gráficos, inclua a origem dos dados:

Fonte: Open-Meteo Air Quality API, com dados do CAMS Global Atmospheric Composition Forecast.

Referências oficiais#

Os destinos correspondem às páginas oficiais do Matplotlib, da Open-Meteo e do uv.