Matplotlib 直方图:Python plt.hist() 完全指南
Updated on
你有一个包含数千个数值的数据集——年龄、考试分数、响应时间、传感器读数——你需要理解这些值是如何分布的。它们是否集中在某个中心点附近?是否偏向某一端?是否遵循正态分布?散点图无法帮助你。条形图是为分类数据设计的,而不是连续数据。你需要的是直方图,在 Python 中,matplotlib.pyplot.hist() 是创建直方图的标准方法。
问题是 plt.hist() 有十几个参数,默认输出通常看起来简陋或具有误导性。选择错误的 bin 数量可能会隐藏数据中的重要模式。在一个图表中比较多个分布需要了解正确的选项组合。本指南涵盖了每个重要参数,并提供可直接复制到笔记本或脚本中的可运行代码示例。
什么是直方图,什么时候应该使用?
直方图将数值范围划分为等宽的区间(称为 bins),并计算每个 bin 中有多少个数据点。x 轴显示值的范围,y 轴显示每个 bin 的频率(计数)或密度。与显示分类数据的条形图不同,直方图表示连续数值数据的分布。
在以下情况下使用直方图:
- 查看分布的形状(正态、偏斜、双峰、均匀)
- 识别数据中的异常值或缺口
- 比较各组之间的值的分散程度
- 在建模前决定数据转换方式
基本的 plt.hist() 语法
最简单的直方图只需要一个参数:数据数组。
import matplotlib.pyplot as plt
import numpy as np
# Generate 1000 normally distributed values
np.random.seed(42)
data = np.random.normal(loc=50, scale=15, size=1000)
plt.hist(data)
plt.title('Basic Histogram')
plt.xlabel('Value')
plt.ylabel('Frequency')
plt.show()默认情况下,matplotlib 将数据分成 10 个 bin。该函数返回三个对象:bin 计数、bin 边界和 patch 对象(绘制的矩形)。我们将在后面详细介绍这些返回值。
完整签名
plt.hist(x, bins=None, range=None, density=False, weights=None,
cumulative=False, bottom=None, histtype='bar', align='mid',
orientation='vertical', rwidth=None, log=False, color=None,
label=None, stacked=False, edgecolor=None, alpha=None)控制 Bins
bins 参数是直方图中最重要的设置。bin 太少会隐藏模式。bin 太多会产生噪声。
设置固定数量的 Bins
fig, axes = plt.subplots(1, 3, figsize=(14, 4))
axes[0].hist(data, bins=5, edgecolor='black')
axes[0].set_title('5 Bins')
axes[1].hist(data, bins=30, edgecolor='black')
axes[1].set_title('30 Bins')
axes[2].hist(data, bins=100, edgecolor='black')
axes[2].set_title('100 Bins')
plt.tight_layout()
plt.show()使用 5 个 bin 时,你只能看到大致的形状。使用 100 个 bin 时,每个 bin 的样本量较小,会引入视觉噪声。对于这个 1,000 个点的数据集,30 个 bin 产生了正态分布的清晰图像。
自定义 Bin 边界
向 bins 传递一个序列来定义精确的边界:
custom_edges = [0, 20, 35, 50, 65, 80, 100]
plt.hist(data, bins=custom_edges, edgecolor='black', color='steelblue')
plt.title('Histogram with Custom Bin Edges')
plt.xlabel('Value')
plt.ylabel('Frequency')
plt.show()当你的数据具有有意义的阈值时,这很有用——字母等级、年龄段或绩效等级。
自动 Bin 算法
Matplotlib 支持多种算法,可根据数据特征计算最佳 bin 数量:
| 算法 | bins= 值 | 方法 | 最适合 |
|---|---|---|---|
| Sturges | 'sturges' | 1 + log2(n) | 小型、大致正态的数据集 |
| Scott | 'scott' | 基于标准差和 n | 正态或接近正态的数据 |
| Freedman-Diaconis | 'fd' | 基于 IQR 和 n | 对异常值稳健 |
| 平方根 | 'sqrt' | sqrt(n) | 快速粗略估计 |
| Auto | 'auto' | Sturges 和 FD 的最大值 | 通用默认值 |
fig, axes = plt.subplots(1, 3, figsize=(14, 4))
for ax, method in zip(axes, ['sturges', 'scott', 'fd']):
ax.hist(data, bins=method, edgecolor='black', color='#4C72B0')
ax.set_title(f'bins="{method}"')
plt.tight_layout()
plt.show()对于大多数情况,bins='auto' 是一个可靠的起点。当数据包含异常值时,切换到 'fd',因为它使用四分位距而不是标准差。
归一化和密度直方图
默认情况下,y 轴显示原始计数。设置 density=True 来归一化直方图,使柱体下方的总面积等于 1。这将 y 轴从频率转换为概率密度。
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].hist(data, bins=30, edgecolor='black', color='#55A868')
axes[0].set_title('Frequency (default)')
axes[0].set_ylabel('Count')
axes[1].hist(data, bins=30, edgecolor='black', color='#C44E52', density=True)
axes[1].set_title('Density (density=True)')
axes[1].set_ylabel('Probability Density')
plt.tight_layout()
plt.show()密度归一化在你想要叠加理论分布曲线或比较不同大小的数据集时是必不可少的:
from scipy import stats
plt.hist(data, bins=30, density=True, edgecolor='black', color='#55A868', alpha=0.7)
# Overlay the theoretical normal curve
x_range = np.linspace(data.min(), data.max(), 200)
plt.plot(x_range, stats.norm.pdf(x_range, loc=50, scale=15), 'r-', linewidth=2, label='Normal PDF')
plt.legend()
plt.title('Density Histogram with Normal Curve Overlay')
plt.show()自定义外观
颜色、边框颜色和透明度
plt.hist(data, bins=30, color='#4C72B0', edgecolor='white', alpha=0.85)
plt.title('Styled Histogram')
plt.xlabel('Value')
plt.ylabel('Frequency')
plt.show()直方图类型
histtype 参数改变视觉样式:
histtype 值 | 描述 |
|---|---|
'bar' | 传统填充柱体(默认) |
'barstacked' | 多数据集的堆叠柱体 |
'step' | 未填充的线条轮廓 |
'stepfilled' | 带阶梯轮廓的填充区域 |
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
types = ['bar', 'barstacked', 'step', 'stepfilled']
for ax, ht in zip(axes.flat, types):
ax.hist(data, bins=30, histtype=ht, edgecolor='black', color='#4C72B0')
ax.set_title(f'histtype="{ht}"')
plt.tight_layout()
plt.show()'step' 类型在叠加多个分布时特别有用,因为未填充的轮廓不会互相遮挡。
在一个图上绘制多个直方图
重叠直方图
使用 alpha(透明度)来叠加两个或多个分布:
np.random.seed(42)
group_a = np.random.normal(loc=50, scale=10, size=800)
group_b = np.random.normal(loc=65, scale=12, size=800)
plt.hist(group_a, bins=30, alpha=0.6, color='#4C72B0', edgecolor='black', label='Group A')
plt.hist(group_b, bins=30, alpha=0.6, color='#C44E52', edgecolor='black', label='Group B')
plt.legend()
plt.title('Overlapping Histograms')
plt.xlabel('Score')
plt.ylabel('Frequency')
plt.show()并列直方图
传递一个数组列表来使用分组柱体绘制:
plt.hist([group_a, group_b], bins=20, color=['#4C72B0', '#C44E52'],
edgecolor='black', label=['Group A', 'Group B'])
plt.legend()
plt.title('Side-by-Side Histograms')
plt.xlabel('Score')
plt.ylabel('Frequency')
plt.show()当你传递一个数组列表时,matplotlib 会在每个 bin 内将每个数据集的柱体并排放置。
堆叠直方图
设置 stacked=True 将一个数据集堆叠在另一个之上。这同时显示了各个分布及其合并总计。
np.random.seed(42)
freshmen = np.random.normal(loc=68, scale=8, size=500)
sophomores = np.random.normal(loc=72, scale=7, size=400)
juniors = np.random.normal(loc=75, scale=6, size=300)
plt.hist([freshmen, sophomores, juniors], bins=25, stacked=True,
color=['#4C72B0', '#55A868', '#C44E52'], edgecolor='black',
label=['Freshmen', 'Sophomores', 'Juniors'])
plt.legend()
plt.title('Stacked Histogram: Exam Scores by Class Year')
plt.xlabel('Score')
plt.ylabel('Frequency')
plt.show()堆叠直方图在你想展示子组如何对整体分布做出贡献时效果很好。但是,当超过三四个组时,它们就变得难以阅读了。
累积直方图
设置 cumulative=True 来显示值如何从左到右累积。最后一个柱体达到总计数(如果 density=True 则为 1.0)。
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].hist(data, bins=30, cumulative=True, edgecolor='black', color='#DD8452')
axes[0].set_title('Cumulative Histogram (Count)')
axes[0].set_ylabel('Cumulative Count')
axes[1].hist(data, bins=30, cumulative=True, density=True, edgecolor='black', color='#8172B3')
axes[1].set_title('Cumulative Histogram (Density)')
axes[1].set_ylabel('Cumulative Probability')
plt.tight_layout()
plt.show()累积直方图对于回答"低于 60 的值占多少百分比?"这类问题非常有用,可以直接从 y 轴读取。
水平直方图
设置 orientation='horizontal' 来翻转轴。当值标签很长或者你想将直方图放在另一个垂直图表旁边时,这很有用。
plt.hist(data, bins=30, orientation='horizontal', color='#64B5CD', edgecolor='black')
plt.title('Horizontal Histogram')
plt.xlabel('Frequency')
plt.ylabel('Value')
plt.show()plt.hist() 的返回值
plt.hist() 返回三个值,让你可以以编程方式访问直方图数据:
n, bin_edges, patches = plt.hist(data, bins=20, edgecolor='black', color='#4C72B0')
plt.show()
print(f"Bin counts (n): shape = {n.shape}, first 5 = {n[:5]}")
print(f"Bin edges: shape = {bin_edges.shape}, first 5 = {bin_edges[:5]}")
print(f"Patches: {len(patches)} Rectangle objects")| 返回值 | 类型 | 描述 |
|---|---|---|
n | ndarray | 每个 bin 的计数(或密度) |
bin_edges | ndarray | 每个 bin 的边界值(长度 = len(n) + 1) |
patches | Rectangle 列表 | 每个柱体的 matplotlib patch 对象 |
你可以使用 patches 根据高度或位置为各个柱体着色:
n, bin_edges, patches = plt.hist(data, bins=30, edgecolor='black')
# Color bars based on height
for count, patch in zip(n, patches):
if count > 50:
patch.set_facecolor('#C44E52')
else:
patch.set_facecolor('#4C72B0')
plt.title('Conditional Bar Coloring')
plt.show()plt.hist() 常用参数参考
| 参数 | 类型 | 描述 | 默认值 |
|---|---|---|---|
x | array-like | 输入数据 | 必需 |
bins | int、序列或 str | bin 数量、bin 边界或算法名称 | 10 |
range | tuple | bin 的上下限范围 | (x.min(), x.max()) |
density | bool | 归一化使面积等于 1 | False |
weights | array-like | 每个数据点的权重 | None |
cumulative | bool | 计算累积直方图 | False |
histtype | str | 'bar'、'barstacked'、'step'、'stepfilled' | 'bar' |
orientation | str | 'vertical' 或 'horizontal' | 'vertical' |
color | color 或列表 | 柱体颜色 | None |
edgecolor | color | 柱体边框颜色 | None |
alpha | float | 透明度(0 到 1) | None |
label | str | 图例标签 | None |
stacked | bool | 堆叠多个数据集 | False |
log | bool | 对数 y 轴 | False |
rwidth | float | 柱体的相对宽度(0 到 1) | None |
bottom | array-like 或标量 | 每个柱体的基线 | 0 |
plt.hist() 与 sns.histplot():何时使用哪个
如果你同时使用 seaborn 和 matplotlib,你可能会想用哪个直方图函数。这里是一个直接对比:
| 特性 | plt.hist() | sns.histplot() |
|---|---|---|
| 库 | matplotlib | seaborn |
| 输入类型 | Array、列表、Series | Array、Series、DataFrame 列 |
| KDE 叠加 | 手动(需要 scipy) | 内置(kde=True) |
| 默认样式 | 最小化 | 出版品质 |
| 多组数据 | 传递数组列表 | hue 参数 |
| 统计选项 | 计数、密度 | 计数、密度、频率、概率、百分比 |
| Bin 算法 | sturges、scott、fd、sqrt、auto | auto、fd、doane、scott、stone、rice、sturges、sqrt |
| 对数刻度 | log=True | log_scale=True |
| 分类轴 | 不支持 | 通过 hue 支持 |
| 性能(大数据) | 更快 | 略慢 |
| 自定义深度 | 完整的 matplotlib API | seaborn + matplotlib API |
使用 plt.hist() 的场景:需要完全控制每个视觉元素时、使用子图时、或 seaborn 不可用时。使用 sns.histplot() 的场景:需要 KDE 叠加、更整洁的默认样式、或需要用最少的代码按分类变量拆分数据时。
使用 PyGWalker 创建交互式直方图
静态直方图非常适合报告和脚本,但在探索性数据分析过程中,你经常需要更改 bin、过滤子集,以及在图表类型之间快速切换。PyGWalker (opens in a new tab) 是一个开源 Python 库,可以将任何 pandas 或 polars DataFrame 转换为 Jupyter Notebook 中的交互式拖放可视化界面——无需前端代码。
pip install pygwalkerimport pandas as pd
import pygwalker as pyg
# Load your dataset into a DataFrame
df = pd.DataFrame({
'score': np.random.normal(70, 12, 2000),
'group': np.random.choice(['A', 'B', 'C'], 2000)
})
# Launch the interactive UI
walker = pyg.walk(df)界面打开后,将 score 拖到 x 轴,PyGWalker 会自动生成直方图。你可以调整 bin 大小、使用颜色编码按 group 分割、切换到密度模式,以及导出生成的图表——所有这些都无需编写额外的代码。当你需要在为报告编写最终 matplotlib 代码之前快速探索多个变量时,这特别有用。
常见问题
如何为 matplotlib 直方图选择合适的 bin 数量?
从 bins='auto' 开始,它使用 Sturges 和 Freedman-Diaconis 方法的最大值。对于有异常值的数据,使用 bins='fd'。对于小数据集(200 个点以下),bins='sturges' 效果不错。你也可以传递一个整数并通过目测调整:如果分布看起来过于平滑,就增加数量;如果柱体看起来噪声太多,就减少数量。
plt.hist() 中 density=True 和 cumulative=True 有什么区别?
density=True 归一化直方图,使所有柱体下方的总面积等于 1,将 y 轴转换为概率密度。cumulative=True 使每个柱体表示所有前面柱体的总和加上自身。你可以将两者结合使用:density=True, cumulative=True 生成一个累积分布函数,其中最后一个柱体达到 1.0。
如何在 matplotlib 中叠加两个直方图?
使用相同的 bins 值调用 plt.hist() 两次,并将 alpha 设置为小于 1 的值(例如 0.5 或 0.6),这样两个分布都保持可见。为每次调用添加 label,并以 plt.legend() 结束。使用 histtype='step' 作为替代方案,可以完全避免透明度的需要,因为它只绘制轮廓。
plt.hist() 能直接处理 pandas Series 和 DataFrame 列吗?
可以。plt.hist() 接受任何 array-like 输入,包括 pandas Series。你可以直接传递 df['column_name']。要使用 pandas 的内置方法从 DataFrame 绘图,请使用 df['column_name'].plot.hist(bins=30),它在底层使用 matplotlib。
如何将 matplotlib 直方图保存为图片文件?
调用 plt.hist() 后,在 plt.show() 之前使用 plt.savefig('histogram.png', dpi=150, bbox_inches='tight')。bbox_inches='tight' 参数可防止标签被裁切。支持的格式包括 PNG、PDF、SVG 和 EPS。