Criar Histogramas Preenchidos com Hachuras usando Matplotlib

Beginner

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

Introdução

Neste laboratório, aprenderemos como criar histogramas preenchidos com hachuras usando Matplotlib. Um histograma é uma representação gráfica de dados que utiliza barras para mostrar a frequência de dados numéricos. Um histograma preenchido com hachuras é um histograma no qual as barras são preenchidas com um padrão de linhas, pontos ou outros símbolos.

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 a limitações no 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

Importaremos as bibliotecas necessárias para este laboratório. Precisamos das seguintes bibliotecas:

  • numpy para gerar dados aleatórios
  • matplotlib.pyplot para criar gráficos
  • matplotlib.ticker para definir as localizações dos ticks dos eixos
  • cycler para criar ciclos de estilo
  • functools.partial para criar uma função parcial
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from cycler import cycler
from functools import partial

Definir a função do histograma

Definiremos uma função para desenhar um histograma como um patch em degraus. A função receberá os seguintes parâmetros:

  • ax: os Axes (eixos) para plotar
  • edges: um array de comprimento n+1 que fornece as bordas esquerdas de cada bin e a borda direita do último bin
  • values: um array de comprimento n de contagens ou valores de bin
  • bottoms: um float ou array, opcional, um array de comprimento n da base das barras. Se None, zero é usado.
  • orientation: uma string, opcional, a orientação do histograma. 'v' (padrão) tem as barras aumentando na direção y positiva.
def filled_hist(ax, edges, values, bottoms=None, orientation='v', **kwargs):
    """
    Desenha um histograma como um patch em degraus.

    Parâmetros
    ----------
    ax : Axes
        Os eixos para plotar

    edges : array
        Um array de comprimento n+1 que fornece as bordas esquerdas de cada bin e a
        borda direita do último bin.

    values : array
        Um array de comprimento n de contagens ou valores de bin

    bottoms : float ou array, opcional
        Um array de comprimento n da base das barras. Se None, zero é usado.

    orientation : {'v', 'h'}
       Orientação do histograma. 'v' (padrão) tem
       as barras aumentando na direção y positiva.

    **kwargs
        Argumentos de palavra-chave extras são passados para `.fill_between`.

    Retorna
    -------
    ret : PolyCollection
        Artista adicionado aos Axes
    """
    if orientation not in 'hv':
        raise ValueError(f"orientation must be in {{'h', 'v'}} not {orientation}")

    kwargs.setdefault('step', 'post')
    kwargs.setdefault('alpha', 0.7)
    edges = np.asarray(edges)
    values = np.asarray(values)
    if len(edges) - 1 != len(values):
        raise ValueError(f'Must provide one more bin edge than value not: {len(edges)=} {len(values)=}')

    if bottoms is None:
        bottoms = 0
    bottoms = np.broadcast_to(bottoms, values.shape)

    values = np.append(values, values[-1])
    bottoms = np.append(bottoms, bottoms[-1])
    if orientation == 'h':
        return ax.fill_betweenx(edges, values, bottoms, **kwargs)
    elif orientation == 'v':
        return ax.fill_between(edges, values, bottoms, **kwargs)
    else:
        raise AssertionError("you should never be here")

Definir a função do histograma empilhado

Definiremos uma função para criar um histograma empilhado. A função receberá os seguintes parâmetros:

  • ax: os eixos para adicionar artistas
  • stacked_data: um array com formato (M, N). A primeira dimensão será iterada para calcular histogramas linha a linha
  • sty_cycle: um Cycler (ciclo) ou operável de dict, estilo a ser aplicado a cada conjunto
  • bottoms: um array, padrão: 0, as posições iniciais das bases.
  • hist_func: um callable (chamável), opcional. Deve ter a assinatura bin_vals, bin_edges = f(data). Espera-se que bin_edges seja um elemento mais longo que bin_vals
  • labels: uma lista de strings, opcional, o rótulo para cada conjunto. Se não for fornecido e os dados empilhados forem um array, o padrão será 'conjunto padrão {n}'. Se stacked_data for um mapeamento e labels for None, o padrão será as chaves. Se stacked_data for um mapeamento e labels for fornecido, apenas as colunas listadas serão plotadas.
  • plot_func: um callable, opcional, função a ser chamada para desenhar o histograma. Deve ter a assinatura ret = plot_func(ax, edges, top, bottoms=bottoms, label=label, **kwargs)
  • plot_kwargs: um dicionário, opcional, quaisquer argumentos de palavra-chave extras para passar para a função de plotagem. Isso será o mesmo para todas as chamadas para a função de plotagem e substituirá os valores em sty_cycle.
