59  绘制饼图

本章定位(复习与补充训练层):本章对应正课第20章《常用图形绘制》(章节 20,“占比用饼图”的选型要点),并与第49章《数据框可视化》(章节 50)互为补充训练(本章为分项版),用于复习、补缺与额外练习。建议先不看讲解,直接尝试下方平台任务,再对照解析补弱项。本章不属于必修主线。先做本章『动手与思考』第 1 题与平台任务自测,通过即可跳过本章。

59.1 引言比例的可视化

饼图(Pie Chart)是展示部分与整体关系的经典工具,金融分析中用于资产配置构成、收入结构、市场份额与行业分布等占比类数据。

59.2 本章学习目标

通过本章复习,你将能够:

  1. 写出饼图扇形角度公式 \(\theta_i=x_i/\sum_j x_j\times360°\),并说出饼图的适用条件(类别少、重在构成、比例差异明显)与失效条件(类别多、比例接近、需精确比较)
  2. 复述平台任务的数据流:把社会融资规模存量按行(融资方式)、列(时点)组织为DataFrame,iloc[:,k] 取某期末的一列,plt.pie(x=..., labels=AFRE.index) 按行索引标注类别,plt.axis('equal') 保证正圆
  3. 设置 autopctstartangleexplodelabeldistancecounterclock 等参数改善饼图可读性
  4. 构造环形图(中心挖空放总量)与嵌套饼图(内外两层展示大类与细分),并说明何时应改用条形图

先修:章节 20(第20章的图表选型,饼图作为”占比”类数据的标准选择在该章引入)。

59.3 饼图的数学基础

饼图将圆划分为若干扇形,每个扇形的角度对应一个类别的比例:

\[ \theta_i = \frac{x_i}{\sum_{j=1}^n x_j} \times 360° \]

其中:

  • \(x_i\):第 \(i\) 个类别的数值
  • \(\theta_i\):第 \(i\) 个扇形的角度

59.4 基础饼图

任务要求(归纳自块内任务注释与代码):用列表给出 4 个时点与 8 类融资方式,用 np.array 按行组织社会融资规模存量,构造行=融资方式、列=时点的 DataFrame AFRE,用 print 输出整张表。请将代码原样输入教学平台(注释除外),判定以平台为准。

平台任务1(平台原始代码)

以下代码与教学平台任务要求完全一致:

列表 59.1: 平台原始代码 (1)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd  # 导入Pandas数据分析库

import numpy as np  # 导入NumPy数值计算库

time=['2018年12月末','2019年3月末','2019年6月末','2019年9月末']#创建日期的列表

name=['人民币贷款','债券','委托贷款','信托贷款','股票','承兑汇票','贷款核销','外币贷款']#创建指标名称的列表

# 创建NumPy数组datas
datas=np.array([[134.69,140.98,144.71,148.58],[29.25,30.53,31.89,33.54],[12.36,12.15,11.89,11.73],[7.85,7.88,7.88,7.68],[7.01,7.06,7.13,7.24],[3.81,4.01,3.77,3.28],[3.01,3.18,3.43,3.66],

[2.21,2.18,2.21,2.19]])#创建具体数据的数组

AFRE = pd.DataFrame(data=datas,index=name,columns=time)  # 创建数据框AFRE

print(AFRE)  # 输出变量AFRE的值

预期输出(本机实际运行结果,数据内联于代码中;以平台运行结果为准):

       2018年12月末  2019年3月末  2019年6月末  2019年9月末
人民币贷款     134.69    140.98    144.71    148.58
债券         29.25     30.53     31.89     33.54
委托贷款       12.36     12.15     11.89     11.73
信托贷款        7.85      7.88      7.88      7.68
股票          7.01      7.06      7.13      7.24
承兑汇票        3.81      4.01      3.77      3.28
贷款核销        3.01      3.18      3.43      3.66
外币贷款        2.21      2.18      2.21      2.19

任务要求(归纳自块内任务注释与代码):取 AFRE 最后一列(2019 年 9 月末)的 8 类融资存量绘制饼图,以行索引作为扇区标签(字号 13),plt.axis("equal") 保证正圆,添加图例与标题,显示图形并保存为 1.png。请将代码原样输入教学平台(注释除外),判定以平台为准。

