Python在多子图布局中嵌入饼状图的方法
深入解析在Python中利用Matplotlib于多子图布局中嵌入饼状图的完整方法,涵盖plt.subplots与GridSpec两种布局方案、等比例坐标轴控制、标签重叠防范及完整可运行代码示例。
ax.pie() 渲染扇区,并配合 ax.axis('equal') 强制锁定等比例缩放,即可有效解决饼图在子图网格内被拉伸为椭圆、标签相互覆盖以及多图层叠混乱的常见问题。
一、多子图环境下饼状图的核心机制与变形根源
常规直角坐标图表(如折线图、柱状图、散点图)在多子图网格中根据窗口尺寸自由拉伸宽高比例,通常不会破坏数据所表达的信息。然而,饼状图属于依赖极坐标几何形态表达占比关系的图形。当我们在多子图环境中绘制饼图时,Matplotlib默认会将数据轴范围映射为子图的矩形边界。如果该子图容器的物理宽高比不是严格的1:1,生成的圆形饼图就会不可避免地被横向或纵向拉伸成“椭圆形饼图”。
为了防止这种几何失真,必须向承载饼图的特定子图对象显式声明等比例约束。调用 ax.axis('equal') 可以通知绘图引擎将X轴与Y轴的数据单位长度保持严格相等,确保圆形轮廓在任何子图尺寸缩落下均维持规整圆度。
二、方法一:使用 plt.subplots 实现规则网格嵌入
当页面布局呈现规律的行列排布时(例如 1行2列 或 2行2列),plt.subplots() 是最简洁易维护的方案。该方法一次性生成画布对象 fig 与子图数组 axes。在编写代码时,需要特别注意以下标准执行步骤:
以下提供完整的可执行代码示例。我们在一个 1行2列 的多子图画布中,左侧绘制季度营收柱状图,右侧嵌入对应的业务线占比饼状图:
import matplotlib.pyplot as plt
# 1. 准备示例数据
quarters = ['Q1', 'Q2', 'Q3', 'Q4']
revenue = [120, 160, 140, 210]
departments = ['云原生服务', '智能硬件', '企业软件', '技术咨询']
dept_ratios = [35, 25, 25, 15]
colors = ['#394957', '#AD8B58', '#6A7F93', '#D6C8B2']
# 2. 创建 1行2列 的多子图画布
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 3. 在左侧子图 (axes[0]) 绘制常规柱状图
axes[0].bar(quarters, revenue, color='#394957', width=0.55)
axes[0].set_title('各季度整体营收走势(万元)', fontsize=12, pad=10)
axes[0].set_ylabel('营收额')
axes[0].grid(axis='y', linestyle='--', alpha=0.4)
# 4. 在右侧子图 (axes[1]) 嵌入饼状图
wedges, texts, autotexts = axes[1].pie(
dept_ratios,
labels=departments,
autopct='%1.1f%%',
startangle=140,
colors=colors,
pctdistance=0.75
)
# 5. 关键设定:强制右侧子图保持正圆并设置标题
axes[1].axis('equal')
axes[1].set_title('全年业务线收入占比构成', fontsize=12, pad=10)
# 美化饼图内部百分比文字样式
for autotext in autotexts:
autotext.set_color('white')
autotext.set_fontsize(10)
# 6. 自适应紧凑排版
plt.tight_layout()
plt.show()
三、方法二:使用 GridSpec 实现非对称多子图混排
在实际数据分析看板中,各类图表往往需要占据不同大小的版面。例如,左侧需要一张较宽的时间序列折线图,右侧需要垂直堆叠两张小型饼状图。此时规则的 plt.subplots 无法直接满足跨行或跨列需求,必须引入 matplotlib.gridspec.GridSpec。
GridSpec 允许将画布视作一个具有指定行数与列数的几何网格,通过类似 NumPy 数组切片的语法(例如 gs[:, 0] 表示左侧整列,gs[0, 1] 表示右上单元格),分别创建尺寸各异的子图坐标轴。
下面是基于 GridSpec 构建复合看板的具体实现代码:
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
fig = plt.figure(figsize=(13, 6))
# 定义 2行3列 的网格系统,分配宽度比例
gs = GridSpec(nrows=2, ncols=3, figure=fig, width_ratios=[2, 1, 1])
# 1. 主时序图:占据第 0 至 1 行的前 2 列 (gs[:, 0:2])
ax_main = fig.add_subplot(gs[:, 0:2])
months = [f'{i}月' for i in range(1, 11)]
trends = [45, 52, 58, 63, 60, 68, 75, 82, 89, 94]
ax_main.plot(months, trends, marker='o', color='#394957', linewidth=2)
ax_main.set_title('核心业务指标月度增长趋势', fontsize=12)
ax_main.grid(True, linestyle=':', alpha=0.6)
# 2. 嵌入右上角饼图:第 0 行第 2 列 (gs[0, 2])
ax_pie1 = fig.add_subplot(gs[0, 2])
device_labels = ['移动端', '桌面端', '平板']
device_data = [62, 28, 10]
ax_pie1.pie(device_data, labels=device_labels, autopct='%1.0f%%',
colors=['#394957', '#AD8B58', '#A2B0BC'], startangle=90)
ax_pie1.axis('equal')
ax_pie1.set_title('访问渠道终端分布', fontsize=11)
# 3. 嵌入右下角环形饼图:第 1 行第 2 列 (gs[1, 2])
ax_pie2 = fig.add_subplot(gs[1, 2])
user_labels = ['新访客', '留存用户']
user_data = [42, 58]
# 通过 wedgeprops 设置内圈挖空形成环形图
ax_pie2.pie(user_data, labels=user_labels, autopct='%1.0f%%',
colors=['#AD8B58', '#5C6D7E'], startangle=45,
wedgeprops=dict(width=0.45, edgecolor='w'))
ax_pie2.axis('equal')
ax_pie2.set_title('新老用户比例构成', fontsize=11)
plt.tight_layout()
plt.show()
四、细节控制与常见排版陷阱排查
1. 避免使用全局 plt.pie()
在多子图场景中,直接调用 plt.pie() 是最容易导致逻辑混乱的错误做法。plt.pie() 隐式作用于“当前激活坐标轴”(Current Axes)。一旦子图生成顺序调整或引入其他绘图语句,饼图很可能会绘制到错误的子图甚至覆盖原有的柱状图。必须始终严格调用具体子图句柄的方法:ax.pie()。
2. 扇区百分比与类别标签防重叠处理
当子图尺寸较小时,饼图边缘的外置文字标签很容易与邻近子图或画布边框发生裁切碰撞。针对这一问题,通常有两项标准解决策略:
- 使用图例(Legend)替代外周文本:不向
ax.pie()传入labels参数,而是将标签传递给ax.legend(loc='center left', bbox_to_anchor=(1, 0.5)),将图例文本放置在饼图侧边。 - 微调百分比距离:通过调节
pctdistance参数(默认约为0.6),将数值百分比放置在扇区中段偏内位置,避免与扇区边缘的标签挤压。
plt.tight_layout() 后仍发现右侧外置图例被画布截断,可以在代码末尾使用 fig.subplots_adjust(right=0.82) 显式为右侧图例留出安全边距,或者在保存时声明 plt.savefig('output.png', bbox_inches='tight')。
五、方法选型与最佳实践总结
在多子图布局中成功嵌入饼状图的关键并不在于复杂的样式修饰,而在于清晰的层次拆解与严谨的对象控制。理解何时使用直接等分网格、何时依赖动态切分系统,能够大幅减少反复调试样式的时间。
综上所述,在多子图布局中嵌入饼状图并不复杂。只要建立起明确的坐标轴对象意识,在布局切分、对象定位、等比锁定与重叠预防四个维度上做好规范化控制,即可稳定产出专业、美观的数据可视化作品。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









