当我们谈论线性回归时,R²常常成为衡量模型好坏的唯一标准。但真实的数据分析远不止于此——一个优秀的模型需要经受统计假设检验的严格考验,而Python的statsmodels库为我们提供了完整的诊断工具包。本文将带您从基础拟合跃升到专业级回归分析,通过代码实战掌握模型验证的全套方法论。
在开始之前,确保您的Python环境已安装以下库:
python复制import numpy as np
import pandas as pd
import statsmodels.api as sm
import seaborn as sns
import matplotlib.pyplot as plt
from scipy import stats
让我们加载示例数据集——航空公司正点率与投诉次数的业务场景:
python复制# 构建示例数据
data = pd.DataFrame({
'on_time_rate': [81.8, 76.6, 76.6, 75.7, 73.8, 72.2, 71.2, 70.8, 69.6, 68.5],
'complaints': [8, 10, 14, 16, 17, 18, 20, 24, 26, 29]
})
提示:实际工作中建议使用pd.read_csv()加载真实业务数据。保持数据清洁是分析的第一步,务必检查缺失值和异常值。
使用statsmodels进行OLS回归建模:
python复制# 添加常数项
X = sm.add_constant(data['on_time_rate'])
y = data['complaints']
# 构建并拟合模型
model = sm.OLS(y, X).fit()
print(model.summary())
模型输出包含丰富信息,我们需要重点关注的几个关键指标:
| 统计量 | 含义 | 理想值 |
|---|---|---|
| R-squared | 解释变异比例 | 0.7以上较好 |
| Adj. R-squared | 调整后的解释力 | 接近R² |
| F-statistic | 模型显著性 | p<0.05 |
| coef p> | t | |
| Durbin-Watson | 自相关检验 | 1.5-2.5 |
对于我们的航空数据,回归方程为:
code复制投诉次数 = 430.19 - 4.7 × 正点率
这意味着正点率每提升1%,投诉次数平均减少4.7次。但仅知道这些还远远不够——我们需要验证这个关系是否真实可靠。
绘制残差图是检验模型假设的首要步骤:
python复制# 计算预测值和残差
predictions = model.predict(X)
residuals = y - predictions
# 绘制残差散点图
plt.figure(figsize=(10,4))
plt.scatter(predictions, residuals)
plt.axhline(y=0, color='r', linestyle='--')
plt.xlabel('Predicted Values')
plt.ylabel('Residuals')
plt.title('Residual Plot')
plt.show()
健康模型的残差应该:
python复制# 绘制正态QQ图
sm.qqplot(residuals, line='45', fit=True)
plt.title('Q-Q Plot of Residuals')
plt.show()
如果点基本落在45度参考线上,则支持残差正态性的假设。明显偏离可能意味着需要数据转换或考虑其他模型。
使用统计检验验证残差方差是否恒定:
python复制# 异方差检验
from statsmodels.stats.diagnostic import het_breuschpagan
bp_test = het_breuschpagan(residuals, model.model.exog)
print(f"BP检验统计量: {bp_test[0]:.3f}, p值: {bp_test[1]:.3f}")
注意:p值<0.05表明存在显著异方差问题,此时标准误估计可能不可靠,需要考虑加权最小二乘法(WLS)等解决方案。
模型摘要中已包含DW统计量:
对于时间序列数据,还需进行更严格的Ljung-Box检验。
python复制# 计算影响力指标
influence = model.get_influence()
cooks_d = influence.cooks_distance[0]
# 可视化
plt.stem(np.arange(len(cooks_d)), cooks_d, markerfmt=",")
plt.title("Cook's Distance Plot")
plt.xlabel('Observation Index')
plt.ylabel("Cook's Distance")
plt.show()
Cook距离大于1的点需要特别关注,可能是对模型影响过大的异常值。
python复制# 绘制部分回归图
fig = plt.figure(figsize=(8,6))
sm.graphics.plot_partregress('on_time_rate', 'complaints', '', data=data, obs_labels=False)
plt.show()
这种图去除了其他变量的影响,更纯粹地展示两个变量之间的关系。
计算特定正点率(如80%)时的预测区间:
python复制# 设置预测点
new_X = pd.DataFrame({'const':1, 'on_time_rate':[80]})
# 获取预测结果
predictions = model.get_prediction(new_X)
frame = predictions.summary_frame(alpha=0.05)
print(f"预测值: {frame['mean'].values[0]:.1f}")
print(f"95%置信区间: [{frame['mean_ci_lower'].values[0]:.1f}, {frame['mean_ci_upper'].values[0]:.1f}]")
print(f"95%预测区间: [{frame['obs_ci_lower'].values[0]:.1f}, {frame['obs_ci_upper'].values[0]:.1f}]")
当诊断发现问题时,我们可以考虑以下解决方案:
常见问题与对策表
| 问题类型 | 诊断方法 | 解决方案 |
|---|---|---|
| 异方差 | Breusch-Pagan检验 | WLS回归/数据转换 |
| 非线性 | 残差图观察 | 多项式项/样条回归 |
| 异常值 | Cook距离 | 稳健回归/删除离群点 |
| 自相关 | DW检验 | 时间序列模型 |
| 多重共线性 | VIF>10 | 删除变量/PCA |
例如,处理非线性关系的代码实现:
python复制# 添加二次项
data['on_time_rate_sq'] = data['on_time_rate']**2
X_quad = sm.add_constant(data[['on_time_rate', 'on_time_rate_sq']])
model_quad = sm.OLS(y, X_quad).fit()
# 比较模型
print(f"线性模型AIC: {model.aic:.1f}, 二次模型AIC: {model_quad.aic:.1f}")
在实际业务分析中,我曾遇到一个案例:初始模型的残差呈现明显的"微笑"模式,通过添加二次项后AIC值降低了15,不仅改善了拟合效果,还发现了变量间的真实非线性关系。这种细致的诊断过程往往能揭示数据背后的真实故事。