平台任务2(平台原始代码)

以下代码与教学平台任务要求完全一致:

列表 59.2: 平台原始代码 (2)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import matplotlib.pyplot as plt  # 导入Matplotlib绘图库
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库
plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置Matplotlib全局参数
time=['2018年12月末','2019年3月末','2019年6月末','2019年9月末']#创建日期的列表
name=['人民币贷款','债券','委托贷款','信托贷款','股票','承兑汇票','贷款核销','外币贷款']#创建指标名称的列表
# 创建NumPy数组datas
datas=np.array([[134.69,140.98,144.71,148.58],[29.25,30.53,31.89,33.54],[12.36,12.15,11.89,11.73],[7.85,7.88,7.88,7.68],[7.01,7.06,7.13,7.24],[3.81,4.01,3.77,3.28],[3.01,
3.18,3.43,3.66],[2.21,2.18,2.21,2.19]])#创建具体数据的数组
AFRE = pd.DataFrame(data=datas,index=name,columns=time)  # 创建数据框AFRE
plt.figure(figsize=(9,6))  # 创建图形画布
plt.pie(x=AFRE.iloc[:,-1],labels=AFRE.index,textprops={"fontsize":13}) #绘制2019年9月末各项融资存量的饼图(iloc[:,-1]取最后一列,即2019年9月末)
plt.axis("equal") #使饼图是一个图形
plt.legend(fontsize=12)  # 添加图例
plt.title(u"2019年9月末各类融资存量的占比",fontsize=13)  # 设置图表标题
plt.show()  # 显示图形
plt.savefig("1.png")  # 保存图形至文件

预期输出(本机实际运行结果,数据内联于代码中;以平台运行结果为准):本块无控制台输出,生成文件 1.png——8 个扇区的饼图,因未设 autopct 扇区上没有百分比数字,数值需从图例与表格对照读出;按 2019 年 9 月末存量(合计 217.90)计算,人民币贷款占 68.19%、债券占 15.39%,其余六类合计仅约 16.4%,其中最小的外币贷款仅 1.01%,扇区已非常细窄。

任务要求(归纳自块内任务注释与代码):在 2×2 子图中分别绘制 2018 年 12 月末、2019 年 3 月末、6 月末、9 月末四期 8 类融资存量的饼图(labeldistance=1.03counterclock=False 顺时针、字号 12),plt.axis('equal') 保证正圆并配各期标题,显示图形并保存为 2.png。请将代码原样输入教学平台(注释除外),判定以平台为准。

平台任务3(平台原始代码)

以下代码与教学平台任务要求完全一致:

列表 59.3: 平台原始代码 (3)
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import matplotlib.pyplot as plt  # 导入Matplotlib绘图库
import pandas as pd  # 导入Pandas数据分析库
import numpy as np  # 导入NumPy数值计算库
plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置Matplotlib全局参数
time=['2018年12月末','2019年3月末','2019年6月末','2019年9月末']#创建日期的列表
name=['人民币贷款','债券','委托贷款','信托贷款','股票','承兑汇票','贷款核销','外币贷款']#创建指标名称的列表
# 创建NumPy数组datas
datas=np.array([[134.69,140.98,144.71,148.58],[29.25,30.53,31.89,33.54],[12.36,12.15,11.89,11.73],[7.85,7.88,7.88,7.68],[7.01,7.06,7.13,7.24],[3.81,4.01,3.77,3.28],[3.01,
3.18,3.43,3.66],[2.21,2.18,2.21,2.19]])#创建具体数据的数组
AFRE = pd.DataFrame(data=datas,index=name,columns=time)  # 创建数据框AFRE
plt.figure(figsize=(11,11))  # 创建图形画布
plt.subplot(2,2,1)#第1行、第1列子图
plt.pie(x=AFRE.iloc[:,0],labels=AFRE.index,labeldistance=1.03,counterclock=False,textprops={'fontsize':12})#绘制2018年12月末各项融资存量的饼图
plt.axis('equal')#使饼图是一个圆形
plt.title(u"2018年12月末",fontsize=14)  # 设置图表标题
plt.subplot(2,2,2)#第1行、第2列子图
plt.pie(x=AFRE.iloc[:,1],labels=AFRE.index,labeldistance=1.03,counterclock=False,textprops={'fontsize':12})#绘制2019年3月末各项融资存量的饼图
plt.axis('equal')#使饼图是一个圆形
plt.title(u"2019年3月末",fontsize=14)  # 设置图表标题
plt.subplot(2,2,3)#第2行、第1列子图
plt.pie(x=AFRE.iloc[:,2],labels=AFRE.index,labeldistance=1.03,counterclock=False,textprops={'fontsize':12})#绘制2019年6月末各项融资存量的饼图
plt.axis('equal')#使饼图是一个圆形
plt.title(u"2019年6月末",fontsize=14)  # 设置图表标题
plt.subplot(2,2,4)#第2行、第2列子图
plt.pie(x=AFRE.iloc[:,3],labels=AFRE.index,labeldistance=1.03,counterclock=False,textprops={'fontsize':12})#绘制2019年9月末各项融资存量的饼图
plt.axis('equal')#使饼图是一个圆形
plt.title(u"2019年9月末",fontsize=14)  # 设置图表标题
plt.show()  # 显示图形
plt.savefig("2.png")  # 保存图形至文件

