Lab 5:数据分析进阶——模型、检验与因果推断实战

课程:数据库技术与应用:AI 时代的基石数据技能
授课教师:王健楠(计算机科学与技术系)
学期:2026 年(春季)

实验概述与学习目标

本实验涵盖从自动化特征工程到因果推断的完整方法链,共四个任务。

任务 主题 分值
任务 1 自动化特征工程 20 分
任务 2 可解释机器学习 30 分
任务 3 假设检验 25 分
任务 4 因果推断 25 分
合计 100 分

完成本实验后,你应能够:

  1. 掌握自动化特征工程的基本方法: 能够使用 featuretools 对多张关联表进行深度特征合成(Deep Feature Synthesis, DFS),并通过特征选择提升模型表现。
  2. 理解并实现机器学习可解释性技术: 能够从参数解读、置换重要性、部分依赖图、LIME 等多个维度解释模型行为,区分透明模型与事后解释方法的适用场景。
  3. 掌握假设检验的核心思想与实现: 能够从零实现置换检验和卡方检验,理解 p 值的含义,并识别 p-hacking 的风险。
  4. 理解因果推断的基本框架与估计方法: 能够绘制因果图,实现精确匹配、近邻匹配、倾向得分匹配和线性回归四种平均处理效应(ATE)估计方法。

第 0 步:环境准备


任务 1:自动化特征工程(20 分)

任务背景

实际数据往往以多张关联表的形式存在,手工构造聚合特征耗时且容易遗漏有价值的组合。featuretools 提供了深度特征合成 方法:自动遍历表间关系,系统性地生成 COUNT、SUM、MEAN、MODE 等聚合特征及时间衍生特征,将繁琐的特征工程过程自动化。

本任务使用电商订单数据集(三张关联表),预测用户在未来 4 周内是否购买香蕉(二分类)。你需要构建 EntitySet、运行 DFS,并从候选特征中筛选 10 个,使随机森林 AUC 超过基线 0.61

如尚未安装 featuretools,请运行:pip install featuretools

数据说明与加载

数据包含三张关联表:

1.1 定义数据实体集(8 分)

featuretools 的第一步是将多张关联表组织成一个 EntitySet(实体集),明确声明每张表的主键、外键关系,以及时间列的数据类型。实体集是 DFS 的输入,只有声明正确,featuretools 才能正确推断跨表聚合的方向。

请实现函数 load_entityset(orders, order_products, users),构建并返回一个正确的 EntitySet。

要求:

  1. EntitySet 中包含三个实体:usersordersorder_products
  2. 正确声明各表的主键(user_idorder_idorder_product_id);
  3. 声明两条关系:orders.user_id → users.user_idorder_products.order_id → orders.order_id
  4. order_time 列声明为时间类型(datetime)。

参考文档: featuretools 文档

1.2 深度特征合成与特征选择(12 分)

第一步:运行 DFS,生成候选特征

请调用 ft.dfs()users 为目标实体(target_dataframe_name)执行深度特征合成,将结果存入 feature_matrixfeature_defs

第二步:特征选择,击败基线

从生成的所有特征中,筛选出恰好 10 个你认为最有预测价值的特征。

要求:

  1. 将选出的 10 个特征存入 X(shape 应为 (767, 10)),标签存入 y
  2. 处理特征矩阵中可能存在的缺失值(如用 0 填充);
  3. 使用下方给出的随机森林和评估代码,确保 AUC 超过 0.61

思考题(必填)

  1. 请列举 featuretools 的 三个优点三个缺点
  2. 针对你列出的缺点,有没有可行的改进思路?

你的回答:

(请在此处作答)


任务 2:可解释机器学习(30 分)

任务背景

高精度模型往往是"黑盒"——梯度提升树和神经网络的内部结构难以直接解读。可解释机器学习使模型的预测结果可供人类理解与审查,是信贷审批、医疗诊断、公共政策等高风险决策场景的必要条件。

本任务使用 UCI 森林覆盖类型(Forest Covertype) 数据集,依次实践六种解释方法:

子任务 方法 解释粒度 模型依赖 分值
2.1 透明模型(逻辑回归系数) 全局 透明模型 8
2.2 置换重要性 全局 与模型无关 8
2.3 部分依赖图(PDP) 全局 与模型无关 6
2.4 全局代理模型 全局 与模型无关 4
2.5 LIME 局部 与模型无关 4

