课程作业 · 数据可视化 · 第二章

使用 Matplotlib 绘制简单图表

从 pyplot 的第一行代码开始,把折线、柱状、散点、饼图、直方图画出来,最后排进一张 2×2 的画布。六个例子,每段代码都能直接运行。

学 号022540348
姓 名黄俊杰
课 程数据可视化
章 节第二章 · Matplotlib
01

折线图:看趋势plt.plot()

一周气温变化 —— 最基础、也最常用的图表,按顺序连接数据点

折线图:看趋势

图 2-1 一周最高 / 最低气温折线图

01_line.py Python
1# 折线图:一周气温变化2import matplotlib.pyplot as plt3 4plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei']  # 正常显示中文5plt.rcParams['axes.unicode_minus'] = False                        # 正常显示负号6 7days = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']8high = [26, 28, 31, 29, 27, 30, 32]   # 最高气温9low = [18, 19, 22, 21, 19, 20, 23]    # 最低气温10 11plt.figure(figsize=(6.4, 4.2), dpi=120)12 13# label 是图例文字,marker 是数据点形状,linestyle 是线型14plt.plot(days, high, color='#f43f5e', marker='o', linewidth=2.2,15         markersize=7, label='最高气温')16plt.plot(days, low, color='#2563eb', marker='s', linewidth=2.2,17         markersize=6, linestyle='--', label='最低气温')18 19plt.title('本周气温变化趋势', fontsize=15, pad=14)20plt.xlabel('星期', fontsize=11)21plt.ylabel('温度(℃)', fontsize=11)22plt.ylim(14, 36)23plt.legend(frameon=False)          # frameon=False 去掉图例边框24plt.grid(alpha=0.25, linestyle=':')25plt.tight_layout()26plt.savefig('line.png', dpi=150)   # 保存为图片27plt.show()

要点

  • 三条核心参数color 定线色,linewidth 定粗细,linestyle 定线型:'-' 实线、'--' 虚线、':' 点线。
  • 标记点用 marker'o' 圆、's' 方、'^' 三角、'*' 星号;配 markersize 控制大小。
  • 图例不是自动的每次 plot() 都要给 label,最后调用 plt.legend() 才会出现图例。
  • 中文与负号默认字体没有中文,必须设 font.sans-serif;负号要额外关掉 axes.unicode_minus。
02

柱状图:比大小plt.bar()

四个季度的销售额 —— 类别之间比较数量,长度编码最准确

柱状图:比大小

图 2-2 各季度销售额柱状图(柱顶标注数值)

02_bar.py Python
1# 柱状图:各季度销售额对比2import matplotlib.pyplot as plt3 4plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei']5plt.rcParams['axes.unicode_minus'] = False6 7quarters = ['第一季度', '第二季度', '第三季度', '第四季度']8sales = [128, 156, 141, 203]9colors = ['#bfdbfe', '#93c5fd', '#60a5fa', '#2563eb']   # 由浅到深,暗示递增10 11plt.figure(figsize=(6.4, 4.2), dpi=120)12bars = plt.bar(quarters, sales, width=0.56, color=colors, edgecolor='white')13 14# 遍历每根柱子,把数值标在柱顶15for bar, value in zip(bars, sales):16    plt.text(bar.get_x() + bar.get_width() / 2,   # 柱子中心 x 坐标17             value + 4,                            # 略高于柱顶18             f'{value}', ha='center', va='bottom',19             fontsize=10.5, color='#334155')20 21plt.title('2025 年各季度销售额', fontsize=15, pad=14)22plt.ylabel('销售额(万元)', fontsize=11)23plt.ylim(0, 240)24plt.grid(axis='y', alpha=0.25, linestyle=':')   # 只保留横向网格线25plt.tight_layout()26plt.savefig('bar.png', dpi=150)27plt.show()

要点

  • 宽度的视觉作用width 取 0.5–0.7 比较协调;太宽显得笨重,太窄像牙签。
  • 柱顶标数值bar.get_x() 拿左边界,加半个 get_width() 得到中心;再用 plt.text 写上去。
  • 颜色可以编码顺序同一量级的数据用同色系由浅到深,比四种撞色更能传达"逐季增长"。
  • 类别不要用折线折线会暗示类别之间存在连续变化,把 Q1 和 Q3 连起来是没有意义的。
03

散点图:找关系plt.scatter()

90 名学生的学习时长与成绩 —— 观察两个变量是否相关

散点图:找关系

图 2-3 学习时长与成绩散点图(颜色映射学习时长)

