Skip to content

第 6 章 饼图、箱线图与更多图表

学习目标

  • 会用 ax.pie 画占比
  • 会用 ax.boxplot 画五数概括
  • 会用 ax.violinplot 画分布形状
  • 会用 ax.fill_between 填充区间
  • 能根据数据形态选择合适的图表类型

6.1 pie:饼图看占比

饼图(pie chart)用扇形面积表达各部分占整体的比例,适合「构成」型数据(各部分相加等于整体)。ax.pie(数值, labels=标签):

python
import matplotlib.pyplot as plt
import numpy as np

labels = ["房租", "餐饮", "交通", "娱乐", "其他"]
expense = np.array([3500, 2200, 800, 600, 900])

fig, ax = plt.subplots()
ax.pie(expense, labels=labels, autopct="%1.1f%%",
       colors=["#11557c", "#ee8c18", "#40c0a6", "#d33f49", "#9ca3af"],
       startangle=90, counterclock=False)
ax.set_title("某家庭月度支出构成")
plt.show()

图 6-1 真实输出:autopct="%1.1f%%" 在每块上标注百分比;startangle 决定第一块从哪个角度开始;counterclock=False 让数据按顺时针排列:

图 6-1 家庭月度支出饼图

饼图的适用范围比想象中窄:它只能表达「占比」,且类别一多(超过 5~6 个)就难读。若只是比大小,柱状图(第 5 章)通常更好。

6.2 boxplot:箱线图看分布的五数概括

箱线图(box plot)用一个箱子概括一组数据的分布:最小值、下四分位数(Q1)、中位数、上四分位数(Q3)、最大值这五个数字。箱子是 Q1~Q3,箱中横线是中位数,两端的须延伸到非异常值的最值,须外的点视为离群值(outlier)

ax.boxplot(数据列表, tick_labels=标签):

python
import matplotlib.pyplot as plt
import numpy as np

rng = np.random.default_rng(21)
class_a = rng.normal(75, 10, 40)
class_b = rng.normal(82, 14, 40)
class_c = rng.normal(70, 8, 40)

fig, ax = plt.subplots()
ax.boxplot([class_a, class_b, class_c],
           tick_labels=["一班", "二班", "三班"],
           patch_artist=True,
           boxprops=dict(facecolor="#11557c", alpha=0.6),
           medianprops=dict(color="#d33f49", linewidth=2))
ax.set_title("三个班级成绩分布(箱线图)")
ax.set_xlabel("班级")
ax.set_ylabel("成绩(分)")
plt.show()

图 6-2 真实输出。一个箱子就传达五维信息,适合「多组数据对比分布」:

图 6-2 三个班级成绩的箱线图

解读:二班的箱子最「高」(成绩离散度大),三班最「矮」(成绩集中);几个圆点是离群值。

注意:Matplotlib 3.9 起,boxplotlabels 参数更名为 tick_labels。用旧参数 labels= 会报 TypeError,换名字即可。

6.3 violinplot:小提琴图看分布形状

小提琴图(violin plot)是箱线图的「升级版」:它不仅给出五数概括,还用核密度估计画出分布的完整形状——宽的地方数据多,窄的地方数据少。ax.violinplot(数据列表):

python
fig, ax = plt.subplots()
vp = ax.violinplot([class_a, class_b, class_c], positions=[1, 2, 3], showmedians=True)
ax.set_xticks([1, 2, 3])
ax.set_xticklabels(["一班", "二班", "三班"])
ax.set_title("三个班级成绩分布(小提琴图)")
plt.show()

图 6-3 真实输出:showmedians=True 在每把小提琴里标出中位数。与箱线图对比:小提琴图能看出二班成绩「中间高两边低、还带一个左偏的尾巴」,这是箱线图看不到的形状细节:

图 6-3 三个班级成绩的小提琴图

6.4 fill_between:填充区间

ax.fill_between(x, y1, y2) 在两条曲线之间填充颜色,常用来画「区间」「范围带」「高于阈值的部分」。以 30 天气温为例,填充最高与最低气温之间的区域:

python
import matplotlib.pyplot as plt
import numpy as np

rng = np.random.default_rng(21)
day30 = np.arange(1, 31)
high = 28 + 6 * np.sin(day30 / 30 * 2 * np.pi) + rng.normal(0, 1.5, 30)
low  = 20 + 5 * np.sin(day30 / 30 * 2 * np.pi + 0.5) + rng.normal(0, 1.2, 30)

fig, ax = plt.subplots()
ax.plot(day30, high, color="#d33f49", label="最高气温")
ax.plot(day30, low, color="#11557c", label="最低气温")
ax.fill_between(day30, low, high, color="#9ca3af", alpha=0.3, label="温差区间")
ax.set_title("30 天气温区间")
ax.set_xlabel("日期")
ax.set_ylabel("气温(°C)")
ax.legend()
plt.show()

图 6-4 真实输出:灰色带子的高度就是每天的温差,一目了然:

