测量误差对因果推断的影响:理论与校正方法
一、引言:当观测不等于真实——测量误差的幽灵
在因果推断的理想世界中,我们假设能够精确观测所有变量。然而,真实业务与科研场景充斥着测量误差:
- 医疗场景:患者自我报告的"每日吸烟量"与实际生化指标存在30-50%偏差
- 经济研究:企业申报的"研发投入"与真实支出因会计准则差异偏离20%
- 用户行为:APP记录的"使用时长"因后台运行、统计口径问题虚高40%
- 临床试验:血压测量因"白大褂效应"单次读数波动达15mmHg
测量误差的致命性:在简单回归中,解释变量X的测量误差使估计量向0衰减(Attenuation Bias)。在因果推断中,这种偏误会:
- 扭曲处理效应:将真实β=0.5的因果效应低估至0.2
- 破坏工具变量有效性:使第一阶段F统计量从20降至3,沦为弱工具
- 加剧选择偏倚:混淆变量测量误差导致反事实估计完全失效
本文将构建从理论诊断、偏误量化到校正方法的全套工具箱。
章节总结:引言
Lexical error on line 12. Unrecognized text. ... F --> G[效应扭曲: β=0.5→0.2] F --> H[IV -----------------------^二、测量误差的分类学:从经典到现代的模型演进
2.1 测量误差的四维分类
| 分类维度 | 类型 | 数学特征 | 业务实例 | 危害程度 |
|---|---|---|---|---|
| 误差来源 | 随机误差 | 血压计读数波动 | ★★★★☆ | |
| 系统误差 | (固定偏倚) | 血糖仪校准偏差 | ★★★★★ | |
| 误差位置 | 解释变量误差 | BMI计算误差 | ★★★★★ | |
| 结果变量误差 | 收入自报虚高 | ★★★☆☆ | ||
| 依赖结构 | 经典误差 | 独立测量噪声 | ★★★★☆ | |
| Berkson误差 | 分组暴露估计 | ★★★☆☆ | ||
| 误差分布 | 正态误差 | 大多数连续变量 | ★★★★☆ | |
| 非经典误差 | 偏态/重尾分布 | 收入分布右偏 | ★★★★★ |
2.2 经典测量误差模型(Classical Error Model)
解释变量含误差:
结果变量含误差:
因果模型:
观测方程:
偏误推导:
在OLS中使用代替,估计量:
衰减系数:
结论:真实效应被向0衰减,且与误差方差成正比。
章节总结:测量误差分类
Parse error on line 16: ...> H2[衰减系数 λ = σ_X^2/(σ_X^2+σ_δ^2)] -----------------------^ Expecting 'SEMI', 'NEWLINE', 'SPACE', 'EOF', 'GRAPH', 'DIR', 'subgraph', 'SQS', 'SQE', 'end', 'AMP', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'ALPHA', 'COLON', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'START_LINK', 'LINK', 'STYLE', 'LINKSTYLE', 'CLASSDEF', 'CLASS', 'CLICK', 'DOWN', 'UP', 'DEFAULT', 'NUM', 'COMMA', 'MINUS', 'BRKT', 'DOT', 'PCT', 'TAGSTART', 'PUNCTUATION', 'UNICODE_TEXT', 'PLUS', 'EQUALS', 'MULT', 'UNDERSCORE', got 'PS'三、测量误差对因果推断的破坏机制
3.1 对OLS估计的影响
真实DGP:
观测DGP:
OLS偏误:
双重灾难:主效应衰减,混淆变量系数也偏倚!
3.2 对工具变量(IV)估计的影响
第一阶段(真实):
第一阶段(含误差):
F统计量衰减:
弱工具风险:即使真实F=20,当λ=0.5时,观测F降至5,IV估计严重偏倚。
3.3 对双重差分(DID)的影响
若处理变量存在测量误差:
面板数据特殊问题:即使误差经典且独立,固定效应估计量仍不一致,因为。
章节总结:破坏机制
Lexical error on line 3. Unrecognized text. ... B --> B1[主效应衰减: β1·λ] B --> B2[混淆变 -----------------------^四、经典校正方法:工具变量与代理变量
4.1 工具变量法(IV)校正
核心逻辑:找到与相关但与误差无关的。
校正步骤:
- 识别有效IV:满足相关性与排他性
- 第一阶段:
- 第二阶段:
有效性条件:
- I. 相关性条件:
- II. 排他性条件:(即工具仅通过影响)
- III. 可识别条件:,且
4.2 代理变量法(Surrogate Variable)
当无法获得IV时,使用**代理变量**估计误差方差。
代理变量需满足:
- I.
- II. (代理与测量误差无关)
- III. (代理与结果误差无关)
Frost估计量:
思想:利用代理与真实/观测变量的协方差比校正衰减。
4.3 多重估计法(Multiple Imputation)
适用于**结果变量**含误差:
- I. 估计的误差分布:基于重复测量或验证子样本
- II. 生成M个插补数据集:
- III. 分别因果估计:得到
- IV. 合并结果:Rubin规则
章节总结:经典校正
Lexical error on line 9. Unrecognized text. ...] C --> C2[W与δ_x⊥] C --> C3[Fros ----------------------^五、现代校正方法:Deconvolution与贝叶斯框架
5.1 Deconvolution方法:从观测分布还原真实分布
问题:已知,,,求。
数学原理:
校正步骤:
- I. 估计:使用核密度估计(KDE)
- II. 假设:基于重复测量或验证样本估计噪声分布
- III. 反卷积:使用傅里叶变换
5.2 贝叶斯分层模型(Bayesian Hierarchical Model)
思想:将测量误差视为隐变量,通过MCMC同时估计因果参数和误差分布。
模型结构:
- 似然层:
- 测量层:
- 先验层:,
推断:
使用Stan/PyMC采样后验分布,校正误差。
章节总结:现代方法
六、实例分析:糖尿病治疗效果评估(2000字以上)
6.1 业务背景与问题定义
场景:某三甲医院评估二甲双胍对2型糖尿病患者HbA1c(糖化血红蛋白)的降低效果。研究基于5000名患者12个月的电子病历数据,但面临严重测量误差:
误差来源全景:
- HbA1c测量误差:不同科室设备未校准,CV达8%(真实CV应<3%)
- 用药依从性测量误差:患者自报 vs 实际药盒电子记录,差异率35%
- BMI计算误差:门诊测量时衣物、进食影响,偏差±2kg/m²
- 饮食记录误差:24小时回忆法,热量低估约22%
- 并发症诊断误差:ICD编码错误率18%,导致混淆变量偏倚
初步分析危机:OLS估计显示二甲双胍降低HbA1c 0.8%,但医生临床经验认为应在1.2-1.5%。0.7%的差异可能源于测量误差导致的效应衰减。
研究目标:
- I. 量化各变量测量误差的偏倚贡献
- II. 校正后重新估计真实处理效应
- III. 为临床指南提供可信证据
6.2 数据生成与误差模拟
# I. 真实数据生成(含真实因果结构)
def generate_diabetes_cohort(n_patients=5000, n_months=12, random_state=42):
"""
模拟2型糖尿病队列研究数据
真实因果结构:
1. Metformin → HbA1c_Reduction (β1 = -1.5%)
2. Baseline_HbA1c → HbA1c_Reduction (β2 = -0.3)
3. BMI → HbA1c_Reduction (β3 = +0.1,肥胖削弱效果)
4. Diet_Compliance → HbA1c_Reduction (β4 = -0.5)
5. Diabetic_Years → Baseline_HbA1c (混淆路径)
"""
np.random.seed(random_state)
# I. 外生变量(无测量误差)
age = np.round(np.random.normal(58, 10, n_patients), 1)
diabetic_years = np.round(np.random.gamma(shape=3, scale=2, size=n_patients), 1)
# 处理分配(非随机,但可忽略性成立)
# 医生根据BMI和病程开药
metformin_prob = 1/(1+np.exp(-(0.5 - 0.03*BMI + 0.1*diabetic_years)))
metformin = np.random.binomial(1, metformin_prob, n_patients)
# II. 内生抽头变量(无误差)
# 基线HbA1c(真实值,7-12%)
baseline_hba1c = 8.5 + 0.15*diabetic_years + 0.02*BMI - 0.5*metformin + np.random.normal(0, 0.4, n_patients)
baseline_hba1c = np.clip(baseline_hba1c, 7.0, 12.0)
# 真实HbA1c降低(主要结果)
true_reduction = -1.5*metformin - 0.3*(baseline_hba1c - 8.0) + 0.1*BMI - 0.5*diet_compliance + np.random.normal(0, 0.3, n_patients)
true_reduction = np.clip(true_reduction, -4.0, 1.0) # 最多降4%,最差升1%
# III. 真实数据DataFrame
true_df = pd.DataFrame({
'patient_id': np.arange(n_patients),
'age': age,
'diabetic_years': diabetic_years,
'true_bmi': BMI,
'true_diet': diet_compliance,
'true_metformin': metformin,
'true_baseline_hba1c': baseline_hba1c,
'true_reduction': true_reduction
})
return true_df
true_data = generate_diabetes_cohort()
print("真实数据生成完成!")
print(true_data[['true_bmi', 'true_diet', 'true_metformin', 'true_reduction']].describe().round(2))
# IV. 测量误差模拟(叠加观测噪声)
def add_measurement_error(true_df, error_config):
"""
按业务场景添加测量误差
error_config = {
'bmi': {'type': 'classical', 'std': 2.0}, # 经典误差,σ=2
'diet': {'type': 'classical', 'std': 0.25, 'bias': -0.2}, # 低估22%
'baseline_hba1c': {'type': 'classical', 'std': 0.5},
'metformin': {'type': 'misclassification', 'FP_rate': 0.15, 'FN_rate': 0.1}
}
"""
obs_df = true_df.copy()
# 1. BMI经典误差(衣物+测量误差)
obs_df['obs_bmi'] = true_df['true_bmi'] + np.random.normal(0, error_config['bmi']['std'], len(true_df))
obs_df['obs_bmi'] = np.maximum(15, obs_df['obs_bmi']) # 边界约束
# 2. 饮食依从性经典误差+系统偏倚(低估)
error_diet = np.random.normal(error_config['diet']['bias'], error_config['diet']['std'], len(true_df))
obs_df['obs_diet'] = true_df['true_diet'] + error_diet
obs_df['obs_diet'] = np.clip(obs_df['obs_diet'], 0, 1)
# 3. 基线HbA1c经典误差(设备+实验室)
obs_df['obs_baseline_hba1c'] = true_df['true_baseline_hba1c'] + np.random.normal(0, error_config['baseline_hba1c']['std'], len(true_df))
obs_df['obs_baseline_hba1c'] = np.clip(obs_df['obs_baseline_hba1c'], 6.5, 14.0)
# 4. 用药误分类(自报 vs 实际)
FP_mask = (true_df['true_metformin'] == 0) & (np.random.random(len(true_df)) < error_config['metformin']['FP_rate'])
FN_mask = (true_df['true_metformin'] == 1) & (np.random.random(len(true_df)) < error_config['metformin']['FN_rate'])
obs_df['obs_metformin'] = true_df['true_metformin'].copy()
obs_df.loc[FP_mask, 'obs_metformin'] = 1 # 假阳性
obs_df.loc[FN_mask, 'obs_metformin'] = 0 # 假阴性
# 5. 结果变量HbA1c降低的测量误差(随访依从性)
obs_df['obs_reduction'] = true_df['true_reduction'] + np.random.normal(0, 0.4, len(true_df))
obs_df['obs_reduction'] = np.clip(obs_df['obs_reduction'], -5.0, 2.0)
return obs_df
# 添加误差
error_config = {
'bmi': {'type': 'classical', 'std': 2.0},
'diet': {'type': 'classical', 'std': 0.25, 'bias': -0.2},
'baseline_hba1c': {'type': 'classical', 'std': 0.5},
'metformin': {'type': 'misclassification', 'FP_rate': 0.15, 'FN_rate': 0.1}
}
obs_data = add_measurement_error(true_data, error_config)
print("\n测量误差添加完成!")
print(f"BMI误差均值: {(obs_data['obs_bmi'] - obs_data['true_bmi']).mean():.2f}")
print(f"饮食误差偏倚: {(obs_data['obs_diet'] - obs_data['true_diet']).mean():.2f}")
print(f"用药误分类率: {1 - (obs_data['obs_metformin'] == obs_data['true_metformin']).mean():.1%}")
数据生成逻辑详解:
- 真实因果链:二甲双胍效果β=-1.5%,BMI正向调节(肥胖削弱效果)
- 混淆路径:病程→基线HbA1c→结果,符合真实临床逻辑
- 误差配置:基于文献报道,BMI CV=5-8%,HbA1c CV=3-5%,依从性差异35%
- 非经典误分类:用药二分类误差,FP=15%(误报),FN=10%(漏报)
6.3 测量误差偏倚量化
# V. 偏倚诊断:三种估计对比
def bias_diagnosis(true_df, obs_df):
"""
对比真实数据、含误差数据、校正方法的估计
"""
print("\n" + "="*80)
print("测量误差偏倚诊断")
print("="*80)
# I. 真实模型(黄金标准)
import statsmodels.api as sm
X_true = sm.add_constant(true_df[['true_metformin', 'true_baseline_hba1c', 'true_bmi', 'true_diet']])
y_true = true_df['true_reduction']
model_true = sm.OLS(y_true, X_true).fit()
print("\n【真实模型(无误差)】")
print(f"二甲双胍效应: {model_true.params['true_metformin']:.4f}")
print(f"标准误: {model_true.bse['true_metformin']:.4f}")
print(f"p值: {model_true.pvalues['true_metformin']:.4f}")
# II. 观测模型(含测量误差)
X_obs = sm.add_constant(obs_df[['obs_metformin', 'obs_baseline_hba1c', 'obs_bmi', 'obs_diet']])
y_obs = obs_df['obs_reduction']
model_obs = sm.OLS(y_obs, X_obs).fit()
print("\n【观测模型(含误差)】")
print(f"二甲双胍效应: {model_obs.params['obs_metformin']:.4f}")
print(f"标准误: {model_obs.bse['obs_metformin']:.4f}")
print(f"p值: {model_obs.pvalues['obs_metformin']:.4f}")
# 计算偏倚
bias = model_obs.params['obs_metformin'] - model_true.params['true_metformin']
bias_pct = bias / model_true.params['true_metformin'] * 100
print(f"\n偏倚大小: {bias:.4f} ({bias_pct:.1f}%)")
print(f"效应衰减: {model_obs.params['obs_metformin'] / model_true.params['true_metformin']:.2%}")
# III. 混淆变量偏倚量化
print("\n【混淆变量偏倚分析】")
for var in ['obs_baseline_hba1c', 'obs_bmi', 'obs_diet']:
true_coef = model_true.params[f'true_{var}']
obs_coef = model_obs.params[var]
conf_bias = obs_coef - true_coef
print(f"{var}: 真实{true_coef:.3f}, 观测{obs_coef:.3f}, 偏倚{conf_bias:.3f}")
return model_true, model_obs
# 执行诊断
true_model, obs_model = bias_diagnosis(true_data, obs_data)
# VI. 工具变量法校正(二甲双胍剂量作为IV)
def iv_correction(obs_df, true_df):
"""
IV校正:使用二甲双胍日剂量作为用药的工具变量
逻辑:
- 相关性:剂量与是否用药高度相关(F>30)
- 排他性:剂量不直接影响HbA1c降低(仅通过用药与否)
- 验证:剂量与HbA1c测量误差无关
"""
print("\n" + "="*80)
print("IV法校正测量误差")
print("="*80)
# 模拟剂量数据(用药患者剂量1500-2500mg,未用药=0)
obs_df['metformin_dose'] = 0
on_met_idx = obs_df['true_metformin'] == 1
obs_df.loc[on_met_idx, 'metformin_dose'] = np.random.randint(1500, 2500, on_met_idx.sum())
# I. 第一阶段:观测用药 ~ 剂量 + 混淆变量
X_first = sm.add_constant(obs_df[['metformin_dose', 'obs_baseline_hba1c', 'obs_bmi', 'obs_diet']])
y_first = obs_df['obs_metformin'] # 二元
# 使用Logit(因变量二元)
from statsmodels.discrete.discrete_model import Logit
model_first = Logit(y_first, X_first).fit(disp=0)
print("\n第一阶段回归(用药~剂量)")
print(f"剂量系数: {model_first.params['metformin_dose']:.6f}")
print(f"剂量p值: {model_first.pvalues['metformin_dose']:.2e}")
print(f"伪R²: {model_first.prsquared:.3f}")
# 计算F统计量(对于Logit,使用LR卡方)
lr_stat = model_first.llr
df = model_first.df_model
f_stat = (lr_stat / df) / (model_first.llf / model_first.df_resid)
print(f"等效F统计量: {f_stat:.1f}")
if f_stat > 30:
print("✓ 通过弱IV检验(F>30)")
else:
print("⚠ 弱IV警告")
# II. 第二阶段:使用拟合值
X_second = sm.add_constant(obs_df[['obs_baseline_hba1c', 'obs_bmi', 'obs_diet']])
y_second = obs_df['obs_reduction']
# 计算线性预测值(注意:Logit用probit近似线性)
X_first_linear = X_first.copy()
X_first_linear['metformin_dose'] = X_first['metformin_dose'] / 1000 # 缩放
model_first_linear = sm.OLS(obs_df['obs_metformin'], X_first_linear).fit()
metformin_hat = model_first_linear.predict(X_first_linear)
# 添加预测值
X_second['metformin_hat'] = metformin_hat
model_iv = sm.OLS(y_second, X_second).fit()
print("\n【IV校正后模型】")
print(f"二甲双胍效应: {model_iv.params['metformin_hat']:.4f}")
print(f"标准误: {model_iv.bse['metformin_hat']:.4f}")
# 对比
original_effect = obs_model.params['obs_metformin']
iv_effect = model_iv.params['metformin_hat']
true_effect = true_model.params['true_metformin']
print(f"\n效应恢复率: {(iv_effect - original_effect) / (true_effect - original_effect):.1%}")
return model_iv
# 执行IV校正
iv_model = iv_correction(obs_data, true_data)
# VII. 代理变量法校正(BMI的DEXA测量作为代理)
def surrogate_correction(obs_df):
"""
代理变量校正:使用DEXA体脂率作为BMI的代理
原理:DEXA测量误差与BMI的衣物/进食误差无关
"""
print("\n" + "="*80)
print("代理变量法校正BMI误差")
print("="*80)
# 模拟DEXA体脂率(与真实BMI相关,但误差独立)
obs_df['dexa_fat_pct'] = 25 + 0.8 * (obs_df['true_bmi'] - 22) + np.random.normal(0, 2, len(obs_df))
# 两步估计:
# 1. 用代理估计误差方差比
cov_bmi_dexa = np.cov(obs_df['obs_bmi'], obs_df['dexa_fat_pct'])[0,1]
cov_true_dexa = np.cov(obs_df['true_bmi'], obs_df['dexa_fat_pct'])[0,1]
lambda_surrogate = cov_bmi_dexa / cov_true_dexa
print(f"代理估计的衰减系数 λ: {lambda_surrogate:.3f}")
# 2. 校正BMI系数
bmi_coef_obs = obs_model.params['obs_bmi']
bmi_coef_corrected = bmi_coef_obs / lambda_surrogate
print(f"BMI系数校正: {bmi_coef_obs:.4f} → {bmi_coef_corrected:.4f}")
return bmi_coef_corrected
# 执行代理校正
bmi_corrected = surrogate_correction(obs_data)
章节总结:实例分析
Lexical error on line 5. Unrecognized text. ...] B --> B3[BMI: ±2kg/m²偏倚] B --> ----------------------^七、代码实战:完整校正Pipeline
7.1 自动化校正框架
import pymc as pm
import arviz as az
from scipy.signal import deconvolve
class MeasurementErrorCorrector:
"""
测量误差自动化校正框架
功能:
- 检测误差类型与严重程度
- 选择最优校正方法
- 量化校正前后不确定性
"""
def __init__(self, data, error_vars, validation_data=None):
"""
参数:
- data: 观测数据DataFrame
- error_vars: 字典 {var_name: {'type': 'classical'/'misclass', 'proxy': proxy_name}}
- validation_data: 验证子样本(若有)
"""
self.data = data
self.error_vars = error_vars
self.validation = validation_data
# 诊断结果存储
self.diagnostics = {}
self.corrections = {}
def diagnose_error(self, var_name):
"""
诊断单个变量的测量误差
返回:
- 误差类型
- 估计的σ_δ
- 衰减系数λ
"""
if var_name not in self.error_vars:
return None
var_info = self.error_vars[var_name]
if var_info['type'] == 'classical':
# 使用代理变量或重复测量估计误差方差
if 'proxy' in var_info and var_info['proxy'] in self.data.columns:
# 代理法估计
observed = self.data[f'obs_{var_name}']
proxy = self.data[var_info['proxy']]
# 计算信噪比
cov_op = np.cov(observed, proxy)[0,1]
var_o = observed.var()
# λ = Cov(O,P) / Cov(T,P) 近似
# 假设代理与真实变量相关性更高
estimated_lambda = min(cov_op / (proxy.var()**0.5 * observed.var()**0.5), 0.95)
self.diagnostics[var_name] = {
'type': 'classical',
'lambda_est': estimated_lambda,
'error_var_est': var_o * (1 - estimated_lambda**2)
}
elif var_info['type'] == 'misclass':
# 计算误分类率(需验证数据)
if self.validation is not None:
val_obs = self.validation[f'obs_{var_name}']
val_true = self.validation[f'true_{var_name}']
FP = ((val_obs == 1) & (val_true == 0)).mean()
FN = ((val_obs == 0) & (val_true == 1)).mean()
self.diagnostics[var_name] = {
'type': 'misclass',
'FP_rate': FP,
'FN_rate': FN,
'accuracy': (val_obs == val_true).mean()
}
return self.diagnostics[var_name]
def correct_classical_error(self, var_name, outcome, covariates, method='frost', proxy=None):
"""
校正经典测量误差
方法:
- 'frost': 代理变量法
- 'deconvolution': 反卷积法
- 'mcmc': 贝叶斯分层模型
"""
if method == 'frost':
return self._frost_correction(var_name, outcome, covariates, proxy)
elif method == 'mcmc':
return self._bayesian_correction(var_name, outcome, covariates)
else:
raise ValueError(f"未知校正方法: {method}")
def _frost_correction(self, var_name, outcome, covariates, proxy):
"""
Frost代理变量校正
实现:
β_corrected = β_obs * (σ_XW / σ_X*W)
"""
# 拟合观测模型
X_obs = sm.add_constant(self.data[[f'obs_{var_name}'] + covariates])
y = self.data[outcome]
model_obs = sm.OLS(y, X_obs).fit()
beta_obs = model_obs.params[f'obs_{var_name}']
# 计算校正因子
obs_var = f'obs_{var_name}'
if proxy and proxy in self.data.columns:
cov_xw = np.cov(self.data[obs_var], self.data[proxy])[0,1]
# 假设代理与真实变量相关性更高,用代理估计σ_XW
# 简化:ratio ≈ corr(obs, proxy) / corr(true, proxy)
# 这里用样本相关系数近似
corr_obs_proxy = np.corrcoef(self.data[obs_var], self.data[proxy])[0,1]
# 假设真实变量与代理相关系数为0.95(需验证)
corr_true_proxy = 0.95
correction_factor = corr_obs_proxy / corr_true_proxy
beta_corrected = beta_obs / correction_factor
self.corrections[var_name] = {
'method': 'frost',
'beta_obs': beta_obs,
'beta_corrected': beta_corrected,
'correction_factor': correction_factor,
'lambda_estimate': correction_factor
}
return beta_corrected, model_obs
def _bayesian_correction(self, var_name, outcome, covariates, n_samples=2000):
"""
贝叶斯分层模型校正
使用PyMC建模:
X_true ~ Normal(μ_X, σ_X)
X_obs ~ Normal(X_true, σ_δ)
Y ~ Normal(β0 + β1*X_true + β2*Z, σ_y)
"""
print(f"\n运行贝叶斯校正(需{ n_samples }次采样)...")
# 准备数据
y_data = self.data[outcome].values
x_obs_data = self.data[f'obs_{var_name}'].values
if covariates:
Z_data = self.data[covariates].values
else:
Z_data = np.zeros((len(self.data), 1))
with pm.Model() as model:
# 先验
beta0 = pm.Normal('beta0', mu=0, sigma=10)
beta1 = pm.Normal('beta1', mu=0, sigma=10) # 真实X的系数
beta_z = pm.Normal('beta_z', mu=0, sigma=10, shape=Z_data.shape[1])
sigma_y = pm.Exponential('sigma_y', lam=1)
sigma_x = pm.Exponential('sigma_x', lam=1)
sigma_delta = pm.Exponential('sigma_delta', lam=1)
# 真实X的先验
mu_x = pm.Normal('mu_x', mu=x_obs_data.mean(), sigma=5)
X_true = pm.Normal('X_true', mu=mu_x, sigma=sigma_x, shape=len(self.data))
# 测量层
X_obs = pm.Normal('X_obs', mu=X_true, sigma=sigma_delta, observed=x_obs_data)
# 结果层
mu_y = beta0 + beta1 * X_true + pm.math.dot(Z_data, beta_z)
Y = pm.Normal('Y', mu=mu_y, sigma=sigma_y, observed=y_data)
# 采样
trace = pm.sample(n_samples, chains=4, cores=2, return_inferencedata=True,
progressbar=True, progressbar_theme="notebook")
# 提取后验
beta1_posterior = trace.posterior['beta1'].values.flatten()
beta1_mean = beta1_posterior.mean()
beta1_ci = np.percentile(beta1_posterior, [2.5, 97.5])
self.corrections[var_name] = {
'method': 'bayesian',
'beta_mean': beta1_mean,
'beta_ci': beta1_ci,
'sigma_delta_est': trace.posterior['sigma_delta'].mean().values
}
return trace
# 测试校正框架
corrector = MeasurementErrorCorrector(
obs_data,
error_vars={
'metformin': {'type': 'misclass'},
'bmi': {'type': 'classical', 'proxy': 'dexa_fat_pct'},
'diet': {'type': 'classical'}
},
validation_data=true_data.sample(500)
)
# 诊断metformin误分类
met_diag = corrector.diagnose_error('metformin')
print("\nMetformin误分类诊断:")
print(f"准确率: {met_diag['accuracy']:.1%}")
print(f"假阳性率: {met_diag['FP_rate']:.1%}")
print(f"假阴性率: {met_diag['FN_rate']:.1%}")
# 校正BMI效应
bmi_corrected, model_obs = corrector.correct_classical_error(
'bmi',
outcome='obs_reduction',
covariates=['obs_metformin', 'obs_baseline_hba1c', 'obs_diet'],
method='frost',
proxy='dexa_fat_pct'
)
print(f"\nBMI效应校正结果: {model_obs.params['obs_bmi']:.4f} → {bmi_corrected:.4f}")
章节总结:自动化Pipeline
八、评估与验证
8.1 校正效果评估指标
| 评估维度 | 指标 | 计算公式 | 通过标准 | 失败后果 |
|---|---|---|---|---|
| 效度 | 效应恢复率 | >80% | 校正不足 | |
| 精度 | 后验CI宽度 | <2×SE_true | 过度校正 | |
| 稳健性 | 交叉验证稳定性 | <0.5×SE | 过拟合 | |
| 效度 | 工具变量F统计 | >30 | 弱工具 | |
| 拟合优度 | 残差分析 | ≈1 | 模型误设 |
8.2 交叉验证实现
# 交叉验证评估校正稳定性
from sklearn.model_selection import KFold
def cross_validation_correction(corrector, var_name, outcome, covariates, n_folds=5):
"""
K折交叉验证校正稳定性
"""
kf = KFold(n_splits=n_folds, shuffle=True, random_state=42)
corrected_estimates = []
for fold, (train_idx, val_idx) in enumerate(kf.split(corrector.data)):
# 训练集校正
train_data = corrector.data.iloc[train_idx]
val_data = corrector.data.iloc[val_idx]
# 创建子校正器
sub_corrector = MeasurementErrorCorrector(
train_data,
corrector.error_vars,
validation_data=val_data
)
# 校正
beta_corr, _ = sub_corrector.correct_classical_error(
var_name, outcome, covariates, method='frost'
)
corrected_estimates.append(beta_corr)
print(f"Fold {fold+1}: β_corr = {beta_corr:.4f}")
# 稳定性评估
stability = np.std(corrected_estimates)
mean_est = np.mean(corrected_estimates)
print(f"\n校正稳定性: std = {stability:.4f}")
print(f"均值: {mean_est:.4f}")
if stability < 0.05: # 阈值可根据业务设定
print("✓ 通过稳定性检验")
else:
print("⚠ 校正不稳定,需谨慎")
return corrected_estimates, stability
# 对BMI校正做交叉验证
bmi_cv_est, bmi_stability = cross_validation_correction(
corrector, 'bmi', 'obs_reduction',
['obs_metformin', 'obs_baseline_hba1c', 'obs_diet'],
n_folds=3
)
章节总结:评估验证
- 点赞
- 收藏
- 关注作者
评论(0)