Skip to content

第 47 课:Jupyter Notebook 与实验环境

🎯 核心实操目标

本课目标:建立”一文一码互动伴随”的可复现研究工作流。本课你将掌握 Jupyter Notebook 的基本操作、Python 数据分析速成(pandas + matplotlib),并能产出代码+文字+图表一体化的研究文档——让任何审稿人或同行能一键复现你的全部分析。

📋 课前准备(10 分钟,含安装)

工具/环境(二选一)

方案 A:Anaconda(推荐零基础) — 一键含 Python + Jupyter + pandas + matplotlib

方案 B:纯 Python + pip(适合已装 Python 的进阶轨)

bash
# 创建虚拟环境
python -m venv aicourse_env
source aicourse_env/bin/activate  # Mac/Linux
# 或 aicourse_env\Scripts\activate  # Windows

# 安装依赖
pip install jupyter pandas matplotlib numpy scipy seaborn

# 启动
jupyter notebook

数据/素材

配置验证

打开 Jupyter Notebook,新建一个 Cell 跑通:

python
import pandas as pd
import matplotlib.pyplot as plt
print(pd.__version__, plt.matplotlib.__version__)
# 应输出版本号,无报错即成功

应急通道

场景导入:只给结果、不给过程,为什么难以服人

近年学术界对"结果可复现"的要求日益严格。一种常见的尴尬是:你在论文的 Results 部分直接给出一张折线图,并告诉审稿人"这就是运行得到的结果"——但从数据如何读入、缺失值怎么处理、离群点依据什么剔除,到这张图究竟由哪段代码生成,全都不可见。

问题不在于你算得不认真,而在于呈现方式让人无从核验。如果你曾用鼠标删掉几个"看起来不对"的数据点却没有在论文里说明,审稿人即便无法证明你做错,也有理由对整条分析链存疑。把"只呈现结论"升级为"过程可追溯",需要的正是一种让说明、代码、输出共处一处的记录方式——这正是 Jupyter Notebook 要解决的问题。

📐 原理:为什么"说明 + 代码 + 输出同处"利于可复现分析

要理解 Notebook 的价值,先认清传统分析工作流的断裂点:方法写在论文里,代码躺在某个 .py 脚本里,图表是另存出来的图片,三者彼此分离。读者拿到论文,看不到生成图 2 的究竟是哪段代码、用的是清洗前还是清洗后的数据;几个月后连作者自己都可能对不上。这种断裂正是"算了一下午却说不清"的根源。

Jupyter 继承的是一种叫 文学化编程(literate programming) 的思路——由高德纳(Donald Knuth)提出,主张把"给人读的叙述"与"给机器跑的代码"编织在同一份文档里。Notebook 把它落到实处,靠三点支撑可复现性:

  1. 代码与其输出物理绑定。 每段代码(cell)的运行结果——数字、表格、图——就显示在该段代码正下方。读者看到一张热力图,向上一格就是生成它的确切代码,无需猜测、无法张冠李戴。
  2. 叙述与计算交替推进。 你可以在代码格之间插入 Markdown 格,写明"这一步为什么这样清洗""这个反向题为何要重编码"。论文的 Methods 与实际执行的代码不再是两份可能对不上的东西,而是同一份文档的两种格子。
  3. 整份文档可被他人从头重跑。.ipynb 连同数据交给同行,对方 Restart & Run All 就能在自己机器上复现你的全部结果——这就是"可复现"最朴素、也最有说服力的形态。

一句话:Notebook 不是"花哨的代码编辑器",而是把分析过程本身变成可阅读、可重跑的证据。它的价值不在让图变好看,而在让"我是怎么得到这张图的"不再需要靠你口头解释。


🗺️ Notebook 的结构:说明格与代码格交替排布

Jupyter Notebook 由自上而下排列的单元格(cell)组成,主要有两类:Markdown 格写给人读的说明,Code 格写给机器运行的代码。运行某个 Code 格(Shift+Enter),其输出——文本、表格或图——立即显示在该格正下方。说明、代码、输出就这样上下相邻地编织在一份文档里:

.ipynb Notebook 文档剖面Markdown 格:写给人读的说明import pandas as pd...运行此格输出:紧贴代码正下方显示
📘 关键术语(首次出现,先对齐定义)
  • Jupyter(Notebook):一个开源的交互式计算环境,名字取自其早期支持的三种语言 Julia / Python / R(与天文无关,不是"木星")。它把说明文字、可运行代码与代码输出组织在同一份文档中。
  • 单元格(cell):Notebook 的基本编辑单位。常用两类:Code 格(运行代码并在下方显示输出)与 Markdown 格(渲染为格式化的说明文字)。用 Shift+Enter 运行当前格。
  • 内核(kernel):在后台真正执行代码的进程(本课用 Python 内核)。它在内存中保存已运行格留下的变量状态,因此格与格之间共享变量——这既带来便利,也带来后面要讲的"执行顺序"陷阱。
  • .ipynb:Notebook 的存盘格式,本质是一个 JSON 文本文件,里面同时记录每个格的源代码、Markdown 文字和已保存的输出。它能被打开重跑,但 JSON 结构也使它不利于用 Git 做逐行差异比对(见【边界与局限】)。
  • 可复现(reproducibility):他人(或未来的你)用相同的数据与代码能重新得到相同的结果。Notebook 通过"代码 + 输出 + 说明同处、可整份重跑"来支撑这一点;它保证的是"过程可被复现",而非"结论一定正确"。
  • 文学化编程(literate programming):把面向人的叙述与面向机器的代码编织在同一文档的编程范式,由 Donald Knuth 提出,是 Notebook 形态的思想来源。

🚀 拆解实战:跑通一份"Markdown 说明 + 代码 + 图"的可复现 Notebook

以 Case A 问卷数据为例,一格说明 + 一格代码交替往下排。把 case_A_questionnaire.csv 放到 Notebook 同目录,逐格运行(Shift+Enter)。

① 载入数据(Markdown 格写一句"步骤1:载入并查看数据",下面 Code 格):

python
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

df = pd.read_csv("case_A_questionnaire.csv")
print(df.shape)          # (540, 33)
df.head()

② 清洗:剔除作答过快 + 列表删除缺失(与第 23 课口径一致):

python
items = [c for c in df.columns
         if c.split('_')[0] in ('Anxiety', 'Strategy', 'Efficacy')]
clean = df[df['Duration_Min'] >= 3]        # 作答 < 3 分钟视为无效
clean = clean.dropna(subset=items).copy()  # 列表删除任何题项缺失的行
print(f"清洗后样本量: {len(clean)}")        # 约 500

③ 反向题反转 + 维度均分(反向题 _R = 6 - 原值):

python
clean['Anxiety_4_R'] = 6 - clean['Anxiety_4']     # 认知焦虑反向题
clean['Anxiety_Cog'] = clean[['Anxiety_1', 'Anxiety_2',
                              'Anxiety_3', 'Anxiety_4_R']].mean(axis=1)
clean[['Anxiety_1', 'Anxiety_4', 'Anxiety_4_R', 'Anxiety_Cog']].head()

④ 画相关热力图(结果格正下方即时出图):

python
corr = clean[['Anxiety_1', 'Anxiety_2', 'Anxiety_3', 'Anxiety_4_R']].corr()
plt.figure(figsize=(5, 4))
sns.heatmap(corr, annot=True, cmap='RdBu_r', center=0, vmin=-1, vmax=1)
plt.title('认知焦虑题项相关矩阵')
plt.tight_layout()
plt.show()

全部跑通后,文件 → Download as → PDF/HTML 导出,就得到一份代码 + 文字 + 图一体、审稿人能一键复现的分析记录。这就是 Jupyter 的价值:把分析过程透明化

让 AI 当助手,但你要看懂每一格

你可以让 AI 帮你写某一格代码,但粘贴前先读懂它做了什么——尤其是清洗那一格(它删了哪些行?怎么处理缺失?)。看不懂就让它逐行加注释。"透明地跑出图"不等于"做对了",方法本身要站得住(见下)。


🚦 批判性复核:清洗与插补不能"为了好看"

Jupyter 让过程透明,但透明地做错仍然是错。两条最容易翻车的红线:

  • 离群值不能"鼠标随手删":要预设客观准则(如 |Z| > 3 或箱线图 1.5×IQR),并在论文 Methods 里报告删了几个、依据是什么。绝不能为了让曲线好看而偷偷删点。
  • 缺失值插补要慎重均值填充会低估方差、可能引入偏倚。优先做法:量表得分用该题项所在维度的可用题项求均值(跳过 NA),或对缺失较多时用多重插补;并先判断缺失机制(MCAR/MAR)。无论怎么处理,都要在论文里如实说明

一句话:上一节那种"向下均值强行补录、鼠标删离群点"的做法,恰恰是会被盲审抓的——正确姿势是预设准则 + 如实报告


🚀 第二个实战:把同一套"说明 + 代码 + 输出"搬到模型评测(Case C)

换数据、换图型,Notebook 的工作方式不变:仍是 Markdown 格写清做法、Code 格运行、输出紧贴其下。下面用 Case C:LLM 评测数据(300 篇科研文摘 × 3 个模型,含 Quality_GPT5Quality_Claude47Quality_Gemini25 三列 1—5 分的摘要质量评分,基准模型为 Claude 4.7)演示——任务从"问卷分析"换成"比较三个模型的质量评分分布"。把 case_C_llm_evaluation.csv 放到 Notebook 同目录,同样逐格运行。

① 载入并核对列(Markdown 格写"步骤1:载入评测数据",下面 Code 格):

python
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

df = pd.read_csv("case_C_llm_evaluation.csv")
quality_cols = ["Quality_GPT5", "Quality_Claude47", "Quality_Gemini25"]
print(df.shape)              # (300, ...)
df[quality_cols].describe()  # 先看三列的均值/分位,做到心中有数

② 转成长表(一行一条评分,便于按模型分组画图):

python
long = df[quality_cols].melt(var_name="Model", value_name="Quality")
long["Model"] = long["Model"].str.replace("Quality_", "", regex=False)
long.head()

③ 画质量评分分布箱线图(箱线图同时给出中位数、四分位距与离群点,比只画均值更诚实):

python
plt.figure(figsize=(6, 4))
sns.boxplot(data=long, x="Model", y="Quality")
plt.ylim(1, 5)                          # 评分量表 1—5,固定纵轴避免视觉夸大
plt.title("三模型摘要质量评分分布(基准 Claude 4.7 / GPT-5 / Gemini 2.5,N=300)")
plt.tight_layout()
plt.show()

这张图只呈现分布——"哪个模型显著更高"要靠正文的配对统计检验下结论,不能让箱体的高低替代检验。注意三点诚实义务都写进了图里:样本量 N=300、评分范围 1—5、模型的具体版本(基准 Claude 4.7、GPT-5、Gemini 2.5),不能含糊成"GPT vs Claude vs Gemini",否则会把"某版本在本数据上的得分"误读为"该模型整体更强"。

🔁 两个实战的共同骨架

对比 Case A(问卷→热力图)与 Case C(评测→箱线图):数据变了、图型变了,但 Notebook 的用法完全一致——Markdown 格交代"这步在做什么、为什么",Code 格运行,输出落在正下方,整份可被他人 Restart & Run All 重跑。把数据和图型换成你学科的,这套"叙述 + 代码 + 输出同处"的可复现写法照样成立。


写好 vs 写砸:同一份 Notebook 的逐项对照

同样跑出了图,一份 Notebook 可以"能复现",也可以"换台机器就崩、几个月后自己都看不懂"。下表把最常见的失分点逐项拆开——左列是学员高频写法,右列是把同一处"拧紧"后的写法。

维度写砸 ❌写好 ✅为什么
执行顺序来回跳着运行格、删了又补,靠内存里的"残留变量"凑出结果写完 Restart & Run All 从头跑一遍确认无误内核状态看不见;跳序运行的结果换人重跑往往复现不出
叙述整份只有代码,没有一格说明每个关键步骤前用 Markdown 格写清"做什么、为什么这样做"没有叙述的 Notebook = 没有 Methods 的论文,半年后自己也读不懂
路径写死 C:\Users\我\桌面\data.csv 绝对路径用相对路径 case_A_questionnaire.csv,数据与 .ipynb 同目录绝对路径一换机器就 FileNotFoundError,破坏可复现
清洗透明度直接 dropna() / 删点,不留一句说明在代码旁注明准则(Duration_Min >= 3、列表删除),并打印清洗前后样本量清洗那一格最易"透明地做错",必须可被审稿人核对
随机性用到抽样/打乱却不设种子,每次结果都不同在涉及随机的步骤设 random_state(如 random_state=42不设种子 = 结果不可复现,违背本课核心
依赖版本不记录用了什么库、什么版本在首格 print(pd.__version__),或附 requirements.txt库升级可能改变默认行为,记录版本才能让他人复现

💡 一句话判据

检验一份 Notebook 是否过关,只问一件事:把它和数据原样交给同行,对方 Restart & Run All,能不能一字不差地重现你的全部输出? 能,才叫"可复现";做不到,再漂亮的图也只是你这台机器上的一次性结果。


常见误区与纠正

学员用 Notebook 时的问题高度集中在几处,下表对号入座即可:

常见误区症状(输出会怎样)纠正方法
跳序运行靠"残留变量"当下能出图,换人 Run All 就报 NameError 或结果对不上写完 Restart & Run All 从头跑通,以此为准
只堆代码、零说明自己几个月后、同行当下都看不懂每格在干嘛关键步骤前补 Markdown 格,写清做法与依据
绝对路径写死换机器 / 换目录立即 FileNotFoundError改相对路径,数据与 .ipynb 同目录
把 Notebook 当生产脚本几百格混在一份里反复手动跑,易错难维护稳定后的流程抽成 .py 函数 / 脚本(见边界与局限)
盲信 AI 写的清洗格直接采用,没读懂它删了哪些行、怎么处理缺失粘贴前逐行读懂,看不懂让 AI 加注释,再核对样本量
图好看就当做对了透明地跑出图,却没检验方法是否站得住"出图 ≠ 做对",回到批判性复核那两条红线自查

出错 / 报错怎么办:Notebook 排错的正确姿势

Notebook 最让新手困惑的报错,多半不是语法错,而是内核状态执行顺序带来的。按下面顺序排查,比逐条搜报错更快:

  1. NameError: name 'xxx' is not defined —— 最常见。原因往往是你没按顺序运行:定义 clean 的那格还没跑,就先跑了用到 clean 的格。对策:菜单 Kernel → Restart & Run All 从头跑一遍;问题十有八九消失。
  2. 改了上面的格,下面结果却没变 —— 内核内存里还是旧变量。改完上游格后要重新运行其下游所有格(或直接 Restart & Run All),不能只看旧输出。
  3. FileNotFoundError —— 几乎都是路径问题。先在一个格里跑 import os; os.getcwd() 看 Notebook 的当前工作目录,确认 case_A_questionnaire.csv 是否就在该目录下;用相对路径而非绝对路径。
  4. KeyError: 'Anxiety_4' 之类 —— 列名对不上。跑 df.columns.tolist() 核对真实列名(大小写、下划线都要一致),常见于复制数据后表头有出入。
  5. 图不显示 / 报字体警告 —— plt.show() 漏写,或中文标题缺字体。Jupyter 里通常 plt.show() 即可出图;中文乱码时为 matplotlib 指定中文字体(如 plt.rcParams['font.sans-serif'] = ['SimHei'],Mac 可用 ['Arial Unicode MS'])。

把报错交给 AI 时,连"上下文"一起给

让 AI 帮你修报错时,把完整报错栈 + 那一格代码 + 上游相关格一起贴过去,并说明"我是 Restart & Run All 后在第 N 格报的错"。只贴一行报错,AI 往往只能猜。修完务必再 Restart & Run All 验证,别盲信"它说改好了"。


边界与局限:Notebook 适合什么、不适合什么

Notebook 是探索式分析与可复现汇报的利器,但它不是万能容器。把下面几条边界记牢,比多记一个快捷键更重要——其中前两条是它与本模块其它工具(Git、脚本化)的明确分工。

边界 / 失效场景为什么会这样你应该怎么做
不利于版本 diff.ipynb 是 JSON,且把输出(含图片的 base64)一并存进文件;Git 直接比对会显示成大段乱码,看不出"这次到底改了哪行代码"提交前清空输出(Kernel → Restart & Clear Output),或用 jupytext / nbdime 等工具做配对与差异比对(见 第 43 课 Git
不适合直接生产化几百格的长 Notebook 依赖手动按顺序跑、隐式共享内存状态,难自动化、难测试、难复用探索定稿后,把稳定逻辑抽成 .py 模块 / 函数与脚本,Notebook 只留"讲故事 + 调用"的薄层
执行顺序可被打乱内核允许任意顺序运行格,"当下能出结果"不代表"从头跑也对"交付前一律 Restart & Run All,以从头重跑的结果为准
隐藏状态难察觉删掉了某格代码,它定义的变量仍残留在内核内存里,掩盖了真实的依赖缺失怀疑有"幽灵变量"时重启内核重跑;不要相信删了代码就等于状态被清掉
大数据 / 长任务不友好浏览器前端承载输出,超大表格或超长循环会卡顿、占内存重活交给脚本在后台跑,Notebook 只载入结果做展示与可视化

一句话定位

Notebook 的甜区是"做分析 + 讲清楚 + 能复现";一旦进入"要版本协作"或"要稳定自动化运行",就该把核心逻辑搬到脚本里,让 Notebook 回到它最擅长的"叙述 + 展示"角色。这正是本模块把 Notebook、Git、脚本化分成不同课来教的原因。


📦 本课交付物

按本节实操任务完成并提交以下内容,提交 AI 初审,按 Module_Rubrics.md 对应维度评分:

  • [ ] 可运行的 .ipynb:含 Case A 四格(载入 / 清洗 / 反转 / 出图),每格 Restart & Run All 都能跑通
  • [ ] 导出的 PDF 或 HTML:含代码 + 文字 + 热力图的可复现记录
  • [ ] 清洗方法说明:你的离群值准则 + 缺失处理方式(如实写明,对应批判性复核)
  • [ ] AI 协作日志:让 AI 写某格代码的"指令 → 输出 → 你读懂 / 修正"记录

🏁 本章小结

把本课凝练成可据以复习的几条要点:

  1. Notebook 解决什么问题:它把说明(Markdown 格)+ 代码(Code 格)+ 输出编织在同一份文档里,让"我是怎么得到这张图的"不再靠口头解释——这正是文学化编程在数据分析里的落地,也是"可复现"最朴素的形态。
  2. 核心机制:单元格(cell)是基本单位,内核(kernel)在内存里共享变量;输出紧贴代码正下方;.ipynb 是可重跑的 JSON 文档。便利来自共享状态,陷阱也来自它(执行顺序)。
  3. 可复现的硬指标:把 Notebook 连同数据交给同行,对方 Restart & Run All 能一字不差重现你的全部输出——做不到就不叫可复现。相对路径、设随机种子、记录库版本、留叙述,都是为这一条服务。
  4. 方法红线:透明地做错仍然是错。离群值要预设客观准则(如 |Z|>3、1.5×IQR)并在 Methods 如实报告;缺失值慎用均值填充(会低估方差),优先用维度内可用题项均值或多重插补,并先判断缺失机制(MCAR/MAR)。
  5. 跨场景可迁移:Case A(问卷→热力图)与 Case C(评测→箱线图)数据图型全变,但"叙述 + 代码 + 输出同处、整份可重跑"的写法不变。
  6. 边界要诚实:Notebook 适合"做分析 + 讲清楚 + 能复现",但 .ipynb 是 JSON、不利于 Git 逐行 diff,长 Notebook 也不适合直接生产化——这两件事分别交给 Git(第 43 课)和脚本化。
  7. AI 是助手、你担责:可以让 AI 写某格代码,但粘贴前读懂每一格(尤其清洗格删了什么、怎么处理缺失),核对样本量;"出了图"不等于"做对了"。

自测清单(可保留逐项打勾)

  • [ ] 我能讲清 Notebook 为什么利于可复现分析(说明 + 代码 + 输出同处、可整份重跑),并准确说出 Jupyter 名字来历(Julia/Python/R,不是"木星")。
  • [ ] 我能在 Jupyter 跑通 Case A"载入 → 清洗 → 反转 → 出图"四格,Restart & Run All 无误,并导出可复现的 .ipynb / PDF。
  • [ ] 我清楚反向题要 _R = 6 - 原值,维度均分用反转后的题项。
  • [ ] 我会预设客观准则处理离群值并在 Methods 如实报告;知道均值插补的风险,优先用可用题项均值 / 多重插补。
  • [ ] 我会按"NameError → Restart & Run All、FileNotFoundError → 查工作目录与相对路径"的思路排查常见报错。
  • [ ] 我知道 Notebook 不利于版本 diff 与生产化,能说出分别该交给 Git / 脚本化处理。
  • [ ] 我把 AI 当代码助手,但粘贴前读懂每一格,核对样本量,不盲信"出了图就对了"。

✍️ 思考与练习

下列练习用于把本节概念用起来(区别于"本课交付物"里的任务),建议写在你的本地笔记中。

练习 1(原理辨析)。 有同学说:"我把所有代码塞进一个 .py 脚本跑出图,再把图贴进论文,效果和 Notebook 一样,何必用 Notebook?"请用本课"说明 + 代码 + 输出同处"的原理,说明这种做法在可复现与可核验上具体差在哪里。

好答案要点:脚本 + 贴图把方法、代码、图三者割裂,读者无法确认某张图由哪段代码、用清洗前还是清洗后的数据生成;Notebook 把三者物理绑定、可整份重跑,使过程可追溯。能点明 Notebook 保证的是"过程可复现"而非"结论一定对"即更好。

练习 2(执行顺序陷阱)。 你在 Case A Notebook 里先跑了第③格生成 Anxiety_4_R,又回头改了第②格的清洗条件,但没有重跑第③④格,图却照常显示。一位同行用 Restart & Run All 重跑后却报 NameError。请解释为什么你那边"看起来正常"、对方却报错,正确做法是什么。

好答案要点:内核内存里仍残留旧的 clean/Anxiety_4_R 变量,你看到的是过时输出(隐藏状态);从头重跑会暴露真实依赖。正确做法是改完上游格后重跑其所有下游格,交付前一律 Restart & Run All,以从头重跑结果为准。

练习 3(边界识别,紧扣本模块分工)。 你想把这份 Case A Notebook 纳入小组的 Git 仓库做版本协作,发现每次 git diff 都是大段看不懂的乱码,根本看不出改了哪行代码。请说明这触中了 Notebook 的哪条边界,给出至少两种缓解办法。

好答案要点:.ipynb 是 JSON 且把输出(含图片 base64)一并入库,故 Git 逐行 diff 不可读——这是"不利于版本 diff"的边界。缓解:提交前 Restart & Clear Output 清空输出再提交;或用 jupytext 把 Notebook 配对成 .py 便于 diff、用 nbdime 做 Notebook 专用差异比对。

练习 4(迁移到 Case C,守诚实义务)。 用 Case C 三列质量评分(Quality_GPT5/Quality_Claude47/Quality_Gemini25)画箱线图后,你想在正文写"本实验证明 Claude 4.7 的摘要质量整体优于另两者"。请指出这句话有哪两处问题,应如何修正。

好答案要点:① 箱线图只显示分布,"是否显著更高"必须由配对统计检验下结论,不能用箱体高低替代检验;② "整体优于"是过度外推——评测针对的是特定版本本数据集上的得分,应写清基准版本(Claude 4.7 / GPT-5 / Gemini 2.5)、N=300 与评分范围,避免读成"该模型整体更强"。

助力学者在 AI 时代极速产出高质量学术成果 · 55 课时双轨制 · plan v3.3