☰
航班延误次数预测:泊松回归模型实战指南
2026/10/2 4:43:57 网站建设 项目流程

简介:这份资源面向数据分析初学者与进阶学习者,聚焦计数数据建模场景,以挑战者号航天飞机O形环热损伤数据为案例,完整演示泊松回归的实战流程。内容涵盖数据读入与表头处理、探索性分析、均值方差检验、特征矩阵与目标向量准备,以及基于statsmodels的GLM建模、残差诊断与可视化验证,帮助读者掌握从数据理解到模型解释的完整链路。资源包共1个PDF文件,约521KB,以图文形式呈现代码、输出结果与分析结论,便于对照复现。目前已有1076人学习下载,适合希望用Python完成回归分析练习、理解泊松分布假设与广义线性模型应用的数据挖掘学习者参考。

1. 航班延误次数预测:为什么泊松回归是第一个该试的模型

做航班数据分析的人,早晚会撞上一个问题:延误发生的次数能不能预测?不是“延误多久”,而是“这个航班、这条航线、这个时段,会延误几次”。这跟用 Python 做数据分析时常见的回归任务不太一样——普通线性回归输出的是连续值,可以是负数,但“次数”天然是 0、1、2、3 这样的非负整数,而且低频事件扎堆在 0 附近,分布明显右偏。泊松回归就是为这类计数数据设计的:它假设事件发生次数服从泊松分布,用对数链接函数把线性预测子映射到非负的均值上。

放到航班数据分析的场景里,这个模型的价值很直接。你可以用它回答:某条航线在雷雨季的日均延误次数是多少?某个出发时段是不是显著更容易出问题?不同航空公司的延误频次差异有多大?这些问题背后都是计数型因变量,用泊松回归能给出可解释的系数——系数取指数之后就是发生率比(IRR),能直接说“晚高峰时段的延误次数是早班的 1.8 倍”这种话。适合谁看:已经会用 pandas 做基本清洗、跑过线性回归或逻辑回归,但还没系统处理过计数数据的分析师。下面从数据准备一路讲到模型诊断和踩坑,代码可以直接抄。

2. 从原始航班表到建模数据:字段选择与泊松分布验证

2.1 为什么不能直接拿“延误时长”做因变量

很多人第一次做航班数据分析,会顺手把DepDelay(起飞延误分钟数)当因变量丢进线性回归。这个做法有两个硬伤。第一,延误分钟数是连续值,但大量记录集中在 0 附近,还有负值(提前起飞),分布严重偏离正态,线性回归的残差假设直接崩掉。第二,业务上真正关心的是“会不会延误、延误几次”,而不是精确到分钟的时长——分钟数受单次极端天气影响太大,波动没有规律。

泊松回归的因变量必须是计数。在航班场景里,合理的计数因变量有这么几种构造方式:

  • 把一天按小时切分,统计每个小时段内该航班的延误次数(延误定义为DepDelay > 15分钟);
  • 按航线聚合,统计某条航线一周内每天的延误航班架次;
  • 按机场聚合,统计每个机场每天发生的延误事件数。

我一般会选第一种,粒度适中,既有足够样本量,又能保留时段信息。构造完的因变量是一列非负整数,取值 0、1、2、3……这才对得上泊松分布的假设。

2.2 用方差均值比快速判断是否适合泊松

泊松分布有一个很强的性质:均值等于方差。拿到计数数据后,第一件事就是算这两个统计量,看比值。如果方差远大于均值,说明存在过度离散(overdispersion),标准泊松回归的标准误会偏小,显著性检验会过于乐观,这时候要考虑负二项回归。

import pandas as pd import numpy as np # 假设 df 是清洗后的航班数据,每行一个航班记录 # 构造计数因变量:按 航线+日期+小时段 聚合,统计延误次数 df['is_delay'] = (df['DepDelay'] > 15).astype(int) df['hour_band'] = pd.cut(df['DepTime'] // 100, bins=[0, 6, 9, 12, 15, 18, 21, 24], labels=['0-6', '6-9', '9-12', '12-15', '15-18', '18-21', '21-24']) counts = (df.groupby(['Route', 'FlightDate', 'hour_band']) .agg(delay_count=('is_delay', 'sum'), flight_num=('is_delay', 'count')) .reset_index()) # 方差均值比 mean_c = counts['delay_count'].mean() var_c = counts['delay_count'].var() print(f"均值={mean_c:.3f}, 方差={var_c:.3f}, 比值={var_c/mean_c:.3f}")

