Skip to content

第 5 章 柱状图与直方图:bar 与 hist

学习目标

  • 会用 ax.bar 画分类数据的柱状图
  • 会用 ax.barh 画横向柱状图
  • yerr 添加误差条
  • 掌握并列柱状图与堆叠柱状图
  • ax.hist 画连续数据的分布
  • 理解柱状图(bar)与直方图(hist)的区别

5.1 bar:分类数据的柱状图

柱状图(bar chart)用柱子的高度表达分类数据的数值,横轴是类别(离散)、纵轴是数值。ax.bar(x, 高度):

python
import matplotlib.pyplot as plt
import numpy as np

classes = ["一班", "二班", "三班", "四班"]
scores = np.array([82, 87, 79, 91])

fig, ax = plt.subplots()
bars = ax.bar(classes, scores, color="#11557c", width=0.6)
ax.bar_label(bars, padding=3)
ax.set_title("四个班平均成绩")
ax.set_xlabel("班级")
ax.set_ylabel("平均成绩(分)")
ax.set_ylim(0, 100)
plt.show()

图 5-1 真实输出。width 控制柱宽(默认 0.8);ax.bar_label(bars) 在柱顶标出数值,是 Matplotlib 3.4 起的内置功能:

图 5-1 四个班平均成绩

柱状图适合「比大小」:一眼看出四班最高、三班最低。横轴是类别时,类别本身没有顺序意义,只是分组标签。

5.2 barh:横向柱状图

类别名很长、或想按数值排序展示时,横向柱状图更合适。ax.barh(类别, 数值) 把类别放纵轴、数值放横轴:

python
import matplotlib.pyplot as plt
import numpy as np

cities = ["重庆", "上海", "北京", "广州", "成都", "深圳"]
pops = np.array([3213, 2487, 2185, 1881, 2140, 1768])
order = np.argsort(pops)   # 按人口升序排列下标

fig, ax = plt.subplots()
bars = ax.barh(np.array(cities)[order], pops[order], color="#ee8c18", height=0.6)
ax.bar_label(bars, padding=3)
ax.set_title("主要城市常住人口(万人)")
ax.set_xlabel("人口(万人)")
plt.show()

图 5-2 真实输出:np.argsort 拿到「从小到大」的下标顺序,配合 barh 得到一条整齐的排行——这是排行榜的标准画法:

图 5-2 横向柱状图:按数值排序的排行

barh 的对应参数是 height(柱高/厚度),而不是 width

5.3 误差条:yerr

科学图表常需要表达「测量值 ± 不确定度」。yerr 参数在柱顶加误差条,capsize 控制误差条两端的横线:

python
import matplotlib.pyplot as plt
import numpy as np

methods = ["方法 A", "方法 B", "方法 C"]
mean = np.array([5.2, 6.8, 4.9])
std  = np.array([0.5, 0.8, 0.6])

fig, ax = plt.subplots()
ax.bar(methods, mean, yerr=std, capsize=6, color="#40c0a6", alpha=0.9)
ax.set_title("三种方法的平均耗时(含标准差)")
ax.set_xlabel("方法")
ax.set_ylabel("耗时(秒)")
ax.set_ylim(0, 9)
plt.show()

图 5-3 真实输出:方法 B 平均最快(柱子最矮),但它的误差条也最长,说明结果最不稳定——这就是误差条传达的信息:

图 5-3 带误差条的柱状图

yerr 是数组时逐根柱子对应;误差条还支持「非对称」(上下不同),第 10 章前先用最简单的形式。

5.4 并列与堆叠:对比与构成

并列柱状图在同一个类别里并排放几根柱子,适合「同一类别下的分组对比」。做法是给每组柱子一个横向偏移:

python
import matplotlib.pyplot as plt
import numpy as np

classes = ["一班", "二班", "三班", "四班"]
sem1 = np.array([78, 84, 80, 88])
sem2 = np.array([82, 89, 81, 92])
x = np.arange(len(classes))
w = 0.36

fig, ax = plt.subplots()
b1 = ax.bar(x - w/2, sem1, width=w, label="上学期", color="#11557c")
b2 = ax.bar(x + w/2, sem2, width=w, label="下学期", color="#ee8c18")
ax.set_xticks(x)
ax.set_xticklabels(classes)
ax.set_title("四个班两个学期平均成绩对比")
ax.set_ylabel("平均成绩(分)")
ax.legend()
plt.show()

图 5-4 真实输出:每班两根柱子并排,x - w/2x + w/2 让两组柱子以类别位置为中心对称分布:

图 5-4 并列柱状图

堆叠柱状图把多个分量在同一根柱子上「叠起来」,适合看「总量 + 构成」。用 bottom 参数指定上一段从哪里开始:

python
import matplotlib.pyplot as plt
import numpy as np

quarters = ["一季度", "二季度", "三季度", "四季度"]
product_a = np.array([30, 42, 35, 50])
product_b = np.array([20, 25, 30, 28])
product_c = np.array([15, 18, 22, 25])
x = np.arange(len(quarters))

fig, ax = plt.subplots()
ax.bar(x, product_a, label="产品 A", color="#11557c")
ax.bar(x, product_b, bottom=product_a, label="产品 B", color="#ee8c18")
ax.bar(x, product_c, bottom=product_a + product_b, label="产品 C", color="#40c0a6")
ax.set_xticks(x)
ax.set_xticklabels(quarters)
ax.set_title("四个季度销售额构成")
ax.set_xlabel("季度")
ax.set_ylabel("销售额(万元)")
ax.legend()
plt.show()

