我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

泊松回归实战:航班延误计数数据建模与Python实现

泊松回归实战:航班延误计数数据建模与Python实现 简介这是围绕挑战者航天飞机O形环热损伤数据展开的泊松回归实战文档面向正在学习Python数据分析、广义线性模型或计数数据挖掘的读者也适合统计建模入门者用作课内课外练习。文档以原始数据为基础详细演示了如何通过pandas的read_csv读取无表头CSV并手动设置列名如何使用describe()、shape、columns查看数据概况以及如何绘制因变量频数直方图并计算均值与方差来判断其是否符合泊松分布“分散均衡”的前提条件。建模环节采用statsmodels的GLM模块执行泊松回归结合发射温度、捡漏压力、航班时序等变量解释热损伤数量的变化并包含残差检查、似然比检验、系数显著性分析及可视化诊断等方法。读者可从中掌握计数数据建模的完整流程、回归结果的解释技巧以及如何检验模型有效性和数据条件。资源仅含1个PDF文件总大小521KB内容紧凑且步骤清晰目前已有1076人学习下载适合作为课程实验指导或自学进阶的配套材料。1. 航班数据分析为什么要用泊松回归先看懂计数数据的脾气拿到航班数据很多人第一反应就是跑一个线性回归拿延误分钟数当因变量航司、航线、天气、时刻挨个往里塞。跑完发现 R² 低得没法看残差图长得像一把散弹枪玄学调参折腾两天也不见好转。换个思路别预测“延误多少分钟”改成预测“一天内某航线延误超过 15 分钟的次数”问题反而一下子清爽了——因为这是一个典型的计数数据泊松回归就是为这种“单位时间内事件发生次数”量身定做的模型。泊松回归在这个场景下的核心价值不是拟合延误时长而是拟合“事件计数”某机场一天内取消的航班数、某航线一周内延误超过阈值的天数、某个时刻段内进港排队等待超过 20 分钟的航班架次。这类因变量有两个鲜明特征取值是非负整数而且方差往往随着均值一起变大。线性回归假设误差正态分布、方差齐性碰上计数数据基本是硬套泊松回归假设因变量服从泊松分布均值等于方差配合对数连接函数保证预测值始终为正。这篇文章就沿着“为什么选泊松回归 → 航班数据怎么清洗构造 → 模型怎么跑通 → 系数和拟合优度怎么看 → 过离散、零膨胀这些坑怎么排”的顺序把一次完整的航班数据分析实战走完适合刚接触广义线性模型、想拿真实业务数据练手的数据分析师。2. 泊松回归的原理与航班数据的适配性从分布假设到连接函数2.1 泊松分布到底在描述什么单位时间内的稀有事件计数泊松分布描述的是“在固定时间或空间范围内某事件发生的次数”。两个前提条件一是事件独立二是单位时间内事件发生的平均速率 λ 恒定。航班数据天然贴合这个框架——我们关心的不是“某架飞机晚点多少分钟”而是“一天内延误航班出现的架次”。一架航班是否延误和另一架航班是否延误在统计意义上可以被近似看成独立事件虽然天气、流控会造成区域性联动但建模时我们通过特征变量——比如天气能见度、起飞机场繁忙程度——把这些联动信息作为协变量吸收掉剩下的随机波动再交给分布假设去描述。泊松分布的独特性在于它的方差等于均值。这意味着计数数据的随机波动幅度是随均值水平的增加而增加的当某个机场平均一天只取消 0.5 个航班时观测值在 02 之间波动很正常当另一个机场平均一天取消 20 个航班时观测值在 1525 之间波动也很正常。这种“均值越大、波动越大”的特性方差齐性的线性回归根本处理不了。如果强行用最小二乘法去拟合会出现两个典型问题预测值出现负数取消次数不可能是负数低均值区域的样本被过度加权因为它们的方差天然更小。在航班数据分析中泊松回归的角色是建立“事件计数”与“解释变量”之间的对数线性关系。标准的模型形式是log(λ) β₀ β₁x₁ β₂x₂ … βₖxₖ。等号左边是事件发生速率的对数右边是特征的线性组合。取对数有两个原因一是保证 λ≥0不管右边算出什么值取指数之后都是正的二是把乘法关系转换成加法关系特征每增加一个单位事件速率乘以 exp(β)这个倍数关系非常直观。2.2 连接函数对数链接为什么是默认选项解释性优先于拟合精度泊松回归之所以默认使用对数连接而不是恒等连接或平方根连接主要是为了保住“速率倍数”的解释方式。假设我们建一个模型预测某机场一天内的延误航班数起飞机场繁忙程度这个特征系数是 0.05那么 exp(0.05)≈1.051说明该特征每增加一个单位延误航班数是原来的 1.051 倍。物流行业管这个叫“乘数效应”业务同学问“哪条航线延误风险大”你直接回答“某些航线是其他航线的 1.8 倍”比“延误次数增加 3.2 次”更符合计数数据的语义。另一个原因是它天然处理了边界问题。计数数据的预测值必须非负如果采用恒等连接模型直接预测 λ β₀ β₁x₁ …特征一旦取到极端组合预测值很可能被推成负数。对数链接让模型输出的永远是 log(λ)要还原成原始尺度还得做一次 exp()这个过程从数学上杜绝了负预测。在航班延误分析里这很重要——你不可能向航司解释“今天预计的延误航班数是-2 架”。还有一层容易被忽略的选择理由如果因变量真的是一个稀有的小概率事件泊松分布本身就接近二项分布的极限形态。航班取消、长时间延误、备降这些事件在总航班量中的占比通常很小正好落在泊松分布描述最好的区间。对比负二项回归泊松回归在满足均值等于方差的条件下更高效参数估计的方差更小解释也更简单。2.3 航班数据建模的场景拆解哪些因变量真正适合泊松回归不是所有航班问题都能套泊松回归。我做这类分析时先做一道筛选因变量必须是“一个计数”而且得能说清楚“计数的时间基底”。下面这张表是我常用的场景划分场景因变量是否适合泊松回归说明某航线当日延误超 15 分钟航班数计数适合时间基底是“每天”可用于航线风险排序单架航班的延误分钟数连续非负变量不适合建议用伽马回归或对数正态回归或直接做分位数回归某机场月度取消率取消数/计划数比例可用泊松offset注意用对数计划班次量做暴露变量某机型一年内机械故障次数计数适合暴露量是飞行小时数需要用 offset 调整某时段进港排队超 20 分钟航班数计数适合时间基底明确但需关注高峰时段序列相关性好的因变量候选必须是“在一段明确时间内的发生次数”。航班延误分钟数、机场吞吐量这种连续变量不适合取消率、延误率这种分子分母结构的比例数据则要小心——直接拿比率做泊松回归会丢掉分母信息正确做法是用对数分母做 offset 项。offset 就相当于强制把分母的系数固定为 1把“率”变成“期望计数 暴露量 × 风险率”。常见做法是在模型公式里写成延误次数 ~ 特征 offset(log(计划航班数))这样才算把比例数据用对。3. 航班数据准备与特征工程清洗、聚合、时间窗拆分三步走3.1 清洗规则无效航班、极端延误、机场关闭天怎么处理拿到一份航班原始数据常见格式类似每条记录包含航班号、起飞机场、到达机场、计划起飞时间、实际起飞时间、计划到达时间、实际到达时间、取消标志、延误原因等字段。清洗阶段我会按固定顺序执行以下操作先剔除计划起飞时间缺失的样本这类记录连时间基底都无法确定然后剔除实际起飞时间与计划起飞时间差值超过 12 小时的极端记录这种数据多半是录入错误或系统时间异常取消航班单独保留不能直接删除因为取消也是重要的计数事件。接着做三件容易被忽略的事——去重复航班号、修正时区偏移、补齐计划班次量。航班号加计划起飞日期应该唯一出现重复说明上游系统重放数据跨时区航线要统一换算成 UTC 时间否则高峰时段错位会导致计数失真每天的航班计划总量要单独存储一个表它是后面计算取消率、延误率时必须的分母。这个分母信息千万别从明细里数因为取消航班、返航航班、备降航班在实际记录里可能重复计数。清洗之后做一个核心步骤把粒度从“航班级”聚合到“天级”或“周级”。泊松回归处理的是聚合后的计数数据一粒一粒的航班明细不能直接进模型。聚合的维度取决于业务问题要判断航线风险按“航线 日期”聚合统计每天的延误次数要做机场容量分析按“机场 日期”聚合统计每天的延误与取消总数要做时刻效应分析按“机场 时刻段 日期”聚合统计每个两小时窗口内的延误架次。聚合是特征工程的起点也是让数据适配泊松分布的关键一步。import pandas as pd import numpy as np # 读取原始航班明细假设包含以下字段 df pd.read_csv(flights_raw.csv, parse_dates[scheduled_time, actual_time]) df df.dropna(subset[scheduled_time]) # 统一为 UTC 时间避免跨时区航线导致高峰时段错位 df[scheduled_utc] df[scheduled_time].dt.tz_convert(UTC) df[actual_utc] df[actual_time].dt.tz_convert(UTC) # 计算延误分钟数取消航班标记为 NaN单独处理 df[delay_minutes] (df[actual_utc] - df[scheduled_utc]).dt.total_seconds() / 60 # 去除异常极端延误记录12 小时以上视为数据异常 df df[df[delay_minutes].between(0, 720) | df[delay_minutes].isna()] # 剔除重复航班号与计划起飞日期 df df.drop_duplicates(subset[flight_no, scheduled_utc]) # 生成日期字段用于后续聚合 df[date] df[scheduled_utc].dt.date # 按航线日期聚合得到每日延误超 15 分钟的航班数 daily_delay ( df[df[delay_minutes] 15] .groupby([route, date], as_indexFalse) .size() .rename(columns{size: delay_count}) )这段代码里最值得留意的是between(0, 720)这个范围延误时间不可能是负数超过 720 分钟12 小时的记录在民航运行里几乎可以断定是系统异常但注意我用|保留了取消航班NaT因为取消航班虽然无法计算延误分钟数却可能成为另一个模型的因变量。drop_duplicates(subset[flight_no, scheduled_utc])这一行的目的是防止同一航班在数据中被记录两次——这在一些机场报文系统和航空公司运行系统拼接的数据里很常见。聚合时筛delay_minutes 15对应中国民航对“航班延误”的常见统计口径。3.2 特征构造历史延误率、天气强度、机场繁忙度、季节性聚合完得到的是“行 航线/日期/时刻段列 计数”的数据下一步是给每一行配齐解释变量。特征构造我按四个维度找历史表现类、环境类、运行压力类、时间结构类。历史表现类特征是预测能力最强的一类。某条航线过去 7 天平均每日延误次数过去 30 天平均延误时长前一日同一时段延误次数——这些滞后特征能把“惯性”带进模型。构造方式是把聚合结果做按时间滑窗的 shift 操作注意是按航线分组后组内 shift否则会把其他航线的数据串进来。环境类特征包括降水强度、风速、能见度。天气数据通常按小时给出需要先降采样到和航班数据相同的粒度再做交叉——比如“某机场当日最大风速”“日累计降水量是否超过 20mm”。我的习惯是把天气做成分段条件特征而不是直接丢连续值因为航班运行对天气存在阈值响应风速小于 8m/s 几乎无影响超过 12m/s 影响明显加大连续值在泊松回归里解释起来不够直观分箱之后每个档位对应一个独立的倍数效应。# 构造滞后特征按航线分组组内计算过去 7 天滚动平均延误数 daily_delay daily_delay.sort_values([route, date]) daily_delay[delay_avg_7d] ( daily_delay.groupby(route)[delay_count] .transform(lambda x: x.shift(1).rolling(7, min_periods3).mean()) ) # 构造机场繁忙度当日计划航班总量 scheduled_count df.groupby([date, dep_airport]).size().reset_index(namedep_schedule_count) daily_delay daily_delay.merge(scheduled_count, on[date, dep_airport], howleft) # 构造天气阈值分段特征按日最大风速分档 weather pd.read_csv(weather_daily.csv, parse_dates[date]) weather[wind_level] pd.cut(weather[max_wind_speed], [-np.inf, 8, 12, np.inf], labels[0, 1, 2]) daily_delay daily_delay.merge(weather, on[date, dep_airport], howleft)shift(1).rolling(7)这个组合是防止信息泄露的关键shift(1)表示只用前一天的滚动结果来预测今天rolling(7)表示前 7 天的窗口min_periods3表示至少要有 3 天有数据才参与计算解决了航季初期数据不足的问题。如果忘记shift(1)当天的延误数会被自己的滚动均值预测模型效果虚高一旦上线做真实预测立刻翻车。机场繁忙度特征本质上是一个“体量”特征——航班越多出问题的绝对数越大但拟合时要注意它是否和因变量存在内生性问题这个等会儿在 offset 部分细说。天气分箱用pd.cut的边界值 8/12 参考的是民航运行手册里常见的风速告警阈值也可以根据历史数据与延误次数的关系手动调整分箱点。3.3 训练集与测试集划分按时间切而不是按比例随机切航班数据有强时间序列属性划分训练集和测试集绝不能随便train_test_split否则会出现严重的时序漂移问题。某条航线刚进入夏秋航季换了大机型运力提升 30%如果测试集里混进了航季切换后的数据模型评估结果就会被结构性变化污染。常见做法是按日期切分比如用前 9 个月做训练最后 1 个月做验证——注意不是最后 30 天而是按完整周切避免周内不同日子带来的偏差。我在真实项目里一般会切出两个验证集一个“紧邻验证集”用来检查短期内模型是否稳定一个“远期验证集”用来检查模型在航季边界上的泛化能力。紧邻验证集取最后两周远期验证集取跨航季前后的一个月。两个验证集的评估指标经常出现显著落差——紧邻验证集表现好、远期验证集崩盘这本身就是一种信号模型过拟合了短期的重复模式没有学到真正的业务结构。# 按时间切分约定最后 8 周做测试 weekly_bounds daily_delay[date].max() - pd.Timedelta(weeks8) train daily_delay[daily_delay[date] weekly_bounds].copy() test daily_delay[daily_delay[date] weekly_bounds].copy() # 验证检查训练集和测试集的日期范围是否无重叠、无断档 print(训练期, train[date].min(), →, train[date].max()) print(测试期, test[date].min(), →, test[date].max()) # 构造公式需要的 X 与 y feature_cols [delay_avg_7d, dep_schedule_count, wind_level, is_weekend, month] X_train, y_train train[feature_cols], train[delay_count] X_test, y_test test[feature_cols], test[delay_count]切分之后必须检查两件事训练集的最后一天和测试集的第一天是否连续测试集是否包含了完整的周一到周日。第一件保证没有跨天信息泄漏第二件保证评估周期均匀。如果切分出了断档比如测试集跳过了三天那三天的航班运行特征可能和相邻日完全不同评估结果就会带上额外的噪声。顺带提一个细节is_weekend和month这两个时间特征要在切分之前构造好不能在构建模型时再去取测试集日期的属性否则测试集的信息在特征工程阶段就已经被看到了。4. 用 Python 实现泊松回归从 statsmodels 公式到参数解读4.1 最小可复现步骤GLM 模型拟合与预测Python 里做泊松回归常用 statsmodels 的 GLM 接口公式风格和 R 类似对熟悉回归建模的同学非常友好。它的核心是把分布族指定为Poisson连接函数默认就是log()不需要额外写。最小可复现的代码包含四步构造train数据、写公式、拟合模型、输出指标。下面这段代码可以直接替换你的特征和列名跑通import statsmodels.api as sm import statsmodels.formula.api as smf # 泊松回归延误次数由历史表现、机场体量、时刻、季节共同解释 model smf.glm( delay_count ~ delay_avg_7d dep_schedule_count C(wind_level) C(is_weekend) C(month), datatrain, familysm.families.Poisson(), ).fit() print(model.summary()) # 模型预测输出的是 log(λ)取指数还原为期望延误数 test[pred_delay] model.predict(test) test[pred_delay_exp] np.exp(model.predict(test, linearTrue))smf.glm里C(wind_level)表示把风速档位当成分类型变量每个档位得到一个单独的系数参照组是 wind_level0。C(month)同理把一年 12 个月做成分季节特征因为不同月份的航班运行受航季、天气模式、旅客出行结构的影响差异很大直接放连续月份会强行假设一月到二月的变化方向与七月到八月一致这个假设通常不成立。model.predict(test)返回的是经过连接函数逆变换后的原始尺度预测值严格说 statsmodels 对训练好的模型调 predict 时默认已做逆变换而且对于计数模型它返回的是期望值 λ——注意这里是“期望出现多少次”不是一个具体的整数这是泊松回归预测值和传统回归预测的最大区别。4.2 读模型摘要系数、p值、伪R²与偏差指标分别看什么模型摘要输出最需要盯住三个部分系数表、拟合优度、偏差统计量。系数表的列包括 coef、std err、z 值、P|z| 和置信区间。coef 是在对数尺度下的解读时必须先取指数。比如 wind_level 为 2大风档的系数是 0.45exp(0.45)≈1.57代表大风天气下延误次数的期望是基准天气的 1.57 倍。z 值和 P|z| 对应的原假设是“系数为 0”如果 P 值大于 0.05 说明该特征的效应在统计上不显著不显著的特征通常不是没影响而是样本量不够或者特征之间相关性太高导致标准误膨胀。伪 R² 在泊松回归里不能像线性回归那样直接解读为“解释了多少百分比方差”。statsmodels 输出的Pseudo R-squ.是 McFadden 形式计算公式是 1 - 全模型的 log-likelihood / 空模型的 log-likelihood。它在 0.20.4 之间就属于相当不错的拟合了别拿线性回归 R²0.85 的标准来要求——计数数据天生噪声更大。真正重要的是Deviance和Pearson chi2这两行Results: Generalized linear model Model: GLM Df Residuals: 2190 Model Family: Poisson Df Model: 14 Link Function: Log Scale: 1.0000 Method: IRLS Log-Likelihood: -1542.3 Date: 2025-XX-XX Deviance: 2781.5 No. Observations: 2205 Pearson chi2: 3.12e03Deviance 除以残差自由度得到的值就是过离散检验的核心指标2781.5 / 2190 ≈ 1.27略大于 1说明存在轻微过离散。这个值大于 2 就要警惕了意味着泊松模型的标准误被低估系数的显著性检验偏乐观需要切换为负二项回归或者使用准泊松quasi-Poisson矫正。Pearson chi2 的值可以同理解读它评估的是方差假设是否成立——如果模型正确Pearson 统计量应当接近残差自由度大幅度超出说明方差远大于均值假设。这两个指标在summary()里没有直接列出来需要手动访问模型的deviance和df_resid去算。4.3 模型比较与选择AIC 作为候选特征组合的仲裁者泊松回归的特征选择我一般不依赖逐步回归而是直接在几个业务上说得通的候选模型之间用 AIC 做比较。AIC 是 -2 乘对数似然加 2 乘参数个数它同时惩罚了拟合优度和参数数量防止你往模型里塞进一堆没解释力的特征。两个模型嵌套时可以用似然比检验LRT看复杂模型是否显著更优不嵌套时AIC 直接比较差值超过 2 才说明有实质差异。# 候选模型一只含历史与体量 model_simple smf.glm( delay_count ~ delay_avg_7d dep_schedule_count, datatrain, familysm.families.Poisson() ).fit() # 候选模型二加上天气与时间结构 model_full smf.glm( delay_count ~ delay_avg_7d dep_schedule_count C(wind_level) C(is_weekend) C(month), datatrain, familysm.families.Poisson() ).fit() # 似然比检验比较嵌套模型的优劣 from scipy import stats lr_stat 2 * (model_full.llf - model_simple.llf) p_value stats.chi2.sf(lr_stat, df(model_full.df_model - model_simple.df_model)) print(fLR {lr_stat:.3f}, p {p_value:.4f})这段代码里llf是模型的对数似然值df_model是模型的自由度两者差值衡量了“多出来的参数是否显著改善了拟合”。如果 p 值大于 0.05说明天气和时间结构没有带来显著提升那么选简单模型——它的预测更稳碰到新数据更不容易抖。需要特别提醒泊松回归的对数似然值依赖样本量如果两个候选模型的样本量不同比如一个模型因为特征缺失被剔除了更多行AIC 和似然比检验的结果就不可信。所以在做模型比较前先把所有模型输入的数据行对齐也就是统一用“无任何特征缺失”的子集来训练。5. 航班数据泊松回归的 4 个典型坑过离散、零膨胀与暴露偏移如何排查5.1 过离散Deviance/df 远超 1 时模型白学了现象模型摘要里 Deviance 除以残差自由度大于 2甚至更大。最直观的感受是预测值和实际值偏差极大模型在训练集上的置信区间偏窄但验证集上一塌糊涂。原因泊松分布假设方差等于均值但航班延误计数往往是“某些日子集中爆发大部分日子风平浪静”方差明显大于均值——方差是均值的两倍三倍都不奇怪。这种额外波动可能来自未观测到的变量比如流控政策变化、临时军事活动、机场跑道检修这些没有进入模型但真实影响了计数。解决两条路一是换负二项回归NegativeBinomial它自带一个离散参数来吸收额外方差二是用准泊松quasi-Poisson只修正标准误而不改变系数估计。实操上我更喜欢负二项因为它的输出仍然是完整概率分布可以计算预测区间而准泊松只改了推断结果。# 过离散检验计算 Deviance / df_resid dispersion model.deviance / model.df_resid print(fDispersion {dispersion:.3f}) # 若 dispersion 明显大于 1切换为负二项回归 model_nb smf.glm( delay_count ~ delay_avg_7d dep_schedule_count C(wind_level) C(month), datatrain, familysm.families.NegativeBinomial(alpha0.01), # alpha 为离散参数初值 ).fit()负二项回归的alpha参数是额外离散参数0 时退化为泊松越大说明方差膨胀越严重。statsmodels 的NegativeBinomial需要给alpha一个初值常见做法是先用泊松模型算出 Pearson 统计量估计一个粗略的离散系数再把它作为alpha初值。切换模型后最重要的检查是看负二项模型的 AIC 是否比泊松模型小——如果差不多甚至更大那说明过离散是假象大概率是模型缺了关键特征而不是数据结构真的过离散。5.2 零膨胀大量“无延误日期”把泊松模型的均值拉偏现象训练数据的延误计数有大约 40% 以上都是 0模型预测的均值总是高于实际 0 的频率。从业务上看也可以理解天气好的日子、非高峰季节很多支线航线一周里根本没有航班延误0 的次数远超泊松分布的预期。原因泊松分布同时负责“发不发生”和“发生几次”两层决定当大量 0 出现时模型为了迁就 0 的比例会把整体均值压低结果真实有延误的日子也被低估。解决换零膨胀泊松模型ZIP它由两个部分组成——逻辑回归负责估计“是否会进入计数状态”泊松部分负责估计“计数状态下的期望次数”。# 零膨胀泊松假设延误计数存在大量结构性 0 from statsmodels.discrete.count_model import ZeroInflatedPoisson zip_model ZeroInflatedPoisson( endogy_train, exogsm.add_constant(X_train[[delay_avg_7d, dep_schedule_count]]), exog_inflsm.add_constant(X_train[[is_weekend]]), ).fit() print(zip_model.summary())exog_infl是“膨胀部分”的特征它建模的是“0 是结构性的还是随机性的”。比如周末很多支线机场航班总量本来就不高延误数天然为 0那is_weekend放进膨胀方程是合理的。判断是否真的需要零膨胀模型可以用 Vuong 检验或者直接比较 ZIP 和普通泊松的 AIC——一旦 ZIP 的 AIC 显著小于泊松说明零膨胀结构是真实的。零膨胀模型最大的风险是膨胀方程设定错误把不该膨胀的特征放了进去导致计数部分和膨胀部分互相纠缠系数乱套。我的习惯是先跑普通泊松看 0 的预测比例如果预测 0 的比例远低于实际 0 的比例再考虑零膨胀。5.3 暴露偏移航线航班量不同计数对比失去公平性现象分子是延误次数分母是航班计划量但模型里没有出现分母。结果就是大型航空枢纽的延误次数天然比支线机场高体量特征解释了一切小机场的真实运行风险反而被淹没。原因泊松回归的 λ 是“期望事件数”不是“事件率”。不同航线的航班量不同比较期望事件数就没有意义。解决把对数航班计划量作为偏移项放进模型强制它的系数为 1把“次数模型”变成“率模型”。# 使用 offset把计划航班数的对数作为暴露量 model_offset smf.glm( delay_count ~ delay_avg_7d C(wind_level) C(month), datatrain, offsetnp.log(train[scheduled_count]), # 关键log 后的计划班次量 familysm.families.Poisson(), ).fit()offset是np.log(train[scheduled_count])而不是原始计划量因为模型的结构是 log(λ) offset β₀ βxoffset 必须在对数尺度上。使用 offset 之后因变量解释从“期望延误次数”变为“单位航班量下的期望延误率”。这时候如果另一个特征dep_schedule_count还在模型里就要警惕了——它和 offset 高度相关会出现多重共线性系数解释变得很困难。我的做法是用了 offset 就把总量类特征从普通特征里拿掉只保留结构变量天气、时段、航线特征和滞后的率类特征。5.4 时间序列依赖性连续多日延误之间的自相关破坏独立性现象残差相关图显示第 t 天的残差与第 t1 天的残差显著相关模型看似拟合不错但预测区间严重失准。原因延误是有“记忆”的——前一天的流控导致飞机和机组滞留第二天一早的航班就会连锁延误。这就是泊松回归“事件独立”假设被破坏的典型案例。解决最直接的办法是加滞后项——把前一天的延误次数作为一个显式特征放进模型。滞后项能吸收掉相当一部分自相关。更彻底的办法是用泊松分布结合状态空间模型或自回归结构这对大多数业务团队来说生产成本过高滞后特征加滚动均值通常就够了。# 加上前一日延误次数作为自回归项 daily_delay[delay_lag1] daily_delay.groupby(route)[delay_count].shift(1) # 重新拟合并检验一阶自相关 model_ar smf.glm( delay_count ~ delay_avg_7d delay_lag1 C(wind_level) C(month), datatrain.dropna(subset[delay_lag1]), familysm.families.Poisson(), ).fit() # 提取皮尔逊残差检查一阶自相关 from statsmodels.stats.stattools import durbin_watson resid model_ar.resid_pearson dw durbin_watson(resid) print(fDurbin-Watson {dw:.3f}) # 接近 2 说明自相关弱远离 2 要警惕Durbin-Watson 统计量接近 2 表示残差无自相关接近 0 表示正自相关接近 4 表示负自相关。如果加了delay_lag1之后 DW 从 1.2 升到 1.9说明自相关大部分被吸收。但注意delay_lag1与delay_avg_7d存在高度相关因为它们本质上都在描述“过去的延迟水平”只是窗口长度不同。放进同一个模型要看方差膨胀因子 VIF——某个特征 VIF 超过 5 就得考虑去掉一个。滞后滑动窗口之间的关系处理不好会引发另一种“特征重复解释”问题系数符号都可能反转。6. 从“拟合”到“预警”用偏离度排序机场风险并做预测区间验证模型拟合出来不是终点真正的业务交付是把预测值转成可操作的预警信号。我的做法是计算“偏离度”预测延误数与实际延误数之差除以预测标准差偏离度超过 1.5 或 2 的机场/航线进入预警名单。泊松回归在预测阶段可以给出完整分布所以能顺势算出 80% 预测区间这比单纯的点预测可靠得多。偏离度这个指标天然做了一件事把“绝对偏差大”和“统计学意义上的异常”区分开来——枢纽机场延误 30 架次可能只是正常波动支线机场延误 8 架次可能已经是极端事件。# 计算预测均值和 80% 预测区间 lam model.predict(test) upper stats.poisson.ppf(0.90, lam) # 泊松分布累积概率 90% 的分位点 lower stats.poisson.ppf(0.10, lam) # 10% 分位点 # 计算偏离度残差标准化 test[deviation] (test[delay_count] - lam) / np.sqrt(lam) alert_list test[test[deviation] 1.5].sort_values(deviation, ascendingFalse) print(alert_list.head(10))在预测区间这件事上泊松分布给的是一个整数边界比如“预测延误 5.2 架次80% 区间是 38 架次”而不是线性回归那样的连续区间。业务侧看这个数字非常好理解——值班经理只需要知道“今天某机场的延误数落在正常波动之外”至于是 7 还是 9不影响他决定是否启动流量管理预案。偏离度阈值 1.5 不是黄金标准样本量大时可以提高样本量小时建议用 1.2。这个阈值可以拿历史数据做回测选过去 60 天逐日计算偏离度看 1.5 这个阈值能不能正确召回真实发生的大型延误事件。另一个值得做进习惯的验证是“按周聚合误差”而不是“按日聚合误差”。日级别泊松回归的噪声非常大即便模型正确每天的偏差也可能让人怀疑模型坏了。把预测值和实际值按周一、周二这样分组求和再看误差——周一到周五的误差通常在 ±5% 以内周末可能偏大。如果某一天系统性偏大检查当日是否有节日、大型活动、体育赛事这些没进模型的特征才是真正的预测误差来源。我习惯把这种检查做成固定动作每次模型更新后先跑一周的按日交叉验证再看按星期几聚合的误差表二者都过关再上线。做航班数据分析跑泊松回归最深的教训是数据粒度决策比模型选择更重要把航班明细聚合到天级、按航线分组、补偿暴露量、加滞后特征四步做完就是一个能用的模型反过来拿到数据直接套泊松回归大概率会被过离散和零膨胀击穿。模型输出不显著时先怀疑特征和聚合逻辑而不是急着换算法。希望这篇实战笔记能帮你少踩几个我已经帮你踩过的坑。本文还有配套的精品资源点击获取
返回列表