Python饼状图扇区过多导致重叠怎么处理

Ada
Ada
发布时间:2026-11-12| 4人浏览过
原创

详细解析在Python中使用Matplotlib绘制饼状图时,因分类过多、小数值占比过低导致扇区挤压与文字重叠的机理与解决方法,涵盖Top N合并为“其它”、引线标注与图例分离、双层子母饼图以及转为横向条形图等完整实现与代码示例。

Python 数据可视化 · 实战避坑指南
适用工具:Python 3.x / Matplotlib / Pandas • 场景:类别繁多、长尾细分项重叠治理 • 阅读时间:约 8 分钟
Python饼状图扇区过多导致重叠怎么处理
图 0:Matplotlib 饼图扇区重叠现象与治理前后的直观对比
核心结论与快速解决路径:在 Python 中使用 Matplotlib 的 plt.pie() 绘制饼状图时,若分类超过 7 项或存在单项占比低于 3% 的情况,极坐标下微小的扇区夹角必然导致数值文本与类别标签发生严重重叠。解决这一问题的首选方案是在数据预处理阶段,通过 Pandas 将占比靠后的长尾项归并为“其它”(Other);若业务要求必须完整保留每个明细类别,则应使用 ax.annotate() 配合扇区中心角计算实现外部折线引线标注;若需同时兼顾整体大盘与长尾明细,可构建子母饼图(Bar/Pie of Pie);而当类别超过 10 项且无明显主次时,最规范的做法是直接改用横向条形图(plt.barh())。

一、问题表现与发生机理:为什么饼图扇区多了必然会重叠?

许多初学者在遇到饼图标签重叠时,第一反应往往是减小字号(fontsize=8)或增大画布尺寸(figsize=(12, 12))。但稍作尝试就会发现,当数据项达到 10 个以上时,靠近底部的微小扇区依然挤成一团黑块,甚至标签文字直接叠置在一起。这并不是 Matplotlib 的 Bug,而是极坐标系下的几何必然结果。

要彻底解决重叠,先要理解 Matplotlib 绘制饼图的两个底层逻辑:

  • 极坐标弧长挤压:饼图各扇区的圆心角公式为 θ = 360° × (数值 / 总和)。若某细分项占比仅有 1.2%,其分配到的圆心角仅有 4.32°。在此角度下,即使半径很大,扇区对应的外弧弦长也只有几像素至十几像素宽。
  • 文本包围盒(Bounding Box)物理重叠:Matplotlib 的 autopct 默认沿着各扇区角平分线、在固定比例半径(默认约为 0.6R)处放置文字。一个形如 "1.2%" 的文字块在屏幕上至少需要 35~50 像素的宽度。当两个相邻小扇区的角平分线在 0.6R 处的物理间距只有 8 像素时,两个 40 像素的矩形文本框便必然发生交叉相撞。
Python饼状图扇区过多导致重叠怎么处理
图 1:长尾数据在极坐标映射与固定半径文字排版下的几何碰撞机理

二、四步排查与处理流程

面对扇区过多引发的重叠,不需要凭感觉盲目微调参数,可以遵循一套标准化的工程排查处理流程:

  1. 第一步:阈值量化诊断。统计总类别数 N 以及各分类占比。只要 N > 7 或存在占比 < 3% 的类别,立刻判定为重叠高风险。
  2. 第二步:数据层归并聚合。业务是否强制要求在饼图内部看清所有微小碎项?若不需要,取前 5 大核心项,其余长尾统一汇总为“其它”。
  3. 第三步:空间调度与排版微调。对于保留下的 5~7 个扇区,微调 pctdistance(百分比距离圆心比例)与 labeldistance(标签距离圆心比例),或将文本标签移至图例(plt.legend())。
  4. 第四步:效果评估与图表升迁。若必须审查每个微小项的独立变动,果断放弃单饼图形态,升迁为子母图或横向条形图。
Python饼状图扇区过多导致重叠怎么处理
图 2:解决 Python 饼图扇区重叠的四步标准化流程

三、方案一:数据层治理——将低占比长尾项归并为“其它”(最推荐)

