使用 Matplotlib 创建带阴影的填充直方图

Beginner

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

简介

在本实验中,我们将学习如何使用 Matplotlib 创建带有阴影填充的直方图。直方图是一种数据的图形表示形式,它使用条形来展示数值数据的频率。带有阴影填充的直方图是指条形用线条、点或其他符号的图案进行填充的直方图。

虚拟机使用提示

虚拟机启动完成后,点击左上角切换到“笔记本”标签页,以访问 Jupyter Notebook 进行练习。

有时,你可能需要等待几秒钟让 Jupyter Notebook 完成加载。由于 Jupyter Notebook 的限制,操作验证无法自动化。

如果你在学习过程中遇到问题,随时向 Labby 提问。课程结束后提供反馈,我们会及时为你解决问题。

导入必要的库

我们将为本实验导入必要的库。我们需要以下库:

  • numpy 用于生成随机数据
  • matplotlib.pyplot 用于创建图表
  • matplotlib.ticker 用于设置坐标轴刻度位置
  • cycler 用于创建样式循环
  • functools.partial 用于创建偏函数
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
from cycler import cycler
from functools import partial

定义直方图函数

我们将定义一个函数,用于绘制作为阶梯状补丁的直方图。该函数将接受以下参数:

  • ax:要绘制到的 Axes 对象
  • edges:一个长度为 n + 1 的数组,给出每个区间的左边缘以及最后一个区间的右边缘
  • values:一个长度为 n 的区间计数或值的数组
  • bottoms:一个浮点数或数组,可选,一个长度为 n 的数组,表示条形的底部。如果为 None,则使用零
  • orientation:一个字符串,可选,直方图的方向。'v'(默认)表示条形在正 y 方向上增加
def filled_hist(ax, edges, values, bottoms=None, orientation='v', **kwargs):
    """
    绘制作为阶梯状补丁的直方图。

    参数
    ----------
    ax : Axes
        要绘制到的轴

    edges : 数组
        一个长度为 n + 1 的数组,给出每个区间的左边缘以及最后一个区间的右边缘。

    values : 数组
        一个长度为 n 的区间计数或值的数组

    bottoms : 浮点数或数组,可选
        一个长度为 n 的数组,表示条形的底部。如果为 None,则使用零。

    orientation : {'v', 'h'}
        直方图的方向。'v'(默认)表示条形在正 y 方向上增加。

    **kwargs
        额外的关键字参数将传递给 `.fill_between`。

    返回
    -------
    ret : PolyCollection
        添加到 Axes 的艺术家对象
    """
    if orientation not in 'hv':
        raise ValueError(f"方向必须在{{'h', 'v'}}中,而不是{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'必须提供比值多一个区间边缘,而不是:{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("你不应该在这里")

定义堆叠直方图函数

我们将定义一个函数来创建堆叠直方图。该函数将接受以下参数:

  • ax:要添加艺术家对象的坐标轴
  • stacked_data:一个形状为 (M, N) 的数组。将对第一维进行迭代,按行计算直方图
  • sty_cycle:一个 Cycler 对象或可操作的字典,应用于每组的样式
  • bottoms:一个数组,默认值为 0,底部的初始位置
  • hist_func:一个可调用对象,可选。必须具有签名 bin_vals, bin_edges = f(data)。预期 bin_edgesbin_vals 长一个
  • labels:一个字符串列表,可选,每组的标签。如果未给出,且 stacked_data 是数组,则默认为 'default set {n}'。如果 stacked_data 是映射且 labels 为 None,则默认为键。如果 stacked_data 是映射且给出了 labels,则只绘制列出的列
  • plot_func:一个可调用对象,可选,用于绘制直方图的函数。必须具有签名 ret = plot_func(ax, edges, top, bottoms=bottoms, label=label, **kwargs)
  • plot_kwargs:一个字典,可选,要传递给绘图函数的任何额外关键字参数。这对于绘图函数的所有调用都是相同的,并且将覆盖 sty_cycle 中的值
def stack_hist(ax, stacked_data, sty_cycle, bottoms=None, hist_func=None, labels=None, plot_func=None, plot_kwargs=None):
    """
    参数
    ----------
    ax : axes.Axes
        要添加艺术家对象的坐标轴

    stacked_data : 数组或映射
        一个形状为 (M, N) 的数组。将对第一维进行迭代,按行计算直方图

    sty_cycle : Cycler 或可操作的字典
        应用于每组的样式

    bottoms : 数组,默认值为 0
        底部的初始位置

    hist_func : 可调用对象,可选
        必须具有签名 `bin_vals, bin_edges = f(data)`。
        预期 `bin_edges` 比 `bin_vals` 长一个

    labels : 字符串列表,可选
        每组的标签。

        如果未给出,且 `stacked_data` 是数组,则默认为 'default set {n}'

        如果 *stacked_data* 是映射,且 *labels* 为 None,则默认为键。

        如果 *stacked_data* 是映射且 *labels* 为给定,则只绘制列出的列。

    plot_func : 可调用对象,可选
        用于绘制直方图的函数,必须具有签名:

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

    plot_kwargs : 字典,可选
        要传递给绘图函数的任何额外关键字参数。
        这对于绘图函数的所有调用都是相同的,并且将覆盖 *sty_cycle* 中的值。

    返回
    -------
    arts : 字典
        以标签为键的艺术家对象字典
    """
    ## 处理默认的装箱函数
    if hist_func is None:
        hist_func = np.histogram

    ## 处理默认的绘图函数
    if plot_func is None:
        plot_func = filled_hist

    ## 处理默认值
    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

设置固定区间的直方图函数

我们将使用 numpy.histogram 设置一个具有固定区间的直方图函数。我们将创建 20 个区间,范围从 -3 到 3。

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

设置样式循环

我们将使用 cycler 为直方图设置样式循环。我们将创建三个样式循环:一个用于面颜色,一个用于标签,一个用于阴影图案。

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=['/', '*', '+', '|'])

生成随机数据

我们将使用 numpy.random.randn 生成随机数据。我们将生成 4 组数据,每组有 12250 个点。

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

创建带阴影的填充直方图

我们将使用之前定义的 stack_hist 函数创建一个带阴影的填充直方图。我们将使用之前定义的 stack_datacolor_cyclehist_func。我们还将设置 plot_kwargs 以包含边缘颜色和方向。

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('counts')
ax1.set_xlabel('x')
ax2.set_xlabel('counts')
ax2.set_ylabel('x')

创建带标签的阴影填充直方图

我们将使用之前定义的 stack_hist 函数创建一个带标签的阴影填充直方图。我们将使用之前定义的 dict_datacolor_cyclehist_func。我们还将把 labels 设置为 ['set 0','set 3'],以便只绘制第一组和最后一组。

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('counts')
ax1.set_ylabel('x')
ax2.set_ylabel('x')

总结

在本实验中,我们学习了如何使用 Matplotlib 创建带阴影的填充直方图。我们定义了两个函数:filled_hist 用于将直方图绘制为阶梯状补丁,以及 stack_hist 用于创建堆叠直方图。我们还使用 numpy.histogram 设置了一个具有固定 bins 的直方图函数,并使用 cycler 为直方图定义了三个样式循环。最后,我们生成了随机数据,并使用 stack_hist 函数创建了两个带阴影的填充直方图。