Week 04:可视化 + EDA 报告

Published 2026-07-26 04:00 5222 words 27 min read

This post is not yet available in English. Showing the original.
Week 04:可视化 + EDA 报告。fish-first 终端教学,面向 CachyOS、VS Code、uv 和 Python 学习路线。
Oh My Pi / weekly tutorial / week04-visualization-eda
miku@cachyos:~/Code/python-learning$ omp teach week04-visualization-eda --fish-first --step-by-step
source713 行教学文档
weekWeek 04
shellfish-first 命令版
backlink20 周计划

Week 04:可视化 + EDA 报告

返回主线计划:USTC 统计学 AI / 量化 20 周成长计划

本文命令默认使用 fish shell。本周目标不是“画很多图”,而是学会用图表支持数据观察,并把观察写成一份结构清楚、可复现、不过度解释的 EDA 报告。

0. 本周详细教学:语法、规范、验收

本节不是追加在尾部的复习,而是本周正文的入口。先读这里,再做后面的命令和项目。

0.1 本周真正要学会什么

维度要求
知识点matplotlib、分布、箱线图、相关性、报告叙事
代码语法能从空文件写出本周核心脚本,而不是只复制运行
程序规范函数拆分、路径清楚、输入输出明确、错误能解释
交付物reports/week04_eda_report.md
验收方式从 fish 终端运行命令,得到可复查的文件或指标

0.2 代码语法精讲

下面的代码不是最终答案,而是本周必须理解的最小骨架:

omppython
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 本周和主线的连接

1. 本周目标

完成 Week 04 后,你应该能:

  1. 使用 matplotlib 绘制直方图、散点图、箱线图。
  2. 在安装 seaborn 时使用 seaborn 画更省力的统计图。
  3. 把图保存到 figures/*.png,而不是只显示在 notebook 输出里。
  4. 建立 EDA 报告结构:数据来源、字段说明、缺失值、主要分布、相关性观察、可能建模目标。
  5. 理解 notebook 和 script 的分工:探索可以用 notebook,最终结果要能用脚本复现。

文件布局

建议本周交付物:

ompprompt
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 项目

ompfish
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 打开项目

检查安装:

ompfish
uv run python -c "import pandas, matplotlib; print('ok')"

如果你想使用 seaborn,可以额外安装:

ompfish
uv add seaborn

本教程会给出 matplotlib 主线;seaborn 是可选增强。

4. 准备数据

创建原始数据:

ompfish
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

检查:

ompfish
head data/raw/student_scores.csv
wc -l data/raw/student_scores.csv

这里仍然是练习数据,但比 Week 03 多几行,方便画图。

5. 写数据准备脚本

创建 src/prepare_data.py

omppython
from pathlib import Path

import 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()

运行:

ompfish
uv run python src/prepare_data.py
ls data/processed
head data/processed/student_scores_clean.csv

这一步保证后续画图总是使用同一份清洗后数据。

6. matplotlib 基础概念

matplotlib 的核心是 figureaxes

  • figure:整张画布。
  • axes:画布上的一个坐标系,真正画图的位置。

常用模板:

omppython
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

omppython
from pathlib import Path

import 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()

运行完整流程:

ompfish
uv run python src/prepare_data.py
uv run python src/make_figures.py
ls figures

你应该看到:

ompprompt
average_score_by_major.png
average_score_hist.png
correlation_heatmap.png
study_hours_vs_average_score.png

8. 图表一:直方图看分布

直方图回答的问题是:一个数值变量大致集中在哪里?是否偏态?是否有异常值?

本教程中的直方图:

omppython
ax.hist(df["average_score"], bins=8, edgecolor="black")

解释:

参数 含义
df["average_score"] 要观察分布的变量
bins=8 分成 8 个区间
edgecolor="black" 给柱子加边框,方便阅读

报告中不要只写“画了直方图”,要写观察:

ompprompt
平均分主要集中在 80 到 90 分之间,样本中没有特别低的平均分。由于样本量很小,这个分布只能用于流程练习,不能代表真实学生群体。

9. 图表二:散点图看两个变量关系

散点图回答的问题是:两个数值变量之间是否有明显关系?

本教程中:

omppython
ax.scatter(df["study_hours"], df["average_score"])

横轴是学习时长,纵轴是平均分。

报告写法示例:

ompprompt
散点图显示学习时长和平均分大致同向变化:学习时长较高的学生平均分往往也较高。但样本只有 12 行,不能据此得出稳定因果结论。

注意“相关不等于因果”。统计学同学尤其要避免把 EDA 观察写成因果判断。

10. 图表三:箱线图看组间差异

箱线图适合比较不同组的数值分布。这里按 major 比较 average_score

代码核心:

omppython
majors = sorted(df["major"].unique())
values = [df.loc[df["major"] == major, "average_score"] for major in majors]
ax.boxplot(values, labels=majors)

报告中可以写:

ompprompt
statistics 组平均分中位数较高,computer 和 finance 组略低。但每组样本数量很少,箱线图主要用于展示方法,不适合做群体差异结论。

真实项目中还应补充每组样本数,否则箱线图容易误导。

11. 图表四:相关性热力图

相关性热力图用来观察多个数值变量之间的线性相关关系。

核心代码:

omppython
corr = df[["math", "english", "python", "average_score", "study_hours"]].corr()

corr() 默认计算 Pearson 相关系数,范围从 -1 到 1:

含义
接近 1 强正相关
接近 -1 强负相关
接近 0 线性相关较弱

注意:average_score 本来就是由 mathenglishpython 算出来的,所以它和各科成绩相关很正常。这不是新的发现,而是变量定义带来的结果。

12. seaborn 可选增强

如果已经安装 seaborn:

ompfish
uv add seaborn

可以用更少代码画热力图:

omppython
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/

ompfish
ls figures

命名建议:

文件名 含义
average_score_hist.png 平均分直方图
study_hours_vs_average_score.png 学习时长与平均分散点图
average_score_by_major.png 不同专业平均分箱线图
correlation_heatmap.png 数值变量相关性热力图

不要用:

ompprompt
1.png
new.png
final_final.png

图片文件名应该让读者不用打开也能猜到内容。

14. EDA 报告结构

创建 reports/week04_eda_report.md,建议结构如下:

ompmarkdown
# Week 04 EDA 报告

1. 数据来源

说明数据来自本周练习 CSV,包含学生专业、性别、三科成绩和每周学习时长。

2. 字段说明

字段含义类型
student学生编号分类
major专业分类
gender性别分类
math数学成绩数值
english英语成绩数值
pythonPython 成绩数值
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 引用图片写法:

ompmarkdown
![平均分直方图](../figures/average_score_hist.png)

如果报告和 figures 都在项目根目录下的不同子目录,上面这种 ../figures/... 路径是合理的:从 reports/ 回到上一级,再进入 figures/

15. Notebook vs script:本周更要分清

Notebook 的优点:

  • 适合一步步看数据。
  • 适合临时尝试不同图表。
  • 适合在同一页面写图和解释。

Script 的优点:

  • 能一键复现所有图片。
  • Git diff 更清楚。
  • 不依赖手动运行某个单元格。
  • 适合以后放到作品集。

推荐流程:

ompprompt
notebooks/week04_visualization_eda.ipynb:探索
src/prepare_data.py:固定清洗流程
src/make_figures.py:固定画图流程
reports/week04_eda_report.md:写最终解释

如果使用 notebook:

ompfish
uv add ipykernel

然后在 VS Code 中选择当前项目 .venv 作为 kernel。即使使用 notebook,也要保留 src/make_figures.py,确保图片可重新生成。

16. 一键复现本周结果

在项目根目录执行:

ompfish
source .venv/bin/activate.fish
uv run python src/prepare_data.py
uv run python src/make_figures.py
ls figures

这组命令应该能从原始 CSV 生成清洗数据,再生成所有图片。

如果以后换电脑或把项目交给同学,理想流程是:

ompfish
uv sync
uv run python src/prepare_data.py
uv run python src/make_figures.py

这里的关键思想是“可复现”。EDA 不是只在你电脑上偶然跑通,而是别人按说明也能得到同样文件。

17. 本周练习

练习 A:增加单科成绩直方图

src/make_figures.py 中增加函数:

omppython
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() 中调用它。

运行:

ompfish
uv run python src/make_figures.py
ls figures

练习 B:按性别画箱线图

仿照 plot_major_boxplot,按 gender 比较 average_score

输出文件命名为:

ompprompt
average_score_by_gender.png

报告中必须写样本量限制,不能把练习数据解释成真实群体差异。

练习 C:保存描述统计表

创建 src/export_summary.py,把描述统计保存为 CSV:

omppython
from pathlib import Path

import 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”))

运行:

ompfish
uv run python src/export_summary.py
ls reports

练习 D:改进报告

reports/week04_eda_report.md 中为每张图写三句话:

  1. 这张图看什么变量?
  2. 从图中观察到什么?
  3. 这个观察有什么限制?

18. 验收检查

在项目根目录执行:

ompfish
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,或没有使用项目环境运行。

修复:

ompfish
cd ~/Code/python-learning/week04-visualization-eda
uv add matplotlib
uv run python src/make_figures.py

错误 2:图片没有生成

先检查脚本是否真的运行到保存函数:

ompfish
uv run python src/make_figures.py
ls figures

如果没有 figures/,检查是否调用了:

omppython
fig.savefig(output_path, dpi=150)

以及是否创建目录:

omppython
FIGURE_DIR.mkdir(parents=True, exist_ok=True)

错误 3:FileNotFoundError: data/processed/student_scores_clean.csv

原因:还没运行数据准备脚本。

修复:

ompfish
uv run python src/prepare_data.py
uv run python src/make_figures.py

错误 4:中文标题显示成方块

matplotlib 默认字体可能不支持中文。初学阶段最简单的办法是图表标题先使用英文,报告正文使用中文解释。等后续需要正式中文图表时,再单独配置中文字体。

错误 5:热力图颜色看起来很夸张

如果不固定颜色范围,不同数据集的颜色尺度可能不一致。建议相关性热力图使用:

omppython
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

If you enjoyed this, leave a comment~

© 2026 江无没有月 @miku
Powered by theme astro-koharu · Inspired by Shoka