在信息图表设计规范中,饼图的核心使命是展示“部分与整体的关系”,最适宜传达的是“占据大头的是哪几家”。过多的长尾碎末不仅造成视觉拥堵,更会分散读者的注意力。因此,把占比小的项收敛为“其它”,是业界最常用、效果最清爽的解法。

1. 完整代码示例

下面的代码展示了从原始长尾数据生成、阈值判定、动态聚合到完成规范饼图绘制的全流程:

import matplotlib.pyplot as plt
import pandas as pd

# 1. 确保中文字体正常显示
plt.rcParams['font.sans-serif'] = ['PingFang SC', 'Microsoft YaHei', 'SimHei', 'sans-serif']
plt.rcParams['axes.unicode_minus'] = False

# 2. 构造具有长尾特性的模拟数据(共12个品类,存在大量微小项)
raw_data = {
    'category': ['电子产品', '服饰内衣', '日用百货', '食品生鲜', '美妆个护', 
                 '家居家装', '母婴玩具', '运动户外', '汽配摩托', '图书文娱', '医药保健', '虚拟服务'],
    'sales': [4200, 2600, 1500, 1100, 800, 320, 210, 180, 140, 110, 80, 60]
}
df = pd.DataFrame(raw_data)

# 3. 动态归并:保留前 5 项,其余归并为“其它”
TOP_N = 5
df_sorted = df.sort_values(by='sales', ascending=False).reset_index(drop=True)

df_top = df_sorted.iloc[:TOP_N].copy()
other_sales = df_sorted.iloc[TOP_N:]['sales'].sum()

# 构建包含“其它”的新数据行
df_final = pd.concat([
    df_top,
    pd.DataFrame([{'category': '其它 (包含{}项)'.format(len(df_sorted) - TOP_N), 'sales': other_sales}])
], ignore_index=True)

# 4. 绘制规范饼图
fig, ax = plt.subplots(figsize=(8, 6), facecolor='#FFFFFF')
colors = ['#6B88A6', '#71A397', '#8CA7C4', '#8EC3B6', '#A3BFD9', '#D9E2EC']

wedges, texts, autotexts = ax.pie(
    df_final['sales'],
    labels=df_final['category'],
    autopct='%1.1f%%',
    startangle=140,
    colors=colors,
    pctdistance=0.75,          # 百分比数值置于扇区 75% 半径处
    labeldistance=1.08,        # 标签放置于饼图外缘略微偏移处
    wedgeprops={'edgecolor': '#FFFFFF', 'linewidth': 1.5, 'antialiased': True}
)

# 统一文本样式
plt.setp(texts, size=11, color='#334155')
plt.setp(autotexts, size=10, weight='bold', color='#FFFFFF')
# 将“其它”扇区(颜色浅)的百分比文字设为深色以便阅读
autotexts[-1].set_color('#1E293B')

ax.set_title('各品类销售额占比分布(Top 5 归并展示)', fontsize=14, pad=20, weight='bold', color='#1E293B')
plt.tight_layout()
plt.show()
执行输出效果验证(示例):
原始 12 个杂乱分类被精炼为 6 个规整扇区(电子产品 36.8%、服饰内衣 22.8%、日用百货 13.1%、食品生鲜 9.6%、美妆个护 7.0%、其它 10.7%)。所有文本包围盒互相远离,不再有任何压叠。
💡 关键参数技巧:pctdistance 与 startangle
调整 startangle=140(顺时针旋转起始角)可以避开饼图顶部或左右两侧的标题与留白干涉;而将 pctdistance 设置为 0.70~0.80,能使百分比文字远离逼仄的圆心三角区,获得更大的容纳空间。

四、方案二:排版层治理——几何外置引线标注法(必须保留全部明细)

在某些严谨的审计、合规或学术汇报中,业务方明确要求不能汇总掩盖任何一个明细分类,哪怕占比只有 0.5% 也必须在图中标出名字。此时,直接将标签文字全部外置,并通过折线引线(Callout lines)与对应扇区建立连接,是解决文字重合的有效排版手段。

1. 外部引线算法原理

