直方图的箱子、密度和权重#

.Axes.hist 方法可以用几种不同的方式灵活地创建直方图,这既灵活又有帮助,但也可能导致混淆。特别是,你可以:

  • 按您想要的方式对数据进行分箱,无论是自动选择的箱数,还是固定的箱边缘。

  • 将直方图归一化,使其积分为一,

  • 并为数据点分配权重,使得每个数据点对其所在箱子的计数影响不同。

Matplotlib 的 hist 方法调用了 numpy.histogram 并绘制结果,因此用户应查阅 numpy 文档以获取权威指南。

直方图是通过定义箱边缘,将数值数据集排序到这些箱中,并计算或汇总每个箱中的数据量来创建的。在这个简单的例子中,1 到 4 之间的 9 个数字被排序到 3 个箱中:

import matplotlib.pyplot as plt
import numpy as np

rng = np.random.default_rng(19680801)

xdata = np.array([1.2, 2.3, 3.3, 3.1, 1.7, 3.4, 2.1, 1.25, 1.3])
xbins = np.array([1, 2, 3, 4])

# changing the style of the histogram bars just to make it
# very clear where the boundaries of the bins are:
style = {'facecolor': 'none', 'edgecolor': 'C0', 'linewidth': 3}

fig, ax = plt.subplots()
ax.hist(xdata, bins=xbins, **style)

# plot the xdata locations on the x axis:
ax.plot(xdata, 0*xdata, 'd')
ax.set_ylabel('Number per bin')
ax.set_xlabel('x bins (dx=1.0)')
histogram normalization

修改箱子#

改变bin的大小会改变这个稀疏直方图的形状,因此根据你的数据仔细选择bin是一个好主意。这里我们将bin的宽度减半。

xbins = np.arange(1, 4.5, 0.5)

fig, ax = plt.subplots()
ax.hist(xdata, bins=xbins, **style)
ax.plot(xdata, 0*xdata, 'd')
ax.set_ylabel('Number per bin')
ax.set_xlabel('x bins (dx=0.5)')
histogram normalization

我们也可以让 numpy(通过 Matplotlib)自动选择箱子,或者指定一个自动选择的箱子数量:

fig, ax = plt.subplot_mosaic([['auto', 'n4']],
                             sharex=True, sharey=True, layout='constrained')

ax['auto'].hist(xdata, **style)
ax['auto'].plot(xdata, 0*xdata, 'd')
ax['auto'].set_ylabel('Number per bin')
ax['auto'].set_xlabel('x bins (auto)')

ax['n4'].hist(xdata, bins=4, **style)
ax['n4'].plot(xdata, 0*xdata, 'd')
ax['n4'].set_xlabel('x bins ("bins=4")')
histogram normalization

归一化直方图:密度和权重#

每箱计数是直方图中每个条形的默认长度。然而,我们也可以使用 density 参数将条形长度归一化为概率密度函数:

fig, ax = plt.subplots()
ax.hist(xdata, bins=xbins, density=True, **style)
ax.set_ylabel('Probability density [$V^{-1}$])')
ax.set_xlabel('x bins (dx=0.5 $V$)')
histogram normalization

这种标准化在仅仅探索数据时可能有点难以解释。每个柱子附带的值被数据点的总数 柱子的宽度所除,因此在整个数据范围内积分时,这些值 _积分_ 为一。例如

density = counts / (sum(counts) * np.diff(bins))
np.sum(density * np.diff(bins)) == 1

这种归一化是统计学中如何定义 概率密度函数 的方式。如果 \(X\)\(x\) 上的随机变量,那么 \(f_X\) 就是概率密度函数,如果 \(P[a<X<b] = \int_a^b f_X dx\)。如果 x 的单位是伏特,那么 \(f_X\) 的单位是 \(V^{-1}\) 或每电压变化的单位概率。

当我们从已知分布中抽取数据并尝试与理论进行比较时,这种标准化的有用性会变得更加明显。因此,从 正态分布 中选择1000个点,并计算已知的概率密度函数:

xdata = rng.normal(size=1000)
xpdf = np.arange(-4, 4, 0.1)
pdf = 1 / (np.sqrt(2 * np.pi)) * np.exp(-xpdf**2 / 2)

如果我们不使用 density=True ,我们需要将期望的概率分布函数按数据的长度和箱子的宽度进行缩放:

fig, ax = plt.subplot_mosaic([['False', 'True']], layout='constrained')
dx = 0.1
xbins = np.arange(-4, 4, dx)
ax['False'].hist(xdata, bins=xbins, density=False, histtype='step', label='Counts')

# scale and plot the expected pdf:
ax['False'].plot(xpdf, pdf * len(xdata) * dx, label=r'$N\,f_X(x)\,\delta x$')
ax['False'].set_ylabel('Count per bin')
ax['False'].set_xlabel('x bins [V]')
ax['False'].legend()

ax['True'].hist(xdata, bins=xbins, density=True, histtype='step', label='density')
ax['True'].plot(xpdf, pdf, label='$f_X(x)$')
ax['True'].set_ylabel('Probability density [$V^{-1}$]')
ax['True'].set_xlabel('x bins [$V$]')
ax['True'].legend()
histogram normalization

