第 2 周:计算基础与 AI 辅助可重复工作流
中国人民大学商学院
2026-04-25
第 1 周核心内容
本节课的问题
在你开始做任何实证分析之前——你的计算环境准备好了吗?
现实场景
本节课目标
四个模块:计算环境搭建 → AI 工具集成 → Monte Carlo 直觉 → 工作流全过程
为什么不各自为政?
RStudio 只能写 R,Stata GUI 只能写 Stata,MATLAB IDE 只能写 MATLAB—— 当你的研究需要多种语言协作时,你需要一个统一平台。
VS Code 的优势
Jupyter Notebook 的结构化使用
不是“随便写写代码”——而是可执行的研究文档。
最佳实践
| 习惯 | 原因 |
|---|---|
| 每个 cell 只做一件事 | 方便调试和重复运行 |
| Markdown 记录“为什么” | 代码说“做了什么”,文字说“为什么这么做” |
| 开头设置种子和路径 | 确保可重复 |
| 结尾清理临时变量 | 保持环境干净 |
stata_setup:在 Jupyter 中直接运行 Stata
Python 是“胶水语言”——它可以调用 Stata 做你最擅长的事。
关键:Stata 做回归,Python 做数据处理和可视化——各用所长。
R 开发者也不需要离开 VS Code
{r} 直接执行推荐包:fixest(面板回归)、did(Callaway-Sant’Anna)、ggplot2(可视化)
Julia:当你需要速度的时候
Julia 结合了 Python 的易用性和 C 的速度。 结构估计、数值优化、大规模模拟——Julia 是理想选择。
VS Code 中的 Julia 工作流
| 步骤 | 操作 |
|---|---|
| 安装 | julialang.org 下载,添加到 PATH |
| VS Code 扩展 | 安装 Julia 扩展(语法、REPL、调试) |
| Jupyter 内核 | using Pkg; Pkg.add("IJulia") 安装 IJulia |
| 运行 | 在 Jupyter notebook 中选择 Julia 内核 |
典型场景:BLP 需求估计的内层收缩映射——Python 太慢,Julia 快 10-100 倍。
每种语言各司其职
不是要学会所有语言——而是知道什么时候用什么。
| 语言 | 擅长场景 | 本课程中的角色 |
|---|---|---|
| Python | 开源:数据处理、ML、LLM 调用、胶水 | 主力语言 |
| Stata | 闭源:面板回归、DID、IV、聚类标准误 | 因果推断主力 |
| R | 开源:可视化、统计建模、前沿包 | 补充与对照 |
| Julia | 开源:数值优化、大规模模拟 | 结构估计加速 |
| Matlab | 闭源:矩阵运算、数值分析 | 结构估计传统选择 |
环境管理要点:venv/conda 管理 Python 环境,种子固定(np.random.seed(42)),路径用相对路径。
“在我电脑上能跑”不是可重复性
换一台机器、换一个包版本、换一个操作系统——你的代码还能跑吗?
| 问题 | 解决方案 |
|---|---|
| Python 包版本冲突 | venv 或 conda 创建隔离环境 |
| 路径在不同机器上不同 | 用相对路径,不用绝对路径 |
| 随机结果不可重复 | 在脚本开头固定种子 |
| 忘记安装了哪些包 | pip freeze > requirements.txt |
| Stata ado 版本不一致 | 记录 ssc install 的包名和日期 |
底线:你的合作者(包括三个月甚至若干年后的你自己)应该能一键重现所有结果。
五步完成环境搭建
python -m venv .venvpip install numpy pandas matplotlib jupyterpython -m ipykernel install --userimport numpy; print(numpy.__version__)
三类工具,三种交互模式——但都通过 MCP 协议连接到你的研究工具链。
Copilot 做什么
在你写代码的过程中,实时建议下一行或下一段。 不需要切换窗口——它就在你的编辑器里。
实际场景
reg y d x,——Copilot 建议 robust cluster(id)# 画事件研究图——Copilot 生成 coefplot 代码pd.read_csv(——Copilot 建议路径和参数Copilot 的局限
Claude Code 做什么
不是补全单行代码——而是理解你的研究目标, 帮你规划、实现、验证整个工作流。
工作模式:Plan → Implement → Verify
典型对话
“帮我用 Callaway-Sant’Anna 方法估计交错 DID,数据在 data/panel.csv, 处理变量是 policy,结果变量是 y,要画事件研究图。”
Claude Code 不仅写代码——它会组织项目结构、处理数据清洗、生成图表、写文档。
OpenAI Codex CLI 做什么
在终端中用自然语言描述任务,直接生成并执行代码。 适合批量处理和自动化脚本。
典型用法
与 Claude Code 的区别:Codex 更轻量,适合单次任务;Claude Code 更适合多步骤项目。
什么是 MCP(Model Context Protocol)
MCP 是一个标准化接口,让 AI 助手能够调用外部工具—— 包括 Stata、R、Julia、Matlab、数据库、文件系统等。
为什么这改变了游戏规则?
实际效果
你对 Claude Code 说“用 Stata 跑一个面板固定效应回归”—— 它通过 MCP Stata Server 直接执行 reghdfe,返回结果表格。 你不需要打开 Stata 窗口。

