1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
print("\n" + "="*60)
print("6. 模型假设诊断")
print("="*60)
# 获取预测值和残差
y_pred = reduced_model.fittedvalues
residuals = reduced_model.resid
# 3.1 线性与独立性诊断
print("\n6.1 线性与独立性诊断...")
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 残差 vs 拟合值图
axes[0].scatter(y_pred, residuals, alpha=0.6)
axes[0].axhline(y=0, color='r', linestyle='--')
axes[0].set_xlabel('拟合值')
axes[0].set_ylabel('残差')
axes[0].set_title('残差 vs 拟合值图(检验线性与独立性)')
axes[0].grid(True, alpha=0.3)
# 添加Lowess平滑线看趋势
import statsmodels.nonparametric.smoothers_lowess as lowess
lowess_line = lowess.lowess(residuals, y_pred, frac=0.3)
axes[0].plot(lowess_line[:, 0], lowess_line[:, 1], color='green', linewidth=2, label='Lowess平滑线')
axes[0].legend()
# 残差 vs 自变量图(示例:收入)
if 'MedInc' in selected_vars:
axes[1].scatter(X['MedInc'], residuals, alpha=0.6)
axes[1].axhline(y=0, color='r', linestyle='--')
axes[1].set_xlabel('收入中位数 (MedInc)')
axes[1].set_ylabel('残差')
axes[1].set_title('残差 vs 自变量(检验线性关系)')
axes[1].grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 3.2 同方差性检验
print("\n6.2 同方差性检验...")
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 残差 vs 拟合值(看散点带宽度)
axes[0].scatter(y_pred, np.abs(residuals), alpha=0.6)
axes[0].set_xlabel('拟合值')
axes[0].set_ylabel('残差绝对值')
axes[0].set_title('残差绝对值 vs 拟合值(检验异方差)')
axes[0].grid(True, alpha=0.3)
# 添加趋势线
z = np.polyfit(y_pred, np.abs(residuals), 1)
p = np.poly1d(z)
axes[0].plot(np.sort(y_pred), p(np.sort(y_pred)), color='red', linewidth=2)
# 正式检验:Breusch-Pagan检验
from statsmodels.stats.diagnostic import het_breuschpagan
bp_test = het_breuschpagan(residuals, reduced_model.model.exog)
bp_labels = ['LM统计量', 'LM检验p值', 'F统计量', 'F检验p值']
bp_result = dict(zip(bp_labels, bp_test))
axes[1].bar(range(4), [bp_test[0], bp_test[1], bp_test[2], bp_test[3]],
color=['blue', 'red', 'blue', 'red'])
axes[1].set_xticks(range(4))
axes[1].set_xticklabels(['LM统计量', 'LM-p值', 'F统计量', 'F-p值'], rotation=45)
axes[1].set_title('Breusch-Pagan检验结果')
axes[1].axhline(y=0.05, color='green', linestyle='--', label='显著性水平0.05')
axes[1].legend()
plt.tight_layout()
plt.show()
print(f"Breusch-Pagan检验结果:")
print(f" LM统计量: {bp_test[0]:.4f}, p值: {bp_test[1]:.4f}")
print(f" F统计量: {bp_test[2]:.4f}, p值: {bp_test[3]:.4f}")
if bp_test[1] > 0.05:
print(" 结论:不能拒绝同方差原假设(不存在显著异方差)")
else:
print(" 警告:存在异方差问题,可能需要使用加权最小二乘法")
# 3.3 正态性检验
print("\n6.3 正态性检验...")
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# Q-Q图
sm.qqplot(residuals, line='45', fit=True, ax=axes[0])
axes[0].set_title('正态Q-Q图(检验残差正态性)')
# 残差直方图
axes[1].hist(residuals, bins=30, edgecolor='black', alpha=0.7, density=True)
axes[1].set_xlabel('残差')
axes[1].set_ylabel('密度')
# 添加正态分布曲线
from scipy.stats import norm
mu, std = norm.fit(residuals)
xmin, xmax = axes[1].get_xlim()
x = np.linspace(xmin, xmax, 100)
p = norm.pdf(x, mu, std)
axes[1].plot(x, p, 'r', linewidth=2, label=f'正态分布\nμ={mu:.2f}, σ={std:.2f}')
axes[1].set_title('残差分布直方图')
axes[1].legend()
plt.tight_layout()
plt.show()
# 正式检验:Shapiro-Wilk检验
shapiro_test = stats.shapiro(residuals)
print(f"\nShapiro-Wilk正态性检验:")
print(f" 统计量: {shapiro_test[0]:.4f}, p值: {shapiro_test[1]:.4f}")
if shapiro_test[1] > 0.05:
print(" 结论:不能拒绝正态性原假设(残差近似正态分布)")
else:
print(" 警告:残差不服从正态分布")
# 3.4 多重共线性诊断
print("\n6.4 多重共线性诊断...")
# 计算VIF
vif_data = pd.DataFrame()
vif_data["变量"] = X_selected.columns
vif_data["VIF"] = [variance_inflation_factor(X_selected.values, i)
for i in range(X_selected.shape[1])]
vif_data["1/VIF"] = 1 / vif_data["VIF"]
print("方差膨胀因子(VIF):")
print(vif_data.sort_values("VIF", ascending=False))
# 可视化VIF
plt.figure(figsize=(10, 6))
bars = plt.barh(vif_data["变量"], vif_data["VIF"], color='skyblue')
plt.axvline(x=5, color='orange', linestyle='--', label='VIF=5 (轻度共线性)')
plt.axvline(x=10, color='red', linestyle='--', label='VIF=10 (严重共线性)')
plt.xlabel('方差膨胀因子 (VIF)')
plt.title('多重共线性诊断')
plt.legend()
# 在柱子上添加数值
for bar in bars:
width = bar.get_width()
plt.text(width + 0.1, bar.get_y() + bar.get_height()/2,
f'{width:.2f}', ha='left', va='center')
plt.tight_layout()
plt.show()
# 3.5 自相关检验(如果数据有时间顺序)
print("\n6.5 自相关检验...")
# Durbin-Watson检验
dw_test = sm.stats.stattools.durbin_watson(residuals)
print(f"Durbin-Watson统计量: {dw_test:.4f}")
print("解释:")
print(" 2.0:无自相关")
print(" 0-2:正自相关")
print(" 2-4:负自相关")
if 1.5 < dw_test < 2.5:
print(" 结论:无明显自相关问题")
else:
print(" 警告:可能存在自相关问题")
# 自相关图
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
fig, axes = plt.subplots(1, 2, figsize=(14, 4))
plot_acf(residuals, lags=20, ax=axes[0])
axes[0].set_title('残差自相关图')
plot_pacf(residuals, lags=20, ax=axes[1])
axes[0].set_title('残差偏自相关图')
plt.tight_layout()
plt.show()
|