Pandas教学-21:subplots的使用

先看四种最常用的子图布局结构,然后给出对应的 6 个案例代码。

先放一段所有案例共用的"数据准备 + 中文显示"前置代码,之后每个案例只写核心部分。

python 复制代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 中文显示两行标配:指定中文字体 + 修复负号乱码
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False

# 示例数据:12个月的销售流水(模拟)
np.random.seed(42)
月份 = np.arange(1, 13)
df = pd.DataFrame({
    "月份": 月份,
    "销售额": np.random.randint(80, 200, 12),
    "订单量": np.random.randint(300, 800, 12),
    "退货量": np.random.randint(10, 60, 12),
})
df["利润"] = (df["销售额"] * 0.2 - df["退货量"] * 0.05).round(1)
df["单价"] = (df["销售额"] * 1000 / df["订单量"]).round(1)

案例 1:一行两列 ------ 最基础的并排对比

返回值是 fig, (ax1, ax2),一维时用元组解包最顺手。

python 复制代码
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))

ax1.plot(df["月份"], df["销售额"], marker="o", color="steelblue")
ax1.set_title("销售额趋势")          # 子图设置标题用 ax.set_title,不是 plt.title

ax2.bar(df["月份"], df["订单量"], color="darkseagreen")
ax2.set_title("订单量分布")

fig.suptitle("月度经营概览")          # 整张大图的标题用 fig.suptitle
plt.tight_layout()                   # 自动收紧,防止标签互相遮挡
plt.show()

要点 :plt. 前缀管"整张图",ax. 前缀管"某个子图"------写多子图时一律改用面向对象的 ax. 写法。

案例 2:两行两列 ------ axes 变二维数组

行、列都大于 1 时,axes 是 2×2 的数组,用 axes[行, 列] 索引。

python 复制代码
fig, axes = plt.subplots(2, 2, figsize=(10, 8))

axes[0, 0].plot(df["月份"], df["销售额"], color="steelblue")
axes[0, 0].set_title("销售额")

axes[0, 1].hist(df["利润"], bins=8, color="salmon", edgecolor="white")
axes[0, 1].set_title("利润分布")

axes[1, 0].scatter(df["订单量"], df["销售额"], color="darkseagreen")
axes[1, 0].set_title("订单量 vs 销售额")

axes[1, 1].barh(df["月份"].astype(str), df["退货量"], color="gray")
axes[1, 1].set_title("退货量(横向条形图)")

plt.tight_layout()
plt.show()

要点:一张图里放了折线、直方图、散点、横向条形四种图型,讲清"子图就是各自的独立画布"。

案例 3:sharex 共享横轴 ------ 价格在上、成交量在下

股票软件的经典布局:两个子图共用一条 x 轴,缩放平移联动。柱状图按涨跌涂色(红涨绿跌)。

python 复制代码
收盘价 = pd.Series([100, 102, 101, 105, 103, 108, 107, 110, 109, 112, 111, 115])
涨跌 = 收盘价.diff()                       # 与前一天差值,正涨负跌

fig, (ax1, ax2) = plt.subplots(
    2, 1, figsize=(10, 6), sharex=True,   # 共享 x 轴
    gridspec_kw={"height_ratios": [2, 1]} # 上图高度是下图 2 倍
)

ax1.plot(月份, 收盘价, marker="o", color="dimgray")
ax1.set_title("收盘价走势")

颜色 = ["red" if v >= 0 else "green" for v in 涨跌.fillna(0)]   # 红涨绿跌
ax2.bar(月份, df["订单量"], color=颜色)
ax2.set_title("成交量(红=上涨日,绿=下跌日)")
ax2.set_xlabel("月份")                    # x 轴标签只标在最下面一张

plt.tight_layout()
plt.show()

要点 :sharex=True 后上图的 x 刻度标签自动隐藏;gridspec_kw={"height_ratios"} 控制上下高度比。

案例 4:子图比数据多 ------ 隐藏多余画布

3 组数据放进 2×2 的网格,第 4 个空位用 axis("off") 关掉,画布才干净。

python 复制代码
指标 = ["销售额", "订单量", "利润"]
fig, axes = plt.subplots(2, 2, figsize=(10, 8))

for ax, 列名 in zip(axes.flat, 指标):     # axes.flat 把二维数组摊平成一维遍历
    ax.plot(df["月份"], df[列名], marker="o")
    ax.set_title(列名)

axes[1, 1].axis("off")                    # 关掉右下角空白子图的坐标轴

fig.suptitle("三项指标巡览")
plt.tight_layout()
plt.show()

要点 :axes.flat(或 axes.ravel())是循环遍历所有子图的标准姿势;ax.axis("off") 隐藏空位。

案例 5:subplot_mosaic ------ 一大两小的仪表盘布局

plt.subplots 只能切等大的格子;要让主图占左侧整列、两个辅图叠在右边,用 subplot_mosaic 按"字母草图"排版。

python 复制代码
fig, axes = plt.subplot_mosaic(
    [["主图", "辅图1"],
     ["主图", "辅图2"]],
    figsize=(10, 6)
)

axes["主图"].plot(df["月份"], df["销售额"], marker="o", color="steelblue")
axes["主图"].set_title("销售额主图(占左侧整列)")

axes["辅图1"].hist(df["利润"], bins=8, color="salmon")
axes["辅图1"].set_title("利润分布")

axes["辅图2"].scatter(df["订单量"], df["销售额"], color="darkseagreen")
axes["辅图2"].set_title("量额关系")

