1 figure 与 axes —— 画布与坐标系
是什么?为什么学?
figure(画布)是整张图,axes(坐标系)是画布上真正画图的那块区域。matplotlib 所有绘图都遵循同一模式:先有画布,再建坐标系,最后往里画东西。不理解这两个概念,画多子图、调布局时一定绕晕——这是今天最重要的 10%。前面 24 天我们学会处理数据,但一堆数字摆在那里,人很难一眼看出规律——图表是给眼睛看的,今天学 Python 最常用的绘图库 matplotlib。
底层原理
plt.subplots() 一次创建两者,返回 (figure, axes)。plt.plot() 这类快捷写法内部也偷偷创建了默认 figure + 默认 axes(pyplot 状态机机制)。一个 figure 能装多个 axes:plt.subplots(1, 2) 返回「一行两列」的 axes 数组,每个元素是独立坐标系。
生活类比
figure 像一张画纸,axes 像画纸上的取景框:一张纸可排好几个取景框(多子图),整张纸就是最终交给别人的那张图。
注意事项总结
1 figure 与 axes 的分工(离线可跑,图保存到 Temp 目录):
import os
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False
os.makedirs("Temp", exist_ok=True) # 确保 Temp 目录存在
fig, ax = plt.subplots() # 画布 + 坐标系
ax.plot([1, 2, 3, 4], [1, 4, 9, 16]) # 往坐标系里画折线
ax.set_title("y = x^2") # axes 自带设置方法
plt.savefig("Temp/day25_fig_axes.png", dpi=150)
fig, ax = plt.subplots() 同时拿到画布和坐标系;ax.plot(...) 往坐标系里画线(注意是 ax. 不是 plt.);savefig 保存的是整个 figure。
2 一个画布装两个坐标系(离线可跑):
fig, axes = plt.subplots(1, 2, figsize=(8, 3)) # 1 行 2 列
axes[0].plot([1, 2, 3], [1, 2, 3]) # 左框:直线
axes[1].bar(["A", "B", "C"], [3, 1, 2]) # 右框:柱状
plt.tight_layout()
plt.savefig("Temp/day25_subplots.png", dpi=150)
subplots(1, 2) 返回坐标系数组,按下标 axes[0] / axes[1] 分别画图。多子图用 plt.subplots(2, 2) 后用 axes[0, 0].plot(...) 指定子图。
易错点
- 错误写法:
axes.plot(...)(axes 是数组时) → 问题:数组没有.plot,报AttributeError→ 正确写法:按下标取单个:axes[0].plot(...)。 - 错误写法:
plt.plot()和ax.set_title()混用 → 问题:标题加不到刚画的那张图上 → 正确写法:要么全用plt.xxx(),要么全用ax.xxx()。
记忆口诀:figure 是画纸,axes 是取景框;一张纸可排多框,先建后画不慌忙。
2 绘图三步法 —— 数据、类型、样式
是什么?为什么学?
任何一张 matplotlib 图表都只有三步:准备数据 → 选图表类型并传入数据 → 调样式。今天要学的四种图都是这三步的变体。把「三步法」刻在脑子里,遇到新图表类型也不会慌。
底层原理
每种图对应一个绘图函数:plt.plot(x, y) 把点连成线(连续数据看趋势);plt.bar(x, y) 为每个类别画柱子(离散类别比大小);plt.scatter(x, y) 只画点不连线(找两个变量的关系);plt.pie(values) 按占比画扇形(看部分占整体多少)。样式参数:color 颜色、linestyle 线型(-- 虚线)、marker 数据点标记(o 圆点)。
生活类比
像做 PPT 图表:先有数据表(准备数据),再选图表类型(柱状还是折线),最后换配色(调样式)。数据没准备好就选类型,等于没量尺寸就裁衣服。
注意事项总结
1 三步法画一张精致的折线图(离线可跑):
import os
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False
os.makedirs("Temp", exist_ok=True)
months = [1, 2, 3, 4, 5, 6] # 第一步:准备数据
temperature = [5, 8, 15, 22, 28, 33]
plt.plot(months, temperature, color="red", linestyle="--", marker="o")
plt.title("上半年气温变化")
plt.xlabel("月份")
plt.ylabel("气温(℃)")
plt.savefig("Temp/day25_line.png", dpi=150)
先准备两个等长列表 x=months、y=temperature;plt.plot(...) 画折线,color 红、linestyle="--" 虚线、marker="o" 数据点画圈;title/xlabel/ylabel 配齐三要素。
2 四种图的选择:折线看趋势、柱状比大小、散点找相关、饼图看占比。横向柱状图 barh 适合科目名很长的情况;饼图 autopct="%.1f%%" 在每块上显示百分比,explode 让某块突出。
易错点
- 错误写法:
plt.plot(x)只传一个列表 → 问题:x 自动变成下标 0,1,2…,图与预期不符 → 正确写法:成对传:plt.plot(x, y)。 - 错误写法:x、y 长度不同 → 问题:报
ValueError: x and y must have same first dimension→ 正确写法:先print(len(x), len(y))检查对齐。
记忆口诀:先数据后类型再样式,三步画图不慌张;趋势折线、比大小柱状、找关系散点、看占比饼图。
3 中文显示与 rcParams —— 字体配置
是什么?为什么学?
matplotlib 的默认字体不含中文字形,直接画中文标题会变成方框(□)。解决办法:脚本开头用 plt.rcParams 指定系统中文字体。注意:不同系统的字体名不同——Windows 自带 SimHei(黑体),macOS 是 “Arial Unicode MS” 或 “PingFang SC”,Linux 常见 “Noto Sans CJK SC”。
底层原理
plt.rcParams 是 matplotlib 的全局配置字典,font.sans-serif 是「无衬线字体候选列表」,渲染时按列表顺序找字体,找不到中文字形就退回默认英文字体,中文变方框(专业说法叫 tofu)。axes.unicode_minus = False 让负号用普通连字符「-」渲染,否则负号按 Unicode 数学符号处理照样变方框。
生活类比
像给打印机装字库:打印机(matplotlib)默认只认识英文字库,打中文就是 □□□;装上中文字库(rcParams 指定字体)之后,中文才能正常打印出来。
注意事项总结
1 配置中文字体 + 验证字体真的可用(离线可跑):
import os
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False
os.makedirs("Temp", exist_ok=True)
fonts = [f.name for f in fm.fontManager.ttflist] # 系统已注册字体名
print("SimHei 可用:", "SimHei" in fonts)
plt.plot([1, 2, 3, 4], [10, 5, -5, 12], marker="o")
plt.title("中文标题测试:气温变化")
plt.savefig("Temp/day25_chinese.png", dpi=150)
fm.fontManager.ttflist 是系统已注册字体列表,"SimHei" in fonts 检查黑体是否可用——建议先跑这行确认字体存在再画图。运行后中文标题清清楚楚,纵轴负数 -5 也显示正常。
2 所有画图脚本开头的标准两行:
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False # 让负号正常显示
易错点
- 错误写法:只配
font.sans-serif不配axes.unicode_minus→ 问题:负号(如 -5)显示成方框 → 正确写法:两行一起写。 - 错误写法:macOS / Linux 上照抄 SimHei → 问题:系统没装黑体,中文还是方框 → 正确写法:换成本机字体名(macOS 用 “Arial Unicode MS”)。
记忆口诀:开头两行 rcParams,中文方框不再见;字体名随系统换,负号配置别忘关。
4 savefig 保存图片 —— 先保存,再显示
是什么?为什么学?
plt.savefig("文件名.png", dpi=150) 把当前 figure 保存成图片文件,报告、PPT、作业提交全靠它。服务器没有图形界面、以及自动化批量出图的场景,savefig 是唯一的取图方式。
底层原理
savefig 按格式写入文件:.png 位图(清晰度由 dpi 控制)、.jpg 位图(体积小但有损)、.svg / .pdf 矢量图(无限放大不模糊)。dpi(dots per inch,每英寸点数)越高越清晰,默认 100,写报告用 150~300。为什么先 savefig 再 show:show() 进入交互展示后,某些环境下画布内容会被重置,之后再 savefig 可能得到空白图。
生活类比
像手机拍照后立刻「保存到相册」:先保存(savefig),再预览(show)。顺序反了,预览关闭后图就没了。
注意事项总结
1 保存图片 + 验证文件存在(离线可跑):
import os
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False
os.makedirs("Temp", exist_ok=True) # 目录不存在就先创建
plt.bar(["1月", "2月", "3月"], [120, 180, 150])
plt.title("季度销量")
plt.savefig("Temp/day25_save.png", dpi=150) # 先保存
print("文件存在:", os.path.exists("Temp/day25_save.png"))
print("文件大小(字节):", os.path.getsize("Temp/day25_save.png"))
os.makedirs("Temp", exist_ok=True) 确保目录存在(不存在时 savefig 直接报错);savefig 保存(此时还没调用 show);os.path.exists 检查文件生成、os.path.getsize 看大小——非 0 说明不是空白图。
易错点
- 错误写法:
plt.show()之后再savefig()→ 问题:可能保存到空白图 → 正确写法:先savefig()再show(),或只savefig()。 - 错误写法:保存路径的目录不存在 → 问题:报
FileNotFoundError→ 正确写法:先os.makedirs("Temp", exist_ok=True)。
记忆口诀:先保存来后展示,dpi 记得调清晰;目录先建文件验,非 0 大小才放心。
5 标签、图例与网格 —— 让图会说话
是什么?为什么学?
一张图光有线条没人看得懂:得告诉别人「横轴是什么、纵轴是什么、每条线代表什么」。title / xlabel / ylabel 是图的三要素,legend(图例)标注多条线,grid(网格)方便读数。配齐这些,图表才从「草稿」变成「作品」。
底层原理
plt.plot(..., label="最高温") 里的 label 给这条线起名,但名字不会自动显示——要再调 plt.legend(),它把当前坐标系里所有带 label 的元素收集起来渲染成图例。plt.grid(True, linestyle="--", alpha=0.5) 画刻度网格线,alpha 是透明度(0~1),淡网格不抢主体。多子图时每个 axes 要单独 ax.legend()、ax.set_xlabel()。
生活类比
图例像地图右下角的图例说明:没有它,别人只能猜哪条线是最高温;有了它,一眼对号入座。
注意事项总结
1 双折线 + 图例 + 网格(离线可跑):
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False
days = [1, 2, 3, 4, 5, 6, 7]
high = [32, 34, 31, 29, 33, 35, 30] # 最高温
low = [24, 25, 23, 22, 24, 26, 23] # 最低温
plt.plot(days, high, marker="o", label="最高温")
plt.plot(days, low, marker="s", label="最低温")
plt.title("一周气温走势")
plt.xlabel("日期(1~7)")
plt.ylabel("温度(℃)")
plt.legend() # 显示图例
plt.grid(True, linestyle="--", alpha=0.5) # 淡虚线网格
plt.savefig("Temp/day25_legend.png", dpi=150)
两条 plot 都加 label 参数;plt.legend() 把名字渲染到图例框;plt.grid(...) 加淡虚线网格方便读数。
易错点
- 错误写法:画了两条线但没写
label就调legend()→ 问题:图例是空框,没有名字 → 正确写法:每条线都写label="...",再调一次legend()。 - 错误写法:把
label写进plt.title()→ 问题:标题和图例混为一谈 → 正确写法:label给线命名,title给整张图命名。
记忆口诀:标题轴标加图例,网格一开更好读;label 给线起名字,legend 负责展示它。
6 动手实践:一周学习可视化分析报告
把本周 7 天、每天 3 个维度的学习数据画成 4 张图,并生成一张「可视化分析报告」图片(一页多子图)。
# report.py —— 一周学习可视化分析报告
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False
days = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
hours = [3, 4, 3, 2, 4, 5, 4] # 每天学习时长(小时)
code_lines = [120, 200, 150, 80, 210, 320, 180] # 每天写的代码行数
errors = [6, 4, 5, 8, 4, 2, 3] # 每天遇到的报错次数
subjects = ["语法基础", "文件与异常", "面向对象", "数据分析"]
subject_hours = [8, 10, 12, 15] # 各主题累计学习时长
fig, axes = plt.subplots(2, 2, figsize=(12, 8)) # 2 行 2 列共 4 个子图
fig.suptitle("一周学习可视化分析报告", fontsize=16)
# 子图1:每天学习时长柱状图
axes[0, 0].bar(days, hours, color="steelblue")
axes[0, 0].set_title("每天学习时长(小时)")
# 子图2:代码行数 vs 报错次数折线图
axes[0, 1].plot(days, code_lines, marker="o", label="代码行数")
axes[0, 1].plot(days, errors, marker="s", label="报错次数")
axes[0, 1].set_title("代码量 vs 报错数")
axes[0, 1].legend()
# 子图3:学习时长与报错的散点图(相关关系)
axes[1, 0].scatter(hours, errors, color="coral")
axes[1, 0].set_title("学习时长与报错次数")
axes[1, 0].set_xlabel("学习时长(小时)")
axes[1, 0].set_ylabel("报错次数")
# 子图4:各主题时间占比饼图
axes[1, 1].pie(subject_hours, labels=subjects, autopct="%.1f%%")
axes[1, 1].set_title("各主题学习时间占比")
plt.tight_layout() # 自动调整子图间距,避免重叠
plt.savefig("week_report.png", dpi=150)
print("报告已保存:week_report.png")
运行效果:
报告已保存:week_report.png
打开 week_report.png,能看到 2×2 排列的 4 张图:柱状图看每天时长、折线图对比代码量和报错、散点图看相关性、饼图看主题占比。试着写一句分析结论,比如「周六学得最多但报错最少,说明熟练度上来了」——这就是一份简单的可视化分析报告。
升级挑战:换成你自己的真实数据(用 JSON 文件读数据再画图),并把报告保存成 my_report.png。
今日总结
figure 是画布、axes 是画布上的坐标系;plt.subplots(1, 2) 返回数组,按下标取单个坐标系
绘图三步法:准备数据 → 选图表类型 → 调样式;折线看趋势、柱状比大小、散点找相关、饼图看占比,选图不纠结
每张图都配好标题、轴标签,需要区分多条线时加图例(label + legend),淡网格方便读数
中文显示两件套:font.sans-serif 指定系统中文字体 + axes.unicode_minus = False;字体名随系统变
保存图片:先 savefig() 再 show();dpi=150~300;目录先建、文件用 os.path.exists 验证
完成「一周学习可视化分析报告」:一页 4 子图(2×2),并写出一句分析结论
| 报错 | 原因 | 修复 |
|---|---|---|
ModuleNotFoundError: No module named 'matplotlib' | 没安装 matplotlib | 命令行执行 pip install matplotlib |
| 图里的中文全是方框(□) | 默认字体不含中文字形 | 脚本开头加 plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"] |
ValueError: x and y must have same first dimension | x 和 y 长度不一致 | 检查两个列表元素个数是否相等 |
| 保存出来的图片是空白 | show() 之后才 savefig(),窗口已关闭画布被清空 | 先 savefig() 再 show(),或只调用 savefig() |
AttributeError: 'numpy.ndarray' object has no attribute 'plot' | plt.subplots(2, 2) 返回的是 2×2 的数组,不能直接 .plot() | 用 axes[0, 0].plot(...) 指定子图,不要写 axes.plot(...) |