通过遍历 ax.pie() 返回的 wedges 对象,可以获取每个扇区的起始角度(theta1)与终止角度(theta2)。其中心角为 ang = (theta2 - theta1) / 2 + theta1。根据三角函数:

  • 扇区内测起点:x1 = r × cos(ang), y1 = r × sin(ang)(通常取 r = 1.0 边缘);
  • 外部拐点与标签点:取 r = 1.25 甚至更大半径,根据角度位于圆心左右侧(cos(ang) > 0),将文本对齐方式(horizontalalignment)分别设为 'left' 与 'right'。

2. 引线标注实现代码

import numpy as np
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['PingFang SC', 'Microsoft YaHei', 'SimHei', 'sans-serif']
plt.rcParams['axes.unicode_minus'] = False

categories = ['核心架构', '业务模块', '缓存层', '日志分析', '定时任务', '消息网关', '监控采集', '安全校验']
weights = [38, 24, 14, 8, 5, 4, 4, 3]

fig, ax = plt.subplots(figsize=(9, 6), subplot_kw=dict(aspect="equal"))
colors = ['#6B88A6', '#71A397', '#8CA7C4', '#8EC3B6', '#A3BFD9', '#CBD5E1', '#94A3B8', '#B0C4DE']

# 绘制纯色块饼图,不渲染内置标签
wedges, _ = ax.pie(
    weights,
    colors=colors,
    startangle=60,
    wedgeprops=dict(width=0.6, edgecolor='white', linewidth=1.5)  # 环形饼图提升空间层次
)

# 设定标注引线配置
bbox_props = dict(boxstyle="square,pad=0.3", fc="w", ec="#CBD5E1", lw=0.72)
kw = dict(arrowprops=dict(arrowstyle="-", color="#64748B", lw=1),
          bbox=bbox_props, zorder=0, va="center")

for i, p in enumerate(wedges):
    ang = (p.theta2 - p.theta1) / 2. + p.theta1
    y = np.sin(np.deg2rad(ang))
    x = np.cos(np.deg2rad(ang))
    
    # 动态判断标签位于图表左侧还是右侧
    horizontalalignment = "left" if x >= 0 else "right"
    connection_style = f"angle,angleA=0,angleB={ang}"
    kw["arrowprops"].update({"connectionstyle": connection_style})
    
    # 将文本外移至 1.35 倍半径处
    ax.annotate(
        f"{categories[i]}: {weights[i]}%",
        xy=(x, y),
        xytext=(1.35 * np.sign(x), 1.25 * y),
        horizontalalignment=horizontalalignment,
        fontsize=10.5,
        color='#1E293B',
        **kw
    )

ax.set_title("微服务各模块资源开销占比(外置引线标注)", fontsize=13, weight='bold', pad=25)
plt.tight_layout()
plt.show()
⚠️ 注意事项:分类若超过 10 个,引线也会发生交叉!
外置引线法仅能缓解 7~9 个中等规模分类的冲突。如果细分项达到 12 个以上,不仅扇区本身辨识度降为零,外部引线也会像蜘蛛网一样发生连线交叉与标签上下挤压,此时应当立即改用方案三或方案四。

五、方案三:结构层治理——子母图联动拆解(兼顾大盘与长尾细节)

如果业务场景既需要宏观展示前三大板块的主导地位,又必须深入剖析“其它”大类内部 4~5 个小项目的详细占比,构建子母饼图(Bar of Pie / Pie of Pie)是最高阶且符合报表审美的解法。

在 Matplotlib 中,可以通过构建一个具有两个子图的画布(fig, (ax1, ax2) = plt.subplots(1, 2)):

  • 左图(主饼图):展示 Top 3 核心分类与聚合后的“长尾其它项”;
  • 右图(分解条形图):将左侧“长尾其它项”在条形图中 100% 纵向平铺展开;
  • 连接梯形连线:利用 matplotlib.patches.ConnectionPatch 从主饼图的“其它”扇区上下两角连接到右侧条形图的顶底边,建立直观的下钻视觉引导。

六、方案四:视觉选型替代——何时果断放弃饼状图?

在数据可视化科学中,图形的选择应当服务于人类的认知心理学。人眼对“二维面积”与“角度”的感知精度,天然远逊于对“一维长度与垂直对齐位置”的感知精度。