这段代码做了三件事:把延误定义成二值再求和得到计数、按航线和小时段聚合、计算方差均值比。参数说明:DepDelay > 15是行业常用的延误阈值,你也可以改成 30 或 60,但阈值一变,计数分布也跟着变,需要重新验证。hour_band的分箱边界参考了航班波峰规律,早高峰 6-9 点、晚高峰 15-18 点单独成段。

比值如果在 1.0 到 1.5 之间,泊松回归基本能用;超过 2.0 就要警惕过度离散,后面第 5 章会讲怎么处理。这一步花不了两分钟,但能帮你省掉后面返工的血泪经验。

2.3 自变量筛选:把“航班为什么延误”翻译成建模特征

因变量构造好了,接下来选自变量。航班延误的常见影响因素就那么几类,但不是什么都能直接塞进模型:

特征类型示例字段处理方式注意事项
时间特征小时段、星期几、月份独热编码或周期编码小时段和延误非线性相关,别当连续变量
航线特征航线距离、出发/到达机场距离标准化,机场做目标编码高基数机场直接独热会爆炸
承运人航空公司代码独热编码注意样本量少的航司合并成“其他”
天气出发/到达机场天气二值化或分级缺失率高,需单独处理
飞机特征机型、机龄机型分组,机龄分箱机龄缺失多,谨慎使用

我一般会先跑一个相关性筛查,把和延误计数相关性低于 0.05 的字段先放一边,避免维度太高导致收敛慢。另外,出发机场和到达机场不能同时用独热编码,否则共线性会让系数符号翻转,解释起来就是玄学了。常见做法是保留出发机场,到达机场用目标编码或者直接丢掉。

# 特征工程示例 from sklearn.preprocessing import StandardScaler # 周期编码:把小时段转成 sin/cos,保留周期性 counts['hour_num'] = counts['hour_band'].cat.codes counts['hour_sin'] = np.sin(2 * np.pi * counts['hour_num'] / 7) counts['hour_cos'] = np.cos(2 * np.pi * counts['hour_num'] / 7) # 航线距离标准化 scaler = StandardScaler() counts['distance_std'] = scaler.fit_transform(counts[['Distance']]) # 独热编码承运人,注意 drop_first 避免共线 carrier_dummies = pd.get_dummies(counts['Carrier'], prefix='carrier', drop_first=True) counts = pd.concat([counts, carrier_dummies], axis=1)

周期编码是为了让模型理解 23 点和 0 点是相邻的,而不是首尾相隔最远。drop_first=True是回归建模的常规操作,避免虚拟变量陷阱。这些处理做完,数据就可以喂给泊松回归了。

3. 用 statsmodels 跑通泊松回归:公式、参数与 IRR 解读

3.1 最小可运行代码:GLM 与 Poisson 家族

Python 里做泊松回归,最顺手的是statsmodels的 GLM 接口,因为它直接给出系数、标准误、p 值和置信区间,比 sklearn 的PoissonRegressor更适合做统计推断。下面是最小可运行版本:

import statsmodels.api as sm import statsmodels.formula.api as smf # 构造公式:因变量 ~ 自变量 formula = ('delay_count ~ hour_sin + hour_cos + distance_std + ' 'carrier_AA + carrier_DL + carrier_UA + carrier_WN') # 泊松回归 model = smf.glm(formula=formula, data=counts, family=sm.families.Poisson()).fit() print(model.summary()) # 计算 IRR(发生率比) irr = np.exp(model.params) print("\nIRR:") print(irr)

逻辑说明:smf.glm是广义线性模型接口,family=sm.families.Poisson()指定泊松分布和对数链接。model.params是线性预测子上的系数,取np.exp()之后就是 IRR。参数说明:公式里的carrier_XX是独热编码后的列名,实际跑的时候要换成你数据里的航司代码。如果自变量里有分类变量,也可以直接用C(Carrier)让 statsmodels 自动处理,但那样 IRR 的基准组不好控制,我一般还是手动编码。

3.2 系数怎么读:IRR 才是给业务方看的东西