因此,使用密度的优势之一是直方图的形状和振幅不依赖于箱子的大小。考虑一个极端情况,其中箱子的宽度不相同。在这个例子中,``x=-1.25``以下的箱子比其他箱子宽六倍。通过按密度归一化,我们保留了分布的形状,而如果不这样做,那么较宽的箱子将比较窄的箱子有更高的计数:

fig, ax = plt.subplot_mosaic([['False', 'True']], layout='constrained')
dx = 0.1
xbins = np.hstack([np.arange(-4, -1.25, 6*dx), np.arange(-1.25, 4, dx)])
ax['False'].hist(xdata, bins=xbins, density=False, histtype='step', label='Counts')
ax['False'].plot(xpdf, pdf * len(xdata) * dx, label=r'$N\,f_X(x)\,\delta x_0$')
ax['False'].set_ylabel('Count per bin')
ax['False'].set_xlabel('x bins [V]')
ax['False'].legend()

ax['True'].hist(xdata, bins=xbins, density=True, histtype='step', label='density')
ax['True'].plot(xpdf, pdf, label='$f_X(x)$')
ax['True'].set_ylabel('Probability density [$V^{-1}$]')
ax['True'].set_xlabel('x bins [$V$]')
ax['True'].legend()
histogram normalization

同样地,如果我们想要比较具有不同箱宽的直方图,我们可能想要使用 density=True

fig, ax = plt.subplot_mosaic([['False', 'True']], layout='constrained')

# expected PDF
ax['True'].plot(xpdf, pdf, '--', label='$f_X(x)$', color='k')

for nn, dx in enumerate([0.1, 0.4, 1.2]):
    xbins = np.arange(-4, 4, dx)
    # expected histogram:
    ax['False'].plot(xpdf, pdf*1000*dx, '--', color=f'C{nn}')
    ax['False'].hist(xdata, bins=xbins, density=False, histtype='step')

    ax['True'].hist(xdata, bins=xbins, density=True, histtype='step', label=dx)

# Labels:
ax['False'].set_xlabel('x bins [$V$]')
ax['False'].set_ylabel('Count per bin')
ax['True'].set_ylabel('Probability density [$V^{-1}$]')
ax['True'].set_xlabel('x bins [$V$]')
ax['True'].legend(fontsize='small', title='bin width:')
histogram normalization

有时人们希望归一化,使得计数的总和为1。这类似于离散变量的 概率质量函数 ,其中所有值的概率之和等于1。使用 hist ,如果我们设置 权重 为1/N,我们可以得到这种归一化。请注意,这种归一化直方图的振幅仍然取决于宽度或箱的数量:

fig, ax = plt.subplots(layout='constrained', figsize=(3.5, 3))

for nn, dx in enumerate([0.1, 0.4, 1.2]):
    xbins = np.arange(-4, 4, dx)
    ax.hist(xdata, bins=xbins, weights=1/len(xdata) * np.ones(len(xdata)),
                   histtype='step', label=f'{dx}')
ax.set_xlabel('x bins [$V$]')
ax.set_ylabel('Bin count / N')
ax.legend(fontsize='small', title='bin width:')
histogram normalization

归一化直方图的值在于比较两个具有不同大小群体的分布。这里我们比较了 xdata 的分布,其群体大小为1000,以及 xdata2 的分布,其群体大小为100。

xdata2 = rng.normal(size=100)

fig, ax = plt.subplot_mosaic([['no_norm', 'density', 'weight']],
                             layout='constrained', figsize=(8, 4))

xbins = np.arange(-4, 4, 0.25)

ax['no_norm'].hist(xdata, bins=xbins, histtype='step')
ax['no_norm'].hist(xdata2, bins=xbins, histtype='step')
ax['no_norm'].set_ylabel('Counts')
ax['no_norm'].set_xlabel('x bins [$V$]')
ax['no_norm'].set_title('No normalization')

ax['density'].hist(xdata, bins=xbins, histtype='step', density=True)
ax['density'].hist(xdata2, bins=xbins, histtype='step', density=True)
ax['density'].set_ylabel('Probability density [$V^{-1}$]')
ax['density'].set_title('Density=True')
ax['density'].set_xlabel('x bins [$V$]')

ax['weight'].hist(xdata, bins=xbins, histtype='step',
                  weights=1 / len(xdata) * np.ones(len(xdata)),
                  label='N=1000')
ax['weight'].hist(xdata2, bins=xbins, histtype='step',
                  weights=1 / len(xdata2) * np.ones(len(xdata2)),
                  label='N=100')
ax['weight'].set_xlabel('x bins [$V$]')
ax['weight'].set_ylabel('Counts / N')
ax['weight'].legend(fontsize='small')
ax['weight'].set_title('Weight = 1/N')

plt.show()
No normalization, Density=True, Weight = 1/N

脚本总运行时间: (0 分钟 1.460 秒)

由 Sphinx-Gallery 生成的图库