HOWTO · Matplotlib

如何在 Matplotlib 中为数据列表绘制直方图

使用 Matplotlib 的 plt.hist() 从 Python 列表绘制直方图,然后调整其组距、密度和颜色。

本页内容

要从 Python 列表绘制直方图,只需将列表直接传给 Matplotlib 的 plt.hist() 函数。该函数会把数值分到称为组距(bin)的区间中,统计落入每个区间的数值个数,并为每个区间绘制一根柱条,因此图的形状就能显示数值的分布情况。输入可以是普通的 Python 列表,无需事先转换成 NumPy 数组或 pandas Series。本文先绘制一个包含 21 个 0 到 9 整数的列表,然后调整组距、柱条高度和柱条颜色,最后展示组距数量无效以及列表为空时会发生什么。

使用 plt.hist() 从列表绘制直方图

不带任何额外参数时,plt.hist() 会把数据范围划分为十个等宽的组距。本例还接收该函数返回的三个值并将其打印出来,以便你确切地看到列表是如何被分组的:

import matplotlib.pyplot as plt

data = [3, 4, 2, 3, 4, 5, 4, 7, 8, 5, 4, 6, 2, 1, 0, 9, 7, 6, 6, 5, 4]
counts, bin_edges, patches = plt.hist(data)
print("counts:", counts.tolist())
print("bin_edges:", bin_edges.tolist())
plt.xlabel("Value")
plt.ylabel("Count")
plt.title("Histogram of data")
plt.show()
counts: [1.0, 1.0, 2.0, 2.0, 5.0, 3.0, 3.0, 2.0, 1.0, 1.0]
bin_edges: [0.0, 0.9, 1.8, 2.7, 3.6, 4.5, 5.4, 6.3, 7.2, 8.1, 9.0]

plt.hist() 使用默认的十个组距绘制的列表 21 个数值的直方图;最高的柱条覆盖 3.6 到 4.5 的数值,计数为 5。

plt.hist() 返回三个值。counts 保存每个组距中的观测数量,bin_edges 给出区间的边界,patches 包含柱条的 artist 对象,以备事后需要单独修改某根柱条的样式。counts 中的元素个数就是组距的数量,而 bin_edges 始终多一个元素,因为它标出了每个区间的两端。这里数据范围是 0 到 9,因此十个默认组距每个宽 0.9,最高的柱条是 3.6 到 4.5 的区间,其中包含数值 4 的五次出现。如果你只需要图形,可以省略赋值,只调用 plt.hist(data)。

设置组距数量或组距边界

bins 参数控制数据的划分方式。传入 bins=20 这样的整数会在数据范围内创建相应数量的等宽区间:

import matplotlib.pyplot as plt

data = [3, 4, 2, 3, 4, 5, 4, 7, 8, 5, 4, 6, 2, 1, 0, 9, 7, 6, 6, 5, 4]
counts, bin_edges, patches = plt.hist(data, bins=20)
print("counts:", counts.tolist())
print("number of bins:", len(counts))
plt.xlabel("Value")
plt.ylabel("Count")
plt.title("Histogram with 20 bins")
plt.show()
counts: [1.0, 0.0, 1.0, 0.0, 2.0, 0.0, 2.0, 0.0, 5.0, 0.0, 0.0, 3.0, 0.0, 3.0, 0.0, 2.0, 0.0, 1.0, 0.0, 1.0]
number of bins: 20

用 20 个组距绘制的同一列表;由于数据只包含 0 到 9 的整数,许多狭窄的组距都是空的。

对这个小列表来说,20 个组距太多了:由于数值都是整数,每隔一个组距就是空的,分布看起来比实际更嘈杂。一般来说,组距太少会掩盖数据中的结构,而组距太多太窄会让小数据集看起来破碎,因此应选择与列表大小和分散程度相称的组距数量。除了整数,你也可以传入 bins=[0, 2, 4, 6, 8, 10] 这样的边界值序列,自行设定区间边界;这样各区间的宽度可以各不相同。当计数跨度很大时,可选参数 log=True 会把计数轴切换为对数刻度,使较小的柱条在高柱条旁边仍然可见。它只改变坐标轴的刻度,不改变输入数值,也不改变组距的划分。