03_scatter.py Python
1# 散点图:学习时长与考试成绩的关系2import matplotlib.pyplot as plt3import numpy as np4 5plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei']6plt.rcParams['axes.unicode_minus'] = False7 8rng = np.random.default_rng(7)                       # 固定随机种子,结果可复现9hours = rng.normal(12, 4, 90).clip(3, 24)            # 每周学习时长(小时)10score = 45 + 3.1 * hours + rng.normal(0, 6, 90)      # 成绩 ≈ 45 + 3.1×时长 + 噪声11 12plt.figure(figsize=(6.4, 4.4), dpi=120)13 14# s 控制点的大小,c 按数值映射颜色,cmap 指定色带15scatter = plt.scatter(hours, score, s=52, c=hours, cmap='Blues',16                      alpha=0.85, edgecolor='#1d4ed8', linewidth=0.7)17plt.colorbar(scatter, label='学习时长(小时)')18 19plt.title('学习时长与考试成绩的关系', fontsize=15, pad=14)20plt.xlabel('每周学习时长(小时)', fontsize=11)21plt.ylabel('考试成绩(分)', fontsize=11)22plt.grid(alpha=0.22, linestyle=':')23plt.tight_layout()24plt.savefig('scatter.png', dpi=150)25plt.show()

要点

  • 三个维度一起画x、y 是位置,s 是点大小,c 可以再编码一个数值维度。
  • 颜色映射要配 colorbarc 传数组时按数值上色,必须用 plt.colorbar() 说明颜色代表什么。
  • alpha 解决重叠点密集时透明的 alpha=0.85 能看出叠加的深浅,比纯色更诚实。
  • 固定随机种子np.random.default_rng(7) 让每次生成的随机数据完全一致,方便复现和对比。
04

饼图:看构成plt.pie()

一天的 24 小时怎么分配 —— 部分与整体的比例关系

饼图:看构成

图 2-4 一天时间分配饼图(突出"娱乐"占比)

04_pie.py Python
1# 饼图:一天 24 小时的时间分配2import matplotlib.pyplot as plt3 4plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei']5plt.rcParams['axes.unicode_minus'] = False6 7labels = ['睡眠', '上课', '自习', '娱乐', '运动']8hours = [8, 5, 3.5, 4.5, 1.5]9colors = ['#2563eb', '#60a5fa', '#93c5fd', '#fbbf24', '#14b8a6']10explode = [0, 0, 0, 0.08, 0]        # 把"娱乐"这块稍微分离出来11 12plt.figure(figsize=(6.0, 4.6), dpi=120)13plt.pie(hours,14        labels=labels,15        colors=colors,16        explode=explode,17        autopct='%.1f%%',           # 百分比保留一位小数18        startangle=90,              # 从 12 点方向开始绘制19        counterclock=False,         # 顺时针20        textprops={'fontsize': 11, 'color': '#334155'},21        wedgeprops={'edgecolor': 'white', 'linewidth': 2})22 23plt.title('一天 24 小时时间分配', fontsize=15, pad=14)24plt.axis('equal')                   # 保证是正圆而不是椭圆25plt.tight_layout()26plt.savefig('pie.png', dpi=150)27plt.show()

要点

  • 百分比格式autopct='%.1f%%' 保留一位小数,%% 是一个转义后的百分号。
  • 突出某一块explode 是每块的偏移量列表,只把需要强调的那块设为 0.08。
  • 必须 axis('equal')否则窗口一拉长,饼就变成椭圆,面积比例会被视觉扭曲。
  • 类别超过 6 个别用人眼比较角度的精度远低于比较长度,这时应该换成条形图。
05

直方图:看分布plt.hist()

1000 名学生的成绩分布 —— 数据落在各区间的密集程度

直方图:看分布

图 2-5 成绩分布直方图(均值线与及格线)

05_hist.py Python
1# 直方图:1000 名学生的成绩分布2import matplotlib.pyplot as plt3import numpy as np4 5plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei']6plt.rcParams['axes.unicode_minus'] = False7 8rng = np.random.default_rng(2025)9scores = rng.normal(72, 11, 1000).clip(0, 100)     # 均值 72、标准差 11 的正态分布10scores = scores[(scores >= 0) & (scores <= 100)]11 12plt.figure(figsize=(6.4, 4.3), dpi=120)13 14# bins 是分箱数量,edgecolor 让相邻柱子之间有分隔线15plt.hist(scores, bins=22, color='#60a5fa',16         edgecolor='white', linewidth=1.2, alpha=0.9, label='成绩分布')17 18plt.axvline(scores.mean(), color='#f43f5e', linestyle='--',19            linewidth=2, label=f'均值 = {scores.mean():.1f}')20plt.axvline(60, color='#94a3b8', linestyle=':', linewidth=2, label='及格线 60')21 22plt.title('1000 名学生成绩分布直方图', fontsize=15, pad=14)23plt.xlabel('成绩(分)', fontsize=11)24plt.ylabel('人数', fontsize=11)25plt.legend(frameon=False)26plt.grid(axis='y', alpha=0.2, linestyle=':')27plt.tight_layout()28plt.savefig('hist.png', dpi=150)29plt.show()

要点

  • bins 决定颗粒度分箱太少掩盖细节,太多则毛刺丛生;一般 15–30 个箱比较合适。
  • 参考线点睛plt.axvline() 画竖线,标出均值、中位数或及格线,读者立刻有判断基准。
  • 直方图 ≠ 柱状图柱状图比较"不同类别",直方图看"同一变量的分布",外形相似但含义完全不同。
  • 先清洗再画图用布尔索引把数据过滤到合理区间,否则个别极端值会把整个分布压扁成一根柱子。
