Week 04:可视化 + EDA 报告
返回主线计划:USTC 统计学 AI / 量化 20 周成长计划
本文命令默认使用 fish shell。本周目标不是“画很多图”,而是学会用图表支持数据观察,并把观察写成一份结构清楚、可复现、不过度解释的 EDA 报告。
0. 本周详细教学:语法、规范、验收
本节不是追加在尾部的复习,而是本周正文的入口。先读这里,再做后面的命令和项目。
0.1 本周真正要学会什么
| 维度 | 要求 |
|---|---|
| 知识点 | matplotlib、分布、箱线图、相关性、报告叙事 |
| 代码语法 | 能从空文件写出本周核心脚本,而不是只复制运行 |
| 程序规范 | 函数拆分、路径清楚、输入输出明确、错误能解释 |
| 交付物 | reports/week04_eda_report.md |
| 验收方式 | 从 fish 终端运行命令,得到可复查的文件或指标 |
0.2 代码语法精讲
下面的代码不是最终答案,而是本周必须理解的最小骨架:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(8, 4)) df[“value”].hist(ax=ax, bins=30) ax.set_title(“Value distribution”) ax.set_xlabel(“value”) ax.set_ylabel(“count”) fig.tight_layout() fig.savefig(“figures/value_distribution.png”, dpi=160)
读代码时按四步检查:输入从哪里来;中间变量的类型和 shape 是什么;函数或脚本输出什么;哪些错误应该显式报出来。
0.3 本周程序规范
- 所有路径用相对路径或 `pathlib.Path`,不要写死 `/home/miku/...`。
- 核心逻辑进 `src/`,notebook 只做探索和解释。
- 每个脚本能从 fish 终端运行,并在 README 写出命令。
- 输出必须落盘到 `reports/`、`figures/` 或 `outputs/`,不能只在屏幕上看。
0.4 本周练习分层
| 层级 | 任务 | 不合格表现 | 合格验收 |
|---|---|---|---|
| 最小练习 | 手写上面的最小骨架 | 只在 notebook 里运行 | 终端运行成功 |
| 标准练习 | 把逻辑拆成函数/模块 | 一个大脚本从头写到尾 | 至少 2 个函数,职责清楚 |
| 项目练习 | 生成本周交付物 reports/week04_eda_report.md | 只有屏幕输出 | 文件落盘,可复查 |
| 复盘练习 | 写 3 个错误和修复 | 只写“已解决” | 写清报错、原因、修复、预防 |
0.5 本周和主线的连接
- 回到总计划:USTC AI / Quant 练习手册
- 查详细练习索引:技术练习详解
- 查质量评分:最终质量门槛
1. 本周目标
完成 Week 04 后,你应该能:
- 使用 matplotlib 绘制直方图、散点图、箱线图。
- 在安装 seaborn 时使用 seaborn 画更省力的统计图。
- 把图保存到
figures/*.png,而不是只显示在 notebook 输出里。 - 建立 EDA 报告结构:数据来源、字段说明、缺失值、主要分布、相关性观察、可能建模目标。
- 理解 notebook 和 script 的分工:探索可以用 notebook,最终结果要能用脚本复现。
文件布局
建议本周交付物:
week04-visualization-eda/
├── data/
│ ├── raw/
│ │ └── student_scores.csv
│ └── processed/
│ └── student_scores_clean.csv
├── figures/
│ ├── average_score_hist.png
│ ├── study_hours_vs_average_score.png
│ ├── average_score_by_major.png
│ └── correlation_heatmap.png
├── notebooks/
│ └── week04_visualization_eda.ipynb
├── reports/
│ └── week04_eda_report.md
├── src/
│ ├── prepare_data.py
│ └── make_figures.py
├── .venv/
├── pyproject.toml
└── README.md
2. 前置要求
你需要已经会:
- 用 pandas 读取 CSV。
- 检查缺失值和描述统计。
- 使用
uv run python ...运行脚本。 - 在 VS Code 中编辑
.py和.md文件。 - 使用 fish 激活虚拟环境。
如果 Week 03 的项目还在,也可以直接复制 Week 03 的清洗后数据到 Week 04 项目。为了教程完整,下面从新项目开始。
3. 创建 Week 04 项目
mkdir -p ~/Code/python-learning/week04-visualization-eda
cd ~/Code/python-learning/week04-visualization-eda
mkdir -p src data/raw data/processed figures notebooks reports
uv init
uv venv
source .venv/bin/activate.fish
uv add numpy pandas matplotlib
code .
解释:
| 命令 | 作用 |
|---|---|
mkdir -p ... |
创建本周项目目录和子目录 |
uv init |
初始化项目配置 |
uv venv |
创建 .venv |
source .venv/bin/activate.fish |
fish 中激活虚拟环境 |
uv add numpy pandas matplotlib |
安装数据处理和绘图库 |
code . |
用 VS Code 打开项目 |
检查安装:
uv run python -c "import pandas, matplotlib; print('ok')"
如果你想使用 seaborn,可以额外安装:
uv add seaborn
本教程会给出 matplotlib 主线;seaborn 是可选增强。
4. 准备数据
创建原始数据:
printf 'student,major,gender,math,english,python,study_hours\nA,statistics,F,88,79,85,12\nB,statistics,M,92,81,90,14\nC,finance,F,75,86,78,9\nD,finance,M,80,,82,8\nE,statistics,F,95,88,,15\nF,computer,M,70,76,88,10\nG,computer,F,,90,91,11\nH,statistics,M,84,82,86,13\nI,finance,F,78,84,80,9\nJ,computer,M,73,78,84,10\nK,statistics,F,90,85,92,14\nL,finance,M,82,80,79,7\n' > data/raw/student_scores.csv
检查:
head data/raw/student_scores.csv
wc -l data/raw/student_scores.csv
这里仍然是练习数据,但比 Week 03 多几行,方便画图。
5. 写数据准备脚本
创建 src/prepare_data.py:
from pathlib import Pathimport pandas as pd
RAW_PATH = Path(“data/raw/student_scores.csv”) OUTPUT_PATH = Path(“data/processed/student_scores_clean.csv”) NUMERIC_COLUMNS = [“math”, “english”, “python”, “study_hours”] SCORE_COLUMNS = [“math”, “english”, “python”]
def load_data(path: Path) -> pd.DataFrame: return pd.read_csv(path)
def clean_data(df: pd.DataFrame) -> pd.DataFrame: cleaned = df.copy()
plain for column in NUMERIC_COLUMNS: cleaned[column] = pd.to_numeric(cleaned[column], errors=“coerce”) cleaned[column] = cleaned[column].fillna(cleaned[column].median())
plain cleaned[“average_score”] = cleaned[SCORE_COLUMNS].mean(axis=1) return cleaned
def save_data(df: pd.DataFrame, path: Path) -> None: path.parent.mkdir(parents=True, exist_ok=True) df.to_csv(path, index=False)
def main() -> None: raw = load_data(RAW_PATH) cleaned = clean_data(raw) save_data(cleaned, OUTPUT_PATH) print(f”Rows: {len(cleaned)}”) print(f”Columns: {len(cleaned.columns)}”) print(f”Saved: {OUTPUT_PATH}”)
if name == “main”: main()
运行:
uv run python src/prepare_data.py
ls data/processed
head data/processed/student_scores_clean.csv
这一步保证后续画图总是使用同一份清洗后数据。
6. matplotlib 基础概念
matplotlib 的核心是 figure 和 axes:
figure:整张画布。axes:画布上的一个坐标系,真正画图的位置。
常用模板:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(8, 5)) ax.hist(values, bins=10) ax.set_title(“Title”) ax.set_xlabel(“X label”) ax.set_ylabel(“Y label”) fig.tight_layout() fig.savefig(“figures/example.png”, dpi=150) plt.close(fig)
为什么要 plt.close(fig):脚本批量生成多张图时,关闭旧图可以避免内存和状态混乱。
7. 写可复现画图脚本
创建 src/make_figures.py:
from pathlib import Pathimport matplotlib.pyplot as plt import pandas as pd
DATA_PATH = Path(“data/processed/student_scores_clean.csv”) FIGURE_DIR = Path(“figures”) SCORE_COLUMNS = [“math”, “english”, “python”, “average_score”]
def load_data(path: Path) -> pd.DataFrame: return pd.read_csv(path)
def save_current_figure(fig, filename: str) -> None: FIGURE_DIR.mkdir(parents=True, exist_ok=True) output_path = FIGURE_DIR / filename fig.tight_layout() fig.savefig(output_path, dpi=150) plt.close(fig) print(f”Saved {output_path}”)
def plot_average_score_hist(df: pd.DataFrame) -> None: fig, ax = plt.subplots(figsize=(8, 5)) ax.hist(df[“average_score”], bins=8, edgecolor=“black”) ax.set_title(“Distribution of Average Score”) ax.set_xlabel(“Average score”) ax.set_ylabel(“Student count”) save_current_figure(fig, “average_score_hist.png”)
def plot_study_hours_scatter(df: pd.DataFrame) -> None: fig, ax = plt.subplots(figsize=(8, 5)) ax.scatter(df[“study_hours”], df[“average_score”]) ax.set_title(“Study Hours vs Average Score”) ax.set_xlabel(“Study hours per week”) ax.set_ylabel(“Average score”) save_current_figure(fig, “study_hours_vs_average_score.png”)
def plot_major_boxplot(df: pd.DataFrame) -> None: majors = sorted(df[“major”].unique()) values = [df.loc[df[“major”] == major, “average_score”] for major in majors]
plain fig, ax = plt.subplots(figsize=(8, 5)) ax.boxplot(values, labels=majors) ax.set_title(“Average Score by Major”) ax.set_xlabel(“Major”) ax.set_ylabel(“Average score”) save_current_figure(fig, “average_score_by_major.png”)
def plot_correlation_heatmap(df: pd.DataFrame) -> None: corr = df[SCORE_COLUMNS + [“study_hours”]].corr()
plain fig, ax = plt.subplots(figsize=(7, 6)) image = ax.imshow(corr, cmap=“coolwarm”, vmin=-1, vmax=1)
plain ax.set_xticks(range(len(corr.columns))) ax.set_yticks(range(len(corr.index))) ax.set_xticklabels(corr.columns, rotation=45, ha=“right”) ax.set_yticklabels(corr.index) ax.set_title(“Correlation Heatmap”)
plain for row in range(len(corr.index)): for col in range(len(corr.columns)): ax.text(col, row, f”{corr.iloc[row, col]:.2f}”, ha=“center”, va=“center”, color=“black”)
fig.colorbar(image, ax=ax) save_current_figure(fig, "correlation_heatmap.png")def main() -> None: df = load_data(DATA_PATH) plot_average_score_hist(df) plot_study_hours_scatter(df) plot_major_boxplot(df) plot_correlation_heatmap(df)
if name == “main”: main()
运行完整流程:
uv run python src/prepare_data.py
uv run python src/make_figures.py
ls figures
你应该看到:
average_score_by_major.png
average_score_hist.png
correlation_heatmap.png
study_hours_vs_average_score.png
8. 图表一:直方图看分布
直方图回答的问题是:一个数值变量大致集中在哪里?是否偏态?是否有异常值?
本教程中的直方图:
ax.hist(df["average_score"], bins=8, edgecolor="black")
解释:
| 参数 | 含义 |
|---|---|
df["average_score"] |
要观察分布的变量 |
bins=8 |
分成 8 个区间 |
edgecolor="black" |
给柱子加边框,方便阅读 |
报告中不要只写“画了直方图”,要写观察:
平均分主要集中在 80 到 90 分之间,样本中没有特别低的平均分。由于样本量很小,这个分布只能用于流程练习,不能代表真实学生群体。
9. 图表二:散点图看两个变量关系
散点图回答的问题是:两个数值变量之间是否有明显关系?
本教程中:
ax.scatter(df["study_hours"], df["average_score"])
横轴是学习时长,纵轴是平均分。
报告写法示例:
散点图显示学习时长和平均分大致同向变化:学习时长较高的学生平均分往往也较高。但样本只有 12 行,不能据此得出稳定因果结论。
注意“相关不等于因果”。统计学同学尤其要避免把 EDA 观察写成因果判断。
10. 图表三:箱线图看组间差异
箱线图适合比较不同组的数值分布。这里按 major 比较 average_score。
代码核心:
majors = sorted(df["major"].unique())
values = [df.loc[df["major"] == major, "average_score"] for major in majors]
ax.boxplot(values, labels=majors)
报告中可以写:
statistics 组平均分中位数较高,computer 和 finance 组略低。但每组样本数量很少,箱线图主要用于展示方法,不适合做群体差异结论。
真实项目中还应补充每组样本数,否则箱线图容易误导。
11. 图表四:相关性热力图
相关性热力图用来观察多个数值变量之间的线性相关关系。
核心代码:
corr = df[["math", "english", "python", "average_score", "study_hours"]].corr()
corr() 默认计算 Pearson 相关系数,范围从 -1 到 1:
| 值 | 含义 |
|---|---|
| 接近 1 | 强正相关 |
| 接近 -1 | 强负相关 |
| 接近 0 | 线性相关较弱 |
注意:average_score 本来就是由 math、english、python 算出来的,所以它和各科成绩相关很正常。这不是新的发现,而是变量定义带来的结果。
12. seaborn 可选增强
如果已经安装 seaborn:
uv add seaborn
可以用更少代码画热力图:
import seaborn as sns
corr = df[[“math”, “english”, “python”, “average_score”, “study_hours”]].corr() fig, ax = plt.subplots(figsize=(7, 6)) sns.heatmap(corr, annot=True, cmap=“coolwarm”, vmin=-1, vmax=1, ax=ax) fig.tight_layout() fig.savefig(“figures/correlation_heatmap_seaborn.png”, dpi=150) plt.close(fig)
但本周主线仍应掌握 matplotlib,因为它是 Python 可视化生态的底层基础。seaborn 很好用,但不应成为你完全不理解坐标轴、标题、标签、保存图片的理由。
13. 保存图像的规范
建议统一把图放在 figures/:
ls figures
命名建议:
| 文件名 | 含义 |
|---|---|
average_score_hist.png |
平均分直方图 |
study_hours_vs_average_score.png |
学习时长与平均分散点图 |
average_score_by_major.png |
不同专业平均分箱线图 |
correlation_heatmap.png |
数值变量相关性热力图 |
不要用:
1.png
new.png
final_final.png
图片文件名应该让读者不用打开也能猜到内容。
14. EDA 报告结构
创建 reports/week04_eda_report.md,建议结构如下:
# Week 04 EDA 报告1. 数据来源
说明数据来自本周练习 CSV,包含学生专业、性别、三科成绩和每周学习时长。
2. 字段说明
字段 含义 类型 student 学生编号 分类 major 专业 分类 gender 性别 分类 math 数学成绩 数值 english 英语成绩 数值 python Python 成绩 数值 study_hours 每周学习时长 数值 average_score 三科平均分 数值 3. 缺失值情况
说明原始数据中哪些列有缺失值,以及使用中位数填补数值列。
4. 主要分布
引用 figures/average_score_hist.png,描述平均分集中区间和异常值情况。
5. 分组观察
引用 figures/average_score_by_major.png,描述不同专业组的平均分分布,并说明样本量限制。
6. 相关性观察
引用 figures/study_hours_vs_average_score.png 和 figures/correlation_heatmap.png,描述学习时长与平均分、各科成绩之间的相关性。
7. 可能的建模目标
可以提出一个后续建模问题,例如:根据学习时长、专业和前两科成绩预测 Python 成绩,或根据特征判断 average_score 是否高于 85。
8. 局限性
说明样本量小、数据为练习数据、不能代表真实 USTC 学生群体。
报告必须用文字解释图,而不是只把图片贴进去。
Markdown 引用图片写法:

如果报告和 figures 都在项目根目录下的不同子目录,上面这种 ../figures/... 路径是合理的:从 reports/ 回到上一级,再进入 figures/。
15. Notebook vs script:本周更要分清
Notebook 的优点:
- 适合一步步看数据。
- 适合临时尝试不同图表。
- 适合在同一页面写图和解释。
Script 的优点:
- 能一键复现所有图片。
- Git diff 更清楚。
- 不依赖手动运行某个单元格。
- 适合以后放到作品集。
推荐流程:
notebooks/week04_visualization_eda.ipynb:探索
src/prepare_data.py:固定清洗流程
src/make_figures.py:固定画图流程
reports/week04_eda_report.md:写最终解释
如果使用 notebook:
uv add ipykernel
然后在 VS Code 中选择当前项目 .venv 作为 kernel。即使使用 notebook,也要保留 src/make_figures.py,确保图片可重新生成。
16. 一键复现本周结果
在项目根目录执行:
source .venv/bin/activate.fish
uv run python src/prepare_data.py
uv run python src/make_figures.py
ls figures
这组命令应该能从原始 CSV 生成清洗数据,再生成所有图片。
如果以后换电脑或把项目交给同学,理想流程是:
uv sync
uv run python src/prepare_data.py
uv run python src/make_figures.py
这里的关键思想是“可复现”。EDA 不是只在你电脑上偶然跑通,而是别人按说明也能得到同样文件。
17. 本周练习
练习 A:增加单科成绩直方图
在 src/make_figures.py 中增加函数:
def plot_python_score_hist(df: pd.DataFrame) -> None:
fig, ax = plt.subplots(figsize=(8, 5))
ax.hist(df["python"], bins=8, edgecolor="black")
ax.set_title("Distribution of Python Score")
ax.set_xlabel("Python score")
ax.set_ylabel("Student count")
save_current_figure(fig, "python_score_hist.png")
然后在 main() 中调用它。
运行:
uv run python src/make_figures.py
ls figures
练习 B:按性别画箱线图
仿照 plot_major_boxplot,按 gender 比较 average_score。
输出文件命名为:
average_score_by_gender.png
报告中必须写样本量限制,不能把练习数据解释成真实群体差异。
练习 C:保存描述统计表
创建 src/export_summary.py,把描述统计保存为 CSV:
from pathlib import Pathimport pandas as pd
df = pd.read_csv(Path(“data/processed/student_scores_clean.csv”)) summary = df.describe() summary.to_csv(Path(“reports/descriptive_statistics.csv”))
运行:
uv run python src/export_summary.py
ls reports
练习 D:改进报告
在 reports/week04_eda_report.md 中为每张图写三句话:
- 这张图看什么变量?
- 从图中观察到什么?
- 这个观察有什么限制?
18. 验收检查
在项目根目录执行:
pwd
ls
source .venv/bin/activate.fish
uv run python src/prepare_data.py
uv run python src/make_figures.py
ls data/processed
ls figures
验收标准:
- 当前目录是
week04-visualization-eda。 source .venv/bin/activate.fish不报错。data/processed/student_scores_clean.csv存在。figures/average_score_hist.png存在。figures/study_hours_vs_average_score.png存在。figures/average_score_by_major.png存在。figures/correlation_heatmap.png存在。reports/week04_eda_report.md包含数据来源、字段说明、缺失值情况、主要分布、相关性观察、可能的建模目标。- 报告中的结论使用“观察”“可能”“样本有限”等谨慎表述,而不是过度因果解释。
19. 常见错误
错误 1:ModuleNotFoundError: No module named 'matplotlib'
说明当前环境没有安装 matplotlib,或没有使用项目环境运行。
修复:
cd ~/Code/python-learning/week04-visualization-eda
uv add matplotlib
uv run python src/make_figures.py
错误 2:图片没有生成
先检查脚本是否真的运行到保存函数:
uv run python src/make_figures.py
ls figures
如果没有 figures/,检查是否调用了:
fig.savefig(output_path, dpi=150)
以及是否创建目录:
FIGURE_DIR.mkdir(parents=True, exist_ok=True)
错误 3:FileNotFoundError: data/processed/student_scores_clean.csv
原因:还没运行数据准备脚本。
修复:
uv run python src/prepare_data.py
uv run python src/make_figures.py
错误 4:中文标题显示成方块
matplotlib 默认字体可能不支持中文。初学阶段最简单的办法是图表标题先使用英文,报告正文使用中文解释。等后续需要正式中文图表时,再单独配置中文字体。
错误 5:热力图颜色看起来很夸张
如果不固定颜色范围,不同数据集的颜色尺度可能不一致。建议相关性热力图使用:
ax.imshow(corr, cmap="coolwarm", vmin=-1, vmax=1)
这样 -1 到 1 的颜色含义稳定。
错误 6:报告只有图片,没有分析
EDA 报告不是图库。每张图至少要回答:
- 这张图为什么画?
- 图中最明显的模式是什么?
- 这个结论有什么限制?
如果答不出来,说明这张图可能不该放进报告,或者你还没理解变量含义。
20. 下一步
Phase 1 到这里结束。你已经从 Python 环境、Linux / Git、Pandas 表格处理走到了完整 EDA 报告。Week 05 开始进入机器学习基础:用 scikit-learn 做回归 / 分类、训练集测试集划分、交叉验证和评价指标。Week 04 的清洗数据与 EDA 报告,会成为后续建模项目的输入。
plain
喜欢的话,留下你的评论吧~