当满足以下任意条件时,应停止在饼图上耗费精力调参,果断改用横向条形图(Horizontal Bar Chart):

  • 独立类别总数超过 10 个;
  • 多项分类的数值极其接近(例如 4.2%、4.5%、4.8%),在饼图中肉眼无法分辨差异;
  • 重点在于对比各分类之间的严格排序,而非强调各自占总体的份额。

横向条形图最佳实践代码

import matplotlib.pyplot as plt
import pandas as pd

plt.rcParams['font.sans-serif'] = ['PingFang SC', 'Microsoft YaHei', 'SimHei', 'sans-serif']
plt.rcParams['axes.unicode_minus'] = False

# 包含 14 个长尾细项的数据
data = {
    'channel': ['搜索引擎', '直接访问', '社交分享', '信息流广告', '外部友情链接', 
                '电子邮件', '应用市场', '短信推送', 'KOL推广', '线下扫码', '合作伙伴', '裂变活动', '社区论坛', '其它渠道'],
    'users': [5200, 3100, 2400, 1800, 950, 720, 680, 510, 430, 320, 290, 210, 160, 90]
}
df = pd.DataFrame(data).sort_values(by='users', ascending=True)  # 升序排列,使最大项位于最上方

fig, ax = plt.subplots(figsize=(8, 6), facecolor='#FFFFFF')
bars = ax.barh(df['channel'], df['users'], color='#6B88A6', height=0.65, edgecolor='none')

# 高亮前三名条目
for bar in bars[-3:]:
    bar.set_color('#71A397')

# 在条形右侧直接标注数值与百分比,免除查图例的折返成本
total_users = df['users'].sum()
for bar in bars:
    w = bar.get_width()
    pct = (w / total_users) * 100
    ax.text(w + 80, bar.get_y() + bar.get_height()/2, f'{w:,} ({pct:.1f}%)', 
            va='center', fontsize=9.5, color='#475569')

# 隐藏多余外边框,保持现代清爽排版
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_color('#CBD5E1')
ax.spines['bottom'].set_color('#CBD5E1')
ax.xaxis.grid(True, linestyle='--', alpha=0.5, color='#E2E8F0')
ax.set_axisbelow(True)

ax.set_title('各渠道新增获客量与占比分布(条形图替代方案)', fontsize=13, weight='bold', pad=15)
plt.tight_layout()
plt.show()
方案对比收益(示例):
横向条形图纵向按纸张从上到下自然扫描,分类名称水平排列,容纳 15~30 个分类毫无压力;无需任何计算即可精确对比微小项之间的长度差异。
Python饼状图扇区过多导致重叠怎么处理
图 3:四种应对策略在适用分类数、细节保留度、认知成本与实现难度上的综合对比

七、总结与选型决策指南

处理 Python 饼状图扇区重叠并非单纯的“格式调优”,而是一个从数据清洗、视觉编码到图表选型的系统化决策过程。在实际业务中,可以根据以下决策树快速确定技术方案:

Python饼状图扇区过多导致重叠怎么处理
图 4:Python 饼图扇区重叠治理全景与选型决策指南

常见避坑要点速查:

  • 严禁滥用 explode(扇区爆破):许多人试图通过 explode=[0.1]*len(data) 把扇区炸开以求拉开距离,但这会导致饼图半径缩水、长尾扇区边缘互相撕裂,重叠反而更加凌乱。
  • 杜绝使用 3D 饼图:3D 倾斜视角引入了严重的几何透视畸变,前排扇区被夸大、后排扇区被压缩,加剧了文字遮挡与数值失真。
  • 避免图例多列堆叠遮挡画幅:若将标签全部转移到 plt.legend(),应将其锚定在图表外缘(例如 bbox_to_anchor=(1.05, 1)),并设置 loc='upper left',严禁让图例覆盖在饼图正上方。
  • 百分比格式化与四舍五入防呆:当合并“其它”项时,确保所有百分比使用标准浮点格式(如 %1.1f%%),避免因取整导致的各部分总和不等于 100% 的误解。

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。