plt.tight_layout()
plt.show()

要点 :网格里相同字母自动合并成一个子图,返回值是字典------用名字取子图,比数字下标更不易错。

案例 6:批量生成 + 保存 ------ DataFrame 有多少列就画多少张

真实工作场景:列数不固定,按列数自动算行列数,最后 savefig 存成图片文件。

python 复制代码
数据列 = ["销售额", "订单量", "退货量", "利润", "单价"]
n = len(数据列)
行数 = (n + 2) // 3                        # 每行 3 张,自动计算需要几行

fig, axes = plt.subplots(行数, 3, figsize=(12, 4 * 行数))
axes = np.atleast_2d(axes)                 # 只有1行时保证仍能 axes[i, j] 索引

for i, 列名 in enumerate(数据列):
    ax = axes[i // 3, i % 3]               # 一维序号换算成二维位置
    ax.plot(df["月份"], df[列名], marker="o")
    ax.set_title(列名)

for j in range(n, 行数 * 3):               # 多出来的空位全部关掉
    axes[j // 3, j % 3].axis("off")

fig.suptitle("全指标巡览")
plt.tight_layout()
plt.savefig("全指标巡览.png", dpi=150)     # 存图放在 show() 之前
plt.show()

要点 :i // 3, i % 3 是"序号 → 行列位置"的通用换算;savefig 必须在 show() 之前调用,否则保存的是空白图。


速查对照:

需求 写法
等分网格 plt.subplots(行, 列)
一维解包 fig, (ax1, ax2) = plt.subplots(1, 2)
二维索引 axes[i, j]
共享轴 sharex=True / sharey=True
高度比例 gridspec_kw={"height_ratios": [2, 1]}
不等分合并 plt.subplot_mosaic(...)
遍历子图 axes.flat
隐藏空位 ax.axis("off")
整图标题 / 子图标题 fig.suptitle / ax.set_title
防遮挡 / 保存 tight_layout() / savefig(...)

多个子图,读出的顺序,按"从左到右、从上到下"的行优先顺序(先行后列) 。看个 2×3 的直观编号,再讲为什么。

答案:行优先(先行后列)

以案例 4 / 案例 6 里的写法为例:

python 复制代码
fig, axes = plt.subplots(2, 3)
for ax, 列名 in zip(axes.flat, 数据列):
    ...

配对结果一定是:

text 复制代码
序号0 → axes[0,0] ←→ 数据列[0]   左上
序号1 → axes[0,1] ←→ 数据列[1]   中上
序号2 → axes[0,2] ←→ 数据列[2]   右上
序号3 → axes[1,0] ←→ 数据列[3]   左下
序号4 → axes[1,1] ←→ 数据列[4]   中下
序号5 → axes[1,2] ←→ 数据列[5]   右下

为什么是这个顺序

axes 本质是一个 NumPy 数组,axes.flat(以及 axes.ravel()、axes.flatten())默认按 C 语言风格的行主序(row-major) 展开------先把第 0 行的所有列走完,再进入下一行。zip 只是按两个序列各自的顺序一一配对,所以子图的"出厂顺序"决定了配对结果。

python 复制代码
fig, axes = plt.subplots(2, 3)
print([tuple(x) for x in np.ndindex(axes.shape)])  # 显式查看遍历顺序
# [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)]

三个相关细节

  1. axes.flat / ravel() / flatten() 顺序一致,可以互换用;想验证就打印:
python 复制代码
for i, ax in enumerate(axes.flat):
    ax.set_title(f"序号{i}")     # 跑一遍,编号直接画在图上
  1. zip 以短的为准 :数据列只有 5 个而子图有 6 个时,第 6 个空位不会被遍历到------所以案例 6 里要单独再循环一次把 axes[1,2] 关掉。

  2. 例外是 subplot_mosaic :它返回的是字典不是数组,不能用 zip 配位置,要用名字取:

python 复制代码
axes["主图"].plot(...)   # 按字母草图里的名字取,与顺序无关

想要列优先(先列后行)的顺序,也有办法:axes.T.flat(转置后再摊平)或 axes.flatten(order="F")。

相关推荐
AI你一生一世1 小时前
当推理遇上晶圆级引擎:拆解 GPT-5.6 Sol 超快推理背后的系统权衡
系统优化·1024程序员节·大模型推理·晶圆级引擎·延迟与吞吐·内存带宽·gpt-5.6 sol
device_Any1 小时前
容量评估:峰值、均值,还有被漏掉的余量
1024程序员节
码士集团小青1 小时前
面霸篇:高频 Java 基础问题(核心卷一)
linux·服务器·数据库·redis·缓存·1024程序员节
2601_967980111 小时前
Python开发的顶级开源项目全景图:2025-2026年生态概览
python·机器学习·开源项目·数据科学·生态概览
一级新生1 小时前
提示词工程:跟 AI 打交道的手艺
人工智能·大模型·dify·1024程序员节·提示词工程
无所谓จุ๊บ1 小时前
02_折扣机制与选型建议
1024程序员节
程序猿乐锅1 小时前
Python 面向对象案例:学生成绩管理系统
1024程序员节
slacker-kian1 小时前
[工程]:智能招聘--一个可组装的人才智能推荐系统
ai·ai编程·向量数据库·1024程序员节·相似性搜索·人力推荐系统
热心网友俣先生1 小时前
2026年MathorCup大数据竞赛-B题数据集分享
数学建模·1024程序员节