核心思想:研究者发出自然语言指令 → AI 翻译为工具调用 → MCP 路由到正确的执行引擎 → 结果返回给研究者审查。
AI 写的代码,风险在哪?
审计流程
每次 AI 生成代码后:
核心思想
用计算机大量重复一个随机实验, 通过统计重复结果的分布来理解随机现象的性质。
名字的由来
Monte Carlo——摩纳哥的赌场。Von Neumann 和 Ulam 在曼哈顿计划中 用随机抽样解决中子扩散问题,戏称这个方法为“Monte Carlo 方法”。
本质:当数学推导太复杂时,让计算机帮你“做实验”。
问题:抛一枚公平硬币,正面朝上的概率是 0.5。但如果只抛 10 次呢?
np.random.seed(42)
n_flips = 10000
flips = np.random.binomial(1, 0.5, n_flips) # 1=正面, 0=反面
cumulative_prop = np.cumsum(flips) / np.arange(1, n_flips + 1)
fig, ax = plt.subplots(figsize=(12, 5))
ax.plot(range(1, n_flips + 1), cumulative_prop, color='#003153', linewidth=1)
ax.axhline(y=0.5, color='#00A8CC', linestyle='--', linewidth=2, label='真实概率 p = 0.5')
ax.set_xlabel('抛硬币次数', fontsize=14)
ax.set_ylabel('正面比例', fontsize=14)
ax.set_title('大数定律:样本越大,频率越接近概率', fontsize=16)
ax.set_xscale('log')
ax.legend(fontsize=13)
ax.set_ylim(0, 1)
plt.tight_layout()
plt.show()
大数定律(Law of Large Numbers)
设 X_1, X_2, \ldots, X_n 独立同分布,E[X_i] = \mu,则:
\bar{X}_n = \frac{1}{n}\sum_{i=1}^n X_i \xrightarrow{p} \mu \quad \text{当 } n \to \infty
样本均值以概率收敛于总体均值。
为什么这重要?
问题:从一个非正态总体中抽样,\bar{X} 的分布是什么形状?
np.random.seed(42)
population = np.random.exponential(scale=2, size=100000) # 指数分布,偏态
sample_sizes = [5, 30, 100]
R = 5000 # 重复次数
fig, axes = plt.subplots(1, 3, figsize=(12, 5))
for idx, n in enumerate(sample_sizes):
means = [np.mean(np.random.choice(population, n)) for _ in range(R)]
axes[idx].hist(means, bins=50, color='#003153', alpha=0.7, edgecolor='white', density=True)
axes[idx].axvline(np.mean(population), color='#00A8CC', linestyle='--', linewidth=2)
axes[idx].set_title(f'n = {n}', fontsize=15)
axes[idx].set_xlabel('样本均值', fontsize=12)
if idx == 0:
axes[idx].set_ylabel('密度', fontsize=12)
fig.suptitle('中心极限定理:总体为指数分布(偏态),但样本均值趋于正态', fontsize=14)
plt.tight_layout()
plt.show()
中心极限定理(Central Limit Theorem)
设 X_1, \ldots, X_n 独立同分布,E[X_i] = \mu,\text{Var}(X_i) = \sigma^2,则:
\frac{\bar{X}_n - \mu}{\sigma / \sqrt{n}} \xrightarrow{d} N(0, 1) \quad \text{当 } n \to \infty
无论总体什么分布,样本均值的标准化分布都趋于正态。
CLT 带给我们什么?
问题:OLS 的 \hat{\beta} 真的无偏吗?模拟验证。
np.random.seed(42)
beta_true = 2.0 # 真实参数
R = 2000 # 重复次数
n = 100 # 每次样本量
beta_hats = []
for _ in range(R):
x = np.random.normal(0, 1, n)
epsilon = np.random.normal(0, 1, n)
y = beta_true * x + epsilon # DGP: y = 2x + ε
beta_hat = np.sum(x * y) / np.sum(x ** 2) # OLS 公式
beta_hats.append(beta_hat)
beta_hats = np.array(beta_hats)
fig, ax = plt.subplots(figsize=(12, 5))
ax.hist(beta_hats, bins=60, color='#003153', alpha=0.7, edgecolor='white', density=True)
ax.axvline(beta_true, color='#00A8CC', linestyle='--', linewidth=2.5, label=f'真实值 β = {beta_true}')
ax.axvline(beta_hats.mean(), color='#e74c3c', linestyle=':', linewidth=2, label=f'MC 均值 = {beta_hats.mean():.4f}')
ax.set_xlabel('β̂ 的值', fontsize=14)
ax.set_ylabel('密度', fontsize=14)
ax.set_title(f'OLS 估计量的抽样分布(R={R}, n={n})', fontsize=16)
ax.legend(fontsize=13)
plt.tight_layout()
plt.show()
从模拟中看到了什么?
如果增大样本量呢?
| n | E[\hat{\beta}] | \text{SD}(\hat{\beta}) |
|---|---|---|
| 30 | ≈ 2.00 | ≈ 0.19 |
| 100 | ≈ 2.00 | ≈ 0.10 |
| 1000 | ≈ 2.00 | ≈ 0.03 |
一致性(Consistency):n 越大,\hat{\beta} 的分布越集中在真值附近。
np.random.seed(42)
beta_true = 2.0
R = 2000
fig, axes = plt.subplots(1, 3, figsize=(12, 5), sharey=True)
for idx, n in enumerate([30, 100, 1000]):
betas = []
for _ in range(R):
x = np.random.normal(0, 1, n)
eps = np.random.normal(0, 1, n)
y = beta_true * x + eps
betas.append(np.sum(x * y) / np.sum(x ** 2))
axes[idx].hist(betas, bins=50, color='#003153', alpha=0.7, edgecolor='white', density=True)
axes[idx].axvline(beta_true, color='#00A8CC', linestyle='--', linewidth=2)
axes[idx].set_title(f'n = {n},SD = {np.std(betas):.3f}', fontsize=14)
axes[idx].set_xlabel('β̂', fontsize=13)
axes[idx].set_xlim(1.4, 2.6)
axes[0].set_ylabel('密度', fontsize=13)
fig.suptitle('一致性:样本量越大,估计量分布越集中', fontsize=15)
plt.tight_layout()
plt.show()
课后练习思路
选择以下任一问题,用 MC 模拟验证:
每个实验都遵循同一个框架:设定 DGP → 生成数据 → 计算估计量 → 重复 → 汇总。

