AB测试与因果推断入门指南:从实验设计到结果解读

1次阅读
没有评论

共计 2939 个字符,预计需要花费 8 分钟才能阅读完成。

image.webp

从电商案例看传统 AB 测试的局限性

假设某电商平台想评估新优惠券的效果,传统 AB 测试可能这样设计:随机选取 10% 用户发券(实验组),其余用户作为对照组。两周后统计发现实验组人均消费提升 15%,但进一步分析发现:

AB 测试与因果推断入门指南:从实验设计到结果解读

  • 选择偏差(Selection Bias):领券用户中 60% 是高频消费者(自然消费意愿更强)
  • 辛普森悖论(Simpson’s Paradox):按用户分层分析时,各群体转化率反而下降
# 模拟选择偏差的示例数据
import pandas as pd
import numpy as np

np.random.seed(42)
data = pd.DataFrame({'user_type': ['high']*500 + ['low']*500,
    'received_coupon': np.concatenate([np.random.binomial(1, 0.8, 500),  # 高频用户领券概率 80%
        np.random.binomial(1, 0.2, 500)   # 低频用户领券概率 20%
    ]),
    'spend': np.concatenate([np.random.normal(200, 50, 500),   # 高频用户基线消费
        np.random.normal(80, 30, 500)     # 低频用户基线消费
    ])
})

RCT 与观察性研究的关键区别

  1. 随机对照试验(RCT)
  2. 黄金标准,通过随机化消除混淆因子(Confounder)
  3. 缺点:成本高、可能不道德(如医疗试验)

  4. 观察性研究(Observational Study)

  5. 分析现有数据中的关联关系
  6. 必须使用因果推断技术控制偏差
  7. 典型场景:
    • 用户自主选择是否参与活动
    • 历史数据中的自然实验(如政策变化)

因果推断核心方法实战

1. 构建因果图(DAG)

graph LR
    U[用户类型] --> T[是否领券]
    U --> Y[消费金额]
    T --> Y

2. 倾向得分匹配 (PSM) 实现

from sklearn.linear_model import LogisticRegression
from matplotlib import pyplot as plt

# 步骤 1:计算倾向得分
X = data[['user_type']].replace({'high':1, 'low':0})
y = data['received_coupon']
model = LogisticRegression().fit(X, y)
data['propensity_score'] = model.predict_proba(X)[:, 1]

# 步骤 2:最近邻匹配
from sklearn.neighbors import NearestNeighbors
treated = data[data.received_coupon==1]
control = data[data.received_coupon==0]

nbrs = NearestNeighbors(n_neighbors=1).fit(control[['propensity_score']])
_, indices = nbrs.kneighbors(treated[['propensity_score']])
matched_control = control.iloc[indices.flatten()]

# 步骤 3:平衡性检验
plt.figure(figsize=(10,4))
plt.subplot(121)
plt.hist(treated['propensity_score'], alpha=0.5, label='实验组')
plt.hist(matched_control['propensity_score'], alpha=0.5, label='匹配对照组')
plt.legend()

3. 双重差分法 (DID) 示例

# 假设有预处理期和后处理期数据
did_data = pd.DataFrame({'time': ['pre']*1000 + ['post']*1000,
    'group': ['treat']*500 + ['control']*500 + ['treat']*500 + ['control']*500,
    'metric': np.concatenate([np.random.normal(100, 10, 500),  # 实验组预处理
        np.random.normal(95, 10, 500),   # 对照组预处理
        np.random.normal(120, 10, 500),  # 实验组后处理
        np.random.normal(100, 10, 500)   # 对照组后处理
    ])
})

# DID 计算公式:(E[Y_treat_post] - E[Y_treat_pre]) - (E[Y_control_post] - E[Y_control_pre])
agg = did_data.groupby(['time', 'group']).mean().unstack()
did_effect = (agg.loc['post','metric']['treat'] - agg.loc['pre','metric']['treat']) \
            - (agg.loc['post','metric']['control'] - agg.loc['pre','metric']['control'])
print(f"DID 效应量: {did_effect:.2f}")

五大避坑指南

  1. 混淆变量选择
  2. 同时影响 treatment 和 outcome 的变量必须控制
  3. 工具变量 (Instrumental Variable) 不能放入 PSM 模型

  4. 协变量平衡检验

  5. 匹配后各变量标准化均值差 (SMD) 应 <0.1
  6. 使用 tableone 库快速检查:

    from tableone import TableOne
    matched = pd.concat([treated, matched_control])
    print(TableOne(matched, groupby='received_coupon'))

  7. 小样本解决方案

  8. 使用 Bootstrap 重复抽样:

    from sklearn.utils import resample
    boot_effects = []
    for _ in range(1000):
        sample = resample(matched, replace=True)
        effect = sample[sample.received_coupon==1].spend.mean() - \
                 sample[sample.received_coupon==0].spend.mean()
        boot_effects.append(effect)
    print(f"95% 置信区间: [{np.percentile(boot_effects, 2.5):.1f}, {np.percentile(boot_effects, 97.5):.1f}]")

  9. 不可观测混淆处理

  10. 使用断点回归 (RDD) 或工具变量(IV)

  11. 敏感度分析

  12. 测试结论对模型假设的稳健性

进阶思考题

当实验组用户天然更活跃时,可以尝试:
1. 分层 PSM:对不同活跃度用户分别匹配
2. 合成控制法(Synthetic Control):构建虚拟对照组
3. 面板数据模型:跟踪同一用户在不同时期的表现

关键认知:因果推断不是银弹,需要结合业务理解选择合适方法。建议从 PSM+DID 开始实践,逐步掌握更复杂的模型。

正文完
 0
评论(没有评论)