图 6-4 fill_between 填充温差区间

fill_between 的经典用法还有:填满曲线与零轴之间、只填满足条件的区间(where= 参数,参考第 8 章的布尔掩码思路)、画置信区间带。

6.5 怎么选图:数据形态决定图表

你想表达数据类型首选图表
趋势随时间变化时间序列折线图(第 3 章)
两个变量的关系两列连续散点图(第 4 章)
类别间比大小类别 + 数值柱状图(第 5 章)
数值的分布一列连续直方图 / 箱线图 / 小提琴图
部分的占比构成类别 + 占比饼图(占比类目少时)
多组分布对比多组连续箱线图 / 小提琴图
区间或范围带上下界两列fill_between

判断顺序:先问「横轴是类别还是连续数值」,再问「我想突出趋势、比较、分布还是构成」。

6.6 读懂报错:饼图标签数量不匹配

ax.pie(数值, labels=标签) 要求标签数量与数值完全一致:

>>> ax.pie([1, 2, 3], labels=["a", "b"])
Traceback (most recent call last):
  ...
  File "matplotlib/axes/_axes.py", line 3680, in pie
    raise ValueError(f"'labels' must be of length 'x', not {len(labels)}")
ValueError: 'labels' must be of length 'x', not 2

解读:3 个数值、2 个标签,一一对应不上。错误信息直接给出两者长度,照改即可。

动手实践

  1. 画一张你自己月支出的饼图,复现图 6-1。
  2. 生成三个班级的成绩,画箱线图(注意用 tick_labels 而不是 labels),复现图 6-2。
  3. 用同样的数据画小提琴图,与箱线图对比,说出各自能看到什么。
  4. 画 30 天气温的「最高/最低 + fill_between 区间」图,复现图 6-4。
  5. 按 6.5 的表格,为以下需求各选一种图表并说明理由:一年 12 个月的销售额变化;全班身高分布;各省 GDP 比较;支出构成。

常见错误

错误写法现象原因
ax.boxplot(..., labels=[...])TypeError: got an unexpected keyword argument 'labels'3.9 起参数更名为 tick_labels
ax.pie(x, labels=[...]) 数量不一致ValueError: 'labels' must be of length 'x'标签必须与数值一一对应
类别超过 7 个还用饼图图难以阅读类别多改用柱状图
violinplot 忘记 showmedians没有中位数标记需要时显式开启
fill_between(x, y1, y2) 参数写反填充范围错注意是「从 y1 到 y2」
把饼图数据改成「不是整体」的数据占比无意义饼图只适用于构成型数据

章末练习

基础

  1. 画一张 5 项支出的饼图,标注百分比。
  2. 生成两组正态数据,画箱线图对比。
  3. 生成一组数据,画小提琴图。

提高

  1. fill_between 画出 sincos 之间的区域。
  2. 对比同一组数据的直方图、箱线图、小提琴图,说出三种图各自的优点。

挑战

  1. 生成两组「一组左偏、一组右偏」的数据,用小提琴图画出,并解释为什么形状不同。
  2. 给 6.5 的选图表格补上「不推荐/踩坑」一列(比如:饼图类别过多、柱状图用于连续数据等),并各画一张错误示范图说明问题。

章末自测

每题选择一个最佳答案。本书不附答案:完成后交由老师或 AI 老师批改讲解。

  1. 饼图适合表达?
    • A. 时间趋势
    • B. 各部分占比
    • C. 分布形状
    • D. 相关性
  2. 箱线图的「箱子」范围是?
    • A. 最小值到最大值
    • B. Q1 到 Q3
    • C. 平均值加减标准差
    • D. 中位数附近
  3. 箱线图里箱中的横线代表?
    • A. 均值
    • B. 中位数
    • C. 众数
    • D. 最大值
  4. 箱线图须外的孤立点称为?
    • A. 噪声点
    • B. 离群值
    • C. 空值
    • D. 误差点
  5. ax.boxplot 里给各组命名的参数(3.9 起)是?
    • A. labels
    • B. tick_labels
    • C. names
    • D. legend
  6. 与箱线图相比,小提琴图额外能看出?
    • A. 数值大小
    • B. 分布的完整形状
    • C. 数据总量
    • D. 颜色
  7. 填充两条曲线之间的区域用?
    • A. ax.fill()
    • B. ax.fill_between(x, y1, y2)
    • C. ax.area(x, y1, y2)
    • D. ax.hatch()
  8. 想对比 4 个班级的成绩分布,首选?
    • A. 饼图
    • B. 箱线图/小提琴图
    • C. 折线图
    • D. 散点图
  9. autopct="%1.1f%%" 的作用是?
    • A. 设置饼图半径
    • B. 在扇区标注百分比
    • C. 设置颜色
    • D. 设置旋转角度
  10. 想表达一年 12 个月销售额的变化趋势,首选?
    • A. 饼图
    • B. 折线图
    • C. 箱线图
    • D. 小提琴图