线性回归实现PM2.5预测:从数据清洗到滚动预测全流程解析 简介基于线性回归的PM2.5预测系统Python源码面向环境数据分析、机器学习入门及课程设计人群解决从原始监测数据到浓度预测的完整回归建模问题。压缩包共19个文件以12个csv数据文件为主体覆盖训练集、测试集及特征拼接、中间变量等处理结果另有1个py主程序、1个npy模型参数文件和5张过程截图大小仅2.14MB轻量便于学习。该代码读取繁体编码的训练数据按小时维度整理18类污染物指标通过特征构造、最小二乘正规方程训练最终输出预测值csv流程完整。目前已有594人浏览学习。读者可直接调参运行并对照数据文件核验中间结果借助截图与模型参数理解关键步骤通过观察arrayx、concatenateX等中间矩阵还能弄清特征拼接与数据标准化的具体实现是机器学习课程设计、毕业论文实验或PM2.5回归预测入门的一份完整可运行参考资料。1. 线性回归做PM2.5预测先搞定基线再谈花活做空气质量预测十有八九是从线性回归起步的。PM2.5浓度与历史浓度、气象条件之间存在大量可被线性模型捕捉的关联小时级数据上前几小时的浓度对当前值有极强的自相关。这套基于线性回归的PM2.5预测系统本质是把“历史浓度气象特征”映射到未来浓度的回归模型训练快、可解释、易部署适合做业务初版和复杂模型的对标基线。它解决的不是“预测最准”而是“用最小成本换来稳定可用的结果”。适合两类人刚接触时序预测的开发者想看清从数据清洗、滞后特征到训练评估的完整链路已有深度学习方案的工程师想拿它当对照基线。这篇从原理讲到代码再落到高频踩坑点照着做就能把整套系统搭起来。2. 线性回归预测PM2.5的原理与数据准备特征矩阵决定上限2.1 线性回归为什么能预测PM2.5机理与适用边界线性回归假设目标变量 y 是特征 x 的线性组合加噪声写成公式是 y β0 β1x1 … βnxn ε训练时用最小二乘法求一组系数 β让预测值与真实值的平方误差和最小。这个过程没有任何黑匣子每个系数都对应一个特征的影响方向和大小这也是它至今仍是工业界基线首选的原因。PM2.5 的短期浓度变化能用线性模型描述核心支撑有两点。第一是惯性大气污染物的扩散和清除需要时间当前小时浓度与过去几小时浓度高度相关滞后项能捕捉这段自相关而自相关是时序预测里最值钱的信息。第二是气象驱动风速决定稀释能力湿度影响二次生成气压关系到垂直扩散这些气象变量在正常波动范围内对浓度的贡献近似线性。把这两类信息编成特征线性回归就能拟合出一条可用的预测线。但必须认清边界。二次化学反应、逆温层、沙尘输送这类强非线性过程线性回归天生预测不了极端峰值。实际项目里我一般把它定位成短期基线预测未来1到24小时的浓度趋势评估重点放在正常波动区间的拟合质量而不是搏极端事件。如果业务需要预测重污染红警再往上叠加树模型或深度学习但线性回归这条基线必须存在——它是判断复杂模型是否真有用的尺子。2.2 数据从哪来监测字段、时间对齐与样本组织这套系统最常用的数据源是空气质量监测站的小时级记录字段一般包括 PM2.5、PM10、SO2、NO2、CO、O3 六项污染物浓度加同时段的气温、气压、湿度、风速、风向。不同来源的数据粒度可能不同有的按整点有的按分钟先统一再使用。拿到数据先做三件事按时间升序排序并去重、处理缺失、校正字段类型。监测设备断网重传经常产生重复记录时间戳偏移也很常见表现为整点记录落在 00:01 或 23:59。我一般先把时间戳取整到小时再按“同一时刻只保留最后一条”去重。# 数据加载与时间整理 import pandas as pd df pd.read_csv(air_quality.csv, parse_dates[datetime]) df[datetime] df[datetime].dt.floor(h) # 取整到小时 df df.sort_values(datetime).reset_index(dropTrue) # 时间升序 df df.drop_duplicates(subset[datetime], keeplast) # 同刻只留一条 print(df.shape, df.dtypes)dt.floor(h)会把 00:01 归到 00:00避免滞后特征串位。sort_values加drop_duplicates是最低保障后面所有特征构造都依赖这一步的正确性。字段类型也要顺手检查湿度、风速这类列如果被读成字符串后面所有运算都会报错。提示时间序列数据严禁在排序前做滚动或滞后操作否则滞后值会串到别的时刻上训练完的模型全是错觉。2.3 特征矩阵构建滞后项、滚动统计量与环形气象编码特征工程是这套系统里性价比最高的环节。我常用的特征分三类目标变量滞后项取 lag1、lag3、lag6、lag24覆盖短期惯性和日周期滚动统计量例如过去6小时 PM2.5 均值相当于简易低通滤波抑制单小时尖刺噪声气象特征其中风向是环形变量0度和360度含义完全相同直接拿数值塞进模型会让回归系数莫名其妙推荐拆成 sin 和 cos 两个分量。特征类别特征名构造方式作用滞后项pm25_lag1 / lag3 / lag6 / lag24shift(n)捕捉短期惯性与日周期滚动统计pm25_roll6_meanrolling(6).mean()平滑单小时尖刺气象temp / pressure / humidity原始数值气象驱动气象wind_speed原始数值扩散能力风向wind_sin / wind_cos角度三角函数环形变量编码# 构造滞后、滚动与风向特征 import numpy as np def build_features(data, targetpm25, lags(1, 3, 6, 24)): df data.copy() for lag in lags: df[f{target}_lag{lag}] df[target].shift(lag) df[f{target}_roll6_mean] df[target].rolling(6).mean() df[wind_sin] np.sin(np.deg2rad(df[wind_direction])) df[wind_cos] np.cos(np.deg2rad(df[wind_direction])) return df.dropna().reset_index(dropTrue) df build_features(df) print(df.head())shift和rolling都会在序列头部产生 NaNdropna会顺带丢掉最前面几行这是正常现象不是数据错误。滞后档位的选择跟预测步长强相关预测下一小时lag1 的权重通常最大预测未来24小时lag24 和滚动均值的作用更突出。这个函数默认给四档滞后覆盖小时级预测最常用的配置想预测更长周期就自己加 lag48、lag72。3. 用Python源码搭训练流程数据清洗、特征构造到预测一行不落3.1 数据清洗缺失值插值、异常值截断与脏数据兜底监测设备断网、标定维护都会造成整段缺失。线性回归对缺失值零容忍scikit-learn 的 LinearRegression 遇到 NaN 直接抛错。常见做法是线性插值加阈值截断组合短缺失用前后值插值补上长缺失整段丢弃因为连续缺失超过6小时时插出来的值基本是猜带进模型只会污染系数。# 缺失值插值与异常截断 df df.interpolate(methodlinear, limit6) # 最多连续插值6小时 df df.dropna() # 超长缺失直接丢弃 cap df[pm25].quantile(0.995) # 99.5分位作为上限 df[pm25] df[pm25].clip(uppercap)interpolate的limit参数是关键它限制连续插值长度超过的部分保留为 NaN 再dropna丢掉。这个组合比一刀切fillna(0)或fillna(mean)靠谱得多也避免把设备故障期间的假数据当成真实波动。异常处理上用分位数截断而非三倍标准差原因是 PM2.5 分布右偏严重正常高污染日的浓度就可能超过均值加三倍标准差直接删会误伤样本截断则保留样本量同时压制极端值对回归线的拉扯。3.2 特征与标签切分时间顺序交叉验证不能随机打乱分类任务可以随机打乱数据时间序列不行。随机划分会让模型在训练时看到测试时刻之前的未来信息验证指标虚高部署上线后立刻现原形。这是整套系统里最隐蔽的坑后面 5.1 会细讲。这里先用 TimeSeriesSplit 按时间顺序做交叉验证。# 时间顺序交叉验证 from sklearn.model_selection import TimeSeriesSplit features [pm25_lag1, pm25_lag3, pm25_lag6, pm25_lag24, pm25_roll6_mean, temp, pressure, humidity, wind_speed, wind_sin, wind_cos] X df[features].values y df[pm25].values tscv TimeSeriesSplit(n_splits5) for fold, (tr_idx, te_idx) in enumerate(tscv.split(X)): X_tr, X_te X[tr_idx], X[te_idx] y_tr, y_te y[tr_idx], y[te_idx] # 每一折训练集都严格早于测试集TimeSeriesSplit与KFold的本质区别就在这里它不洗牌且每一折的训练集时间上严格早于测试集。用它的前提是原始数据已经按时间升序排好这一点忘了检查后面所有评估指标都是白算。n_splits 设5到10都行小时级数据样本量大5折足够折数太多会缩短每折训练长度折数太少评估方差大。3.3 训练与预测LinearRegression 最小实现与结果解读# 线性回归训练与评估 from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, r2_score import numpy as np model LinearRegression() model.fit(X_tr, y_tr) y_pred model.predict(X_te) rmse np.sqrt(mean_squared_error(y_te, y_pred)) r2 r2_score(y_te, y_pred) print(fRMSE{rmse:.2f} ug/m3, R2{r2:.3f})这段代码不需要标准化。普通线性回归对特征量纲不敏感系数会自动适应不同尺度滞后项浓度是几十到几百气压是上千百帕模型照样收敛。如果换成 Ridge 或 Lasso 正则化才必须提前做标准化否则正则项会对大数值特征惩罚过重这一点很多人搞反。跑完之后先看 RMSE 的量级如果 RMSE 和测试集浓度的标准差差不多说明模型几乎没学到信息优先回头查特征和清洗流程别急着换模型。加了 lag1 之后 R² 通常能做到 0.7 以上达不到这个水平数据链路大概率有 bug。每折都打印这两个指标观察折与折之间的稳定性比单看某一折的结果有意义得多。4. 模型评估与调参R²、RMSE、正则化与残差诊断4.1 评估指标怎么选R²看拟合度RMSE看业务误差评估回归模型最常用的三个指标各有侧重。R² 衡量模型解释了多少方差越高越好但容易被极端值拉高RMSE 是误差的均方根单位和原始数据一致直接对应业务上的“平均偏差多少微克”MAE 是绝对误差均值比 RMSE 更抗异常值干扰。小时级 PM2.5 预测里我一般同时报 RMSE 和 R²RMSE 给业务看R² 给技术评审看。指标含义适用判断注意点R²1 - 残差平方和/总平方和模型整体拟合度极端值会虚高RMSE均方误差开根号业务实际偏差对异常值敏感MAE绝对误差均值稳健偏差不反映大误差这三个指标都不能只看训练集。训练集 R² 很高、测试集掉一半几乎可以断定过拟合先用 4.3 的残差分析定位再考虑加正则化或砍特征。还有一个常用技巧把测试集按季度切片分别算 RMSE能直观看到模型在冬夏两季的表现差距这比一个全局指标更有业务参考价值。4.2 正则化选型Ridge还是Lasso取决于共线性结构特征多了之后共线性会让最小二乘解的系数方差变大训练集上拟合得很好测试集上飘。两个常用解法Ridge 加 L2 惩罚把系数往零压但不真正归零适合特征间存在相关性的场景Lasso 加 L1 惩罚会把不重要的系数直接压成零自带特征选择。# Ridge与Lasso对比 from sklearn.linear_model import Ridge, Lasso from sklearn.preprocessing import StandardScaler from sklearn.pipeline import make_pipeline scaler StandardScaler() ridge make_pipeline(scaler, Ridge(alpha1.0)) lasso make_pipeline(scaler, Lasso(alpha0.01)) for name, pipe in [(ridge, ridge), (lasso, lasso)]: pipe.fit(X_tr, y_tr) y_p pipe.predict(X_te) print(name, fR2{r2_score(y_te, y_p):.3f}, fRMSE{np.sqrt(mean_squared_error(y_te, y_p)):.2f})注意这里用了 StandardScaler配合make_pipeline保证标准化和训练在同一条流水线里避免在测试集上泄漏统计量。alpha 的取值要靠小网格搜索调我一般从 0.01、0.1、1、10 几档里选交叉验证 RMSE 最低的。PM2.5 预测这个场景里Ridge 通常比 Lasso 稳因为气象特征之间多少都相关L1 硬切容易把有用信息一起删掉。4.3 残差分析模型失效的信号灯训练完别急着收工画残差图是排查模型问题最直接的手段。把 y_te - y_pred 按时间顺序画出来如果残差围绕零线随机波动模型基本正常如果出现明显的上升或下降趋势说明有未建模的时间效应比如季节漂移如果残差方差随时间变大说明模型在浓度高的时候系统性失灵。# 残差趋势检查 import matplotlib.pyplot as plt residual y_te - y_pred plt.figure(figsize(12, 4)) plt.plot(residual, alpha0.6) plt.axhline(0, colorred, linestyle--) plt.title(Residual over time) plt.show()残差还有一个高频特征重污染时段残差为正且很大说明线性模型系统性低估峰值这符合 2.1 里说的非线性边界。如果业务确实需要峰值预测就得在特征里加入风速湿度等气象交互项或者换树模型。残差分析不需要每折都做但至少要在最后一折上做一次作为上线的放行条件之一。5. 避坑指南PM2.5线性回归预测的五个真实踩坑记录5.1 数据泄露随机划分让指标虚高上线就翻车现象训练集 R² 0.91测试集 R² 0.89漂亮得不像话结果部署到线上实时预测误差比测试时大了将近一倍。原因代码里用了train_test_split默认的随机划分。PM2.5 序列自相关极强随机切分会让训练集里混入测试时刻前后的样本模型等于提前看到了答案测试指标纯属自欺欺人。解决按 3.2 的方式改用 TimeSeriesSplit或直接按时间比例手动切分保证训练集永远在测试集之前。所有涉及时间的特征构造shift、rolling也必须在切分之前完成且不能引入未来信息。这是时序预测的头号血泪教训没有之一。5.2 预测值出现负数浓度不可能是负的现象预测结果里出现 -10、-25 这类负数业务方直接质疑数据有问题。原因线性回归没有非负约束。当特征组合落入训练数据稀疏的区域回归平面外推预测值就可能越过零轴。PM2.5 浓度分布左端紧贴零外推时最容易出现负值。解决最简单的是np.clip(y_pred, 0, None)在输出端兜底。更讲究一点把目标变量取对数再回归预测完再指数还原这样预测值天然为正同时压缩右偏分布的影响。注意取对数后 RMSE 要在对数域计算别拿还原后的值直接算。5.3 多重共线性PM10和PM2.5打架系数符号反转现象PM2.5 和 PM10 高度相关单独只放一个时 R² 差不多两个都放时系数符号突然反转PM10 系数变负。原因共线性特征之间可以相互替代最小二乘解在相关方向上不稳定系数估计方差被放大符号翻转是典型表现。回归系数的“黑匣子”效应在这里体现得最明显。解决先看相关系数矩阵把相关性超过 0.8 的特征合并或保留其中业务意义更明确的一个。PM10 和 PM2.5 同源实际项目里我通常保留目标滞后项把 PM10 作为辅助特征或直接去掉。也可以用 Ridge 缩小系数方差但不解决解释性问题。5.4 异常值把回归线带偏一次标定故障毁掉一折训练现象模型整体 RMSE 正常但某天重污染时误差特别大且系数明显偏向那个方向。原因监测设备标定异常会产出离谱的高值比如某个小时 PM2.5 记录到 3000。线性回归的损失函数是平方误差一个 3000 的样本对回归线的拉扯力度相当于几百个正常样本。解决用 3.1 的 clip 截断或把极端值缩到分位数边界。更稳的做法是用 HuberRegressor 代替 LinearRegression它对异常值的鲁棒性天生更强损失函数在残差大于阈值时从平方退化为线性高污染日的数据不会单点带偏整条回归线。5.5 标准化后忘记还原预测值数字对不上领导现象评估时 RMSE 看着挺小但预测值和真实浓度画在同一张图上差了一个量级全是零点几的小数。原因训练前对特征或者目标做了 StandardScaler预测结果自然落在标准化后的尺度上没做 inverse_transform 还原。解决目标变量不要标准化保持原始浓度单位如果真的标准化了预测后必须用scaler.inverse_transform(y_pred)还原。如果用的是make_pipeline包标准化预测输出会自动反标准化。凡是训练和预测走了两条路的都要检查是否做了可逆变换这条经验同样适用于其他回归项目。6. 从静态模型到滚动预测小系统重训练、可视化与输出6.1 滚动预测与重训练让模型跟上季节漂移静态模型一次性训练完就上线PM2.5 的浓度分布会随季节漂移冬夏差异巨大模型用久了精度必然下滑。常见做法是滚动预测加定期重训练每天用最新数据预测未来24小时每7天用最近三个月的数据重新训练一次既保证样本量又跟上漂移。# 滚动预测示例用当前特征递推未来24小时 def rolling_predict(model, df, steps24): preds [] temp df.copy() for _ in range(steps): x temp[features].iloc[-1:].values pred model.predict(x)[0] preds.append(max(0, pred)) # 负数兜底 new_row temp.iloc[-1:].copy() new_row[pm25] pred # 预测值接回序列 temp pd.concat([temp, new_row], ignore_indexTrue) return preds滚动预测的关键是“预测即数据”每预测出一步就把结果接回序列尾部供下一步的滞后特征使用否则多步预测会退化成重复同一个值。这种递推方式误差会累积预测步数越远越不可信所以24小时以上的预测建议直接用 lag24 特征一次到位而不是滚动24次。重训练我放在一个定时任务里每次拉最新数据、重建特征、重新拟合用模型文件覆盖旧的。6.2 可视化与结果输出把预测变成业务能用的东西模型跑通只是第一步落地要解决“预测结果给谁看、怎么用”。可视化我一般画两样一是近48小时真实值与预测值的对比曲线直接反映模型状态二是未来24小时的预测曲线加置信带置信带用历史残差的标准差近似业务看到的是一个区间而不是一个单点。# 预测结果导出 result pd.DataFrame({datetime: future_time, pm25_pred: preds}) result.to_csv(pm25_forecast.csv, indexFalse)导出格式保持与监测数据同构带上时间戳和浓度单位这样下游的告警、报表、大屏可以直接消费。我这套系统跑完给我的最大教训是评估指标再漂亮也不如一个能稳定输出的 CSV 文件有价值。所有后处理逻辑——负数截断、置信带、单位标注——必须在导出前统一处理好别把原始预测丢给下游让人家二次加工。希望帮到你。本文还有配套的精品资源点击获取