预期输出(本机实际运行结果,数据内联于代码中;以平台运行结果为准):本块无控制台输出,生成文件 2.png——四幅同构的饼图,均顺时针绘制、标签外移;四期构成高度接近,人民币贷款占比自 2018 年 12 月末的 67.28% 逐期升至 2019 年 9 月末的 68.19%(各期存量合计自 200.19 增至 217.90),肉眼不易察觉差异,这正是”占比接近、类别偏多时饼图失效”的实例。

注:平台任务2与任务3的原始代码都是先 plt.show()plt.savefig(),在本地交互环境(弹窗模式)运行会保存出空白图片,应先保存后显示;本机实测所用无图形界面后端(Agg)下 show 为空操作,仍能保存出完整图形。平台环境以平台判定为准。

关键参数解析:

  1. autopct:显示百分比的格式字符串
    • '%1.1f%%':保留1位小数
    • '%1.2f%%':保留2位小数
    • '%d%%':显示整数
  2. startangle:起始角度
    • 90(或-90):从12点方向开始,符合时钟习惯
    • 0:从3点方向开始
    • 通常用90度更直观
  3. explode:突出显示
    • 元素为0:正常显示
    • 元素>0:向外偏移,突出该扇形

59.5 环形图

列表 59.4
# =============================================================================
# 题目: 环形图——收入构成分析
# =============================================================================
# 本代码展示如何绘制环形图(甜甜圈图),在饼图中心添加信息
# 应用于金融场景:展示收入构成,中心显示总收入

# ==================== 收入数据 ====================
revenue_sources = ['主营业务', '其他业务', '投资收益', '资产处置']  # 收入来源
revenues = [850, 120, 80, 50]  # 各项收入,单位:亿元
colors_revenue = ['#2E86AB', '#008080', '#F0A700', '#E3120B']
# 配色:蓝、青、黄、红

# ==================== 绘制环形图 ====================
fig, ax = plt.subplots(figsize=(8, 8))  # 创建画布和坐标轴对象

# 绘制饼图,然后中心挖空形成环形
wedges, texts, autotexts = ax.pie(revenues, labels=revenue_sources,
                                   colors=colors_revenue,
                                   autopct='%1.1f%%',
                                   startangle=90,
                                   pctdistance=0.85)
# pctdistance=0.85: 百分比标签距离圆心的位置(0-1之间)
# 0.85表示距离圆心85%的位置

# ==================== 创建白色圆心,形成环形 ====================
center_circle = plt.Circle((0, 0), 0.60, fc='white')
# 创建一个白色圆,半径0.60(相对于饼图半径1),覆盖在中心
# (0, 0)是圆心坐标
ax.add_artist(center_circle)  # 将圆心添加到图表中

