简介:这份资源面向数据分析初学者与进阶学习者,聚焦计数型数据的回归建模实战,以挑战者号航天飞机O形环热损伤数据为案例,讲解如何用Python完成泊松回归全流程。内容涵盖数据读入与表头处理、描述性统计与直方图探索、均值方差检验判断分散均衡、特征矩阵与目标向量准备,以及基于statsmodels的GLM建模、残差分析与可视化验证,帮助读者掌握泊松回归在真实场景中的适用条件与解释方法。资源包共1个PDF文件,约521KB,以图文形式完整呈现代码、输出结果与分析结论,便于对照复现。目前已有1076人学习下载,适合希望补齐回归分析技能、理解计数数据建模思路的数据挖掘与统计学习者参考。
1. 泊松回归做航班 O 形环数据分析:23 行数据里藏着多少门道
拿到o-ring-erosion-only.csv的时候,很多人第一反应是「就 23 行,能分析出什么」。但恰恰是这份挑战者号航天飞机的 O 形环热损伤数据,把泊松回归在计数数据上的价值体现得淋漓尽致——因变量Number experiencing thermal distress是 0、1、2 这样的非负整数计数,均值 0.391,方差 0.412,两者几乎相等,正好落在泊松分布「分散均衡」的适用区间里。如果你手头也有类似的计数型业务数据(比如某段时间内的故障次数、投诉工单数、页面点击量),这套用 Python + statsmodels 走 GLM 泊松回归的流程可以直接迁移。这篇笔记面向的是想真正跑通一遍泊松回归、看懂摘要表、并且知道哪里容易翻车的数据分析从业者,不是泛泛讲回归原理的科普。
2. 数据读入与探索:从无表头 CSV 到分散均衡判断
2.1 无表头 CSV 的读入与列名重建
原始文件没有表头,直接read_csv会把第一行数据当成列名,后面所有数值列全变成 object 类型,describe()直接报错。常见做法是用names参数手动指定列名,列名建议同时保留可读性和后续建模的简洁性——我一般会先用完整英文名读进来,确认数据没问题后再统一重命名。
import pandas as pd # 原始 CSV 无表头,用 names 手动指定列名 col_names = [ 'Number of O-ring at risk on a given flight', 'Number experiencing thermal distress', 'Launch temperature(degrees F)', 'Leak-check pressure(psi)', 'Temporal order of flight' ] df_erosion = pd.read_csv('o-ring-erosion-only.csv', names=col_names) print(df_erosion.shape) # (23, 5) print(df_erosion.columns.tolist()) print(df_erosion.head())这里names的长度必须和实际列数严格一致,少一个会报Too many columns specified,多一个会在末尾多出一列全 NaN。读进来之后先看shape确认是 23 行 5 列,再看head()确认第一行不是被误当成表头。如果dtypes里出现 object,八成是某列混进了非数值字符,需要单独排查。
2.2 描述性统计与因变量分布判断
describe()给出的是建模前最重要的一次体检。这份数据里Number experiencing thermal distress的均值 0.391、方差 0.412,两者接近相等,这是泊松回归能用的前提条件。如果方差远大于均值(过度分散),标准泊松回归的标准误会偏小,p 值会假性显著,这时候要考虑负二项回归;如果方差远小于均值(分散不足),泊松假设也不成立。
import numpy as np import matplotlib.pyplot as plt # 基础描述性统计 print(df_erosion.describe()) # 因变量均值与方差对比,判断是否分散均衡 mean_val = np.mean(df_erosion['Number experiencing thermal distress']) var_val = np.var(df_erosion['Number experiencing thermal distress']) print(f'mean={mean_val:.4f}, var={var_val:.4f}') # 频数直方图,直观看因变量分布形态 plt.rcParams['font.family'] = 'simHei' plt.hist(df_erosion['Number experiencing thermal distress'], bins=10, facecolor='blue', edgecolor='black', alpha=0.7) plt.xlabel('区间') plt.ylabel('频数') plt.title('因变量频数分布直方图') plt.show()np.mean和np.var默认按总体计算(ddof=0),和describe()里 pandas 的样本标准差口径不同,对比时要注意统一。直方图的作用是看分布是否严重左偏——如果绝大多数样本都集中在 0,只有极少数取到 2 以上,即使均值方差接近,模型对高计数段的预测也会很弱,这时候要谨慎解读系数。
2.3 列名重命名与特征顺序调整
原始列名太长,写 formula 的时候容易出错,而且 statsmodels 的 formula 接口对含空格和括号的列名支持不好。重命名时我习惯把因变量放到最后一列,自变量在前,这样column_stack构造特征矩阵时顺序一目了然。
# 重命名为简洁列名 df_erosion.rename(columns={ 'Number of O-ring at risk on a given flight': 'num_rings', 'Launch temperature(degrees F)': 'temperature', 'Leak-check pressure(psi)': 'pressure', 'Number experiencing thermal distress': 'num_distress', 'Temporal order of flight': 'order' }, inplace=True) # 调整列顺序:自变量在前,因变量在最后 order = ['num_rings', 'temperature', 'pressure', 'order', 'num_distress'] df_erosion = df_erosion[order] print(df_erosion.head())inplace=True会直接修改原 DataFrame,如果你后面还要用原始列名做对照,建议先copy()一份。列顺序调整本身不影响 GLM 的 formula 写法,但影响column_stack构造的 X 矩阵列序,进而影响你手动核对系数时的对应关系——这一点在排查「系数对不上变量」的问题时特别关键。
3. 泊松回归建模:GLM 调用、系数解读与预测
3.1 statsmodels GLM 的 formula 接口与数组接口
statsmodels 提供两种建模入口:formula 接口(smf.glm)和数组接口(sm.GLM)。formula 接口可读性好,适合变量少、需要反复调整组合的场景;数组接口在变量多、需要程序化生成特征时更灵活。这份数据只有 4 个自变量,用 formula 接口最省事。
import statsmodels.formula.api as smf import statsmodels.api as sm # formula 接口:因变量 ~ 自变量,family 指定泊松分布 glm = smf.glm( formula='num_distress ~ num_rings + temperature + pressure + order', data=df_erosion, family=sm.families.Poisson() ) results = glm.fit() print(results.summary())family=sm.families.Poisson()默认使用 log 链接函数,即log(E[y]) = β₀ + β₁x₁ + ...,所以系数解读时要取指数:exp(β)表示该自变量每增加一个单位,因变量期望值的倍数变化。temperature的系数是 -0.0883,exp(-0.0883) ≈ 0.915,意味着温度每升高 1 华氏度,热损伤 O 形环的期望数量乘以 0.915,即下降约 8.5%。
3.2 模型摘要表逐项解读
摘要表里几个关键位置:coef是系数,std err是标准误,z是 Wald 统计量,P>|z|是 p 值,[0.025 0.975]是 95% 置信区间。这份结果里只有temperature的 p 值 0.036 小于 0.05,其余三个变量都不显著。这说明在控制温度的前提下,O 形环数量、检漏压力和航班时序对热损伤没有统计上显著的影响。
# 单独提取系数,方便后续做 exp 转换 print(results.params) # Intercept 0.098418 # num_rings 0.590510 # temperature -0.088329 # pressure 0.007007 # order 0.011480 # 系数取指数,解读为期望值倍数变化 import numpy as np print(np.exp(results.params))num_rings的系数 0.5905 看起来很大,但 p 值 0.274 不显著,不能直接下结论说「O 形环越多热损伤越多」。这里有个容易翻车的点:num_rings在所有 23 条记录里恒等于 6,标准差为 0,它本质上是个常数,放进模型只会吸收截距的一部分,不可能显著。如果你拿到的是别的数据集,先检查每个自变量的方差,方差为 0 的列直接剔除,否则会浪费自由度还干扰解读。
3.3 预测结果与 RMSE 评估
results.predict(df_erosion)返回的是期望计数,不是整数,需要 round 之后和真实值对比。RMSE 用sklearn.metrics.mean_squared_error开根号即可。
from sklearn.metrics import mean_squared_error # 预测并保留三位小数 df_erosion['predict_result'] = results.predict(df_erosion) df_erosion['predict_result'] = df_erosion['predict_result'].apply(lambda x: round(x, 3)) # 计算 RMSE rmse = np.sqrt(mean_squared_error(df_erosion['predict_result'], df_erosion['num_distress'])) print(f'RMSE: {rmse:.4f}')RMSE 约 0.490,因变量本身均值 0.391、最大值 2,这个误差水平说明模型有一定预测能力但不算强。注意mean_squared_error的参数顺序是(y_true, y_pred),写反了结果一样但语义不对,团队协作时容易造成误解。另外预测值 round 到三位小数后再算 RMSE,和直接用原始预测值算会有微小差异,报告里要注明口径。
4. 避坑与排查:泊松回归落地时最容易翻车的五件事
4.1 现象:describe()报错或数值列变成 object
原因:CSV 无表头且未指定names,第一行数据被当成列名,后续数值列混入字符串。解决:读入时显式传names,读完后用df.dtypes检查每列类型,发现 object 列用pd.to_numeric(df[col], errors='coerce')转换并检查 NaN 数量。
4.2 现象:模型摘要里所有变量都不显著
原因:自变量之间存在共线性,或者某个自变量方差为 0(如本例的num_rings恒等于 6)。解决:建模前先算df.corr()看自变量两两相关,再算df[col].std()剔除零方差列。共线性严重时考虑逐步回归或正则化。
4.3 现象:预测值出现负数
原因:泊松回归的 log 链接理论上保证期望值为正,但results.predict返回的是线性预测的指数变换,数值上不会为负。如果出现负数,八成是你手动对线性预测部分做了减法。解决:直接用results.predict,不要自己拆params手算。
4.4 现象:RMSE 很小但模型没有业务意义
原因:因变量绝大多数为 0,模型只要全预测接近 0 就能拿到低 RMSE。解决:除了 RMSE,还要看Pseudo R-squ. (CS)(本例 0.2633)和残差分布,必要时对高计数段单独评估。
4.5 现象:formula里列名含空格或括号报错
原因:statsmodels 的 formula 解析器把空格和括号当语法符号。解决:先rename成合法 Python 标识符,再写 formula。重命名后记得同步更新column_stack里的列名引用。
5. 进阶技巧:用对数似然和残差图验证泊松假设
跑完基础模型只是第一步,真正决定这份分析能不能写进报告的是假设检验。泊松回归的核心假设是「均值等于方差」,但模型拟合完之后,残差的分布同样重要。我一般会做两件事:一是对比对数似然和偏差统计量,二是画残差与预测值的散点图。
# 对数似然与偏差 print(f'Log-Likelihood: {results.llf:.4f}') print(f'Deviance: {results.deviance:.4f}') print(f'Pearson chi2: {results.pearson_chi2:.4f}') # 残差图:预测值 vs 残差 residuals = df_erosion['num_distress'] - df_erosion['predict_result'] plt.scatter(df_erosion['predict_result'], residuals) plt.axhline(y=0, color='red', linestyle='--') plt.xlabel('预测值') plt.ylabel('残差') plt.title('残差 vs 预测值') plt.show()Deviance15.407 和自由度 19 对比,比值小于 1,说明没有明显的过度分散。Pearson chi223.4 和自由度 19 也比较接近,进一步支持泊松假设成立。残差图如果呈现喇叭口形状(预测值越大残差越分散),说明存在异方差,需要考虑准泊松或负二项。这份数据的残差图基本围绕 0 均匀分布,没有明显模式,可以认为模型设定合理。
另一个容易被忽略的点是Pseudo R-squ. (CS),本例 0.2633,Cox-Snell 伪 R² 在计数模型里不像线性回归的 R² 那样直观,但可以用来对比不同模型对同一数据的拟合优度。如果你尝试加入交互项或去掉不显著变量,这个值的变化方向能帮你判断模型是变好还是变差。
最后说个习惯:每次跑完 GLM,我都会把results.params、results.pvalues、results.conf_int()三个输出拼成一张表,和原始变量名一一对应存下来。因为 statsmodels 的摘要表在变量多的时候会换行,肉眼核对系数和变量名很容易串行。从那以后我每次做泊松回归都强制走一遍「零方差检查 → 共线性检查 → 残差图 → 系数对照表」这四步,再也没在报告里写错过变量。希望帮到你。
本文还有配套的精品资源,点击获取