model.summary()输出的系数是对数尺度上的,直接读会很别扭。比如hour_sin的系数是 0.32,意思是小时段的 sin 值每增加 1,延误次数的对数均值增加 0.32。业务方听不懂这个。取指数之后,IRR = 1.38,意思是“在其他条件不变时,该变量每增加一个单位,延误次数变为原来的 1.38 倍”。

对于独热编码的航司变量,IRR 的解释是“相对于基准航司,该航司的延误次数是基准的多少倍”。比如carrier_WN的 IRR 是 1.25,说明 WN 航司的延误次数比基准航司高 25%。基准航司是drop_first=True时被丢掉的那一个,通常是字母序最靠前的。

提示:IRR 大于 1 表示增加延误风险,小于 1 表示降低风险。置信区间跨过 1 的变量,在 95% 水平上不显著,解释时要谨慎。

3.3 模型拟合优度:Deviance 和 AIC 怎么看

summary()里有两个关键指标:Deviance和Pearson chi2。泊松回归的 Deviance 服从卡方分布,自由度是样本数减去参数个数。如果 Deviance 除以自由度接近 1,说明模型拟合良好;如果远大于 1,说明过度离散,需要换负二项回归或者加变量。

AIC 用来比较不同模型,越小越好,但只能比较同一数据集上的模型。我一般会跑一个只有截距的零模型,再跑全模型,看 Deviance 下降了多少。下降幅度大且显著,说明自变量有解释力。

# 零模型对比 null_model = smf.glm('delay_count ~ 1', data=counts, family=sm.families.Poisson()).fit() print(f"零模型 Deviance: {null_model.deviance:.2f}") print(f"全模型 Deviance: {model.deviance:.2f}") print(f"Deviance 下降: {null_model.deviance - model.deviance:.2f}") print(f"自由度差: {null_model.df_resid - model.df_resid}")

Deviance 下降量服从卡方分布,自由度差就是新增参数个数。如果下降量对应的 p 值小于 0.05,说明新增变量整体显著。这一步是模型比较的基本功,别跳过。

4. 避坑与排查:泊松回归在航班数据上的 5 个翻车现场

4.1 现象:模型不收敛,报“Perfect separation”或“Singular matrix”

原因:某个自变量和因变量完全共线,或者某个分类水平下所有样本的延误次数都是 0。比如你独热编码了一个只有 3 条记录的小航司,这 3 条全是 0 延误,模型无法估计它的系数。

解决:检查每个分类水平的样本量和事件率。样本量少于 30 或者事件率为 0 的水平,合并到“其他”类别。连续变量如果方差接近 0,也要删掉。

4.2 现象:IRR 大得离谱,比如 1e6 这种数量级

原因:某个自变量存在极端异常值,或者量纲没有标准化。比如航线距离直接用公里数,系数会非常小,但取指数后可能因为数值精度问题变成奇怪的值。更常见的是把“航班号”这种高基数标识符当自变量了。

解决:连续变量做标准化或归一化;标识符类字段一律不进模型;检查model.params里有没有绝对值超过 10 的系数,有的话逐个排查对应变量。

4.3 现象:Deviance/自由度比值超过 3,p 值全都显著但业务上不显著

原因:过度离散。航班延误数据里,同一航线不同日期的延误次数波动很大,泊松分布的均值等于方差假设不成立。这时候标准误被低估,t 值虚高,什么变量都“显著”。

解决:换负二项回归(sm.families.NegativeBinomial),或者在泊松模型里加scale='X2'做准泊松估计。负二项回归多一个离散参数,能吸收额外方差。我一般先跑负二项,对比 AIC,如果负二项明显更小,就用负二项。

4.4 现象:预测值出现大量 0,但实际数据里 0 的比例没那么高

原因:零膨胀(zero-inflation)。航班数据里,有些航线可能因为航班取消、数据缺失等原因,延误次数永远是 0,这些“结构性零”和“随机零”混在一起,标准泊松回归拟合不好。

解决:用零膨胀泊松回归(ZIP)或者 hurdal 模型。statsmodels 里没有现成的 ZIP,但可以用statsmodels.discrete.count_model.ZeroInflatedPoisson。不过 ZIP 的解释更复杂,如果零的比例不超过 40%,我一般先不折腾。