# ==================== 在中心添加总计 ====================
total_revenue = sum(revenues)  # 计算总收入
ax.text(0, 0, f'总收入\n{total_revenue}亿元',
        ha='center', va='center',  # 水平和垂直居中
        fontsize=14, fontweight='bold')  # 字体大小14,加粗
# 使用\n换行,将"总收入"和数值分两行显示

# ==================== 添加标题 ====================
plt.title('收入构成', fontsize=16, fontweight='bold', pad=20)
plt.tight_layout()
plt.show()

# ==================== 收入结构分析 ====================
print('收入结构分析:')  # 打印标题
for i, source in enumerate(revenue_sources):  # 遍历所有收入来源
    pct = revenues[i] / total_revenue * 100  # 计算百分比
    print(f'{source}: {revenues[i]}亿元 ({pct:.1f}%)')  # 打印每项收入和占比

环形图的优势:

  • 更现代、美观
  • 中心可放置额外信息(总计、图标等)
  • 减小扇形面积的视觉误导

59.6 条形图 vs 饼图

列表 59.5
# =============================================================================
# 题目: 对比——条形图与饼图
# =============================================================================
# 本代码展示同一数据用饼图和条形图两种方式呈现,对比优缺点
# 应用于金融场景:帮助读者选择合适的图表类型

# ==================== 同一数据,两种展示方式 ====================
companies = ['公司A', '公司B', '公司C', '公司D', '公司E']  # 公司名称
market_share = [35, 25, 20, 12, 8]  # 市场份额,单位:%
colors_comparison = ['#E3120B', '#008080', '#F0A700', '#2C3E50', '#8E9EAA']
# 配色方案,保持一致性

# ==================== 创建子图 ====================
fig, axes = plt.subplots(1, 2, figsize=(14, 6))  # 1行2列的子图布局

# ==================== 左:饼图 ====================
axes[0].pie(market_share, labels=companies, colors=colors_comparison,
            autopct='%1.1f%%', startangle=90)  # 绘制饼图
axes[0].set_title('饼图', fontsize=14, fontweight='bold')  # 子图标题

# ==================== 右:条形图 ====================
bars = axes[1].bar(companies, market_share, color=colors_comparison, alpha=0.8)
# 绘制垂直条形图,alpha=0.8设置透明度
axes[1].set_title('条形图', fontsize=14, fontweight='bold')
axes[1].set_ylabel('市场份额(%)', fontsize=12)  # Y轴标签
axes[1].grid(axis='y', alpha=0.3)  # 只显示Y轴网格线

# ==================== 添加数值标签 ====================
for bar in bars:  # 遍历每个条形
    height = bar.get_height()  # 获取条形高度
    axes[1].text(bar.get_x() + bar.get_width()/2., height,  # 计算标签位置
                f'{height}%', ha='center', va='bottom', fontsize=10)  # 添加标签
# bar.get_x(): 条形左边界坐标
# bar.get_width(): 条形宽度
# ha='center': 水平居中对齐
# va='bottom': 垂直底部对齐(标签在条形上方)

plt.tight_layout()  # 调整布局
plt.show()  # 显示

print('使用建议:')  # 打印使用建议
print('- 饼图:展示构成,适合类别较少(≤5)的情况')
print('- 条形图:精确比较数值,适合类别较多或需要精确比较')

选择指南:

场景 推荐图表 理由
类别≤5,重点是构成 饼图 直观展示比例
类别>5 条形图 避免饼图过于细碎
需要精确比较数值 条形图 长度比角度更易比较
层次结构(饼中饼) 环形图或嵌套饼图 展示多级构成
时间序列变化 堆叠条形图 饼图难以展示变化

59.7 嵌套饼图

列表 59.6
# =============================================================================
# 题目: 嵌套饼图——多层级数据展示
# =============================================================================
# 本代码展示如何绘制嵌套饼图,展示两层级的层次结构
# 应用于金融场景:展示大类资产及其细分

# ==================== 两层数据 ====================
# 外层:大类资产
major_assets = ['权益类', '固定收益类', '另类投资']  # 三大类
major_alloc = [50, 35, 15]  # 大类配置比例

# 内层:细分类别
sub_assets = ['A股', '港股', '国债', '信用债', '商品', 'REITs']  # 细分
sub_alloc = [30, 20, 20, 15, 8, 7]  # 细分配置比例

# 确定每个子类别属于哪个大类
major_index = [0, 0, 1, 1, 2, 2]  # 对应major_assets的索引
# A股、港股属于权益类(索引0)
# 国债、信用债属于固定收益类(索引1)
# 商品、REITs属于另类投资(索引2)

# ==================== 绘制嵌套饼图 ====================
fig, ax = plt.subplots(figsize=(10, 10))  # 创建10×10英寸画布

# ==================== 内层饼图 ====================
wedges_inner, texts_inner, autotexts_inner = ax.pie(
    sub_alloc,
    radius=0.7,  # 内层半径0.7
    labels=sub_assets,  # 标签
    colors=['#E3120B', '#F0656E', '#008080', '#46A1A8', '#F0A700', '#F7C555'],
    # 使用渐变色,同一大类用相近颜色
    autopct='%1.1f%%',
    startangle=90,
    pctdistance=0.85,  # 百分比标签距离
    labeldistance=0.6   # 类别标签距离
)

# ==================== 外层饼图 ====================
wedges_outer, texts_outer, autotexts_outer = ax.pie(
    major_alloc,
    radius=1.0,  # 外层半径1.0
    labels=major_assets,
    colors=['#E3120B', '#008080', '#F0A700'],  # 大类颜色
    autopct='%1.1f%%',  # 显示百分比(需返回3个值)
    startangle=90,
    labeldistance=1.05,  # 标签距离略大于1,避免与内层重叠
    textprops={'fontsize': 12, 'fontweight': 'bold'}  # 标签字体加粗
)

# ==================== 添加标题 ====================
plt.title('投资组合多层级构成', fontsize=16, fontweight='bold', pad=20)
plt.tight_layout()
plt.show()

# ==================== 打印层级结构 ====================
print('投资组合层级结构:')  # 打印标题
for i, major in enumerate(major_assets):  # 遍历每个大类
    sub_indices = [j for j, x in enumerate(major_index) if x == i]
    # 找出属于该大类的所有子类别的索引
    print(f'\n{major} ({major_alloc[i]}%):')  # 打印大类名称和比例
    for idx in sub_indices:  # 遍历该大类的子类别
        print(f'  - {sub_assets[idx]}: {sub_alloc[idx]}%')  # 打印子类别
        # 缩进两个空格,表示层级关系

59.8 金融应用行业配置

列表 59.7
# =============================================================================
# 题目: 基金行业配置分析
# =============================================================================
# 本代码展示如何对比基金与基准的行业配置差异
# 应用于金融场景:基金投资组合的行业配置偏离度分析

# ==================== 行业配置数据 ====================
industries = ['金融', '科技', '消费', '医疗', '新能源', '原材料', '其他']  # 行业列表
fund_allocation = [25, 20, 18, 12, 10, 8, 7]  # 基金配置比例
benchmark_allocation = [22, 15, 20, 10, 12, 10, 11]  # 基准(如沪深300)配置比例

# ==================== 创建子图 ====================
fig, axes = plt.subplots(1, 2, figsize=(14, 7))  # 1行2列

# ==================== 左:基金配置(饼图) ====================
axes[0].pie(fund_allocation, labels=industries,
            autopct='%1.1f%%', startangle=90,
            colors=['#2E86AB', '#008080', '#E3120B', '#F0A700',
                    '#2C3E50', '#8E9EAA', '#A8B5BF'])
axes[0].set_title('基金行业配置', fontsize=14, fontweight='bold')

# ==================== 右:与基准对比(条形图) ====================
x = np.arange(len(industries))  # 生成0-6的整数序列,作为X轴位置
width = 0.35  # 条形宽度0.35

# 绘制两组条形图,并排显示
axes[1].bar(x - width/2, fund_allocation, width,
           label='基金', color='#2E86AB', alpha=0.8)
# x - width/2: 左移半个条形宽度,居中显示
axes[1].bar(x + width/2, benchmark_allocation, width,
           label='基准', color='#E3120B', alpha=0.8)