数据集说明: 54 个特征——前 10 列为数值特征(海拔、坡度、距水源距离等),其余为独热编码的分类特征(土壤类型、荒野类型),目标变量为覆盖类型(类别 1 vs 其他)。

数据加载与模型训练

下面的代码完成数据加载、划分和三个模型的训练。请直接运行,无需修改。

2.1 透明模型解释(9 分)

逻辑回归是一个"透明模型"——它的参数直接揭示了每个特征的贡献方向和大小,不需要任何额外的解释工具。

子任务 A——全局解释: 实现 explain_logistic_regression(lr, feature_names),以水平柱状图展示每个特征的回归系数,正负贡献用不同颜色区分,帮助理解模型整体依赖哪些特征、方向如何。

子任务 B——局部解释: 实现 explain_logistic_regression_prediction(lr, feature_names, sample),对某条具体样本的预测进行解释:将每个特征的系数 × 该样本的特征值,得到该特征对这次预测的实际贡献量,以水平柱状图展示。

请写下你从上图中观察到的 两个 最有趣的发现。

发现

  1. [请填写]
  2. [请填写]

2.2 置换重要性(9 分)

对于梯度提升树和神经网络,我们无法直接读取参数来理解特征重要性。置换重要性(Permutation Importance) 提供了一种与模型结构无关的解决方案:

对测试集中某个特征的值随机打乱(置换),如果模型误差显著上升,说明该特征非常重要;如果误差变化不大,说明这个特征对模型几乎没有贡献。

重要性分数定义为:$I_j = E'_j - E_0$(打乱第 $j$ 个特征后的误差 − 原始误差)。

请实现函数 permutation_importance(model, feature_names, X, y)

  1. 计算模型在原始测试集上的对数损失 $E_0$ 作为基准;
  2. 对每个特征逐一进行置换,计算置换后的误差 $E'_j$;
  3. 以 $I_j = E'_j - E_0$ 作为重要性分数;
  4. 绘制前 5 个最重要特征的柱状图。

注意: 不得调用 sklearn 的 permutation_importance 函数,需自行实现。

请写下你从上图中观察到的两个最有趣的发现。(两个模型的重要特征有何异同?)

发现

  1. [请填写]
  2. [请填写]

2.3 部分依赖图(6 分)

置换重要性告诉我们"哪个特征重要",但没有告诉我们"这个特征怎么影响预测"。部分依赖图(Partial Dependence Plot, PDP) 填补了这个空白:在控制其他所有特征不变的前提下,单独展示某一特征取值变化对预测概率的边际影响

请分别为梯度提升树多层感知机绘制前 10 个数值特征(feature_names[:10])的 PDP 图。

使用 sklearn.inspection.PartialDependenceDisplay.from_estimator() 实现。

请写下你从上图中观察到的 两个 最有趣的发现。(对比两个模型的 PDP 形态,哪些特征呈线性关系,哪些呈非线性?)

发现

  1. [请填写]
  2. [请填写]

2.4 全局代理模型(4 分)

全局代理模型(Global Surrogate) 是一种间接的解释思路:

  1. 用黑盒模型(梯度提升树 / 神经网络)对训练集进行预测,得到"软标签"(预测概率);
  2. 以软标签为目标,训练一个透明的逻辑回归模型;
  3. 用 2.1 中实现的 explain_logistic_regression() 解读代理模型的系数。

代理模型的系数近似反映了黑盒模型的决策逻辑。

请分别对梯度提升树和多层感知机各训练一个逻辑回归代理模型,并可视化系数。

请写下你从上图中观察到的 两个 最有趣的发现。(与 2.1 中真实逻辑回归的系数相比,代理模型的系数有何异同?说明了什么?)

发现

  1. [请填写]
  2. [请填写]

2.5 LIME 局部解释(2 分)

LIME(Local Interpretable Model-agnostic Explanations)是另一种局部解释方法。LIME 不依赖模型内部结构,适用于任何黑盒模型。其核心思想是:

在待解释样本的附近生成大量扰动样本,用这些扰动样本的黑盒预测结果训练一个简单的线性模型,用该线性模型近似黑盒在该样本周围的行为。

请安装 lime 库,使用 lime.lime_tabular.LimeTabularExplainer 解释多层感知机对测试集第 0 条样本的预测。