def stack_hist(ax, stacked_data, sty_cycle, bottoms=None, hist_func=None, labels=None, plot_func=None, plot_kwargs=None):
    """
    Parâmetros
    ----------
    ax : axes.Axes
        Os eixos para adicionar artistas

    stacked_data : array or Mapping
        Um array com formato (M, N). A primeira dimensão será iterada para
        calcular histogramas linha a linha

    sty_cycle : Cycler ou operável de dict
        Estilo a ser aplicado a cada conjunto

    bottoms : array, padrão: 0
        As posições iniciais das bases.

    hist_func : callable, opcional
        Deve ter a assinatura `bin_vals, bin_edges = f(data)`.
        Espera-se que `bin_edges` seja um elemento mais longo que `bin_vals`

    labels : list of str, opcional
        O rótulo para cada conjunto.

        Se não for fornecido e os dados empilhados forem um array, o padrão será
        'conjunto padrão {n}'

        Se *stacked_data* for um mapeamento, e *labels* for None, o padrão será as
        chaves.

        Se *stacked_data* for um mapeamento e *labels* for fornecido, apenas as
        colunas listadas serão plotadas.

    plot_func : callable, opcional
        Função a ser chamada para desenhar o histograma deve ter a assinatura:

          ret = plot_func(ax, edges, top, bottoms=bottoms,
                          label=label, **kwargs)

    plot_kwargs : dict, opcional
        Quaisquer argumentos de palavra-chave extras para passar para a função de
        plotagem. Isso será o mesmo para todas as chamadas para a função de
        plotagem e substituirá os valores em *sty_cycle*.

    Retorna
    -------
    arts : dict
        Dicionário de artistas indexados em seus rótulos
    """
    ## lidar com a função de binning padrão
    if hist_func is None:
        hist_func = np.histogram

    ## lidar com a função de plotagem padrão
    if plot_func is None:
        plot_func = filled_hist

    ## lidar com o padrão
    if plot_kwargs is None:
        plot_kwargs = {}

    try:
        l_keys = stacked_data.keys()
        label_data = True
        if labels is None:
            labels = l_keys

    except AttributeError:
        label_data = False
        if labels is None:
            labels = itertools.repeat(None)

    if label_data:
        loop_iter = enumerate((stacked_data[lab], lab, s) for lab, s in zip(labels, sty_cycle))
    else:
        loop_iter = enumerate(zip(stacked_data, labels, sty_cycle))

    arts = {}
    for j, (data, label, sty) in loop_iter:
        if label is None:
            label = f'dflt set {j}'
        label = sty.pop('label', label)
        vals, edges = hist_func(data)
        if bottoms is None:
            bottoms = np.zeros_like(vals)
        top = bottoms + vals
        sty.update(plot_kwargs)
        ret = plot_func(ax, edges, top, bottoms=bottoms, label=label, **sty)
        bottoms = top
        arts[label] = ret
    ax.legend(fontsize=10)
    return arts

Configurar a função do histograma para bins fixos

Configuraremos uma função de histograma com bins fixos usando numpy.histogram. Criaremos 20 bins variando de -3 a 3.

edges = np.linspace(-3, 3, 20, endpoint=True)
hist_func = partial(np.histogram, bins=edges)

Configurar ciclos de estilo

Configuraremos ciclos de estilo para os histogramas usando cycler. Criaremos três ciclos de estilo: um para a cor de preenchimento (facecolor), um para o rótulo (label) e um para o padrão de hachura (hatch).

color_cycle = cycler(facecolor=plt.rcParams['axes.prop_cycle'][:4])
label_cycle = cycler(label=[f'set {n}' for n in range(4)])
hatch_cycle = cycler(hatch=['/', '*', '+', '|'])

Gerar dados aleatórios

Geraremos dados aleatórios usando numpy.random.randn. Geraremos 4 conjuntos de dados com 12250 pontos cada.

np.random.seed(19680801)
stack_data = np.random.randn(4, 12250)

Criar o histograma preenchido com hachuras

Criaremos um histograma preenchido com hachuras usando a função stack_hist que definimos anteriormente. Usaremos os stack_data, color_cycle e hist_func que definimos anteriormente. Também definiremos plot_kwargs para incluir edgecolor e orientation.

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 4.5), tight_layout=True)
arts = stack_hist(ax1, stack_data, color_cycle + label_cycle + hatch_cycle, hist_func=hist_func)

arts = stack_hist(ax2, stack_data, color_cycle, hist_func=hist_func, plot_kwargs=dict(edgecolor='w', orientation='h'))
ax1.set_ylabel('contagens')
ax1.set_xlabel('x')
ax2.set_xlabel('contagens')
ax2.set_ylabel('x')

Criar o histograma preenchido com hachuras e rotulado

Criaremos um histograma preenchido com hachuras e rotulado usando a função stack_hist que definimos anteriormente. Usaremos os dict_data, color_cycle e hist_func que definimos anteriormente. Também definiremos labels para ['set 0', 'set 3'] para plotar apenas o primeiro e o último conjunto.

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9, 4.5), tight_layout=True, sharey=True)
dict_data = dict(zip((c['label'] for c in label_cycle), stack_data))
arts = stack_hist(ax1, dict_data, color_cycle + hatch_cycle, hist_func=hist_func)

arts = stack_hist(ax2, dict_data, color_cycle + hatch_cycle, hist_func=hist_func, labels=['set 0', 'set 3'])
ax1.xaxis.set_major_locator(mticker.MaxNLocator(5))
ax1.set_xlabel('contagens')
ax1.set_ylabel('x')
ax2.set_ylabel('x')

Resumo

Neste laboratório, aprendemos como criar histogramas preenchidos com hachuras usando Matplotlib. Definimos duas funções: filled_hist para desenhar um histograma como um patch (remendo) em degraus, e stack_hist para criar um histograma empilhado. Também configuramos uma função de histograma com bins (intervalos) fixos usando numpy.histogram, e definimos três ciclos de estilo para os histogramas usando cycler. Finalmente, geramos dados aleatórios e criamos dois histogramas preenchidos com hachuras usando a função stack_hist.