# x + width/2: 右移半个条形宽度

# ==================== 添加坐标轴标签和标题 ====================
axes[1].set_xlabel('行业', fontsize=12)
axes[1].set_ylabel('配置比例(%)', fontsize=12)
axes[1].set_title('vs 基准对比', fontsize=14, fontweight='bold')
axes[1].set_xticks(x)  # 设置X轴刻度位置
axes[1].set_xticklabels(industries, rotation=45)  # 设置刻度标签,旋转45度避免重叠
axes[1].legend(fontsize=11)  # 显示图例
axes[1].grid(axis='y', alpha=0.3)  # Y轴网格线

plt.tight_layout()
plt.show()

# ==================== 计算偏离度 ====================
deviation = [f - b for f, b in zip(fund_allocation, benchmark_allocation)]
# 计算基金与基准的差值
df_deviation = pd.DataFrame({
    '行业': industries,
    '基金': fund_allocation,
    '基准': benchmark_allocation,
    '偏离度': deviation  # 正值表示超配,负值表示低配
})
print('\n配置偏离分析:')  # 打印标题
print(df_deviation)  # 打印偏离度表格

# ==================== 分析超配和低配 ====================
print(f'\n超配: {[industries[i] for i, d in enumerate(deviation) if d > 0]}')
# 列出所有超配行业(偏离度>0)
print(f'低配: {[industries[i] for i, d in enumerate(deviation) if d < 0]}')
# 列出所有低配行业(偏离度<0)

59.9 饼图的注意事项

何时使用饼图: ✅ 适合:

  • 展示部分与整体的关系
  • 类别较少(≤6个)
  • 各部分比例差异明显
  • 重点在于构成,非精确比较

不适合:

  • 类别过多(>8个)
  • 需要精确比较数值
  • 各部分比例相近(难以区分)
  • 需要展示时间序列变化

最佳实践:

  1. 排序:按大小顺时针排列,从12点开始
  2. 突出关键:用explode或颜色突出重点
  3. 颜色:使用对比色,避免彩虹色
  4. 标签:直接标注,避免图例来回对照
  5. 总数:在标题或中心说明总数值

59.10 本章小结

要点:

  • 饼图用角度编码占比:\(\theta_i=\frac{x_i}{\sum_j x_j}\times360°\),所有扇形角度之和为360°,直观展示部分与整体的关系
  • 平台任务数据链:8种融资方式×4个时点的存量矩阵 → pd.DataFrame(data=datas, index=name, columns=time)iloc[:,k](或 iloc[:,-1])取某一期 → plt.pie(x=AFRE.iloc[:,k], labels=AFRE.index)plt.axis('equal')
  • autopct='%1.1f%%' 在扇形上标注百分比;startangle=90 从12点方向起画;explode 把关键扇形向外拉出;labeldistance 控制类别标签离圆心的距离;counterclock=False 改为顺时针
  • 环形图通过在饼图中心叠加白色圆实现,中心可放总量等信息;嵌套饼图内外两层分别编码大类与其细分
  • 饼图与条形图的分工:类别少、重在构成用饼图;类别多、需精确比较或展示随时间变化用条形图——长度比角度更易比较

易错点:

  • 忘记 plt.axis('equal') 会画出椭圆饼图,角度占比的视觉判断随之失真
  • 平台任务2、3的原始代码先 plt.show()plt.savefig(),本地运行会保存出空白图片;本地练习应先保存后显示
  • autopct 格式串中要写两个 %% 才能输出一个百分号,只写一个会触发格式化错误
  • 类别超过8个时饼图变得细碎难读(本章8类融资方式已接近上限),应改用排序后的条形图
  • 嵌套饼图内层扇形的占比口径要事先说明:是占总体还是占所属大类,读图者无法自行判断
  • 本地缺少SimHei等中文字体时,中文标签显示为方框,属环境问题,以平台渲染结果为准

59.11 动手与思考

