
第 15 章 可视化入门:Matplotlib 做出像样的图
-
- [15.1 本章你将学会什么](#15.1 本章你将学会什么)
- [15.2 先讲清楚:Matplotlib 的正确心智模型](#15.2 先讲清楚:Matplotlib 的正确心智模型)
- [15.3 数据准备:用一个"科研风格"的小数据集贯穿全章](#15.3 数据准备:用一个“科研风格”的小数据集贯穿全章)
- [15.4 折线图:趋势展示(时间序列/实验过程)](#15.4 折线图:趋势展示(时间序列/实验过程))
- [15.5 散点图:相关关系与离群点(最常用)](#15.5 散点图:相关关系与离群点(最常用))
- [15.6 直方图:分布形态(正态性、偏态、峰度)](#15.6 直方图:分布形态(正态性、偏态、峰度))
- [15.7 箱线图:组间差异 + 离群点(论文常用)](#15.7 箱线图:组间差异 + 离群点(论文常用))
- [15.8 柱状图:均值对比(一定要加误差棒)](#15.8 柱状图:均值对比(一定要加误差棒))
- [15.9 让图"像样"的 8 个细节(建议你形成模板)](#15.9 让图“像样”的 8 个细节(建议你形成模板))
- [15.10 字体与中文:最常见的"图一出就崩"问题](#15.10 字体与中文:最常见的“图一出就崩”问题)
- [15.11 导出:让你的图可以直接进论文(强烈建议用 PDF)](#15.11 导出:让你的图可以直接进论文(强烈建议用 PDF))
- [15.12 本章练习(建议你真跑一遍)](#15.12 本章练习(建议你真跑一遍))
-
- [练习 1:画一张"组间差异图"](#练习 1:画一张“组间差异图”)
- [练习 2:画一张"相关图"](#练习 2:画一张“相关图”)
- [练习 3:导出两种格式](#练习 3:导出两种格式)
- [15.13 小结:你应该带走的"工程化结论"](#15.13 小结:你应该带走的“工程化结论”)
- 下一章
做科研数据分析,画图不是"把数据画出来"这么简单。
一张像样的图,至少要做到三件事:信息表达清晰、统计含义正确、可在论文/报告中直接复用 。
这一章用 Matplotlib 走一遍从 0 到"能交付"的绘图流程:从最常用图型,到排版、字体、导出,再到科研场景常见坑的规避。
15.1 本章你将学会什么
- Matplotlib 的基本对象模型:
Figure / Axes(告别"画着画着失控") - 画出科研常用 6 类图:折线、散点、直方图、箱线图、柱状图、误差棒图
- 让图"像样"的关键细节:标题、坐标轴、刻度、网格、注释、图例
- 可复现导出:DPI、尺寸、矢量格式(PDF/SVG)、命名规范
- 建立你自己的"科研绘图模板":一套代码贯穿整篇论文
15.2 先讲清楚:Matplotlib 的正确心智模型
很多初学者是这样画图的:
python
import matplotlib.pyplot as plt
plt.plot([1,2,3],[4,5,6])
plt.show()
能画,但会遇到三个问题:
- 图一复杂就难控制
- 多图排版会乱
- 论文导出格式不稳定
推荐从一开始就用"面向对象(OO)写法":
python
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(6, 4))
ax.plot([1,2,3], [4,5,6])
ax.set_title("Example")
ax.set_xlabel("x")
ax.set_ylabel("y")
plt.show()
记住:
fig 管画布(整体),ax 管坐标系(具体图)。你要控制排版、尺寸、导出,就必须把 fig/ax 拿在手里。
15.3 数据准备:用一个"科研风格"的小数据集贯穿全章
python
import numpy as np
import pandas as pd
np.random.seed(42)
n = 120
df = pd.DataFrame({
"sid": np.arange(1, n+1),
"group": np.random.choice(["control", "treatment"], size=n),
"age": np.random.normal(24, 2.5, size=n).round(1),
"score": np.random.normal(80, 8, size=n).round(1)
})
df.head()
15.4 折线图:趋势展示(时间序列/实验过程)
折线图适用于:随时间、随试次变化的趋势。
python
import matplotlib.pyplot as plt
t = np.arange(1, 11)
y = np.cumsum(np.random.normal(0, 1, size=10)) + 50
fig, ax = plt.subplots(figsize=(7, 4))
ax.plot(t, y, marker="o")
ax.set_title("Learning Curve (Example)")
ax.set_xlabel("Trial")
ax.set_ylabel("Score")
ax.grid(True, alpha=0.3)
plt.show()
科研建议
- 趋势图必须有明确的横轴含义(时间、试次、阶段)
- 不要用过多线条堆叠,容易造成"读不出来"
15.5 散点图:相关关系与离群点(最常用)
python
fig, ax = plt.subplots(figsize=(6, 4))
ax.scatter(df["age"], df["score"], alpha=0.7)
ax.set_title("Age vs Score")
ax.set_xlabel("Age")
ax.set_ylabel("Score")
ax.grid(True, alpha=0.3)
plt.show()
你应该顺手做两件事
- 观察离群点(outliers)
- 初步判断是否线性关系(后续回归分析的直觉基础)
15.6 直方图:分布形态(正态性、偏态、峰度)
python
fig, ax = plt.subplots(figsize=(6, 4))
ax.hist(df["score"], bins=15, alpha=0.8)
ax.set_title("Distribution of Score")
ax.set_xlabel("Score")
ax.set_ylabel("Count")
ax.grid(True, alpha=0.3)
plt.show()
科研建议
bins不要拍脑袋:样本小用少 bins,样本大用多 bins- 直方图是"分布直觉",不是统计检验本身
15.7 箱线图:组间差异 + 离群点(论文常用)
python
control = df[df["group"] == "control"]["score"]
treat = df[df["group"] == "treatment"]["score"]
fig, ax = plt.subplots(figsize=(6, 4))
ax.boxplot([control, treat], labels=["control", "treatment"])
ax.set_title("Score by Group (Boxplot)")
ax.set_ylabel("Score")
ax.grid(True, axis="y", alpha=0.3)
plt.show()
科研建议
- 箱线图非常适合做"组间分布对比",而不是只展示均值
- 论文里"只画柱状图"往往信息不足(容易被审稿人质疑)
15.8 柱状图:均值对比(一定要加误差棒)
柱状图在科研里最大的问题是:只画均值会误导 。
你至少要加上误差棒(标准差/标准误/置信区间),并说明是什么。
python
summary = df.groupby("group")["score"].agg(["mean", "std", "count"])
summary["sem"] = summary["std"] / np.sqrt(summary["count"])
summary
python
fig, ax = plt.subplots(figsize=(6, 4))
groups = summary.index.tolist()
means = summary["mean"].values
sems = summary["sem"].values
ax.bar(groups, means, yerr=sems, capsize=6, alpha=0.9)
ax.set_title("Mean Score by Group (with SEM)")
ax.set_ylabel("Score")
ax.grid(True, axis="y", alpha=0.3)
plt.show()
提醒:误差棒用
std还是sem,必须在图注或正文写清楚。
15.9 让图"像样"的 8 个细节(建议你形成模板)
你可以逐条对照检查:
- 标题是否描述了"图要说明什么",而不是"图是什么"
- 坐标轴是否标注单位(如 ms、%、cm、years)
- 刻度是否过密、是否需要旋转
- 是否加网格(轻微即可)帮助读数
- 图例是否遮挡数据点
- 是否标注关键点(峰值、异常、分界线)
- 是否控制留白(tight_layout)
- 导出是否满足投稿要求(DPI/矢量)
示例:统一排版
python
fig, ax = plt.subplots(figsize=(6, 4))
ax.scatter(df["age"], df["score"], alpha=0.6)
ax.set_title("Age vs Score")
ax.set_xlabel("Age (years)")
ax.set_ylabel("Score")
ax.grid(True, alpha=0.25)
fig.tight_layout()
plt.show()
15.10 字体与中文:最常见的"图一出就崩"问题
科研场景里,中文最容易遇到:
- 中文乱码
- 负号显示为方块
在不同系统字体可用情况不同。推荐你在项目里写一个"尝试多字体"的小段:
python
import matplotlib as mpl
mpl.rcParams["axes.unicode_minus"] = False
mpl.rcParams["font.sans-serif"] = ["Arial Unicode MS", "SimHei", "Microsoft YaHei"]
说明:不同机器字体库不同,这段的关键是"备选列表",提高可移植性。
15.11 导出:让你的图可以直接进论文(强烈建议用 PDF)
- 论文图优先:
pdf(矢量)或svg - 网页展示:
png(高 DPI)
python
fig, ax = plt.subplots(figsize=(6, 4))
ax.hist(df["score"], bins=15, alpha=0.85)
ax.set_title("Score Distribution")
ax.set_xlabel("Score")
ax.set_ylabel("Count")
ax.grid(True, alpha=0.3)
fig.tight_layout()
fig.savefig("fig_score_distribution.png", dpi=300)
fig.savefig("fig_score_distribution.pdf") # 矢量
命名建议(科研习惯)
fig_01_xxx.png/pdffig_02_xxx.png/pdf- 与论文图号一致,减少后期排版痛苦
15.12 本章练习(建议你真跑一遍)
练习 1:画一张"组间差异图"
要求:
- 既包含分布信息(箱线图或散点抖动)
- 又包含均值与误差棒(可叠加,或并排展示)
练习 2:画一张"相关图"
要求:
- 散点图 + 标注 2 个离群点的 sid(用
ax.annotate)
练习 3:导出两种格式
- 导出
png(dpi=300) - 导出
pdf
并检查二者在放大时的差异
15.13 小结:你应该带走的"工程化结论"
- Matplotlib 的核心是 fig/ax,拿住它你才能稳定控制排版与导出
- 科研图最怕"漂亮但不可信":柱状图必须配误差棒,分布最好直接展示
- 做图要形成模板:标题、轴、刻度、网格、tight_layout、导出格式一套固定下来
下一章
《第16章 小项目2:CSV→清洗→统计→图表→报告输出》