显示概率密度而非计数

默认情况下,柱条高度表示计数。当你想要概率密度时,可设置 density=True。Matplotlib 随即缩放高度,使所有柱条的总面积为 1:

import matplotlib.pyplot as plt

data = [3, 4, 2, 3, 4, 5, 4, 7, 8, 5, 4, 6, 2, 1, 0, 9, 7, 6, 6, 5, 4]
counts, bin_edges, patches = plt.hist(data, bins=10, density=True)
print("densities:", [round(d, 4) for d in counts.tolist()])
area = sum(d * (bin_edges[i + 1] - bin_edges[i]) for i, d in enumerate(counts))
print("total area:", round(area, 6))
plt.xlabel("Value")
plt.ylabel("Probability density")
plt.title("Probability histogram of data")
plt.show()
densities: [0.0529, 0.0529, 0.1058, 0.1058, 0.2646, 0.1587, 0.1587, 0.1058, 0.0529, 0.0529]
total area: 1.0

使用 density=True 绘制的列表概率密度直方图;柱条高度表示密度,所有柱条的总面积为 1。

使用 density=True 后,返回元组的第一个值保存的是密度而非计数;本例把每个密度乘以其组距宽度并累加面积,验证归一化结果恰好为 1。请注意,单根柱条的高度是密度,而不是其区间的概率;区间的概率是柱条的面积,即高度乘以宽度。当各组距宽度不同时,这一区别很重要,因为一根宽柱条所代表的概率可能大于一根更高的窄柱条。切换时请记得像本例那样更新纵轴标签,让图形说明其柱条高度的含义。

更改柱条颜色

color 参数设置柱条的填充颜色:

import matplotlib.pyplot as plt

data = [3, 4, 2, 3, 4, 5, 4, 7, 8, 5, 4, 6, 2, 1, 0, 9, 7, 6, 6, 5, 4]
plt.hist(data, bins=10, color="red")
plt.xlabel("Value")
plt.ylabel("Count")
plt.title("Red histogram of data")
plt.show()

使用十个组距并将 color 参数设为红色绘制的红色柱条列表直方图。

任何 Matplotlib 颜色表示法都可以使用,例如 "red" 这样的名称、"#1f77b4" 这样的十六进制字符串,或单字母代码。显示选项可以在一次调用中自由组合,例如 plt.hist(data, bins=20, density=True, color="red") 会绘制二十根带密度高度的红色柱条。无论组合哪些选项,都要让标题和轴标签与之保持一致:密度图不应保留计数标签,二十个组距的图也不应声称显示的是默认的十个。

处理无效的组距数量和空列表

有两个输入错误值得在它们让你措手不及之前就了解。第一个是组距数量不是正数:

import matplotlib.pyplot as plt

data = [3, 4, 2, 3, 4, 5, 4, 7, 8, 5, 4, 6, 2, 1, 0, 9, 7, 6, 6, 5, 4]
plt.hist(data, bins=0)
Traceback (most recent call last):
  ...
ValueError: `bins` must be positive, when an integer

bins=0(以及任何负整数)会引发 ValueError,且不会绘制任何内容。如果组距数量来自用户输入或计算结果,请在绘图前验证它是否为正整数,或者改传一个边界序列。第二个情况是空列表,其行为截然不同:它根本不是错误。

import matplotlib.pyplot as plt

counts, bin_edges, patches = plt.hist([])
print("counts:", counts.tolist())
print("bin_edges:", [round(e, 2) for e in bin_edges.tolist()])
counts: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
bin_edges: [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]

在没有数据时,plt.hist() 会回退到 0 到 1 的默认范围,返回十个为零的计数,并绘制一组空坐标轴而不引发异常。这使得空列表成为一种静默失败模式:如果你的数据来自文件、查询或筛选,请在绘图前确认列表非空,以免缺失的数据集变成一张看似有效结果的空白图。