以下练习每题附参考答案(默认折叠)。请先独立完成并写下你的判断,再点开对照,最后上机验证。

  1. 自测回忆:不看正文,写出饼图扇形角度公式;并回答:某数据的类别有9个、且其中三类的占比都在11%左右,这张饼图的问题出在哪里,应改用什么图。

    参考答案(点开前请先独立完成)

    解题思路:扇形角度公式为 \(\theta_i = x_i / \sum_j x_j \times 360°\),所有扇形角度之和恰为 360°。9 个类别已超过饼图 8 类的可读上限;其中三类占比都在 11% 左右时,对应扇形角度都在 39°—40° 附近,角度差异落在人眼的分辨阈附近——既无法排序、也无法可靠比出谁大谁小,读者只能逐个读标注数字,图形失去意义。应改用按占比降序排列的条形图(类别名长时用水平条形图 barh):长度比较远比角度比较精确,排序后主次一目了然。

    回扣主线:角度公式与”类别超过 8 个应改用排序后的条形图”的告诫见本章”饼图的数学基础”与”易错点”第 4 条,选型分工另见第 章节 20 章。

  2. 输出预测:以人民币贷款、债券、委托贷款三类的存量为输入,先预测输出,再上机检验。

    shares = [134.69, 29.25, 12.36]
    total = sum(shares)
    print(round(shares[0] / total * 360, 1))
    print(round(shares[0] / total * 100, 1))

    参考答案(先写下你的预测再点开)

    解题思路:三类存量合计 134.69+29.25+12.36 = 176.3;人民币贷款的角度份额 134.69/176.3×360 ≈ 274.97°,保留 1 位小数为 275.0;百分比份额 134.69/176.3×100 ≈ 76.41%,保留 1 位小数为 76.4——同一份额在两种口径下的数值之比恰为 360:100。

    # 验证脚本:角度份额与百分比份额
    shares = [134.69, 29.25, 12.36]  # 三类融资方式存量
    total = sum(shares)  # 合计
    print(round(shares[0] / total * 360, 1))  # 换算为扇形角度
    print(round(shares[0] / total * 100, 1))  # 换算为百分比

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    275.0
    76.4

    回扣主线:角度与百分比按 360:100 换算见本章”饼图的数学基础”一节。

  3. 输出预测:autopct 用的是百分号格式化,先预测输出,再上机检验。

    value = 0.3456
    print('%1.1f%%' % value)
    print('%1.2f%%' % value)
    print('{:.1%}'.format(value))

    参考答案(先写下你的预测再点开)

    解题思路'%1.1f%%' 把 value 当作”数值”格式化成 1 位小数(0.3456 → 0.3),%% 输出一个百分号——即 0.3%;'%1.2f%%' 同理保留 2 位小数得 0.35%。而 '{:.1%}'.format 的百分号格式会先把数值乘 100 再加百分号,得 34.6%。两种格式的口径完全不同:%f 系不乘 100,% 类型乘 100——写 autopct 时若想显示”占比百分数”应使用后者(或把份额先乘 100 再用前者)。

    # 验证脚本:三种百分号格式化的口径差异
    value = 0.3456  # 待格式化的比例值
    print('%1.1f%%' % value)  # %f口径:不乘100,直接格式化
    print('%1.2f%%' % value)  # 保留2位小数,同样不乘100
    print('{:.1%}'.format(value))  # %类型口径:先乘100再加百分号

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    0.3%
    0.35%
    34.6%

    回扣主线autopct='%1.1f%%' 中双百分号的转义规则见本章”要点”第 3 条与”易错点”第 3 条。

  4. 变式任务(平台任务同型改造):在平台任务2的基础上,为2019年9月末的饼图增加 autopct='%1.1f%%'startangle=90explode(把占比最大的人民币贷款向外突出),观察标签是否重叠;若重叠,给出两种不删类别的补救办法。

    参考答案(点开前请先独立完成)

    解题思路:先算份额——2019 年 9 月末八类占比实测为人民币贷款 68.19%、债券 15.39%、委托贷款 5.38%、信托贷款 3.52%、股票 3.32%、贷款核销 1.68%、承兑汇票 1.51%、外币贷款 1.01%。加上 autopct 后标签确实出现重叠:占比最小的三类(1.01%—1.68%)对应扇形各不足 6°,类别名与百分比数字在小扇形区相互挤压、难以分辨。两种不删类别的补救办法:第一,把类别标签移出饼图、改用图例呈现(plt.pie 不传 labels,用 plt.legend(AFRE.index, loc='best') 补回类别名),图上只留 autopct 的百分比数字;第二,调大 labeldistance(如 1.1)并配合 pctdistance(如 0.75)把类别标签与百分比沿径向分层错开,同时缩小 textprops 字号。若仍拥挤,最终方案是按本章告诫改用排序后的条形图(仍保留全部 8 类)。

    # 变式脚本:带标注、起始角与外拉效果的饼图
    import matplotlib.pyplot as plt  # 导入绘图库
    import pandas as pd  # 导入pandas库
    import numpy as np  # 导入NumPy库
    time = ['2018年12月末', '2019年3月末', '2019年6月末', '2019年9月末']  # 时点列表
    name = ['人民币贷款', '债券', '委托贷款', '信托贷款', '股票', '承兑汇票', '贷款核销', '外币贷款']  # 类别列表
    datas = np.array([[134.69,140.98,144.71,148.58],[29.25,30.53,31.89,33.54],[12.36,12.15,11.89,11.73],[7.85,7.88,7.88,7.68],[7.01,7.06,7.13,7.24],[3.81,4.01,3.77,3.28],[3.01,3.18,3.43,3.66],[2.21,2.18,2.21,2.19]])  # 存量矩阵
    AFRE = pd.DataFrame(data=datas, index=name, columns=time)  # 组装数据框
    shares_sep = AFRE.iloc[:, -1]  # 取2019年9月末一列
    print((shares_sep / shares_sep.sum() * 100).round(2))  # 先输出八类占比
    explode = [0.1 if v == shares_sep.max() else 0 for v in shares_sep]  # 仅最大扇形外拉
    plt.pie(shares_sep, labels=AFRE.index, autopct='%1.1f%%', startangle=90, explode=explode, textprops={'fontsize': 10})  # 带标注的饼图
    plt.axis('equal')  # 保证正圆
    plt.savefig('pie_variant.png')  # 保存图形

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    人民币贷款    68.19
    债券       15.39
    委托贷款      5.38
    信托贷款      3.52
    股票        3.32
    承兑汇票      1.51
    贷款核销      1.68
    外币贷款      1.01
    Name: 2019年9月末, dtype: float64

    图形:人民币贷款扇形(68.2%)自 12 点方向起顺时针展开并被向外拉出,大扇形及债券扇形标注清晰;三类占比不足 2% 的小扇形区标签与百分比数字明显挤压重叠。

    注意:以上为变式代码;列表 59.2 的平台任务仍须按原始代码原样输入教学平台。

    回扣主线autopct/startangle/explode 的参数语义见本章”要点”第 3 条,小扇形区标签拥挤与”类别超 8 个”的告诫见”易错点”第 4 条。

  5. 思考题:同一组”基金行业配置 vs 基准”的数据,本章分别用了饼图和分组条形图展示。从”比较偏离度”这一分析目的出发,说明为什么分组条形图更适合,并指出饼图在该场景下丢失了什么信息。

    参考答案(点开前请先独立完成)

    解题思路:偏离度是”组合占比 − 基准占比”的逐类别差值,分析要读的是差值的大小与方向(超配还是低配)。分组条形图把组合与基准两根条形放在同一类别位置、共用零基线,差值的大小与正负方向一望即知,超配/低配的行业还可按差值排序突出;饼图把两个占比分别编码成两张图上的角度,而人眼几乎无法跨图比较角度之差——先要各自读出角度再心算相减。饼图在该场景下丢失的信息:一是共同基线/参照系(角度没有可供对齐的起点用于减法),二是差值本身(超配与低配的正负方向在两张饼上无从体现),三是精确排序能力(角度排序远弱于长度排序)。因此”重在构成且类别少”时饼图可用,“重在比较偏离”时分组条形图是正确工具。

    回扣主线:“长度比角度更易比较”与饼图/条形图的分工见本章”要点”第 5 条与第 章节 20 章选型要点。