图 5-5 真实输出:每根柱子的总高度是三个产品之和,分段颜色展示构成:

图 5-5 堆叠柱状图

5.5 hist:连续数据的分布

直方图(histogram)把连续数据按数值范围分桶(bin),统计每个桶里的样本数,画出「分布」的形状。ax.hist(数据, bins=桶数):

python
import matplotlib.pyplot as plt
import numpy as np

rng = np.random.default_rng(5)
grades = np.clip(rng.normal(78, 12, 60), 40, 100)

fig, ax = plt.subplots()
ax.hist(grades, bins=8, color="#11557c", alpha=0.8, edgecolor="white")
ax.set_title("60 名学生成绩分布")
ax.set_xlabel("成绩(分)")
ax.set_ylabel("人数")
plt.show()

图 5-6 真实输出:bins 控制分桶个数,edgecolor="white" 给每根柱描白边让分界清晰:

图 5-6 成绩的直方图

柱状图与直方图的本质区别:柱状图的横轴是类别(离散、无大小顺序),直方图的横轴是数值区间(连续、有大小顺序)。所以柱状图柱子可以任意排列,直方图柱子必须按数值从小到大排,且相邻柱子之间没有间隙(或只有很窄的间隙)。

5.6 读懂报错:误差条长度不匹配

yerr 必须与 y 等长。y 有 3 根柱子、yerr 只有 2 个值时:

>>> ax.bar([1, 2, 3], [4, 5, 6], yerr=[1, 2])
Traceback (most recent call last):
  ...
ValueError: 'yerr' (shape: (2,)) must be a scalar or a 1D or (2, n) array-like
whose shape matches 'y' (shape: (3,))

解读:错误信息直接告诉你两件事——yerr 的形状是 (2,),y 的形状是 (3,),必须匹配。修法:让 yerr 与柱子一一对应,或者给所有柱子同一个标量误差。

动手实践

  1. ax.bar 画 5 门课程的平均分柱状图,并加 bar_label
  2. ax.barh 画 6 个城市人口排行(参考 5.2)。
  3. 画带误差条的柱状图(3 组均值 ± 标准差),复现图 5-3。
  4. 用并列柱状图对比两个学期的成绩,用堆叠柱状图展示季度销售额构成。
  5. 生成 100 个正态随机数,用 ax.hist 画直方图,分别试 bins=5bins=20,观察形状差异。

常见错误

错误写法现象原因
ax.bar(x, y, yerr=[1, 2]) 且 x 有 3 项ValueError: 'yerr' ... shape matches 'y'yerr 长度必须与柱子数一致
ax.plot 画分类数据折线把不相关的类别连起来类别数据用 bar,连续数据用 plot/hist
忘记 ax.set_xticks(x); set_xticklabels(...)刻度是 0,1,2数值横轴要手动换成类别标签
barh 用了 width柱子太粗/异常barhheight 控制柱厚
直方图柱子之间有宽间隙观感不对hist 相邻桶无间隙是特征;有间隙的是 bar
hist 数据里有 NaN图形缺一块先清洗数据(见第 8 章布尔掩码思路)

章末练习

基础

  1. 画一张 4 门课平均分的柱状图,加数值标签。
  2. 画一张横向柱状图,展示你一周 7 天每天的学习时长。
  3. 生成 200 个正态随机数,画 bins=10 的直方图。

提高

  1. 用并列柱状图对比两个学期的各科平均分,加图例。
  2. 用堆叠柱状图展示某公司四个季度三个产品的销售额构成。

挑战

  1. yerr 画「不同样本量下的均值估计」误差条,说明样本量越大误差条越短的统计直觉。
  2. 判断以下情形分别该用哪种图,并各画一张:(a) 比较各省 GDP;(b) 全班成绩分布;(c) 销售额的年度构成;(d) 两学期的学科对比。

章末自测

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

  1. 画柱状图的函数是?
    • A. ax.plot
    • B. ax.bar
    • C. ax.hist
    • D. ax.scatter
  2. 柱状图的横轴数据是?
    • A. 连续数值
    • B. 类别
    • C. 时间戳
    • D. 概率
  3. 横向柱状图的函数是?
    • A. ax.bar
    • B. ax.barh
    • C. ax.hbar
    • D. ax.bar_left
  4. 在柱状图上加误差条用?
    • A. error=y
    • B. yerr=std
    • C. std=True
    • D. capsize
  5. 并列柱状图让两组柱子并排的关键是?
    • A. 修改柱宽
    • B. 给 x 位置加偏移
    • C. 加图例
    • D. 用 bottom
  6. 堆叠柱状图让第二段从第一段顶部开始用?
    • A. yerr
    • B. bottom=
    • C. width=
    • D. offset=
  7. 直方图适合表达?
    • A. 类别比较
    • B. 连续数据的分布
    • C. 两个变量的相关性
    • D. 时间趋势
  8. ax.hist(data, bins=8)bins 指?
    • A. 柱子颜色
    • B. 分桶个数
    • C. 数据点数
    • D. 坐标轴范围
  9. 柱状图与直方图的本质区别是?
    • A. 颜色不同
    • B. 横轴是类别还是数值区间
    • C. 一个能加图例一个不能
    • D. 没有区别
  10. ax.bar_label(bars) 的作用是?
    • A. 设置柱状图标题
    • B. 在柱顶标出数值
    • C. 修改柱子颜色
    • D. 添加图例