MC 设计三要素
np.random.seed(42) 或 set seed 12345——确保可重复场景 1:验证你的估计量
你推导了一个新的 DID 估计量。它真的无偏吗?
数学证明太复杂——用 MC 模拟,设定 DGP,看估计量是否收敛于真值。
场景 2:理解有限样本行为
CLT 说“n 趋向无穷时”——但你只有 50 个个体的面板数据。
模拟告诉你:在 n=50 时,估计量的分布是什么样子?
场景 3:检查稳健性
你怀疑异方差会影响标准误。
模拟不同的误差结构,看你的推断是否还可靠。
Monte Carlo 的核心价值
在你无法用数学证明的地方,MC 模拟给你实验证据。

三个阶段,循环迭代
一个好的项目结构
my_research_project/
├── data/
│ ├── raw/ # 原始数据(只读,永不修改)
│ ├── cleaned/ # 清洗后的数据
│ └── README.md # 数据来源、变量说明
├── code/
│ ├── 01_clean.py # 数据清洗
│ ├── 02_analysis.py # 核心分析
│ └── 03_figures.py # 图表生成
├── output/
│ ├── tables/ # 回归表格
│ └── figures/ # 图表
├── docs/ # 文档、笔记
└── README.md # 项目说明
原则:原始数据只读、代码按顺序编号、输出与代码分离。
从数据到结果的完整链条
每一步都可追溯——如果有人问“你的表 3 是怎么来的”, 你可以指向具体的代码文件和数据文件。
# 一个典型的分析脚本骨架
import pandas as pd
import numpy as np
# === 配置 ===
SEED = 42
DATA_PATH = "data/cleaned/panel.csv"
OUTPUT_PATH = "output/tables/"
np.random.seed(SEED)
# === 数据加载 ===
df = pd.read_csv(DATA_PATH)
print(f"样本量: {len(df)}, 变量数: {df.shape[1]}")
# === 核心分析 ===
# ... (回归、估计、检验)
# === 输出 ===
# results.to_csv(OUTPUT_PATH + "table3.csv")没有版本控制的后果
paper_final.docx → paper_final_v2.docx → paper_final_v2_revised_REAL_FINAL.docx
你永远不知道改了什么、为什么改、能不能回到之前的版本。
Git 解决的问题
最小 Git 工作流:git add → git commit -m "描述" → git push
传统 vs AI 辅助的工作流
| 阶段 | 传统方式 | AI 辅助方式 |
|---|---|---|
| 构建 | 手动创建文件夹和模板 | Claude Code 自动生成项目骨架 |
| 执行 | 手写每行代码 | Copilot 辅助编写,人工审查 |
| 迭代 | 手动改规格,重新跑 | 自然语言描述修改,批量更新 |
| 文档 | 事后补写 | AI 实时生成代码注释和日志 |
不变的是什么
一个小型研究项目的全过程
audit_log.md 中审计日志模板回顾
| 字段 | 内容 |
|---|---|
| 工具 | Copilot / Claude Code / Codex |
| 任务 | 生成数据清洗代码 |
| 接受 | 数据合并逻辑 |
| 修改 | 将 left join 改为 inner join(避免缺失值) |
| 验证 | 手动检查合并后样本量与预期一致 |
第 3 周:面板数据与经典双重差分法
核心思想
有了计算环境和工作流之后, 我们开始用真正的识别策略做因果推断—— 经典 DiD 是应用最广泛的第一站。