资讯动态

用线性回归做疫情预测:从数据清洗到未来7天趋势

发布时间:2026/10/20 23:09:46 来源:尧图企业网站定制
很多人第一次看到“疫情预测 线性回归”这个组合第一反应多半是就这传染病传播明明是非线性的早期接近指数爬坡中期可能碰到平台期用最小二乘拟一条直线上去能有什么参考价值我当初也是带着这个疑问动手的。实际情况是当你需要在一周之内给出一个可解释、可迭代、协作方能直接看懂的预测基线时线性回归往往是第一个站出来的模型。它不一定最准但它能把问题结构暴露得清清楚楚。我最近做了一个桌面级的疫情预测Demo数据是某地流感样病例的每日新增记录已经做过脱敏处理目标是用线性回归预测未来7天的发病趋势。整条链路从数据清洗、特征构造、模型训练到结果解读都跑了一遍没有上任何复杂的时序模型就靠一个普通的LinearRegression。跑完之后我对“简单模型为什么仍然有不可替代的价值”这件事有了更直观的理解也踩了几个时间序列预测里非常典型的坑。这篇文章适合三类人看刚入门机器学习、想找一个完整项目练手的学习者需要做时序预测但还没到上RNN或GBDT阶段的工程师以及所有想知道“回归模型到底怎么处理时间数据”的读者。我会把每一步的数据处理逻辑、模型参数含义、常见坑全部展开尽量做到给个键盘你就能自己复现。1. 疫情预测为什么要先想到线性回归1.1 疫情数据到底长什么样先不看模型看数据。假设某地发布的流感样病例每日新增记录简化之后就是两列日期、当日新增病例数。从数据结构来看这是一条标准的单变量时间序列没有其他协变量就是一个数字接着一个数字往下走。这类序列有一个显著特点不稳定。在疫情爬坡阶段每日新增往往不是缓慢上升而是有明显的加速期、冲顶期、回落期看上去就是一条抛物线的左半边或者右半边。所以“线性”这两个字一开始就容易挨骂真实数据明明不是一条直线。这里要转换一个认知做预测时我们并不要求全过程线性只要求“局部的增量近似线性”。换句话说在足够短的观察窗口内比如3到7天新增病例的变化幅度可以用一条带有斜率的直线去逼近它。这个假设在现实中是站得住的因为短期内影响疫情传播的因素相对稳定干预措施没有突变报告口径没有调整那么每日新增量确实会呈现一种缓变趋势。这正是线性回归能出场的基础。它不是用来解释整个疫情的它是用来捕获“最近一段时间的惯性”。1.2 线性回归在这个项目里的三个角色我在实操中给线性回归分配了三个明确职责每个职责都对应一种价值。第一它是最低标准的可用基线。要做未来7天预测总得先有一个说得过去的基准值。最简单的做法是用过去一段时间平均增量直接外推而线性回归就是把这个“外推”从拍脑袋变成最小二乘意义上的最优解。有了这个基线后面你换了更复杂的模型才知道新模型到底赢了多少。第二它是可解释性最好的模型。回归系数可以直接说明问题日期每往前推一天每日新增大约会变化多少。系数为负说明近期趋势向下系数为正说明还在爬坡绝对值越大趋势越明显。对非技术背景的协作方来说一句“模型估计每天会少20例”比任何黑盒输出都更容易沟通。第三它是特征工程的试金石。时间序列上很多特征比如星期几效应、7日滑动均值、历史滞后值都可以在线性回归框架里直接验证贡献度。如果一个特征在线性模型里都没用放到复杂的树模型里大概率也不会有稳定增益。1.3 先划定边界再开始动手动手之前必须把预期校准。线性回归做疫情预测是有明确边界的我的经验是三条预测期限不要超过7天最多看到14天再远误差会快速累积结果没有使用价值。数据质量要求高报告口径稳定、缺报率低否则模型学到的是假信号。它只能捕捉已有趋势不能预测拐点。模型不知道某天会发生一起聚集性感染也不知道防控措施会在哪天收紧。这几个边界不是我拍脑袋定的是我拿同一份数据换不同预测尺度测出来的。7天以内误差还在可接受范围拉到21天结果基本不能看。后面所有步骤都在这个边界内展开。2. 数据准备把日期变成回归模型能吃的形状2.1 原始数据清洗的完整逻辑我手上的模拟数据覆盖了过去150天格式是每行一个日期和一个新增病例数。拿到数据第一件不是画图爽一下而是做三件事检查缺失、筛查异常、确认口径。缺失在疫情类数据里极其常见。节假日报告系统可能没人维护人工录入可能漏行网络传输可能丢包。单日缺失在3个以内我用前后各3天的均值填充连续缺失超过一周就干脆裁掉这一段不强行制造一条虚假平滑曲线因为填充本身会引入比缺失更危险的信息。异常值筛查才是真正的重头戏。疫情数据经常出现某一天突然比前后均值高3倍的数字最后查出来往往是报告批次延迟归并或者统计口径微调并不是真实病例暴增。我用两道工序识别异常第一道按7日滑动均值的上下3倍标准差画警戒线第二道把工作日数据和周末数据分开比较看哪几天在系统性偏低哪些数字高得异常。对于确认不了的异常点我不会直接删掉真值而是给样本加一个“数据修正标记”特征让模型自己去吸收这个波动。2.2 为什么必须做7日滑动平均流感样病例数据有一个很明显的周期现象就是工作日报告量偏高周末偏低周一还会补报一部分。如果拿原始每日数据直接建模线性回归会把这种伪周期当成真实趋势导致预测结果忽高忽低。我的处理方式是增加两组辅助列7日滑动平均值和7日滑动标准差。滑动均值作为趋势信号让模型知道当前处于什么水平滑动标准差作为波动信号告诉模型当前走势稳不稳定。这里不是用平滑后的值替代原始目标变量而是作为额外特征喂给模型。窗口为什么选7而不是14或21核心原因有两个。第一7刚好覆盖一个自然周能对齐星期周期效应第二窗口太大对趋势转向反应太慢。我对比过14日和21日窗口它们会在疫情增速变化时明显滞后预测结果在峰顶附近偏移得厉害。7日窗口在这个数据上让验证集的RMSE下降最明显说明它抓住的是规律不是噪声。2.3 如何构造回归模型的样本时间序列本身不是监督学习结构因为它只有Y没有X。回归模型要求输入一个特征矩阵所以第一步是把“日期”拆成模型能消费的特征。我构造了三组特征。第一组是时间索引从第0天开始递增相当于告诉模型“时间是往前走的”有了它线性回归才能表达一个长期线性趋势项。第二组是滞后特征包括lag_1、lag_3、lag_7也就是前1天、前3天、前7天的新增病例数。这组特征对识别短期走势最有用因为它们等于把最近几天的发展惯性直接交给了模型。第三组是星期特征把星期几做独热编码让模型自动学会周内波动规律。目标变量也经过设计选择未来第7天的新增病例数而不是明天。为什么是7天因为这个项目关心的是“一周后的发病水平”这是实际工作中比较常见的数据上报节奏。这里有个关键操作构造样本时要做时间移位第t天的特征对应t7天的标签。这意味着最后7天的数据只能作为特征出现没有标签可用它们对训练毫无贡献只对预测和评估有意义。有效训练样本会因此减少7条这是正常现象不要怀疑自己代码写错了。2.4 目标变量到底要不要取对数这个问题我在不同数据上都实验过结论必须分情况。如果目标变量的动态范围不大比如一直稳定在50到200之间波动直接用原始数值就好误差指标容易解释别人也听得懂。但如果出现明显的爆发爬坡数值从100一路冲到5000甚至相差两个数量级那就必须考虑对目标变量和部分强正偏特征做log变换否则最小二乘会被少数大值主导模型会整体偏向大数值方向把低谷期也预测得特别高。让模型学习log(新增病例数)预测后再用指数函数还原本质上是改变了误差的度量方式从绝对误差变成相对误差。在数据跨度大的时候我会优先做log变换训练完成后再把预测值逆变换回真实量纲。逆变换之后还要做一个偏差校正直接exp回去的结果在低段会轻微偏低但这个偏差在疫情类数据上通常不重要可接受。3. 建模实操从数据表跑出第一个预测值3.1 环境准备和一键安装这次实操使用最基础的Python数据科学环境没有依赖任何重量级深度学习框架。pandas负责数据清洗sklearn的LinearRegression负责建模matplotlib负责画诊断图。Python 3.9以上、pandas 1.5以上就能跑版本不用太新。import pandas as pd import numpy as np import matplotlib.pyplot as plt from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score如果你的机器还没有这些库一行命令搞定pip install pandas numpy matplotlib scikit-learn。这里不推荐在这个阶段用statsmodels虽然它也能做线性回归但sklearn的API更贴合后面做特征组合和Pipeline的习惯换特征的时候代码改动更少。3.2 时间序列的划分方式不能乱来做时间序列预测最容易犯的错误就是学普通分类任务那样把数据随机打乱再按8:2切训练集和测试集。这套做法在疫情数据上是灾难性的因为相邻日期的样本高度相似随机划分等于把“太阳升起前的那一刻天空”也放进了训练集模型的验证指标会虚高到没有任何参考意义。正确做法是严格按时序切分前80%天数作为训练集后20%天数作为验证集。150天数据就是前120天训练后30天验证。更严格还可以做滚动前瞻验证每次把训练集向后扩展一个固定步长在增长之后的数据上反复评估。这里为了演示我用了固定的时序切分。feature_cols [date_idx, lag_1, lag_3, lag_7, weekday_0, weekday_1, weekday_2, weekday_3, weekday_4, weekday_5, weekday_6, rolling_mean_7, rolling_std_7] train_end int(len(df) * 0.8) train_df df.iloc[:train_end] valid_df df.iloc[train_end:] X_train train_df[feature_cols] y_train train_df[target_7] X_valid valid_df[feature_cols] y_valid valid_df[target_7]这里要提两个容易忽略的细节。第一个weekday特征用独热编码得到后某一列在周末会呈现出比较强的异动这正常因为周末报告量本来就有波动。第二个因为target是第t7天的值所以train_df里只有前113天真正拥有标签最后7天虽然有特征但没有标签模型训练时天然就用不到不要觉得是代码bug。3.3 训练第一个线性回归模型模型代码简单到几乎没有调参空间。真正有信息量的是训练完成后的系数解读。model LinearRegression() model.fit(X_train, y_train) coef_df pd.DataFrame({ feature: feature_cols, coef: model.coef_ }).sort_values(coef, ascendingFalse) print(coef_df)运行之后第一件要做的事是看lag_1、lag_3、lag_7的系数。它们通常都是正的表示过去基数越高未来发病数越高这个方向符合常识。第二个要看date_idx的系数它决定长期趋势方向正负都有意义。第三个看weekday系列的系数能直观读出星期几效应。我这次跑出来的结果里lag_1系数在0.8附近说明前一天发病数对一周后水平的决定性很强date_idx系数是负的说明验证区间整体处在回落期。这个解读比黑盒模型输出SHAP值还要直白非常适合拿去跟业务方对齐。3.4 预测未来7天的滚动递归策略直接拿一次性训练好的模型去预测未来7天会有一个尴尬问题。预测第1天时模型可以依赖已知的历史滞后值。但预测第4天时lag_1里面可能已经掺了前一天模型自己生成的预测值误差开始滚雪球。预测周期越长误差滚得越厉害。所以更合理的做法是滚动递归预测每推进一步就把上一步的预测值当作下一步的历史值更新进特征然后继续预测。我写了一个简化版的递归函数供理解逻辑用def recursive_forecast(model, last_row, feature_cols, horizon, recent_values): row last_row.copy() predictions [] for _ in range(horizon): pred model.predict(row[feature_cols].values.reshape(1, -1))[0] predictions.append(pred) # 更新滞后特征 recent_values.append(pred) row[lag_7] recent_values[-7] if len(recent_values) 7 else recent_values[0] row[lag_3] recent_values[-3] if len(recent_values) 3 else recent_values[0] row[lag_1] recent_values[-1] # 更新滑动均值 window recent_values[-7:] row[rolling_mean_7] np.mean(window) row[rolling_std_7] np.std(window) return predictions实际生产环境里必须维护一个固定长度为7的队列来保存真实值和预测值并严格按时间顺序更新。函数里的逻辑已经做了这件事关键是别把lag特征的更新顺序写反。我记得第一次跑的时候先更新了lag_1再更新lag_7结果整个预测序列偏移了一个身位定位了半小时才发现。4. 模型评估与结果解读4.1 三个评估指标各看什么训练完模型不能只看训练集上的R²那个数字对时间序列几乎没有意义。因为相邻样本共享趋势训练集上的拟合优度很容易到0.95以上但它不代表预测能力。我在验证集上固定记录三个指标MAE平均绝对误差单位就是“例/天”最直观别人问模型有多准先报这个数。RMSE均方根误差它对大误差更敏感能暴露单点严重偏差的问题。R²这里不要当成绝对好坏只看它相对一个简单均值基线提升了多少。我这次验证集30天MAE大约在40例RMSE在68例。这个误差对每日新增在200到800例之间波动的数据来说可以接受但它保证的是整体趋势不走偏不是要求每一天都预测准。4.2 两张图比任何指标都直观评估线性回归我强烈建议至少画两张图。第一张是预测值和真实值的时间序列对比图直觉比数字敏感得多预测线是整体偏高还是偏低、滞后几个峰一眼就懂。第二张是残差图横轴是预测值纵轴是真实值减预测值的残差残差点不能呈现出明显的漏斗形状否则说明模型的误差随着预测值变大而变大大概率是变量尺度问题。画完图之后要做一件额外的事情把预测结果按星期几分开做误差汇总。因为疫情数据有周内波动如果你的模型把周一的高报数字系统性预测低了这种误差在整体MAE里可能不太显眼但在业务协作时很容易被单独拎出来质疑。分开统计能提前发现这类问题。4.3 置信区间的近似算法疫情预测如果只给一条预测线没有不确定性表达协作方很容易把预测当精确值。我用的是简单办法把验证集预测残差的标准差当成单日不确定性然后乘上预测天数的平方根估算第N天累积不确定度。第一天的误差带最窄第七天最宽。这个算法没有严谨的统计推导但作为业务汇报足够用。它会直观传达一个信息预测越远越不可靠。实际使用中别人看到第7天的误差带比第1天宽一大截自然就降低了对远期数值的期望这对管理预期非常有效。4.4 预测结果落地成报表最终交付的不是一串数字而是一张集成表格未来7天每天的预测值、近似误差带、对比当前7日滑动均值的偏离度。偏离度这一列特别关键它能快速告诉阅读者模型预测的到底是继续走平还是明显上行或下行。如果预测值连续多天高于滑动均值说明模型认为近期惯性向上如果预测值突然掉头低于均值那可能是滞后特征遇到趋势反转要提醒协作方格外谨慎不要盲目相信模型判断。5. 常见问题与排查技巧实录5.1 为什么预测结果总是滞后疫情预测项目最常被问的问题就是你们的预测为什么总是慢半拍本来该到顶了预测线还在爬坡原因其实非常简单线性回归的全部特征都来自历史数据它天然假设“接下来会延续最近一段时间的平均斜率”。一旦趋势在现实中发生反转模型必须等反转信号写进最新几天的滞后特征才会开始修正方向所以滞后是方法上的本质缺陷不是调参能解决的。既然解决不了滞后就要管理滞后。我的做法是把预测范围严格控制在7天并且每次交付时在报表里标注“模型基于过去N天历史窗口不判断拐点”让使用方不要把预测结果当成独立判断只把它当作现有趋势的惯性推演。5.2 预测累计值是个陷阱很多人在疫情数据上习惯先预测累计病例数再用差分得到每日新增。这一步在拟合阶段会漂亮得惊人因为累计数一路向上不管模型多差R²都能跑到0.98以上。但差分之后你立刻会发现问题预测的每日新增经常出现负数完全没法用。原因数学上很清楚回归在最小化累计值的绝对误差时并不关心它的一阶差分是否合理累计值一丁点拟合误差在差分时会被放大甚至改变正负。因此核心原则是直接预测新增数虽然它的R²看起来不像累计值那么惊艳但业务完全可解释累计值留给汇总时再算永远不做建模目标。5.3 统计口径突变会毁掉整个模型疫情数据最痛苦的不是算法而是口径变更。某地某天突然调整了病例定义或者从“流感样病例”变成“实验室确诊感染”序列会瞬间出现跳崖或抬头。这种突变一旦混进训练集会直接污染滞后特征和滑动均值导致模型后面几天预测整体偏移。我的处理是给数据清洗阶段增加一个“断点标记”列。只要判断某一天出现了和前后趋势不一致、且能查到口径变更说明的跳变就在这一天上打标记随后只使用断点之后的数据训练模型或者把断点前后分开建模。这个动作对预测稳定性的提升比我调任何参数都明显。5.4 什么时候该换掉线性回归根据这次实操和过往经验我用数据量为坐标做一个简单分档数据量小于30天线性回归基本没用直接用7日滑动平均外推更稳。数据量30到100天趋势没有重大突变线性回归是很可靠的基线预测7天内误差可接受。数据量超过100天且出现多个峰谷建议换成带季节项的时序模型或梯度提升树线性回归只能当参考。如果要做的是政策模拟或者干预措施评估线性回归完全不够需要往传染病动力学模型思考。我现在的完整工作流是先拿线性回归把全链路跑通把数据质量、口径、自动化报表这些问题全部解决再考虑切换复杂模型。如果复杂模型的结论和线性回归差得离谱第一步不是怀疑复杂模型而是回去检查数据。最后分享一个我保留到现在的小习惯每次训练完线性回归我会把当前的date_idx系数单独打印出来和7日滑动均值的最后一期做交叉验证。这两个数如果方向不一致基本就能断定数据里有漏报或者断点。这套“先相信简单模型再回过头怀疑数据”的做法对我来说比任何花哨的调参工具都管用也让我在每次项目里都能快速把注意力放到真正重要的事情上。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