资讯动态

回归项目实战:用Python预测每日新增病例数

发布时间:2026/10/21 6:21:32 来源:尧图企业网站定制
这几天在整理回归相关的复盘笔记时我给自己定了一个稍微“有手感”的实战题回归项目实战预测新冠人数。通俗点说就是拿到一份公开时间序列数据用前面几天的每日新增报告数预测下一天的新增量。很多人第一反应是把“预测感染人数”当成玄学其实落到代码上就是最典型的回归问题连续型预测、时间序列特征、回归模型再配上验证和误差分析。我选这道题一方面是因为它不需要大数据平台不用GPU只要会 pandas、sklearn 就能复现另一方面是因为复试前能把回归流程走通比背一百遍损失函数更有说服力。这个项目也特别适合准备复试的人用来“自测状态”如果你能解释清楚为什么不能用随机切分、为什么要给特征加 shift、为什么预测新增比预测累计更合理那说明回归这一章基本过关了。下面我就按 DAY4 实际整理的项目笔记把整个流程、踩坑点和最后的复盘结果写清楚。1. 项目整体设计与思路拆解1.1 为什么选择“每日新增数”作为预测目标一开始我盯着数据表想了很久表里既有累计报告数又有每日新增数到底拿哪一列做回归目标更合理经验告诉我累计数列的“自相关性”太强了。今天的累计几乎等于昨天的累计加上今天的少量新增回归模型只要学到“预测值约等于上一个值”就可以获得很低的误差但这根本不是学到了什么规律顶多是“抄作业”。这种问题叫非平稳序列直接回归看起来很美实际上毫无解释能力。所以我选择预测每日新增数。新增数列才有真正的随机波动也正因为有波动回归模型才有发挥空间哪几天高、哪几天低、滞后多少天会影响今天的值这些都是模型需要去学的东西。预测目标定了之后再思考累计值怎么用可以单独对每日新增建立回归模型预测出下一天新增后再把这个预测值累加到累计列上用来还原累计曲线。这样既避免了“预测累计就好似在抄前一格”又能让最终展示结果自然一些。1.2 怎样把“日期序列”改造成“回归问题”很多新手卡在第一步总觉得时间序列很特殊模型不能直接吃日期。其实只要把问题转换成监督学习就好构造特征时拿前一天的数值、前两天数值、前七天数值、星期几等作为 X拿当天的每日新增数作为 y。我这里先提供一个核心伪代码思路后面会给出直接能跑的实现df[lag_1] df[new_cases].shift(1) df[lag_2] df[new_cases].shift(2) df[lag_7] df[new_cases].shift(7) df[roll_7] df[new_cases].rolling(7).mean().shift(1) df[dayofweek] df.index.dayofweek X df[[lag_1, lag_2, lag_7, roll_7, dayofweek]] y df[new_cases]如果你对这段代码没有感觉可以这样理解模型每预测一次不是去看“整个疫情曲线”而是看一张“最近七天的行情表”。这张表格的每一行代表某一天的信息目标值则是后一天的价格。回归模型就是一个根据历史行情表来推测第二天的助手。这个转换之所以关键是因为它把时间顺序问题硬生生变成了普通的回归表格问题。后面做训练、测试、交叉验证时我们只需小心时间顺序不再需要特殊处理。1.3 项目的安排与工作量预估我实际把这个项目压缩在一个下午完成大概三个小时。时间分配大概是数据清洗和缺失值处理 40 分钟特征工程 40 分钟模型训练 60 分钟评估和对比 40 分钟。中间穿插各种报错排查比如 shift 忘记加一、索引错位、验证集划分顺序搞反等预计还要再留半小时。如果是基础较好的读者两小时跑通完全有可能。我选择的数据是某公开数据汇总站导出的 284 条每日记录包含日期、累计报告数、每日新增数三个字段。没有做什么特殊筛选意在让项目尽可能接近真实工作流。2. 核心细节解析与实操要点2.1 缺失日期不能无脑 drop我之前做过一个错误示范拿到数据直接dropna()结果发现预测曲线出现很多“断崖式”空隙。原因很简单数据处理时原始表格可能缺了某些日期尤其早期数据不完整经常有整行缺失。一旦直接丢弃缺失行时间序列就出现了空洞滞后特征也会错位。比如今天明明是 3 月 5 日但因为 3 月 4 日缺失lag_1实际取到的是 3 月 3 日的值信息就歪了。正确的做法是先补全日期再决定缺失值怎么填df[date] pd.to_datetime(df[date]) df df.set_index(date).sort_index() full_index pd.date_range(startdf.index.min(), enddf.index.max(), freqD) df df.reindex(full_index) df[new_cases] df[new_cases].fillna(0)这里把缺失日期的每日新增填为 0是一种短期可用方案。如果缺失的是累计报告数则不能用 0因为累计值不会倒退更好的做法是插值。对每日新增如果一天缺失且前后数值都正常填 0 会让 7 日均值略微拉低如果连续缺多天我会考虑先差分再插值。注意一旦有缺失日期之后所有窗口特征都要重新生成。最稳妥的方式是先补全日期再统一用shift和rolling生成特征。2.2 shift 方向是很多人最容易错的地方生成滞后特征shift(1)代表“取前一天的数值”。如果错用了shift(-1)你就把未来数据拽进了特征里模型会在训练时偷看到当天的实际值。这样做训练集误差会小得惊人但一到测试阶段立刻崩掉。我每次都会做一个简单的检查把特征构造好的表格打印出来看同一行里的日期、目标值、滞后值是否满足时间逻辑。比如某一行日期是 3 月 8 日目标值是 3 月 8 日的新增数lag_1就必须是 3 月 7 日的新增数lag_2就必须是 3 月 6 日。只要打印几十行就能轻易发现错位。2.3 滚动窗口特征要记得再次 shift不仅滞后特征要 shift滚动均值也要 shift。原因很简单如果用当天的 7 日均值来预测当天新增模型就把未来信息“偷看”了。举个例子如果我要预测 3 月 8 日的新增数那 3 月 8 日当天的滚动均值里包含了 3 月 8 日自己这就是泄漏。所以构造特征时一定要先算 7 日窗口再整体往后挪一天df[roll_7] df[new_cases].rolling(7).mean().shift(1)这一步的 shift(1) 是习惯性动作也是排查数据泄漏时的第一个重点。2.4 “星期几”这类日历特征也是有用信息很多人做时间序列预测只关心数值型滞后特征忽略了星期几。但在每日新增数这类序列里周内周期性明显。比如有些发布渠道周初补齐有些周末偏低趋势往往不是完全平滑的。我加入dayofweek列之后模型预测的周内尖峰明显更贴合真实序列。这个特征对树模型尤其有用因为树模型可以做分段比如周六周日走一个专门的分支规则周一再走到另一个分支。你还可以再扩展月内日序、是否为节假日等特征。但刚开始做我建议只加dayofweek和简单的节假日标记不要一上来堆几十个特征容易把模型拖进过拟合。3. 回归模型实操从线性模型到树模型3.1 基线模型昨天的值就是最朴素的基准在训练任何复杂模型前我先建立了一个基线直接用前一天的新增数当作今天的预测值。也就是pred_t real_{t-1}。这个模型几乎不花成本但它非常重要因为后续所有复杂模型的误差都要跟这个基线做对比。如果连 LR 都比不过这个“无脑复制”的基线说明特征工程有问题或者模型选择不合理。实际比较下来基线表现的 RMSE 通常不低因为新增数日波动较大连续两天完全相同或接近的场景很少。任何稍微像样的回归模型都应该明显优于这个基线。3.2 岭回归处理滞后特征之间的严重共线性我首选岭回归而不是普通线性回归原因是滞后特征高度相关。比如lag_1和lag_2本身就存在高度相关性普通最小二乘回归的系数会很不稳定稍微换一段验证数据系数就剧烈摆动。岭回归通过 L2 正则化把系数往零收缩牺牲一点训练误差换取泛化能力。实操中我用RidgeCV在一个小范围内搜索 alphafrom sklearn.linear_model import RidgeCV ridge RidgeCV(alphas[0.01, 0.1, 1.0, 10.0]) ridge.fit(X_train, y_train) y_pred_ridge ridge.predict(X_test)这里不需要太精细地调参因为回归模型的核心竞争力不在超参数而在于特征结构是否合理。如果特征都构造好了默认 alpha 附近也会取得较好的效果。3.3 树模型抓住非线性变化和周期性线性模型适合平滑、线性关系较强的序列但每日新增数经常出现“脉冲式”抬升。比如某些时段报告规则变化导致单日新增突然跳高线性模型很难描述这种分段式变化。这种情况下我习惯用梯度提升回归树。我当时用的是 sklearn 自带的GradientBoostingRegressor参数不复杂from sklearn.ensemble import GradientBoostingRegressor gbm GradientBoostingRegressor( n_estimators120, learning_rate0.05, max_depth3, random_state42 ) gbm.fit(X_train, y_train) y_pred_gbm gbm.predict(X_test)树模型可以对不同区间做切分比如“如果滞后 7 天值高且昨天值偏低则今天预测值中等”这类规则性判断比单个线性公式要灵活。代价是需要稍微控制深度我用max_depth3防止过拟合。当然这个项目也可以考虑 LSTM但 284 条每日数据对神经网络来说太短不容易学出稳定规律。我建议先把传统回归吃透再决定是否上时序模型不要为了炫技硬上深度学习。3.4 把训练流程写成统一的模板为了后面对比模型我把流程封装成一个简单函数输入不同的模型对象都走同一套训练和预测过程def train_evaluate(model, X_train, y_train, X_test): model.fit(X_train, y_train) pred model.predict(X_test) return pred再配合一个函数计算指标方便一次性比较 RMSE、MAE、MAPE。不用做复杂封装只要别让代码重复粘贴三次就行。实际项目里我强烈建议把数据处理、特征工程、模型训练、评估四个阶段分开写每个阶段用少量注释标清楚否则后面排查问题会很痛苦。4. 评估指标与时间序列验证4.1 时间序列数据严禁随机切分普通回归项目里随机划分训练集和测试集没问题因为样本之间互相独立。但时间序列数据完全不能这么做一旦随机打乱某一天的测试数据可能出现在训练数据之前模型就等于拿着“未来”去预测“过去”验证结果虚高。正确的做法是按时间顺序切分。我当时的做法是前 70% 做训练后 15% 做验证最后 15% 做测试。代码实现很简单train_size int(len(df) * 0.7) val_size int(len(df) * 0.15) test_size len(df) - train_size - val_size X_train X.iloc[:train_size] y_train y.iloc[:train_size] X_val X.iloc[train_size:train_sizeval_size] y_val y.iloc[train_size:train_sizeval_size] X_test X.iloc[train_sizeval_size:] y_test y.iloc[train_sizeval_size:]关于第一段时间要足够长的原因我这里不展开太多关键是如果你只用最后 14 天做测试那么测试采样窗口太短模型评估结果会很不稳定。4.2 滚动时间序列交叉验证只看一次测试结果不够在复试问答里如果面试官问“你怎么评估时间序列模型”只回答“按时间切分一次”其实不够更严谨的回答是“滚动时间序列交叉验证”。sklearn 直接提供了TimeSeriesSplit每次切分都用更早的数据训练、更近的数据验证逐步提升起点from sklearn.model_selection import TimeSeriesSplit tscv TimeSeriesSplit(n_splits5) for train_index, val_index in tscv.split(X): X_train_cv, X_val_cv X.iloc[train_index], X.iloc[val_index] y_train_cv, y_val_cv y.iloc[train_index], y.iloc[val_index] # 在这里训练并记录指标这样得到的指标平均分比单次切分更能反映模型稳定性。我当时用这种办法对比了岭回归和梯度提升最后发现梯度提升虽然在训练集上更精确但在滚动交叉验证里优势并不明显说明它有一点过拟合需要控制树的深度。4.3 指标并非越“大”越说明模型好要结合数据尺度看常用回归指标有三个指标含义适用场景RMSE误差平方后开根号放大较大误差新增数有异常尖峰时能反映模型是否被极端值牵着走MAE绝对误差的平均更接近普通人理解的“平均差多少”MAPE百分比误差数值较平稳阶段好用但新增数接近 0 时不稳定我实践后发现只看 RMSE 不够还需要结合每一天新增数的波动范围。比如评估集平均每日新增约 500 例RMSE 在 130-160 之间还是可以接受的如果 RMSE 高于 300那基本相当于完全没用。另外预测值偶尔会跑到负数这时不能直接拿去计算 MAPE要先做clip(lower0)。4.4 对比基线才有说服力我总结出了一个很实用的评价原则任何一个模型先跟“昨天值就是今天预测值”的基线比。如果模型的 RMSE 没有显著低于基线你就要重新审视特征工程而不是继续调参。在实际复现中我得到的是梯度提升模型略优于基线约 15%-25% 的相对误差改善岭回归略微差一点但整体流程是完整的。这个结果并不夸张因为这类短时序数据的可预测性有限预测模型能带来的提升幅度本来就不大。这也提醒我不要迷信复杂模型短序列数据里简单模型加上合理特征往往已经够用。5. 常见问题与排查技巧实录5.1 预测曲线整体“延迟一天”怎么办我最早用岭回归时预测曲线看起来总是比真实曲线慢一天像把真实曲线右移了一样。原因是模型过于依赖lag_1当 lag_1 的系数最大时预测结果本质上就是前一天的数值稍微衰减一点自然存在一天延迟。解决办法是加入更长周期的特征比如lag_7和 7 日均值并适当减少lag_1的权重。我调完之后曲线明显没那么“滞后感”了但也不会完全消除延迟因为时间序列预测本身就存在滞后性完全消除反而可能是泄漏。5.2 训练误差很低但测试误差爆表优先检查数据泄漏排查这问题有两个关键点第一确认没有在算滚动窗口时忘记右边 shift第二确认没有用全数据集均值或全数据集标准差做特征标准化。我举一个典型错例如果先对整个new_cases列计算全局均值再把这个值作为特征放进去测试阶段的未来信息就已经混入训练集。正确做法是只对训练集计算统计量再应用到测试集。我建议你构造特征时把最终生成的 DataFrame 多打印几行日期从前往后看人为检查每行列特征是否都来自更早时间点。这个动作虽然笨但最有保障。5.3 新增数序列中包含大量“尖峰”要不要平滑疫情数据的每日新增数经常出现单日暴增原因是统计口径或补报错位这类尖峰对模型影响很大。我试过把原始新增列先做 3 日移动平均再进入模型模型指标会好看但有一个副作用预测结果也会被平滑丢失单周内的真实波动。我的建议是先做一次实验对原始列直接建模如果尖峰导致模型训练不稳再考虑 3 日移动平均的版本并把两个结果都保留下来复盘时解释清楚。这种方法也能在面试时体现你理解数据结构而不是只会套公式。5.4 常见问题排查表现象可能原因处理方式预测曲线滞后真实曲线一天模型过度依赖 lag_1或窗口特征不足增加 lag_7、roll_7减少单滞后特征权重训练误差极低但测试误差高数据泄漏检查 shift 方向、全局统计量、rolling 窗口是否用了未来数据验证集评估优于测试集切分时没按时间顺序随机抽样了改用严格时间顺序切分或 TimeSeriesSplit预测值出现负数模型输出超出物理下限np.clip(pred, 0, None)加入更多特征后几乎没有提升特征本身与目标相关性低做相关性分析只保留相关性较高的特征预测结果过于平滑尖峰完全丢失平滑过多或模型太简单尝试树模型或减少平滑窗口5.5 复试复盘时最值得讲给面试官的两个细节准备复试时我不会去背整个项目的代码而是把两个最容易暴露水平的点讲清楚。第一个是“为什么用新增数而不是累计数训练”。这个问题的答案体现了对时间序列数据的理解累计序列近乎随机游走模型容易退化成复制上一天的结果没有研究价值预测新增数能验证模型学到的是真实规律而不是惯性。第二个是“为什么特征工程里 shift 如此关键”。我会直接说明 shift 是为了避免“未来数据泄漏”如果只调参数不看 shift模型一切顺利都可能是虚假繁荣。这个细节看似普通但能把做过项目的人和不经大脑复制代码的人迅速区分开。这次 DAY4 最大的收获不是把某个模型调得多好而是真正理解了一条铁律时间序列回归项目里数据构造的严谨性永远优先于模型的复杂度。你先保证每一步的历史信息没有污染未来信息再去比较线性模型、树模型和时序模型才有意义。如果一开始顺序就错了后面每一步都可能白做。如果你也想拿同类型题目练手我强烈建议先不要急着选新的算法而是把这条完整流程跑三遍第一遍用随机数据试通代码第二遍换成真实公开数据第三遍尝试加入一个额外特征并观察效果变化。三遍之后所谓“实战能力”会明显不一样。

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

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

免费获取报价 →
↑