06

子图:排版式plt.subplots()

把折线、柱状、散点、饼图放进同一张 2×2 画布

子图:排版式

图 2-6 subplots 2×2 组合布局

06_subplots.py Python
1# 子图:在一张画布上组合四张图表2import matplotlib.pyplot as plt3import numpy as np4 5plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei']6plt.rcParams['axes.unicode_minus'] = False7 8fig, axes = plt.subplots(2, 2, figsize=(9.2, 6.8), dpi=110)9fig.suptitle('subplots:2×2 组合布局', fontsize=16, y=0.985)10 11# 左上:折线图12ax = axes[0, 0]13ax.plot([1, 2, 3, 4, 5], [3, 7, 5, 9, 11], marker='o', color='#2563eb', linewidth=2)14ax.set_title('折线图 plot()', fontsize=12)15ax.grid(alpha=0.25, linestyle=':')16 17# 右上:柱状图18ax = axes[0, 1]19ax.bar(['A', 'B', 'C', 'D'], [12, 18, 9, 15],20       color=['#93c5fd', '#60a5fa', '#3b82f6', '#2563eb'])21ax.set_title('柱状图 bar()', fontsize=12)22ax.grid(axis='y', alpha=0.25, linestyle=':')23 24# 左下:散点图25ax = axes[1, 0]26rng = np.random.default_rng(3)27ax.scatter(rng.normal(0, 1, 60), rng.normal(0, 1, 60),28           s=26, color='#14b8a6', alpha=0.75)29ax.set_title('散点图 scatter()', fontsize=12)30ax.grid(alpha=0.25, linestyle=':')31 32# 右下:饼图33ax = axes[1, 1]34ax.pie([35, 25, 20, 20], labels=['甲', '乙', '丙', '丁'],35       colors=['#2563eb', '#60a5fa', '#93c5fd', '#fbbf24'],36       autopct='%d%%', textprops={'fontsize': 10},37       wedgeprops={'edgecolor': 'white', 'linewidth': 1.6})38ax.set_title('饼图 pie()', fontsize=12)39 40plt.tight_layout()41plt.savefig('subplots.png', dpi=150)42plt.show()

要点

  • 一次拿到画布和坐标轴fig, axes = plt.subplots(2, 2),axes 是一个二维数组,用 axes[行, 列] 取子图。
  • 两种标题ax.set_title() 是子图标题,fig.suptitle() 是整张画布的总标题。
  • 别忘了 tight_layout自动调整子图间距,避免标题、坐标轴标签互相重叠。
  • 统一风格同一张画布里的四张图要共用一套配色和字号,否则看起来像四个人各画各的。
07

常用参数速查表

画图时反复要查的那几行,集中放在这里

需求写法说明
画布大小与清晰度plt.figure(figsize=(6, 4), dpi=120)dpi 越高越清晰,打印用 300
标题 / 轴标签plt.title() / plt.xlabel() / plt.ylabel()配 fontsize、pad 调位置
坐标轴范围plt.xlim(a, b) / plt.ylim(a, b)不看数据分布时最好手动定范围
图例plt.legend(loc='upper right', frameon=False)loc 控制九宫格位置
网格plt.grid(alpha=0.3, linestyle=':')alpha 调淡,别盖过数据
刻度旋转plt.xticks(rotation=45)类别名太长时的必备操作
保存图片plt.savefig('a.png', dpi=300, bbox_inches='tight')必须写在 plt.show() 之前
显示图片plt.show()脚本里可省,Jupyter 中自动显示
08

六个新手最常踩的坑

这些报错基本每个人都会遇到一次,先记下来能省很多时间

中文全部变成方块

没设中文字体。加上 plt.rcParams['font.sans-serif'] = ['Microsoft YaHei'](Mac 用 'PingFang SC')。如果还不行,清一下 matplotlib 缓存:删除 ~/.matplotlib/fontlist-*.json 后重启。

负号显示成方块

字体不支持 Unicode 负号,加 plt.rcParams['axes.unicode_minus'] = False 即可。

保存出来的图是空白

顺序写反了。plt.show() 之后画布会被清空,savefig 必须放在 show 之前。

图例没有出现

plot/bar/scatter 时漏了 label 参数,或者忘了调用 plt.legend()。

子图标题互相重叠

加一行 plt.tight_layout(),它会自动计算合适的间距。

图片模糊

dpi 太低。保存时 dpi=300,或者直接存 .svg / .pdf 矢量格式,任意放大都清晰。

09

本章小结

六种图表对应六种数据关系,选错图比画得难看更致命

01

先看数据关系,再选图形

随时间变化用折线,类别比大小用柱状,两变量找关系用散点,部分与整体用饼图,看分布用直方图。

02

四要素缺一不可

标题说清"这张图讲什么",轴标签带单位,图例区分系列,必要时补参考线——缺一个读者就得猜。

03

样式服务于信息

去掉多余的边框、压淡网格、统一配色。装饰越少,数据越突出;这一点比记住所有参数都重要。