工具变量法的选择标准与验证方法
I. 引言:内生性问题的挑战与工具变量的作用
内生性问题的根源
在社会科学、经济学和流行病学等领域的研究中,我们经常面临内生性问题。内生性是指解释变量与误差项相关,导致普通最小二乘法(OLS)估计量不一致。内生性的主要来源包括:
- 遗漏变量偏差:未能控制所有影响结果和解释变量的因素
- 测量误差:解释变量的测量不准确
- ** simultaneity**(同时性):解释变量和因变量相互影响
- 选择偏差:个体自我选择进入处理组或对照组
经典案例:在研究教育对收入的影响时,能力是一个典型的遗漏变量。高能力的人可能选择接受更多教育,同时高能力也直接带来更高收入。如果无法观测和测量能力,OLS估计就会产生向上偏误。
工具变量法的基本直觉
工具变量法通过引入一个外部变量(工具变量)来解决内生性问题。这个工具变量需要满足:
- 与内生解释变量相关
- 仅通过内生解释变量影响结果变量
- 与误差项不相关
工具变量法的核心思想是:利用工具变量中"外生"的变异来估计内生解释变量对结果的影响,从而得到一致的估计量。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
from statsmodels.sandbox.regression.gmm import IV2SLS
from linearmodels.iv import IV2SLS as IV2SLS_lm
import scipy.stats as stats
from scipy import linalg
import warnings
warnings.filterwarnings('ignore')
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
print("工具变量法的选择标准与验证方法")
print("=" * 50)
II. 工具变量的三大核心条件
工具变量的有效性依赖于三个核心条件,这些条件既是选择工具变量的标准,也是验证工具变量有效性的基础。
条件1:相关性
工具变量Z必须与内生解释变量D相关。这一条件相对容易检验,通常通过第一阶段回归来验证。
数学表达:Cov(Z, D) ≠ 0
条件2:排他性约束
工具变量Z只能通过内生解释变量D影响结果变量Y,不能有其他直接影响路径。这是工具变量法中最关键也最难验证的条件。
数学表达:Cov(Z, ε) = 0,其中ε是结构方程中的误差项
条件3:外生性
工具变量Z必须与模型中的其他解释变量外生,即与误差项不相关。这一条件与排他性约束密切相关。
# 模拟工具变量的理想情况
np.random.seed(2024)
n = 10000
# 模拟数据:教育对收入的影响,能力为遗漏变量
ability = np.random.normal(0, 1, n) # 能力(不可观测)
family_background = np.random.normal(0, 1, n) # 家庭背景
# 工具变量:学校到家的距离(满足工具变量条件)
distance = np.random.exponential(2, n) # 距离(公里)
# 教育决策(内生变量)
education = (10 +
0.5 * ability +
0.3 * family_background -
0.4 * distance +
np.random.normal(0, 1, n))
# 收入生成过程
income = (20000 +
1000 * education + # 真实教育回报
1500 * ability + # 能力直接影响收入
800 * family_background +
np.random.normal(0, 3000, n))
# 创建数据框
iv_data = pd.DataFrame({
'ability': ability,
'family_background': family_background,
'distance': distance,
'education': education,
'income': income
})
print("工具变量模拟数据描述:")
print(f"样本量: {n}")
print(f"教育年限均值: {education.mean():.2f}")
print(f"收入均值: {income.mean():.2f}")
print(f"距离均值: {distance.mean():.2f} 公里")
# 可视化工具变量关系
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# 工具变量与内生变量
axes[0].scatter(iv_data['distance'], iv_data['education'], alpha=0.5)
axes[0].set_xlabel('学校距离 (公里)')
axes[0].set_ylabel('教育年限')
axes[0].set_title('工具变量与内生变量关系\n(条件1: 相关性)')
z_education_corr = np.corrcoef(iv_data['distance'], iv_data['education'])[0,1]
axes[0].text(0.05, 0.95, f'相关系数: {z_education_corr:.3f}',
transform=axes[0].transAxes, fontsize=12, verticalalignment='top',
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
# 工具变量与结果变量
axes[1].scatter(iv_data['distance'], iv_data['income'], alpha=0.5)
axes[1].set_xlabel('学校距离 (公里)')
axes[1].set_ylabel('收入')
axes[1].set_title('工具变量与结果变量关系\n(条件2: 排他性约束)')
z_income_corr = np.corrcoef(iv_data['distance'], iv_data['income'])[0,1]
axes[1].text(0.05, 0.95, f'相关系数: {z_income_corr:.3f}',
transform=axes[1].transAxes, fontsize=12, verticalalignment='top',
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
# 内生变量与结果变量
axes[2].scatter(iv_data['education'], iv_data['income'], alpha=0.5)
axes[2].set_xlabel('教育年限')
axes[2].set_ylabel('收入')
axes[2].set_title('内生变量与结果变量关系\n(存在遗漏变量偏差)')
education_income_corr = np.corrcoef(iv_data['education'], iv_data['income'])[0,1]
axes[2].text(0.05, 0.95, f'相关系数: {education_income_corr:.3f}',
transform=axes[2].transAxes, fontsize=12, verticalalignment='top',
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
plt.tight_layout()
plt.show()
工具变量条件的实践含义
条件 | 理论要求 | 实践挑战 | 检验方法 |
---|---|---|---|
相关性 | Cov(Z, D) ≠ 0 | 弱工具变量问题 | 第一阶段F统计量 |
排他性约束 | Cov(Z, ε) = 0 | 无法直接检验 | 过度识别检验、理论论证 |
外生性 | Z与误差项独立 | 工具变量可能通过其他路径影响Y | 敏感性分析、 placebo检验 |
III. 工具变量的选择标准
选择合适的工具变量是研究成功的关键。以下是实践中常用的工具变量选择标准。
理论合理性
工具变量必须有坚实的理论基础。研究者应该能够清晰地阐述为什么这个变量满足排他性约束和外生性条件。
好的实践:详细说明工具变量影响内生变量的机制,并论证它为什么不会通过其他渠道影响结果变量。
统计相关性
工具变量必须与内生变量有足够强的相关性,以避免弱工具变量问题。
经验法则:第一阶段F统计量应大于10(Staiger & Stock, 1997)
外生性保证
工具变量应该是一个相对外生的冲击或变异,不受个体选择行为的影响。
常见类型:
- 自然实验(政策变化、自然灾害)
- 地理特征(距离、气候)
- 制度规则(资格门槛、抽签结果)
- 历史变量(历史数据、滞后变量)
# 工具变量选择标准的量化评估
def assess_iv_criteria(data, endogenous_var, instrument_var, outcome_var, controls=None):
"""
评估工具变量的选择标准
"""
assessment = {}
# 1. 相关性评估
if controls is None:
# 简单相关性
assessment['simple_correlation'] = np.corrcoef(
data[instrument_var], data[endogenous_var]
)[0,1]
# 第一阶段回归
X_first = sm.add_constant(data[instrument_var])
first_stage = sm.OLS(data[endogenous_var], X_first).fit()
assessment['first_stage_r2'] = first_stage.rsquared
assessment['first_stage_f'] = first_stage.fvalue
assessment['first_stage_coef'] = first_stage.params[1]
assessment['first_stage_p'] = first_stage.pvalues[1]
else:
# 控制其他变量后的相关性
X_first = sm.add_constant(data[[instrument_var] + controls])
first_stage = sm.OLS(data[endogenous_var], X_first).fit()
assessment['first_stage_r2'] = first_stage.rsquared
assessment['first_stage_f'] = first_stage.fvalue
assessment['first_stage_coef'] = first_stage.params[1]
assessment['first_stage_p'] = first_stage.pvalues[1]
# 2. 排他性约束的间接证据
# 工具变量与可观测特征的相关性
if controls is not None:
balance_results = {}
for control in controls:
corr = np.corrcoef(data[instrument_var], data[control])[0,1]
balance_results[control] = corr
assessment['balance_correlations'] = balance_results
# 3. 工具变量与结果的简单关系
assessment['iv_outcome_correlation'] = np.corrcoef(
data[instrument_var], data[outcome_var]
)[0,1]
return assessment
# 评估我们的工具变量
controls = ['family_background'] # 可观测的控制变量
iv_assessment = assess_iv_criteria(iv_data, 'education', 'distance', 'income', controls)
print("工具变量选择标准评估:")
print(f"1. 相关性条件:")
print(f" - 简单相关系数: {iv_assessment['simple_correlation']:.4f}")
print(f" - 第一阶段R²: {iv_assessment['first_stage_r2']:.4f}")
print(f" - 第一阶段F统计量: {iv_assessment['first_stage_f']:.4f}")
print(f" - 第一阶段系数: {iv_assessment['first_stage_coef']:.4f}")
print(f" - 第一阶段p值: {iv_assessment['first_stage_p']:.4f}")
print(f"\n2. 外生性间接证据 (与可观测变量的相关性):")
for var, corr in iv_assessment['balance_correlations'].items():
print(f" - {var}: {corr:.4f}")
print(f"\n3. 工具变量与结果的相关性: {iv_assessment['iv_outcome_correlation']:.4f}")
# 可视化评估结果
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 第一阶段关系
axes[0,0].scatter(iv_data['distance'], iv_data['education'], alpha=0.5)
z = np.polyfit(iv_data['distance'], iv_data['education'], 1)
p = np.poly1d(z)
axes[0,0].plot(iv_data['distance'], p(iv_data['distance']), "r--", alpha=0.8)
axes[0,0].set_xlabel('学校距离 (公里)')
axes[0,0].set_ylabel('教育年限')
axes[0,0].set_title('第一阶段: 工具变量与内生变量')
axes[0,0].text(0.05, 0.95, f'斜率: {z[0]:.3f}\nR²: {iv_assessment['first_stage_r2']:.3f}',
transform=axes[0,0].transAxes, fontsize=10, verticalalignment='top',
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
# 工具变量与结果变量
axes[0,1].scatter(iv_data['distance'], iv_data['income'], alpha=0.5)
z = np.polyfit(iv_data['distance'], iv_data['income'], 1)
p = np.poly1d(z)
axes[0,1].plot(iv_data['distance'], p(iv_data['distance']), "r--", alpha=0.8)
axes[0,1].set_xlabel('学校距离 (公里)')
axes[0,1].set_ylabel('收入')
axes[0,1].set_title('工具变量与结果变量')
axes[0,1].text(0.05, 0.95, f'相关系数: {iv_assessment['iv_outcome_correlation']:.3f}',
transform=axes[0,1].transAxes, fontsize=10, verticalalignment='top',
bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))
# 平衡性检验
control_vars = list(iv_assessment['balance_correlations'].keys())
corr_values = [iv_assessment['balance_correlations'][var] for var in control_vars]
axes[1,0].barh(control_vars, np.abs(corr_values), color='skyblue', alpha=0.7)
axes[1,0].axvline(x=0.1, color='red', linestyle='--', alpha=0.7, label='阈值 (0.1)')
axes[1,0].set_xlabel('绝对相关系数')
axes[1,0].set_title('工具变量与可观测变量的相关性\n(平衡性检验)')
axes[1,0].legend()
# 第一阶段F统计量评估
f_stat = iv_assessment['first_stage_f']
criteria = [1, 5, 10, 20, 50]
colors = ['red' if x < 10 else 'green' for x in criteria]
axes[1,1].barh([str(x) for x in criteria], criteria, color=colors, alpha=0.6)
axes[1,1].axvline(x=f_stat, color='blue', linestyle='-', linewidth=2, label=f'我们的F值: {f_stat:.1f}')
axes[1,1].set_xlabel('F统计量')
axes[1,1].set_title('弱工具变量检验\n(F统计量 > 10)')
axes[1,1].legend()
plt.tight_layout()
plt.show()
工具变量选择的经验法则
基于实证研究的经验,我们总结了以下工具变量选择的标准:
标准类别 | 具体指标 | 阈值要求 | 解释 |
---|---|---|---|
统计相关性 | 第一阶段F统计量 | > 10 | 避免弱工具变量 |
统计相关性 | 偏R² | > 0.1 | 工具变量解释力 |
统计相关性 | 简单相关系数 | > 0.1 | 直观相关性 |
外生性 | 平衡性检验 | p > 0.1 | 与可观测变量平衡 |
理论合理性 | 排他性约束 | 理论成立 | 基于领域知识 |
IV. 工具变量的验证方法
工具变量的验证是一个多层次的过程,需要结合统计检验、理论论证和敏感性分析。
第一阶段回归与弱工具变量检验
第一阶段回归不仅用于估计,也是检验工具变量相关性的主要方法。
# 详细的第一阶段分析和弱工具变量检验
def detailed_first_stage(data, endogenous_var, instruments, controls=None, outcome_var=None):
"""
详细的第一阶段分析和弱工具变量检验
"""
results = {}
# 准备设计矩阵
if controls is None:
X_first = sm.add_constant(data[instruments])
else:
X_first = sm.add_constant(data[instruments + controls])
# 第一阶段回归
first_stage = sm.OLS(data[endogenous_var], X_first).fit()
results['first_stage'] = first_stage
# 弱工具变量检验
n = len(data)
k = len(instruments) # 工具变量数量
if k == 1:
# 单个工具变量的F统计量
f_stat = first_stage.fvalue
results['f_statistic'] = f_stat
results['weak_iv_threshold'] = 10
results['is_weak'] = f_stat < 10
else:
# 多个工具变量的Sanderson-Windmeijer F统计量
from statsmodels.regression.linear_model import OLS
from statsmodels.tools.tools import add_constant
# 简化型回归
reduced_form = OLS(data[endogenous_var], add_constant(data[controls] if controls else np.ones(n))).fit()
resid_reduced = reduced_form.resid
# 工具变量对残差的回归
iv_on_reduced = OLS(resid_reduced, add_constant(data[instruments])).fit()
r2_iv = iv_on_reduced.rsquared
# F统计量
f_stat = (r2_iv / k) / ((1 - r2_iv) / (n - k - 1))
results['f_statistic'] = f_stat
results['weak_iv_threshold'] = 10
results['is_weak'] = f_stat < 10
# 偏R²计算
if controls is not None:
# 只有控制变量的模型
X_controls = sm.add_constant(data[controls])
model_controls = sm.OLS(data[endogenous_var], X_controls).fit()
r2_controls = model_controls.rsquared
# 完整模型
r2_full = first_stage.rsquared
# 偏R²
partial_r2 = (r2_full - r2_controls) / (1 - r2_controls)
results['partial_r2'] = partial_r2
else:
results['partial_r2'] = first_stage.rsquared
# 工具变量相关性矩阵
if len(instruments) > 1:
iv_corr_matrix = data[instruments].corr()
results['iv_correlation_matrix'] = iv_corr_matrix
return results
# 执行详细的第一阶段分析
first_stage_results = detailed_first_stage(
iv_data, 'education', ['distance'], ['family_background']
)
print("第一阶段回归详细结果:")
print(first_stage_results['first_stage'].summary())
print(f"\n弱工具变量检验:")
print(f"F统计量: {first_stage_results['f_statistic']:.4f}")
print(f"阈值: {first_stage_results['weak_iv_threshold']}")
print(f"是否弱工具变量: {'是' if first_stage_results['is_weak'] else '否'}")
print(f"偏R²: {first_stage_results['partial_r2']:.4f}")
# 对比弱工具变量情况
# 模拟弱工具变量场景
np.random.seed(123)
weak_iv = np.random.normal(0, 1, n) # 弱工具变量
# 弱工具变量与教育的相关性较弱
education_weak = education + 0.1 * weak_iv # 弱相关
iv_data_weak = iv_data.copy()
iv_data_weak['weak_iv'] = weak_iv
iv_data_weak['education_weak'] = education_weak
# 弱工具变量的第一阶段分析
weak_first_stage = detailed_first_stage(
iv_data_weak, 'education_weak', ['weak_iv'], ['family_background']
)
print(f"\n弱工具变量对比:")
print(f"弱工具变量F统计量: {weak_first_stage['f_statistic']:.4f}")
print(f"弱工具变量偏R²: {weak_first_stage['partial_r2']:.4f}")
# 可视化强弱工具变量对比
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 强工具变量
axes[0].scatter(iv_data['distance'], iv_data['education'], alpha=0.5)
z_strong = np.polyfit(iv_data['distance'], iv_data['education'], 1)
p_strong = np.poly1d(z_strong)
axes[0].plot(iv_data['distance'], p_strong(iv_data['distance']), "r--", linewidth=2)
axes[0].set_xlabel('学校距离 (强工具变量)')
axes[0].set_ylabel('教育年限')
axes[0].set_title(f'强工具变量\nF统计量: {first_stage_results['f_statistic']:.1f}')
axes[0].grid(True, alpha=0.3)
# 弱工具变量
axes[1].scatter(iv_data_weak['weak_iv'], iv_data_weak['education_weak'], alpha=0.5)
z_weak = np.polyfit(iv_data_weak['weak_iv'], iv_data_weak['education_weak'], 1)
p_weak = np.poly1d(z_weak)
axes[1].plot(iv_data_weak['weak_iv'], p_weak(iv_data_weak['weak_iv']), "r--", linewidth=2)
axes[1].set_xlabel('弱工具变量')
axes[1].set_ylabel('教育年限')
axes[1].set_title(f'弱工具变量\nF统计量: {weak_first_stage['f_statistic']:.1f}')
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
过度识别检验
当工具变量数量多于内生变量数量时,我们可以进行过度识别检验,间接检验排他性约束。
# 过度识别检验实现
def overidentification_test(data, endogenous_var, instruments, outcome_var, controls=None):
"""
Sargan-Hansen过度识别检验
"""
from linearmodels.iv import IV2SLS
# 准备公式
if controls is None:
formula = f"{outcome_var} ~ 1 + [{endogenous_var} ~ {''.join(instruments)}]"
else:
controls_str = ' + '.join(controls)
formula = f"{outcome_var} ~ 1 + {controls_str} + [{endogenous_var} ~ {''.join(instruments)}]"
# IV估计
iv_model = IV2SLS.from_formula(formula, data=data)
iv_results = iv_model.fit(cov_type='unadjusted')
# 过度识别检验
try:
overid_test = iv_results.overid
return {
'test_statistic': overid_test.stat,
'p_value': overid_test.pval,
'df': overid_test.df,
'test_name': overid_test.name
}
except:
# 如果工具变量数量等于内生变量数量,无法进行过度识别检验
return {
'test_statistic': None,
'p_value': None,
'df': None,
'test_name': '无法进行过度识别检验 (恰好识别)'
}
# 模拟多个工具变量的情况
np.random.seed(456)
# 第二个工具变量:地区教育政策
education_policy = np.random.normal(0, 1, n)
# 教育决策受两个工具变量影响
education_multi_iv = (education +
0.3 * education_policy +
np.random.normal(0, 0.5, n))
iv_data_multi = iv_data.copy()
iv_data_multi['education_policy'] = education_policy
iv_data_multi['education_multi'] = education_multi_iv
# 进行过度识别检验
overid_results = overidentification_test(
iv_data_multi, 'education_multi', ['distance', 'education_policy'],
'income', ['family_background']
)
print("过度识别检验结果:")
print(f"检验统计量: {overid_results['test_statistic']}")
print(f"p值: {overid_results['p_value']}")
print(f"自由度: {overid_results['df']}")
print(f"检验名称: {overid_results['test_name']}")
# 对比恰好识别和过度识别情况
# 恰好识别情况
just_identified = overidentification_test(
iv_data, 'education', ['distance'], 'income', ['family_background']
)
print(f"\n恰好识别情况:")
print(f"检验结果: {just_identified['test_name']}")
平衡性检验与 placebo 检验
平衡性检验和placebo检验为工具变量的外生性提供间接证据。
# 平衡性检验与placebo检验
def comprehensive_balance_test(data, instrument_var, covariates, treatment_var=None):
"""
综合平衡性检验
"""
balance_results = []
for covariate in covariates:
# 工具变量与协变量的关系
corr_coef = np.corrcoef(data[instrument_var], data[covariate])[0,1]
# 回归检验(控制其他变量)
other_covariates = [c for c in covariates if c != covariate]
if other_covariates:
X_balance = sm.add_constant(data[[instrument_var] + other_covariates])
balance_model = sm.OLS(data[covariate], X_balance).fit()
iv_coef = balance_model.params[1]
iv_pvalue = balance_model.pvalues[1]
else:
X_balance = sm.add_constant(data[instrument_var])
balance_model = sm.OLS(data[covariate], X_balance).fit()
iv_coef = balance_model.params[1]
iv_pvalue = balance_model.pvalues[1]
balance_results.append({
'covariate': covariate,
'correlation': corr_coef,
'regression_coef': iv_coef,
'regression_pvalue': iv_pvalue,
'is_balanced': iv_pvalue > 0.1 # 平衡性阈值
})
return pd.DataFrame(balance_results)
# placebo检验:工具变量与伪结果的关系
def placebo_test(data, instrument_var, placebo_outcomes, controls=None):
"""
Placebo检验:工具变量不应与伪结果相关
"""
placebo_results = []
for outcome in placebo_outcomes:
if outcome in data.columns:
if controls is None:
X_placebo = sm.add_constant(data[instrument_var])
else:
X_placebo = sm.add_constant(data[[instrument_var] + controls])
placebo_model = sm.OLS(data[outcome], X_placebo).fit()
placebo_results.append({
'placebo_outcome': outcome,
'iv_coefficient': placebo_model.params[1],
'p_value': placebo_model.pvalues[1],
'is_insignificant': placebo_model.pvalues[1] > 0.1
})
return pd.DataFrame(placebo_results)
# 执行平衡性检验
covariates_for_balance = ['family_background', 'ability'] # 可观测的协变量
balance_test_results = comprehensive_balance_test(iv_data, 'distance', covariates_for_balance)
print("平衡性检验结果:")
print(balance_test_results.round(4))
# 执行placebo检验
# 生成伪结果变量
np.random.seed(789)
iv_data['placebo_outcome1'] = np.random.normal(0, 1, n) # 随机变量
iv_data['placebo_outcome2'] = 0.5 * iv_data['family_background'] + np.random.normal(0, 1, n) # 与家庭背景相关
placebo_outcomes = ['placebo_outcome1', 'placebo_outcome2']
placebo_test_results = placebo_test(iv_data, 'distance', placebo_outcomes, ['family_background'])
print("\nPlacebo检验结果:")
print(placebo_test_results.round(4))
# 可视化验证结果
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 平衡性检验 - 相关系数
balance_corrs = balance_test_results['correlation'].values
covariate_names = balance_test_results['covariate'].values
colors = ['green' if p > 0.1 else 'red' for p in balance_test_results['regression_pvalue']]
axes[0,0].barh(range(len(covariate_names)), np.abs(balance_corrs), color=colors, alpha=0.7)
axes[0,0].set_yticks(range(len(covariate_names)))
axes[0,0].set_yticklabels(covariate_names)
axes[0,0].axvline(x=0.1, color='red', linestyle='--', alpha=0.7, label='平衡阈值')
axes[0,0].set_xlabel('绝对相关系数')
axes[0,0].set_title('工具变量与协变量的相关性\n(平衡性检验)')
axes[0,0].legend()
# 平衡性检验 - p值
p_values = balance_test_results['regression_pvalue'].values
axes[0,1].scatter(range(len(covariate_names)), p_values, s=100, color=colors, alpha=0.7)
axes[0,1].axhline(y=0.1, color='red', linestyle='--', alpha=0.7, label='显著性阈值 (0.1)')
axes[0,1].axhline(y=0.05, color='orange', linestyle='--', alpha=0.7, label='显著性阈值 (0.05)')
axes[0,1].set_xticks(range(len(covariate_names)))
axes[0,1].set_xticklabels(covariate_names, rotation=45)
axes[0,1].set_ylabel('p值')
axes[0,1].set_yscale('log')
axes[0,1].set_title('平衡性检验p值')
axes[0,1].legend()
axes[0,1].grid(True, alpha=0.3)
# Placebo检验结果
placebo_coefs = placebo_test_results['iv_coefficient'].values
placebo_pvals = placebo_test_results['p_value'].values
placebo_names = placebo_test_results['placebo_outcome'].values
placebo_colors = ['green' if p > 0.1 else 'red' for p in placebo_pvals]
axes[1,0].barh(range(len(placebo_names)), placebo_coefs, color=placebo_colors, alpha=0.7)
axes[1,0].axvline(x=0, color='black', linestyle='-', alpha=0.5)
axes[1,0].set_yticks(range(len(placebo_names)))
axes[1,0].set_yticklabels(placebo_names)
axes[1,0].set_xlabel('工具变量系数')
axes[1,0].set_title('Placebo检验: 工具变量与伪结果')
# 过度识别检验可视化
if overid_results['test_statistic'] is not None:
x = np.linspace(0, 20, 100)
y = stats.chi2.pdf(x, df=overid_results['df'])
axes[1,1].plot(x, y, 'b-', alpha=0.7, label=f'χ²分布 (df={overid_results['df']})')
axes[1,1].axvline(x=overid_results['test_statistic'], color='red', linestyle='--',
label=f'检验统计量: {overid_results['test_statistic']:.3f}')
# 显著性区域
critical_value = stats.chi2.ppf(0.95, overid_results['df'])
x_fill = np.linspace(critical_value, 20, 50)
y_fill = stats.chi2.pdf(x_fill, df=overid_results['df'])
axes[1,1].fill_between(x_fill, y_fill, alpha=0.3, color='red', label='拒绝域 (α=0.05)')
axes[1,1].set_xlabel('检验统计量')
axes[1,1].set_ylabel('密度')
axes[1,1].set_title('过度识别检验')
axes[1,1].legend()
axes[1,1].grid(True, alpha=0.3)
else:
axes[1,1].text(0.5, 0.5, '恰好识别情况\n无法进行过度识别检验',
horizontalalignment='center', verticalalignment='center',
transform=axes[1,1].transAxes, fontsize=12)
axes[1,1].set_title('过度识别检验')
plt.tight_layout()
plt.show()
Lexical error on line 9. Unrecognized text.
...统计量] B --> B2[偏R²] B --> B3[系数显著
----------------------^
V. 实例分析:教育回报的IV估计
现在我们将应用前面讨论的方法,完整分析教育对收入的影响,比较OLS和IV估计结果。
数据准备与描述性统计
# 完整的教育回报分析
def comprehensive_education_analysis(data, endogenous_var, instruments, outcome_var, controls):
"""
综合的教育回报分析:比较OLS和IV估计
"""
results = {}
# 1. OLS估计(有偏)
if controls:
X_ols = sm.add_constant(data[[endogenous_var] + controls])
else:
X_ols = sm.add_constant(data[endogenous_var])
ols_model = sm.OLS(data[outcome_var], X_ols).fit()
results['ols'] = {
'coefficient': ols_model.params[endogenous_var],
'se': ols_model.bse[endogenous_var],
'p_value': ols_model.pvalues[endogenous_var],
'ci_lower': ols_model.conf_int().loc[endogenous_var, 0],
'ci_upper': ols_model.conf_int().loc[endogenous_var, 1]
}
# 2. 第一阶段分析
first_stage_results = detailed_first_stage(data, endogenous_var, instruments, controls)
results['first_stage'] = first_stage_results
# 3. 简化型回归
if controls:
X_reduced = sm.add_constant(data[instruments + controls])
else:
X_reduced = sm.add_constant(data[instruments])
reduced_form = sm.OLS(data[outcome_var], X_reduced).fit()
results['reduced_form'] = {
'coefficients': reduced_form.params[instruments],
'p_values': reduced_form.pvalues[instruments]
}
# 4. IV估计(2SLS)
from linearmodels.iv import IV2SLS
if controls:
formula = f"{outcome_var} ~ 1 + {' + '.join(controls)} + [{endogenous_var} ~ {' + '.join(instruments)}]"
else:
formula = f"{outcome_var} ~ 1 + [{endogenous_var} ~ {' + '.join(instruments)}]"
iv_model = IV2SLS.from_formula(formula, data=data)
iv_results = iv_model.fit(cov_type='robust')
results['iv'] = {
'coefficient': iv_results.params[endogenous_var],
'se': iv_results.std_errors[endogenous_var],
'p_value': iv_results.pvalues[endogenous_var],
'ci_lower': iv_results.conf_int().loc[endogenous_var, 'lower'],
'ci_upper': iv_results.conf_int().loc[endogenous_var, 'upper'],
'f_statistic': first_stage_results['f_statistic']
}
# 5. 过度识别检验(如果工具变量多于1个)
if len(instruments) > 1:
overid_results = overidentification_test(data, endogenous_var, instruments, outcome_var, controls)
results['overid_test'] = overid_results
# 6. 平衡性检验
balance_results = comprehensive_balance_test(data, instruments[0], controls)
results['balance_test'] = balance_results
return results
# 执行完整分析
analysis_results = comprehensive_education_analysis(
iv_data, 'education', ['distance'], 'income', ['family_background']
)
print("教育回报分析结果:")
print("=" * 50)
print("\n1. OLS估计结果:")
ols = analysis_results['ols']
print(f"教育回报系数: {ols['coefficient']:.2f}")
print(f"标准误: {ols['se']:.2f}")
print(f"95%置信区间: [{ols['ci_lower']:.2f}, {ols['ci_upper']:.2f}]")
print(f"p值: {ols['p_value']:.4f}")
print("\n2. IV估计结果:")
iv = analysis_results['iv']
print(f"教育回报系数: {iv['coefficient']:.2f}")
print(f"标准误: {iv['se']:.2f}")
print(f"95%置信区间: [{iv['ci_lower']:.2f}, {iv['ci_upper']:.2f}]")
print(f"p值: {iv['p_value']:.4f}")
print(f"第一阶段F统计量: {iv['f_statistic']:.2f}")
print("\n3. 第一阶段分析:")
fs = analysis_results['first_stage']
print(f"工具变量系数: {fs['first_stage'].params['distance']:.4f}")
print(f"工具变量p值: {fs['first_stage'].pvalues['distance']:.4f}")
print(f"偏R²: {fs['partial_r2']:.4f}")
print("\n4. 简化型回归:")
rf = analysis_results['reduced_form']
print(f"工具变量对收入的系数: {rf['coefficients']['distance']:.2f}")
print(f"工具变量对收入的p值: {rf['p_values']['distance']:.4f}")
# 计算IV估计的隐含比例
implied_ratio = (rf['coefficients']['distance'] /
analysis_results['first_stage']['first_stage'].params['distance'])
print(f"\n简化型系数 / 第一阶段系数: {implied_ratio:.2f}")
print(f"IV估计值: {iv['coefficient']:.2f}")
# 可视化比较结果
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# OLS vs IV比较
methods = ['OLS', 'IV']
coefficients = [ols['coefficient'], iv['coefficient']]
ci_lower = [ols['ci_lower'], iv['ci_lower']]
ci_upper = [ols['ci_upper'], iv['ci_upper']]
colors = ['red', 'blue']
axes[0,0].bar(methods, coefficients, color=colors, alpha=0.7, yerr=[
[coefficients[i] - ci_lower[i] for i in range(2)],
[ci_upper[i] - coefficients[i] for i in range(2)]
], capsize=5)
axes[0,0].axhline(y=1000, color='green', linestyle='--', linewidth=2,
label='真实效应: 1000')
axes[0,0].set_ylabel('教育回报估计 (元)')
axes[0,0].set_title('OLS vs IV估计比较')
axes[0,0].legend()
axes[0,0].grid(True, alpha=0.3)
# 第一阶段关系
axes[0,1].scatter(iv_data['distance'], iv_data['education'], alpha=0.3)
z = np.polyfit(iv_data['distance'], iv_data['education'], 1)
p = np.poly1d(z)
axes[0,1].plot(iv_data['distance'], p(iv_data['distance']), "r-", linewidth=2)
axes[0,1].set_xlabel('学校距离 (工具变量)')
axes[0,1].set_ylabel('教育年限')
axes[0,1].set_title(f'第一阶段关系\n斜率: {z[0]:.3f}, F统计量: {fs['f_statistic']:.1f}')
axes[0,1].grid(True, alpha=0.3)
# 简化型关系
axes[1,0].scatter(iv_data['distance'], iv_data['income'], alpha=0.3)
z_red = np.polyfit(iv_data['distance'], iv_data['income'], 1)
p_red = np.poly1d(z_red)
axes[1,0].plot(iv_data['distance'], p_red(iv_data['distance']), "r-", linewidth=2)
axes[1,0].set_xlabel('学校距离 (工具变量)')
axes[1,0].set_ylabel('收入')
axes[1,0].set_title(f'简化型关系\n斜率: {z_red[0]:.1f}')
axes[1,0].grid(True, alpha=0.3)
# 弱工具变量对比
weak_analysis = comprehensive_education_analysis(
iv_data_weak, 'education_weak', ['weak_iv'], 'income', ['family_background']
)
methods_weak = ['OLS', 'IV (强工具)', 'IV (弱工具)']
coefs_weak = [
weak_analysis['ols']['coefficient'],
analysis_results['iv']['coefficient'],
weak_analysis['iv']['coefficient']
]
axes[1,1].bar(methods_weak, coefs_weak, color=['red', 'blue', 'orange'], alpha=0.7)
axes[1,1].axhline(y=1000, color='green', linestyle='--', linewidth=2,
label='真实效应: 1000')
axes[1,1].set_ylabel('教育回报估计 (元)')
axes[1,1].set_title('弱工具变量对估计的影响')
axes[1,1].legend()
axes[1,1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
结果解释与经济学意义
我们的分析显示:
- OLS估计明显高估了教育回报,这是由于能力等遗漏变量的影响
- IV估计更接近真实效应,但置信区间更宽,反映了工具变量方法效率较低的特点
- 弱工具变量会导致IV估计不稳定,且可能偏向OLS估计
VI. 敏感性分析与稳健性检验
工具变量估计对假设的敏感性需要进行系统评估。以下是常用的敏感性分析方法。
Conley et al. (2012) 敏感性分析
当排他性约束可能轻微违反时,Conley方法评估估计结果对违反程度的敏感性。
# Conley敏感性分析
def conley_sensitivity_analysis(data, endogenous_var, instrument_var, outcome_var,
controls=None, gamma_range=np.linspace(-500, 500, 21)):
"""
Conley et al. (2012) 敏感性分析
评估工具变量估计对排他性约束轻微违反的敏感性
"""
results = []
for gamma in gamma_range:
# 调整结果变量:从Y中减去gamma * Z
adjusted_outcome = data[outcome_var] - gamma * data[instrument_var]
# 使用调整后的结果进行IV估计
from linearmodels.iv import IV2SLS
if controls:
formula = f"adjusted_outcome ~ 1 + {' + '.join(controls)} + [{endogenous_var} ~ {instrument_var}]"
else:
formula = f"adjusted_outcome ~ 1 + [{endogenous_var} ~ {instrument_var}]"
iv_model = IV2SLS.from_formula(formula, data=data)
iv_results = iv_model.fit(cov_type='robust')
results.append({
'gamma': gamma,
'coefficient': iv_results.params[endogenous_var],
'se': iv_results.std_errors[endogenous_var],
'ci_lower': iv_results.conf_int().loc[endogenous_var, 'lower'],
'ci_upper': iv_results.conf_int().loc[endogenous_var, 'upper'],
'p_value': iv_results.pvalues[endogenous_var]
})
return pd.DataFrame(results)
# 执行Conley敏感性分析
conley_results = conley_sensitivity_analysis(
iv_data, 'education', 'distance', 'income', ['family_background']
)
print("Conley敏感性分析结果 (前5行):")
print(conley_results.head().round(2))
# 找到估计值不显著的gamma范围
significant_results = conley_results[conley_results['p_value'] < 0.05]
if len(significant_results) > 0:
gamma_significant_min = significant_results['gamma'].min()
gamma_significant_max = significant_results['gamma'].max()
print(f"\n估计值保持显著的gamma范围: [{gamma_significant_min:.2f}, {gamma_significant_max:.2f}]")
else:
print("\n在所有gamma值下估计值都不显著")
# 可视化Conley敏感性分析
plt.figure(figsize=(10, 6))
plt.plot(conley_results['gamma'], conley_results['coefficient'], 'b-', linewidth=2, label='IV估计值')
plt.fill_between(conley_results['gamma'],
conley_results['ci_lower'],
conley_results['ci_upper'],
alpha=0.3, color='blue', label='95%置信区间')
plt.axhline(y=analysis_results['ols']['coefficient'], color='red', linestyle='--',
label=f'OLS估计: {analysis_results['ols']['coefficient']:.0f}')
plt.axhline(y=1000, color='green', linestyle='--',
label=f'真实效应: 1000')
plt.axvline(x=0, color='black', linestyle='-', alpha=0.5, label='完全排他性 (γ=0)')
plt.xlabel('排他性约束违反程度 (γ)')
plt.ylabel('教育回报IV估计值')
plt.title('Conley敏感性分析: 排他性约束违反的影响')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()
工具变量系数稳定性检验
通过逐步加入控制变量,观察工具变量估计值的变化,评估估计的稳健性。
# 系数稳定性检验
def coefficient_stability_test(data, endogenous_var, instrument_var, outcome_var, control_sets):
"""
通过逐步加入控制变量检验系数稳定性
"""
stability_results = []
for i, controls in enumerate(control_sets):
# IV估计
from linearmodels.iv import IV2SLS
if controls:
formula = f"{outcome_var} ~ 1 + {' + '.join(controls)} + [{endogenous_var} ~ {instrument_var}]"
else:
formula = f"{outcome_var} ~ 1 + [{endogenous_var} ~ {instrument_var}]"
iv_model = IV2SLS.from_formula(formula, data=data)
iv_results = iv_model.fit(cov_type='robust')
# 第一阶段分析
first_stage = detailed_first_stage(data, endogenous_var, [instrument_var], controls)
stability_results.append({
'model': f"模型 {i+1}",
'controls': ', '.join(controls) if controls else '无控制变量',
'coefficient': iv_results.params[endogenous_var],
'se': iv_results.std_errors[endogenous_var],
'ci_lower': iv_results.conf_int().loc[endogenous_var, 'lower'],
'ci_upper': iv_results.conf_int().loc[endogenous_var, 'upper'],
'f_statistic': first_stage['f_statistic'],
'n_controls': len(controls)
})
return pd.DataFrame(stability_results)
# 定义不同的控制变量组合
control_sets = [
[], # 无控制变量
['family_background'], # 仅家庭背景
['family_background'] # 这里可以添加更多控制变量
]
# 执行系数稳定性检验
stability_results = coefficient_stability_test(
iv_data, 'education', 'distance', 'income', control_sets
)
print("系数稳定性检验结果:")
print(stability_results.round(2))
# 可视化系数稳定性
plt.figure(figsize=(10, 6))
models = stability_results['model']
coefficients = stability_results['coefficient']
ci_lower = stability_results['ci_lower']
ci_upper = stability_results['ci_upper']
plt.errorbar(range(len(models)), coefficients,
yerr=[coefficients - ci_lower, ci_upper - coefficients],
fmt='o-', capsize=5, capthick=2, linewidth=2, markersize=8)
plt.axhline(y=1000, color='green', linestyle='--', label='真实效应: 1000')
plt.axhline(y=analysis_results['ols']['coefficient'], color='red', linestyle='--',
label=f'OLS估计: {analysis_results['ols']['coefficient']:.0f}')
plt.xticks(range(len(models)), [f'{m}\n({c})' for m, c in zip(stability_results['model'], stability_results['controls'])])
plt.ylabel('教育回报IV估计值')
plt.title('系数稳定性检验: 不同控制变量组合')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
替代工具变量比较
如果存在多个候选工具变量,比较它们的结果可以提供额外的稳健性检验。
# 替代工具变量比较
def alternative_iv_comparison(data, endogenous_var, outcome_var, controls, iv_candidates):
"""
比较不同工具变量的估计结果
"""
comparison_results = []
for iv_name, iv_var in iv_candidates.items():
try:
# IV估计
from linearmodels.iv import IV2SLS
if controls:
formula = f"{outcome_var} ~ 1 + {' + '.join(controls)} + [{endogenous_var} ~ {iv_var}]"
else:
formula = f"{outcome_var} ~ 1 + [{endogenous_var} ~ {iv_var}]"
iv_model = IV2SLS.from_formula(formula, data=data)
iv_results = iv_model.fit(cov_type='robust')
# 第一阶段分析
first_stage = detailed_first_stage(data, endogenous_var, [iv_var], controls)
comparison_results.append({
'instrument': iv_name,
'variable': iv_var,
'coefficient': iv_results.params[endogenous_var],
'se': iv_results.std_errors[endogenous_var],
'ci_lower': iv_results.conf_int().loc[endogenous_var, 'lower'],
'ci_upper': iv_results.conf_int().loc[endogenous_var, 'upper'],
'f_statistic': first_stage['f_statistic'],
'partial_r2': first_stage['partial_r2']
})
except Exception as e:
print(f"工具变量 {iv_name} 估计失败: {e}")
return pd.DataFrame(comparison_results)
# 生成替代工具变量
np.random.seed(321)
iv_data['alternative_iv1'] = np.random.normal(0, 1, n) # 随机工具变量(应该效果差)
iv_data['alternative_iv2'] = iv_data['distance'] + 0.5 * np.random.normal(0, 1, n) # 与距离相关的工具变量
iv_candidates = {
'主要工具变量 (距离)': 'distance',
'替代工具变量1 (随机)': 'alternative_iv1',
'替代工具变量2 (相关)': 'alternative_iv2'
}
# 比较替代工具变量
iv_comparison = alternative_iv_comparison(
iv_data, 'education', 'income', ['family_background'], iv_candidates
)
print("替代工具变量比较结果:")
print(iv_comparison.round(2))
# 可视化工具变量比较
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
# 估计值比较
instruments = iv_comparison['instrument']
coefficients = iv_comparison['coefficient']
ci_lower = iv_comparison['ci_lower']
ci_upper = iv_comparison['ci_upper']
f_stats = iv_comparison['f_statistic']
colors = ['blue' if f > 10 else 'red' for f in f_stats]
ax1.errorbar(range(len(instruments)), coefficients,
yerr=[coefficients - ci_lower, ci_upper - coefficients],
fmt='o', capsize=5, capthick=2, markersize=8, color=colors)
ax1.axhline(y=1000, color='green', linestyle='--', label='真实效应: 1000')
ax1.set_xticks(range(len(instruments)))
ax1.set_xticklabels(instruments, rotation=45)
ax1.set_ylabel('教育回报估计值')
ax1.set_title('不同工具变量的估计结果')
ax1.legend()
ax1.grid(True, alpha=0.3)
# 第一阶段强度比较
ax2.bar(range(len(instruments)), f_stats, color=colors, alpha=0.7)
ax2.axhline(y=10, color='red', linestyle='--', label='弱工具变量阈值 (F=10)')
ax2.set_xticks(range(len(instruments)))
ax2.set_xticklabels(instruments, rotation=45)
ax2.set_ylabel('第一阶段F统计量')
ax2.set_title('工具变量强度比较')
ax2.legend()
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
敏感性分析帮助我们理解工具变量估计对关键假设的依赖程度,是工具变量分析中不可或缺的一部分。
VII. 常见陷阱与最佳实践
基于理论研究和实证经验,我们总结出工具变量分析中的常见陷阱和相应的最佳实践。
常见陷阱
# 工具变量分析陷阱诊断
def diagnose_iv_pitfalls(data, endogenous_var, instrument_var, outcome_var, controls=None):
"""
诊断工具变量分析中的常见陷阱
"""
pitfalls = []
recommendations = []
# 1. 弱工具变量检验
first_stage = detailed_first_stage(data, endogenous_var, [instrument_var], controls)
if first_stage['f_statistic'] < 10:
pitfalls.append("弱工具变量问题")
recommendations.append("考虑寻找更强工具变量或使用LIML估计")
# 2. 排他性约束违反风险
# 检查工具变量与结果的直接关系
if controls:
X_direct = sm.add_constant(data[[instrument_var] + controls])
else:
X_direct = sm.add_constant(data[instrument_var])
direct_effect = sm.OLS(data[outcome_var], X_direct).fit()
iv_direct_pvalue = direct_effect.pvalues[instrument_var]
if iv_direct_pvalue < 0.1:
pitfalls.append("可能违反排他性约束")
recommendations.append("进行Conley敏感性分析和理论论证")
# 3. 样本选择问题
# 检查工具变量的变异性
iv_std = data[instrument_var].std()
if iv_std < 0.5: # 任意阈值
pitfalls.append("工具变量变异性不足")
recommendations.append("检查工具变量的测量和变异性")
# 4. 异质性处理效应
# 检查第一阶段系数的异质性
from scipy.stats import variation
first_stage_coef_variation = variation(data[instrument_var]) # 简化代理
if first_stage_coef_variation > 1.0: # 任意阈值
pitfalls.append("可能存在异质性处理效应")
recommendations.append("考虑LATE解释的局限性")
return pitfalls, recommendations
# 诊断我们的分析
pitfalls, recommendations = diagnose_iv_pitfalls(
iv_data, 'education', 'distance', 'income', ['family_background']
)
print("工具变量分析陷阱诊断:")
if pitfalls:
print("发现的潜在问题:")
for i, pitfall in enumerate(pitfalls, 1):
print(f" {i}. {pitfall}")
print("\n建议:")
for i, rec in enumerate(recommendations, 1):
print(f" {i}. {rec}")
else:
print("未发现明显问题")
# 最佳实践总结
best_practices = {
'设计阶段': [
'基于理论选择工具变量',
'确保工具变量与内生变量有强相关性',
'详细论证排他性约束的合理性'
],
'分析阶段': [
'报告第一阶段结果和F统计量',
'进行过度识别检验(如果可能)',
'展示平衡性检验结果',
'进行敏感性分析'
],
'解释阶段': [
'谨慎解释LATE(局部平均处理效应)',
'讨论排他性约束的潜在威胁',
'比较OLS和IV结果',
'报告置信区间而非仅点估计'
]
}
print("\n工具变量分析最佳实践:")
for stage, practices in best_practices.items():
print(f"\n{stage}:")
for i, practice in enumerate(practices, 1):
print(f" {i}. {practice}")
工具变量选择的实用指南
基于实证研究的经验,我们总结了以下工具变量选择的实用指南:
选择标准 | 理想特征 | 警示信号 | 应对策略 |
---|---|---|---|
相关性 | F > 10, 偏R² > 0.1 | F < 10, 偏R² < 0.02 | 寻找更强工具,使用LIML |
排他性约束 | 理论坚实,placebo检验通过 | 与伪结果相关,理论薄弱 | 敏感性分析,寻找替代工具 |
外生性 | 自然实验,随机分配 | 与可观测变量相关 | 控制相关变量,平衡性检验 |
单调性 | 一致的处理效应方向 | 异质性明显 | 明确LATE解释范围 |
有效性 | 多方法验证一致 | 结果对方法敏感 | 稳健性检验,透明报告 |
- 点赞
- 收藏
- 关注作者
评论(0)