第 47 课:Jupyter Notebook 与实验环境
🎯 核心实操目标
本课目标:建立”一文一码互动伴随”的可复现研究工作流。本课你将掌握 Jupyter Notebook 的基本操作、Python 数据分析速成(pandas + matplotlib),并能产出代码+文字+图表一体化的研究文档——让任何审稿人或同行能一键复现你的全部分析。
📋 课前准备(10 分钟,含安装)
工具/环境(二选一)
方案 A:Anaconda(推荐零基础) — 一键含 Python + Jupyter + pandas + matplotlib
- [ ] 下载:anaconda.com/download(约 500 MB)
- [ ] 国内镜像:mirrors.tuna.tsinghua.edu.cn/anaconda(更快)
- [ ] 安装后开始菜单 → Anaconda Navigator → Launch Jupyter Notebook
方案 B:纯 Python + pip(适合已装 Python 的进阶轨)
# 创建虚拟环境
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 跑通:
import pandas as pd
import matplotlib.pyplot as plt
print(pd.__version__, plt.matplotlib.__version__)
# 应输出版本号,无报错即成功应急通道
- 本地安装失败 → 用 Google Colab(云端 Jupyter,免安装,需 Google 账号)
- 内地访问 Colab 困难 → 用 百度飞桨 AI Studio 或 阿里云 Notebook
场景导入:只给结果、不给过程,为什么难以服人
近年学术界对"结果可复现"的要求日益严格。一种常见的尴尬是:你在论文的 Results 部分直接给出一张折线图,并告诉审稿人"这就是运行得到的结果"——但从数据如何读入、缺失值怎么处理、离群点依据什么剔除,到这张图究竟由哪段代码生成,全都不可见。
问题不在于你算得不认真,而在于呈现方式让人无从核验。如果你曾用鼠标删掉几个"看起来不对"的数据点却没有在论文里说明,审稿人即便无法证明你做错,也有理由对整条分析链存疑。把"只呈现结论"升级为"过程可追溯",需要的正是一种让说明、代码、输出共处一处的记录方式——这正是 Jupyter Notebook 要解决的问题。
📐 原理:为什么"说明 + 代码 + 输出同处"利于可复现分析
要理解 Notebook 的价值,先认清传统分析工作流的断裂点:方法写在论文里,代码躺在某个 .py 脚本里,图表是另存出来的图片,三者彼此分离。读者拿到论文,看不到生成图 2 的究竟是哪段代码、用的是清洗前还是清洗后的数据;几个月后连作者自己都可能对不上。这种断裂正是"算了一下午却说不清"的根源。
Jupyter 继承的是一种叫 文学化编程(literate programming) 的思路——由高德纳(Donald Knuth)提出,主张把"给人读的叙述"与"给机器跑的代码"编织在同一份文档里。Notebook 把它落到实处,靠三点支撑可复现性:
- 代码与其输出物理绑定。 每段代码(cell)的运行结果——数字、表格、图——就显示在该段代码正下方。读者看到一张热力图,向上一格就是生成它的确切代码,无需猜测、无法张冠李戴。
- 叙述与计算交替推进。 你可以在代码格之间插入 Markdown 格,写明"这一步为什么这样清洗""这个反向题为何要重编码"。论文的 Methods 与实际执行的代码不再是两份可能对不上的东西,而是同一份文档的两种格子。
- 整份文档可被他人从头重跑。 把
.ipynb连同数据交给同行,对方Restart & Run All就能在自己机器上复现你的全部结果——这就是"可复现"最朴素、也最有说服力的形态。
一句话:Notebook 不是"花哨的代码编辑器",而是把分析过程本身变成可阅读、可重跑的证据。它的价值不在让图变好看,而在让"我是怎么得到这张图的"不再需要靠你口头解释。
🗺️ Notebook 的结构:说明格与代码格交替排布
Jupyter Notebook 由自上而下排列的单元格(cell)组成,主要有两类:Markdown 格写给人读的说明,Code 格写给机器运行的代码。运行某个 Code 格(Shift+Enter),其输出——文本、表格或图——立即显示在该格正下方。说明、代码、输出就这样上下相邻地编织在一份文档里:
📘 关键术语(首次出现,先对齐定义)
- 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 格):
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 课口径一致):
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 - 原值):
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()④ 画相关热力图(结果格正下方即时出图):
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_GPT5、Quality_Claude47、Quality_Gemini25 三列 1—5 分的摘要质量评分,基准模型为 Claude 4.7)演示——任务从"问卷分析"换成"比较三个模型的质量评分分布"。把 case_C_llm_evaluation.csv 放到 Notebook 同目录,同样逐格运行。
① 载入并核对列(Markdown 格写"步骤1:载入评测数据",下面 Code 格):
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() # 先看三列的均值/分位,做到心中有数② 转成长表(一行一条评分,便于按模型分组画图):
long = df[quality_cols].melt(var_name="Model", value_name="Quality")
long["Model"] = long["Model"].str.replace("Quality_", "", regex=False)
long.head()③ 画质量评分分布箱线图(箱线图同时给出中位数、四分位距与离群点,比只画均值更诚实):
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 最让新手困惑的报错,多半不是语法错,而是内核状态和执行顺序带来的。按下面顺序排查,比逐条搜报错更快:
NameError: name 'xxx' is not defined—— 最常见。原因往往是你没按顺序运行:定义clean的那格还没跑,就先跑了用到clean的格。对策:菜单Kernel → Restart & Run All从头跑一遍;问题十有八九消失。- 改了上面的格,下面结果却没变 —— 内核内存里还是旧变量。改完上游格后要重新运行其下游所有格(或直接
Restart & Run All),不能只看旧输出。 FileNotFoundError—— 几乎都是路径问题。先在一个格里跑import os; os.getcwd()看 Notebook 的当前工作目录,确认case_A_questionnaire.csv是否就在该目录下;用相对路径而非绝对路径。KeyError: 'Anxiety_4'之类 —— 列名对不上。跑df.columns.tolist()核对真实列名(大小写、下划线都要一致),常见于复制数据后表头有出入。- 图不显示 / 报字体警告 ——
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 写某格代码的"指令 → 输出 → 你读懂 / 修正"记录
🏁 本章小结
把本课凝练成可据以复习的几条要点:
- Notebook 解决什么问题:它把说明(Markdown 格)+ 代码(Code 格)+ 输出编织在同一份文档里,让"我是怎么得到这张图的"不再靠口头解释——这正是文学化编程在数据分析里的落地,也是"可复现"最朴素的形态。
- 核心机制:单元格(cell)是基本单位,内核(kernel)在内存里共享变量;输出紧贴代码正下方;
.ipynb是可重跑的 JSON 文档。便利来自共享状态,陷阱也来自它(执行顺序)。 - 可复现的硬指标:把 Notebook 连同数据交给同行,对方
Restart & Run All能一字不差重现你的全部输出——做不到就不叫可复现。相对路径、设随机种子、记录库版本、留叙述,都是为这一条服务。 - 方法红线:透明地做错仍然是错。离群值要预设客观准则(如
|Z|>3、1.5×IQR)并在 Methods 如实报告;缺失值慎用均值填充(会低估方差),优先用维度内可用题项均值或多重插补,并先判断缺失机制(MCAR/MAR)。 - 跨场景可迁移:Case A(问卷→热力图)与 Case C(评测→箱线图)数据图型全变,但"叙述 + 代码 + 输出同处、整份可重跑"的写法不变。
- 边界要诚实:Notebook 适合"做分析 + 讲清楚 + 能复现",但
.ipynb是 JSON、不利于 Git 逐行 diff,长 Notebook 也不适合直接生产化——这两件事分别交给 Git(第 43 课)和脚本化。 - 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 与评分范围,避免读成"该模型整体更强"。
