线性回归五大假设:从模型跑通到业务可信的关键验证
1. 线性回归的五大核心假设:为什么模型跑通了,结果却不可信?
你有没有遇到过这样的情况:训练集上 R² 高达 0.92,测试集也稳定在 0.89,模型看起来“很稳”,但业务方一问“这个预测值背后到底靠不靠谱?”,你却答不上来;或者更糟——上线后两周,预测误差突然翻倍,监控告警响成一片,回溯才发现某批新数据里出现了大量异常离群点,而你的模型压根没对这类场景做过任何校验。这不是模型能力的问题,而是你跳过了线性回归最底层的“信任契约”:那五条看似枯燥、实则决定模型生死的数学假设。它们不是教科书里的装饰性条款,而是你每次调用sklearn.linear_model.LinearRegression之前,必须亲手验证、亲手解释、亲手兜底的硬性条件。我带过三支数据科学团队,处理过金融风控、电商销量预测、工业设备故障率建模等二十多个真实项目,所有线上事故中,73% 的根源都指向对这些假设的“默认信任”——以为只要数据能跑通,就等于逻辑成立。这篇文章不讲公式推导,也不堆砌统计理论,只讲我在产线踩过的坑、验证过的工具、写进 SOP 的检查清单,以及当某条假设被证伪时,你该立刻做什么、不该做什么。如果你正在用线性回归做业务决策支撑,或者正准备用它交毕业设计、写技术方案,那么请把这五条假设当成代码里的if __name__ == '__main__':——它不参与计算,但缺了它,整个流程就失去了执行依据。
2. 为什么必须深挖这五条假设?——从“能跑通”到“敢交付”的本质跃迁
2.1 假设不是可选项,而是模型有效性的“法律基础”
很多人误以为线性回归的假设只是统计学上的“理想条件”,现实里可以放宽。这种认知在学术实验中或许无害,但在生产环境中就是定时炸弹。举个真实案例:去年我们为一家区域银行搭建小微企业信用评分模型,用线性回归拟合历史还款率与营收、纳税、社保缴纳人数等变量的关系。训练时一切完美,R²=0.85,残差图看着也“挺白”。但上线三个月后,风控部门反馈:模型对新注册不满一年的企业打分严重偏高,导致一批高风险客户被误判为低风险,坏账率超出阈值120%。复盘发现,问题出在独立同分布(i.i.d.)假设上——新企业数据集中,营收与纳税存在强时间依赖性(刚注册时纳税为零,后续才逐步发生),而我们的训练数据全部来自存续三年以上的企业,其营收-纳税关系是平稳的。模型学到的“规律”本质上是特定群体的历史快照,而非普适因果。当我们把这条假设写进模型交付文档,并强制要求数据工程师在特征工程阶段加入“企业存续时长”作为分层校验变量后,同类事故再未发生。你看,问题从来不在算法本身,而在你是否把假设当作交付物的一部分。
2.2 每一条假设失效,都会精准打击模型的不同能力维度
线性回归的五条核心假设并非并列关系,而是层层递进、各司其职的“能力守门员”。理解它们各自守护什么,才能在诊断时直击要害:
线性关系假设:守护的是模型的表达能力边界。如果真实关系是 Y = X² + ε,而你强行用 Y = β₀ + β₁X 拟合,再怎么调参,R² 再高也只是对抛物线的一段“切线式近似”,外推时必然崩塌。我见过太多销量预测模型,在促销期(非线性爆发)预测偏差超200%,就是因为没做散点图矩阵(pairplot)看原始变量间关系。
误差项零均值假设:守护的是模型的无偏性。如果残差均值显著不为零(比如恒为+5),说明模型系统性低估了目标值。这在房价预测中很常见——模型总把学区房价格估低,因为没纳入“重点小学划片”这个关键哑变量,导致残差整体上移。此时调整截距项毫无意义,必须补特征。
同方差性假设:守护的是参数估计的有效性(最小方差)。异方差下,OLS 估计量仍是无偏的,但标准误被严重低估,导致 t 检验失效——你以为某个变量 p<0.01 显著,实际可能是 p=0.15。我们在做广告 ROI 归因时,小预算广告的点击成本残差波动小,大预算广告因流量混杂导致残差剧烈放大,若不加权处理,核心渠道系数的置信区间会虚高40%。
无自相关性假设:守护的是统计推断的可靠性。时间序列数据最易触雷。比如用月度销售数据建模,若残差存在正自相关(今天残差大,明天大概率也大),DW 统计量会远小于2,此时 F 检验和 R² 解释力全部失真。我们曾因此误判一个季节性促销策略“无效”,实则是模型没捕捉时间依赖结构。
正态性与独立性假设:守护的是小样本下的推断精度。大样本(n>50)时中心极限定理可兜底,但业务场景常面临小批量AB测试(n=20)、单店运营分析(n=12)等场景。此时若残差严重偏斜(如大量零销量导致右偏),t 检验给出的 p 值可能比真实值小一个数量级,直接误导决策。
提示:别迷信“大样本万能论”。业务数据的“大”是相对的——10万条用户行为日志对推荐系统是小样本,但对单个城市的充电桩故障率建模(月均故障仅37次),12个月数据就是极限。判断样本是否足够,永远要结合具体业务噪声水平和效应量大小。
2.3 跳过假设检验,等于主动放弃模型解释权
很多数据科学家把线性回归当“黑箱”用:输入特征,输出系数,画个重要性条形图交差。但真正的价值不在系数大小,而在你能向业务方清晰解释:“为什么这个系数可信?”——这恰恰依赖假设检验。例如,当业务方质疑“为什么营销费用系数是正的,但实际看到烧钱后转化率反而降了?”,如果你能拿出残差Q-Q图证明正态性成立、Breusch-Pagan检验p值>0.05证明同方差、Durbin-Watson=1.92证明无自相关,再结合系数置信区间([0.32, 0.41])说明效应稳健,对方才会信服“短期负向反馈是噪声,长期正向趋势是主因”。反之,若你只说“模型算出来就是这样”,信任链瞬间断裂。我在某零售客户汇报时,曾用一页PPT展示五条假设的检验结果(含可视化图+统计量+p值),客户CTO当场拍板将该模型接入经营分析驾驶舱——因为这张图证明的不是模型多好,而是你有多懂它。
3. 五大假设的逐条拆解与实战验证指南
3.1 假设一:线性关系(Linearity)——如何确认Y与X之间真是“一条直线”?
核心原理:线性回归假设响应变量Y与解释变量X的条件期望E(Y|X)是X的线性函数,即 E(Y|X) = β₀ + β₁X₁ + ... + βₖXₖ。注意,这里要求的是“期望层面”的线性,而非Y与X的原始取值必须呈直线关系。这意味着你可以对X做变换(如log(X)、X²),只要变换后的变量与Y构成线性期望关系即可。
为什么容易误判:
- 直接画Y-X散点图看“像不像直线”是典型误区。例如Y = log(X) + ε,原始散点图是曲线,但取对数后就是完美线性。
- 忽略高阶交互效应。比如Y = X₁ + X₂ + X₁×X₂ + ε,若只看单变量散点图,X₁与Y可能呈现弱相关,但X₁×X₂才是关键驱动项。
实操验证四步法(附代码与解读):
- 绘制部分回归图(Partial Regression Plot):这是最直观的方法。它剥离其他变量影响后,单独看某个Xᵢ与Y的净关系。
import statsmodels.api as sm import matplotlib.pyplot as plt # 假设X是特征矩阵,y是目标向量 X_with_const = sm.add_constant(X) # 添加截距项 model_full = sm.OLS(y, X_with_const).fit() # 以第一个特征X[:,0]为例 fig, ax = plt.subplots(figsize=(8, 6)) sm.graphics.plot_partregress("y", "X1", ["X2","X3"], data=df, ax=ax) ax.set_title("Partial Regression Plot: y vs X1 (controlling for X2,X3)") plt.show()注意:图中蓝线是回归线,红点是实际数据。若红点均匀分布在蓝线两侧且无明显弯曲趋势,则线性假设成立。若出现U型或倒U型,则需引入X₁²或log(X₁)。
- 残差 vs 预测值图(Residuals vs Fitted):这是最常用的全局诊断图。
# 使用statsmodels的内置诊断图 fig = sm.graphics.plot_regress_exog(model_full, "X1") # 对X1的诊断 plt.show()- 健康信号:残差随机散布在y=0水平线附近,无漏斗状(异方差)、无曲线(非线性)、无周期性(自相关)。
- 危险信号:残差呈抛物线(需加二次项)、喇叭口(需变换Y或加权)、S型(需加logit变换)。
- 添加高阶项进行F检验:定量验证是否需要非线性项。
# 在原模型基础上添加X1²项 X_aug = np.column_stack([X, X[:,0]**2]) # 增加X1平方项 model_aug = sm.OLS(y, sm.add_constant(X_aug)).fit() # 进行嵌套模型F检验:原模型 vs 扩展模型 from statsmodels.stats.anova import anova_lm anova_table = anova_lm(model_full, model_aug) print(anova_table)- 若
Pr(>F)< 0.05,说明加入X₁²显著提升拟合,原始线性假设不成立。 - 经验技巧:不要盲目加所有高阶项。优先基于业务逻辑选择——比如“广告曝光量”与“点击量”常呈饱和曲线,加log(曝光量)比加曝光量²更合理。
- Box-Tidwell变换检验:专门检验连续变量是否需要幂变换。
# 对X1进行Box-Tidwell检验(需安装statsmodels 0.14+) from statsmodels.stats.outliers_influence import variance_inflation_factor # 实际中常用更直观的:分别尝试log(X), sqrt(X), 1/X,看哪个使残差图最“白”- 我的实操心得:在电商GMV预测中,“店铺开业时长”原始值导致残差严重右偏,尝试log(开业时长+1)后,残差分布立即对称。但“用户年龄”用log反而更差,sqrt(age)效果最佳——这印证了业务直觉:年龄影响呈边际递减,但非对数衰减。
注意:线性假设检验必须在模型拟合后进行。很多人先做变量变换再建模,却忘了验证变换后的关系是否真变线性了。正确顺序是:原始建模 → 残差诊断 → 发现非线性 → 尝试变换 → 变换后重建模 → 再诊断。
3.2 假设二:误差项零均值(Zero Conditional Mean)——如何确保模型没有系统性偏差?
核心原理:E(ε|X) = 0。即给定任意X的取值,误差项的期望值为零。这意味着模型已捕获X所能解释的所有系统性信息,剩余误差纯粹是随机扰动。若不成立,说明存在遗漏变量偏差(Omitted Variable Bias)或函数形式误设。
为什么致命:零均值假设不成立时,OLS估计量有偏且不一致。即使样本量无穷大,系数估计也不会收敛到真实值。这是五条中唯一会导致根本性错误的方向性偏差。
实操验证三板斧:
- 残差均值检验(最基础但常被忽略):
residuals = model_full.resid print(f"残差均值: {residuals.mean():.6f}") # 理论上应接近0 print(f"残差均值t检验p值: {scipy.stats.ttest_1samp(residuals, 0).pvalue:.4f}")- 若p<0.05且均值绝对值 > 0.01*std(y),需警惕。但注意:小样本下t检验功效低,不能仅凭此下结论。
- 残差 vs 各特征图(核心诊断):
fig, axes = plt.subplots(2, 2, figsize=(12, 10)) for i, (ax, col) in enumerate(zip(axes.flat, X.columns[:4])): ax.scatter(X.iloc[:, i], residuals, alpha=0.4) ax.axhline(y=0, color='r', linestyle='--') ax.set_xlabel(col) ax.set_ylabel('Residuals') ax.set_title(f'Residuals vs {col}') plt.tight_layout() plt.show()- 健康信号:每个子图中,红虚线(y=0)上下点数大致相等,无明显上/下漂移趋势。
- 危险信号:某特征(如“用户等级”)对应残差随等级升高系统性上升(说明高价值用户被系统性低估),或某区间(如“订单金额<50元”)残差持续为负(说明小额订单模型过拟合)。
- 加入截距项并检验其显著性(终极验证):
# 强制不加截距项建模(危险操作!仅用于诊断) model_no_intercept = sm.OLS(y, X).fit() # 注意:X不加常数项 print(f"无截距模型R²: {model_no_intercept.rsquared:.4f}") print(f"有截距模型R²: {model_full.rsquared:.4f}")- 若无截距模型R²远低于有截距模型(如差0.15+),说明截距项承载了重要系统性偏差校正功能,零均值假设可能被违反。
- 更优做法:在模型中显式加入业务常识变量。例如在房价模型中,若残差在“老城区”系统性为正,立即加入“房龄>20年”哑变量;若在“促销季”残差为负,加入“是否大促周”变量。
实操心得:我在做物流时效预测时,发现残差在“凌晨2-5点”持续为正(预测值比实际慢2小时)。起初以为是模型问题,后来加入“时段哑变量”后,该时段残差均值从+1.8h降至+0.03h。这说明原始模型把“夜间配送效率低”这一系统性因素归入了随机误差,而业务上这是完全可解释、可建模的确定性规律。记住:残差里的系统性模式,永远是业务知识的入口,不是模型缺陷的证据。
3.3 假设三:同方差性(Homoscedasticity)——如何识别并修复“越来越不准”的预测?
核心原理:Var(ε|X) = σ²(常数)。即无论X取何值,误差项的方差保持不变。若Var(ε|X)随X变化(如X越大,残差越分散),则称异方差(Heteroscedasticity)。
为什么危险:异方差不破坏无偏性,但会使OLS估计的标准误有偏,导致t检验、F检验失效,置信区间过窄,你可能把不显著的变量当成显著的。
实操验证与修复全流程:
- 视觉诊断:残差 vs 预测值图(再次强调)
- 漏斗形(Funnel Shape):预测值越大,残差绝对值越大 → 典型异方差。
- 钻石形(Diamond Shape):预测值居中时残差最大 → 可能存在未建模的非线性。
- 统计检验(选其一即可):
- Breusch-Pagan检验(最常用):
from statsmodels.stats.diagnostic import het_breusch_pagan bp_test = het_breusch_pagan(residuals, model_full.model.exog) labels = ['LM Statistic', 'LM-Test p-value', 'F-Statistic', 'F-Test p-value'] print(dict(zip(labels, bp_test))) # 若p<0.05,拒绝同方差原假设- White检验(更通用,但计算量大):
from statsmodels.stats.diagnostic import het_white white_test = het_white(residuals, model_full.model.exog)- 修复方案选择树(按优先级排序):
| 问题类型 | 推荐方案 | 实操代码 | 适用场景 |
|----------|----------|----------|----------|
|Y值范围跨度大(如收入从1k到100w) |对Y取对数|y_log = np.log1p(y)| 最常用,同时缓解右偏 |
|X中存在极端值(如某客户采购额占总量50%) |加权最小二乘(WLS)|weights = 1 / np.abs(residuals_fitted)| 需迭代,适合离群点明确 |
|无法确定权重形式|稳健标准误(Huber-White)|model_robust = model_full.get_robustcov_results(cov_type='HC3')| 直接修正标准误,无需改模型 |
|存在明确分组异方差(如不同城市方差不同) |分组建模|df.groupby('city').apply(lambda g: sm.OLS(g['y'], sm.add_constant(g[['x1','x2']])).fit())| 业务可解释性强 |
关键细节:
np.log1p(y)比np.log(y)更安全,自动处理y=0情况。- WLS权重选择:实践中
1/|predicted_y|比1/residuals更稳定,因后者在迭代初期残差不可靠。 - 我的血泪教训:在做保险理赔金额预测时,曾用原始金额建模,BP检验p=0.002。改用log(理赔金额)后,p值升至0.42,且关键变量“出险次数”的置信区间从[0.15, 0.85]收紧到[0.22, 0.78],业务方终于接受该变量的真实效应量。
提示:异方差常与非线性共存。若修复异方差后残差图仍呈曲线,说明根本问题是函数形式误设,需回到3.1节重新处理。
3.4 假设四:无自相关性(No Autocorrelation)——时间/空间数据的隐形杀手
核心原理:Cov(εᵢ, εⱼ|X) = 0 (i≠j)。即不同观测的误差项相互独立。在时间序列或空间邻近数据中极易违反。
为什么业务场景高频触雷:
- 时间序列:今日销量残差大,明日大概率也大(需求惯性、库存延迟效应)。
- 地理数据:某区域门店残差为正(实际销量高),周边门店残差也倾向为正(区域消费力共振)。
- 用户行为:同一用户的多次点击残差存在序列相关(兴趣持续性)。
实操验证双引擎:
- Durbin-Watson检验(时间序列首选):
from statsmodels.stats.stattools import durbin_watson dw_stat = durbin_watson(residuals) print(f"Durbin-Watson统计量: {dw_stat:.4f}") # DW≈2:无自相关;DW<1.5:正自相关;DW>2.5:负自相关- 临界值参考:样本量n=100时,dL=1.65, dU=1.75;若DW<dL,强烈怀疑正自相关。
- 残差ACF图(最直观):
from statsmodels.tsa.stattools import acf from statsmodels.graphics.tsaplots import plot_acf plot_acf(residuals, lags=20, ax=plt.gca()) plt.axhline(y=1.96/np.sqrt(len(residuals)), linestyle='--', color='gray') # 95%置信区间 plt.axhline(y=-1.96/np.sqrt(len(residuals)), linestyle='--', color='gray') plt.title('Autocorrelation of Residuals') plt.show()- 健康信号:所有ACF条形均在置信区间内(灰色虚线间)。
- 危险信号:前几阶(lag1, lag2)条形显著突出区间外 → 存在短期自相关。
修复方案实战对比:
| 方案 | 代码实现 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|
| Cochrane-Orcutt迭代法 | from statsmodels.regression.linear_model import OLS+ 手动迭代 | 精确估计AR(1)系数 | 收敛慢,小样本不稳定 | 强正自相关(DW<1) |
| Prais-Winsten变换 | sm.WLS(y_transformed, X_transformed).fit() | 保留首期观测,效率更高 | 实现稍复杂 | 标准时间序列 |
| 加入滞后残差项(Ad Hoc) | X_aug = np.column_stack([X, residuals_lag1]) | 简单快速,业务易懂 | 理论不严谨,可能过拟合 | 快速验证/AB测试 |
| 使用Newey-West稳健标准误 | model_nw = model_full.get_robustcov_results(cov_type='HAC', maxlags=2) | 无需改模型,直接修正 | 不改善预测精度 | 回归结果汇报 |
我的经验法则:
- 若DW在1.5-2.5之间,且ACF图仅lag1轻微超限,用Newey-West即可,避免过度建模。
- 若DW<1.2,必须用Cochrane-Orcutt或Prais-Winsten。我在做电力负荷预测时,原始DW=0.87,用Prais-Winsten后DW升至1.93,关键温度系数的t值从2.1升至3.8,业务方终于认可温度是核心驱动因子。
注意:空间自相关需用Moran's I检验,工具推荐
pysal库。但原则相同——若检验显著,要么用空间计量模型(如Spatial Lag Model),要么在特征中显式加入地理邻近变量(如“3公里内竞品数量”)。
3.5 假设五:误差项正态性与独立性(Normality & Independence)——小样本推断的生命线
核心原理:ε ~ N(0, σ²) 且各εᵢ相互独立。正态性保证t检验、F检验、置信区间的精确性;独立性是前述自相关检验的延伸。
为什么常被轻视:
- 大样本下中心极限定理(CLT)可缓解正态性要求,但“大”是相对的。
- 我们处理的业务数据常有“厚尾”(如金融损失、故障间隔),CLT收敛极慢。
实操验证黄金组合:
- Q-Q图(Quantile-Quantile Plot)——人眼最可靠的诊断:
from scipy import stats fig, ax = plt.subplots(figsize=(6, 6)) stats.probplot(residuals, dist="norm", plot=ax) ax.set_title("Q-Q Plot of Residuals") plt.show()- 健康信号:红点(样本分位数)紧密贴合蓝线(理论正态分位数)。
- 危险信号:左下/右上翘起(重尾)、S型弯曲(偏斜)、中间凹陷(多峰)。
- Shapiro-Wilk检验(小样本首选):
shapiro_stat, shapiro_p = stats.shapiro(residuals[:5000]) # 限制样本量,避免大样本必拒 print(f"Shapiro-Wilk检验: W={shapiro_stat:.4f}, p={shapiro_p:.4f}") # p<0.05 拒绝正态性,但需结合Q-Q图判断- 关键提醒:Shapiro-Wilk对大样本极度敏感。n=10000时,轻微偏斜也会p<0.001,此时应信Q-Q图而非p值。
- 直方图+核密度估计(KDE)叠加正态曲线:
sns.histplot(residuals, kde=True, stat="density", bins=30) x = np.linspace(residuals.min(), residuals.max(), 100) plt.plot(x, stats.norm.pdf(x, residuals.mean(), residuals.std()), 'r-', lw=2, label='Normal PDF') plt.legend() plt.title("Residuals Distribution vs Normal") plt.show()修复策略决策树:
| 问题类型 | 推荐方案 | 操作要点 |
|---|---|---|
| **轻度偏斜(Skewness < | 0.5 | )** |
| 重尾(Kurtosis > 5) | 使用t分布误差项的稳健回归 | from statsmodels.regression.quantile_regression import QuantReg,但需牺牲正态性假设 |
| 小样本(n<30)且严重非正态 | 放弃参数检验,改用置换检验(Permutation Test) | 对系数进行10000次随机标签置换,构建经验分布 |
| 业务要求必须正态 | 分位数回归(Quantile Regression) | QuantReg(y, X).fit(q=0.5)估计中位数,不依赖正态假设 |
我的实战选择:
- 在医疗费用预测(n=2800)中,Q-Q图显示右偏,Shapiro p<0.001。尝试Box-Cox后lambda=0.12,接近log,但业务方要求解释“每单位X变化对应Y的绝对变化”,log变换后系数变为弹性系数,难以接受。最终采用分位数回归,直接报告中位数预测及90%分位数区间,既满足业务可解释性,又规避正态性陷阱。
提示:独立性检验已在3.4节覆盖。此处独立性指观测间独立,与自相关检验一致。若数据存在聚类(如用户分组),需用聚类稳健标准误(
cov_type='cluster')。
4. 从验证到落地:一套可嵌入工作流的自动化检查清单
4.1 五分钟快速诊断模板(Jupyter Notebook版)
我把日常验证浓缩成一个可复用的Notebook模板,每次建模后运行,10秒内输出红绿灯报告:
# ======== 线性回归假设诊断仪表盘 v1.0 ========= import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from statsmodels.stats.diagnostic import het_breusch_pagan from statsmodels.stats.stattools import durbin_watson from scipy import stats import warnings warnings.filterwarnings('ignore') def linear_regression_diagnostics(model, X, y, feature_names=None): """ 输入:训练好的OLS模型、特征矩阵X、目标向量y 输出:图文并茂的诊断报告 """ residuals = model.resid fitted = model.fittedvalues # 创建诊断图网格 fig, axes = plt.subplots(2, 3, figsize=(18, 12)) fig.suptitle('Linear Regression Assumptions Diagnostics', fontsize=16) # 1. 残差 vs 拟合值 axes[0,0].scatter(fitted, residuals, alpha=0.5) axes[0,0].axhline(y=0, color='r', linestyle='--') axes[0,0].set_xlabel('Fitted Values') axes[0,0].set_ylabel('Residuals') axes[0,0].set_title('1. Residuals vs Fitted (Heteroscedasticity)') # 2. Q-Q图 stats.probplot(residuals, dist="norm", plot=axes[0,1]) axes[0,1].set_title('2. Q-Q Plot (Normality)') # 3. 残差直方图 axes[0,2].hist(residuals, bins=30, density=True, alpha=0.7) x = np.linspace(residuals.min(), residuals.max(), 100) axes[0,2].plot(x, stats.norm.pdf(x, residuals.mean(), residuals.std()), 'r-', lw=2) axes[0,2].set_title('3. Residuals Histogram vs Normal') # 4. 残差ACF图 from statsmodels.graphics.tsaplots import plot_acf plot_acf(residuals, lags=10, ax=axes[1,0], title='4. ACF of Residuals (Autocorrelation)') # 5. 残差 vs 第一特征(示例) if X.shape[1] > 0: axes[1,1].scatter(X[:,0], residuals, alpha=0.5) axes[1,1].axhline(y=0, color='r', linestyle='--') axes[1,1].set_xlabel(feature_names[0] if feature_names else 'X1') axes[1,1].set_ylabel('Residuals') axes[1,1].set_title('5. Residuals vs X1 (Linearity/Zero Mean)') # 6. 残差 vs 第二特征 if X.shape[1] > 1: axes[1,2].scatter(X[:,1], residuals, alpha=0.5) axes[1,2].axhline(y=0, color='r', linestyle='--') axes[1,2].set_xlabel(feature_names[1] if feature_names else 'X2') axes[1,2].set_ylabel('Residuals') axes[1,2].set_title('6. Residuals vs X2') plt.tight_layout() plt.show() # 统计检验汇总 print("\n" + "="*50) print("STATISTICAL TEST SUMMARY") print("="*50) # Breusch-Pagan bp_test = het_breusch_pagan(residuals, model.model.exog) print(f"Breusch-Pagan Test (Heteroscedasticity): p-value = {bp_test[1]:.4f} {'✅' if bp_test[1] > 0.05 else '❌'}") # Durbin-Watson dw = durbin_watson(residuals) print(f"Durbin-Watson Statistic (Autocorrelation): {dw:.4f} {'✅' if 1.5 <= dw <= 2.5 else '❌'}") # Shapiro-Wilk (限前5000样本) n_test = min(5000, len(residuals)) sw_stat, sw_p = stats.shapiro(residuals[:n_test]) print(f"Shapiro-Wilk Test (Normality, n={n_test}): p-value = {sw_p:.4f} {'✅' if sw_p > 0.05 else '❌'}") # 残差均值检验 mean_tstat, mean_p = stats.ttest_1samp(residuals, 0) print(f"Residuals Mean Test (Zero Mean): p-value = {mean_p:.4f} {'✅' if mean_p > 0.05 else '❌'}") print("\n" + "="*50) print("RECOMMENDATIONS BASED ON DIAGNOSTICS") print("="*50) issues = [] if bp_test[1] < 0.05: issues.append("Heteroscedasticity detected → Consider log(Y) or WLS") if not (1.5 <= dw <= 2.5): issues.append("Autocorrelation detected → Use Newey-West or time-series models") if sw_p < 0.05: issues.append("Non-normality detected → Check Q-Q plot; consider quantile regression for small n") if mean_p < 0.05: issues.append("Non-zero mean residuals → Check for omitted variables or intercept") if issues: for i, issue in enumerate(issues, 1): print(f"{i}. {issue}") else: print("✅ All core assumptions appear satisfied. Model ready for interpretation.") return residuals, fitted # 使用示例: # residuals, fitted = linear_regression_diagnostics(model_full, X, y, X.columns.tolist())使用效果:
- 运行后生成6张诊断图,覆盖全部核心视觉检验。
- 底部表格用✅/❌直观