4.5 现象:训练集 IRR 显著,换一个时间段预测就崩了

原因:时间泄漏或者分布漂移。比如你用全年的数据训练,但自变量里包含了“月份”,而月份和延误的关系在淡旺季完全不同。模型学到了训练集的季节模式,换到另一个季节就不适用。

解决:按时间切分训练集和测试集,不要随机切分。如果要做跨季节预测,把月份从自变量里去掉,或者用滚动窗口验证。航班数据的时序性很强,随机切分是自欺欺人。

5. 进阶技巧:用负二项回归和暴露量处理真实航班数据

5.1 什么时候该从泊松换到负二项

第 4 章提到过度离散,但具体怎么判断?我一般看两个指标:一是 Deviance/自由度比值,超过 1.5 就警惕,超过 2.0 就换;二是跑一个似然比检验,比较泊松和负二项的拟合差异。

# 负二项回归 nb_model = smf.glm(formula=formula, data=counts, family=sm.families.NegativeBinomial()).fit() print(nb_model.summary()) # 对比 AIC print(f"泊松 AIC: {model.aic:.2f}") print(f"负二项 AIC: {nb_model.aic:.2f}") # 似然比检验 from scipy import stats lr_stat = 2 * (nb_model.llf - model.llf) p_value = stats.chi2.sf(lr_stat, df=1) print(f"LR stat: {lr_stat:.2f}, p-value: {p_value:.4f}")

负二项回归的系数解释和泊松一样,取指数就是 IRR。区别在于它多了一个离散参数 alpha,alpha 显著大于 0 就说明确实存在过度离散。如果 LR 检验 p 值小于 0.05,老老实实用负二项。

5.2 暴露量:把“航班数”作为 offset 放进模型

航班数据里有一个很实际的问题:有的航线一天飞 10 班,有的只飞 2 班。延误次数自然和航班数正相关,但你想比较的是“延误率”而不是“延误次数”。这时候用 offset 把航班数放进去,模型就变成在建模“每班航班的平均延误次数”。

# 用 log(flight_num) 作为 offset offset_formula = ('delay_count ~ hour_sin + hour_cos + distance_std + ' 'carrier_AA + carrier_DL + carrier_UA + carrier_WN') offset_model = smf.glm(offset_formula, data=counts, family=sm.families.Poisson(), offset=np.log(counts['flight_num'])).fit() print(offset_model.summary())

参数说明:offset必须是线性预测子尺度上的,所以要对航班数取对数。加了 offset 之后,IRR 的解释变成“每增加一个单位自变量,单位航班数的延误次数变为原来的多少倍”。这个技巧在网约车数据分析、医院感染率分析里也通用,本质是把分母信息纳入模型。

5.3 验证方法:用留存数据做时间外推

模型跑完不能只看 AIC,得用没见过的数据验证。我习惯按时间切:前 8 个月训练,后 4 个月测试。预测值和实际值的对比,用平均绝对误差(MAE)和均方根误差(RMSE)衡量。

from sklearn.metrics import mean_absolute_error, mean_squared_error # 按时间切分 train = counts[counts['FlightDate'] < '2024-09-01'] test = counts[counts['FlightDate'] >= '2024-09-01'] train_model = smf.glm(formula, data=train, family=sm.families.Poisson()).fit() pred = train_model.predict(test) mae = mean_absolute_error(test['delay_count'], pred) rmse = np.sqrt(mean_squared_error(test['delay_count'], pred)) print(f"MAE: {mae:.3f}, RMSE: {rmse:.3f}")

如果测试集 MAE 比训练集大很多,说明模型过拟合或者分布漂移。这时候回头检查自变量里有没有时间相关的字段,或者考虑加正则化。泊松回归本身没有正则化选项,但可以用 sklearn 的PoissonRegressor加 L2 惩罚。

5.4 一个我常犯的错误

早期做航班延误预测,我直接把DepDelay的均值当因变量,跑出来模型 R² 很高,但业务方一问“明天延误几次”就答不上来。后来才想明白,泊松回归的因变量必须是计数,不是均值。均值是连续值,计数是离散的,两者背后的数据生成过程不一样。这个教训让我后来每次建模前都先画因变量的直方图,确认它是计数分布再往下走。

希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询