思考题: 多次重新运行上方代码单元(每次 Kernel 重新执行该单元),你会发现 LIME 的解释结果每次略有不同。

  1. 你如何评价 LIME 的稳定性
  2. 结合 LIME 的算法原理,请解释为什么会出现这种现象。

你的回答:

  1. [请填写]
  2. [请填写]

任务 3:假设检验(25 分)

任务背景

清华网络学堂对课程搜索框进行了 A/B 测试:A 组用户看到原有的空白搜索框,B 组看到带有提示文字的搜索框(如下图)。实验结束后,B 组用户平均搜索次数比 A 组高 0.135 次

这一差异是界面改版的真实效果,还是随机分组带来的统计波动?假设检验提供了严格的决策框架:

  1. 零假设 $H_0$:两组差异由随机分组引起,界面设计无效果。
  2. 备择假设 $H_1$:差异由界面改版引起,具有统计显著性。
  3. p 值:在 $H_0$ 成立的前提下,出现不低于观测差异的概率(本实验采用单尾检验,因为 $H_1$ 是有方向的——B 组搜索量高于 A 组)。
  4. *决策准则:若 p < 0.05,拒绝 $H_0$,认为差异在统计上显著。

数据说明与加载

data/searchlog.json 包含清华网络学堂搜索功能 A/B 测试期间每位用户的行为记录:

字段 说明
uid 用户 ID
is_instructor 是否为教师用户(True)或学生用户(False
search_ui 该用户被分配到的界面版本('A''B'
search_count 该用户在实验期间的总搜索次数

3.1 计算检验统计量(5 分)

在进行假设检验之前,先选定一个检验统计量来量化我们观察到的效应。

这里我们使用 B 组与 A 组搜索次数均值之差:

$$\delta = \bar{X}_B - \bar{X}_A$$

请计算 $\delta$,将结果存入变量 delta

3.2 置换检验(10 分)

$\delta = 0.135$ 看起来不小,但真的有统计意义吗?

置换检验(Permutation Test) 的核心逻辑:如果零假设成立(两组没有真实差异),那么 A/B 的分组标签对用户来说只是一个随机标记,把标签打乱重新分配后,得到的 $\delta^*$ 应该和真实观测值差不多。

如果在成千上万次随机置换中,模拟出的 $\delta^*$ 几乎从来不会达到观测值 $\delta$ 那么大,说明 $\delta$ 在零假设下极不可能发生——p 值很小,可以拒绝零假设。

算法步骤:

  1. 将 A 组和 B 组的搜索次数合并;
  2. 重复 10000 次:随机打乱合并数据,按原始比例重新分成两组,计算新的均值差 $\delta^*$;
  3. p 值 = $\delta^* \geq \delta_{\text{observed}}$ 的比例(单尾)。

单尾 vs 双尾: 本实验的 $H_1$ 是"B 组搜索量高于 A 组",方向明确, 故使用单尾检验,只统计 $\delta^* \geq \delta$ 的比例。 若 $H_1$ 改为"两组存在任何差异"(无方向),则应使用双尾检验: p 值 = $|\delta^*| \geq |\delta|$ 的比例,通常比单尾 p 值更大(更保守)。

请实现函数 permutation_test(A_data, B_data, num_samples=10000),返回 p 值。

注意: 不得调用任何现有的假设检验库函数(如 scipy.stats),需自行实现上述算法。

3.3 p-hacking 风险讨论(5 分)

若基于同一份数据集,对教师用户子群体(is_instructor == True)单独重复上述假设检验,是否存在 p-hacking 的风险?若存在,应如何避免?

你的回答: (请在此处作答,至少 100 字)

3.4 卡方检验(5 分)

卡方检验(Chi-squared Test) 用于检验两个类别变量是否统计独立。这里我们想验证:实验中 is_instructor(教师/学生)和 search_ui(A/B 分组)是否相关?

如果两者相关,说明 A/B 随机分组可能存在问题(例如教师用户被过多分入 B 组),实验结果就不可信了。

卡方统计量的计算方法:

  1. 构建列联表:行为 is_instructor 的取值,列为 search_ui 的取值,单元格为频数;
  2. 计算期望频数:$E_{ij} = \dfrac{\text{行和}_i \times \text{列和}_j}{\text{总计}}$;
  3. 计算统计量:$\chi^2 = \sum_{i,j} \dfrac{(O_{ij} - E_{ij})^2}{E_{ij}}$;
  4. 自由度 $df = (\text{行数} - 1) \times (\text{列数} - 1)$,计算 p 值。

请实现函数 chi_squared_test(df, col1, col2),返回卡方统计量和 p 值。

注意: 不得调用 scipy.stats.chi2_contingency 等现成函数,需自行实现。可以使用 scipy.stats.chi2.sf(chi2_stat, df) 来计算 p 值。

请简要解释:上述卡方检验的结果对我们的 A/B 测试结论意味着什么?

你的回答: (请在此处作答)


任务 4:因果推断(25 分)

任务背景

相关性 ≠ 因果性是数据分析中最重要的原则之一。简单比较两组样本的结果均值,往往受混淆变量的干扰——那些同时影响干预变量与结果变量的因素。

本任务使用 Lalonde 职业培训数据集(因果推断领域的标准基准数据集,收集于 1970 年代美国),研究职业培训项目(treat)对 1978 年收入(re78)的因果效应。

核心概念——反事实(Counterfactual): 对每个个体,我们希望同时知道其接受和不接受培训时的潜在收入,但每个人只能经历其中一条路径。平均处理效应(Average Treatment Effect, ATE) 定义为:

$$\text{ATE} = \mathbb{E}[Y(1) - Y(0)]$$

其中 $Y(1)$、$Y(0)$ 分别为接受和不接受培训时的潜在收入。本任务将实现四种控制混淆变量的 ATE 估计方法。

4.1 绘制因果图(5 分)

在进行 ATE 估计之前,我们首先需要通过因果图(Causal Graph) 明确变量之间的因果关系。

因果图用有向无环图(DAG)表示:节点为变量,有向箭头代表"直接影响"的方向。

在 Lalonde 数据集中:

请用 graphviz 绘制这四个变量之间正确的因果图。

4.2 精确匹配(5 分)

精确匹配(Perfect Matching) 是最直接的反事实估计:对每个对照组(未参加培训)成员,在处理组中寻找所有协变量完全相同的人,以该人的收入作为"如果他也参加了培训会怎样"的估计。

协变量:ageeducblackhispanmarriednodegreere74re75(共 8 个)。

请计算:(1)能进行精确匹配的对照组成员数量和比例;(2)这些成员的 ATE。若处理组中有多个匹配,取第一个。

4.3 近邻匹配(5 分)

精确匹配的覆盖率往往很低(因为现实中很难找到各方面都完全一样的人)。近邻匹配(Nearest Neighbor Matching) 放宽限制,用欧氏距离衡量相似性,为每个对照组成员找到最接近的处理组成员。

要求:

4.4 倾向得分匹配(5 分)

在高维协变量下,欧氏距离的意义减弱("维数灾难")。倾向得分匹配(Propensity Score Matching, PSM) 将多维协变量"压缩"成一个标量:接受处理的条件概率 $P(\text{treat}=1 | X)$,称为倾向得分

再在倾向得分维度上做近邻匹配,可以有效缓解维数问题。

步骤:

  1. 用逻辑回归拟合 $P(\text{treat}=1 | X)$,将结果添加为 lalonde 的新列 psm
  2. 对每个对照组成员,在处理组中找倾向得分差异最小的匹配,阈值 0.01
  3. 统计匹配数量、比例,计算 ATE。

4.5 线性回归(5 分)

线性回归方法 不进行匹配,而是分别对处理组和对照组训练两个线性回归模型:

然后对全体样本估计反事实,计算 ATE:

$$\text{ATE} = \frac{1}{N} \sum_{i=1}^{N} \left[ M_1(X_i) - M_0(X_i) \right]$$

汇总与分析

请简要比较四种方法的优缺点,并解释为什么它们给出的 ATE 估计值不同。

你的回答: (请在此处作答)


实验圆满结束!

任务 主题 核心技术
任务 1 自动化特征工程 featuretools 深度特征合成
任务 2 可解释机器学习 系数解读、Permutation、PDP、LIME
任务 3 假设检验 置换检验、卡方检验、p-hacking 识别
任务 4 因果推断 精确匹配、近邻匹配、PSM、线性回归

四个任务依次覆盖预测 → 理解 → 验证 → 决策的数据科学能力矩阵。


提交前最终检查: