简介:面向机器学习初学者与数据分析实践者,提供一套基于线性回归的 PM2.5 预测系统 Python 源码,完整对应经典空气质量赛题的数据预处理、特征拼接与建模流程。源码读取繁体编码的 train.csv,将 18 种污染物逐项存储,借助 numpy 矩阵运算完成参数拟合并输出预测结果,适合课程设计、算法对比与入门项目参考。压缩包共 19 个文件,含 12 个 CSV 数据文件(原始集、拼接特征、预测结果)、5 张过程截图、1 个模型参数 npy 文件和 1 个主程序 py 文件,整体仅 2.14MB,结构清晰便于直接运行与修改。目前已有 593 人学习下载,常见于线性回归实战场景。借助该源码,可快速获得可执行预测代码,并理解数据清洗、特征矩阵构建、模型保存与评估的完整思路,配套截图便于对照中间结果,免去从零搭建与调错的重复劳动。
1. 线性回归做PM2.5预测:小项目里的大门槛
一个“基于线性回归的PM2.5预测系统python源码.rar”能解决什么问题?如果你只是想在简历里加一个机器学习项目,或者课程设计需要一个能跑通的程序,它能满足你。但如果你以为拿一包源码、pip install几个库就能预测准明天的雾霾,那大概率会翻车。线性回归是机器学习里最朴素也最容易被低估的算法,它用在PM2.5这种时序数据上,真正考验人的不是调包,而是特征怎么构造、数据怎么清洗、预测结果怎么评估。
这个方向适合两类人:一是刚入门机器学习的Python新手,想用一份能跑的源码理解“训练-预测-评估”全流程;二是需要快速做空气质量基准预测的环境工程从业者,线性回归虽然简单,但作为Baseline比直接上LSTM靠谱得多。你最终收获的不只是一个能出数字的程序,而是一整套“拿到时序数据后该怎么下手”的思考方式。这套方式,比源码本身值钱。下面要讲的,就是我从这个项目里拆出来的完整落地路径和踩过的坑。
2. 线性回归凭什么能预测PM2.5:从原理到适用边界
2.1 线性回归在时序预测里的真实位置
PM2.5浓度是一个典型的时序数据,受气象条件、排放源、季节变化影响。非线性模型理论上更能捕捉复杂关系,但在实际项目里,线性回归依然是数据科学家的第一选择。原因很直接:可解释性强,系数能告诉你哪个因素影响大;计算开销小,几千条数据秒级出结果;是后续模型的“及格线”,如果线性模型都打不过,换复杂模型大概率是过度拟合。
用数学语言说,线性回归假设目标值 ( y ) 与特征 ( x_1, x_2, ..., x_n ) 之间存在线性关系:( y = w_1 x_1 + w_2 x_2 + ... + w_n x_n + b )。训练过程就是找一组权重 ( w ) 和偏置 ( b ),让预测值和真实值的均方误差最小。这个过程叫最小二乘法,Python里scikit-learn的LinearRegression类封装的就是这个算法。
但你要明白它的边界:如果PM2.5和温度、湿度之间本来就是曲线关系,线性模型天生拟合不好。解决方法不是直接换模型,而是做特征工程,把原始特征变成多项式特征或做分箱处理,让线性模型也能表达非线性关系。这就是这个项目里最重要的一个思路——模型简单,就用特征来补。
2.2 这个项目里常见的特征工程套路
我见过很多份类似的PM2.5预测源码,质量参差不齐,但好的那一类在特征处理上都有共性。先说原始数据,一份典型的数据集长这样:日期、PM2.5浓度、露点、温度、气压、风向、风速、累计降水量、积雪深度。如果直接拿这些原始列丢给线性回归,效果往往一般,因为特征之间的关系被忽略了。
常见做法是构造滞后特征(Lag Features)。比如用前3小时的PM2.5浓度作为当前时刻的预测输入,公式是 ( PM2.5_{t} = f(PM2.5_{t-1}, PM2.5_{t-2}, ...) )。这个动作把“序列预测”变成了“有监督回归”,是线性回归能处理的形式。还有一个常用特征是滑动平均,比如过去6小时的平均浓度,用来平滑短时波动。
在Python源码里,这一步通常用pandas的shift()函数完成:
import pandas as pd df = pd.read_csv('pm2.5_data.csv', parse_dates=['date']) # 构造滞后特征:用前1小时、前2小时、前3小时的浓度作为特征 for lag in [1, 2, 3]: df[f'pm25_lag_{lag}'] = df['pm25'].shift(lag) # 构造滑动平均特征:过去6小时平均浓度 df['pm25_rolling_mean'] = df['pm25'].rolling(window=6).mean() # 删除有缺失值的行(shift会产生NaN) df.dropna(inplace=True)这段代码的逻辑很清晰:shift(lag)把浓度列向下移动lag行,相当于把“过去第lag个小时的浓度”放到当前行。rolling(6).mean()则是对连续6行做滑动窗口平均。参数说明:lag的值取决于你对时间尺度的判断——做小时级预测,1到3小时是合理范围;做日均值预测,需要加大到1到3天。dropna是必须的,因为前几行没有足够的历史数据,NaN会让模型训练报错。
2.3 数据划分与评估方式的选型
PM2.5数据有一个致命特性:时序相关性。相邻时刻的浓度高度相关,如果像普通分类问题那样随机打乱数据再划分训练集和测试集,模型等于“作弊”——测试集里可能混着训练集相邻时刻的样本,预测准确度虚高。
正确的做法是按时间顺序切分。比如数据覆盖2010年到2014年,那就用前4年做训练,后1年做测试。代码实现很简单:
# 按时间顺序划分,不能用train_test_split的随机切分 train_size = int(len(df) * 0.8) train_df = df.iloc[:train_size] test_df = df.iloc[train_size:] X_train = train_df.drop(['date', 'pm25'], axis=1) y_train = train_df['pm25'] X_test = test_df.drop(['date', 'pm25'], axis=1) y_test = test_df['pm25']这里的核心参数是train_size的比例,0.8是常规选择。但注意,时序预测里训练集占比不是越高越好,如果训练数据和测试数据的时间跨度差异大(比如训练集只覆盖冬季而测试集在夏季),分布漂移会让评估结果失真。我在实际处理时还会做一步验证:看训练集和测试集的目标值分布是否接近,如果差异过大,就需要缩短测试集的时间窗口。
评估指标方面,不要只看R²。PM2.5浓度波动大,R²高不代表预测准。我会同时看均方根误差RMSE和平均绝对误差MAE。RMSE对大误差敏感,能反映极端情况;MAE反映平均偏离程度。一个合格的线性回归基线,RMSE通常在15到25微克每立方米之间,如果超过30,说明特征构造或数据清洗有问题。
3. 把源码跑起来:环境配置到数据预处理全流程
3.1 Python环境配置与依赖安装
拿到“基于线性回归的PM2.5预测系统python源码.rar”,第一步肯定是解压。但解压之后,很多新手直接双击.py文件,然后报错ModuleNotFoundError,这是最常见的开局翻车点。先理清依赖:这类项目基本逃不开pandas、numpy、scikit-learn、matplotlib四个库。
配置环境我推荐用venv虚拟环境,别把包直接装进系统Python,不然多项目之间互相污染版本。具体命令如下:
# 创建虚拟环境 python -m venv pm25_env # 激活环境(Windows) pm25_env\Scripts\activate # 激活环境(macOS/Linux) source pm25_env/bin/activate # 安装依赖 pip install numpy pandas scikit-learn matplotlib这里有个细节:如果你的Python版本是3.10以上,直接用pip install会装最新版scikit-learn,某些旧源码里写的LinearRegression用法依然兼容,不需要担心。但如果源码里用了statsmodels做回归摘要,需要单独安装:pip install statsmodels。
3.2 数据加载与缺失值处理的三个层次
PM2.5监测数据几乎不可能干干净净。缺失值处理是整个项目里最“玄学”的部分,因为不同的填充方式会直接影响模型系数。我见过一份源码,直接对NaN行做dropna删除,如果缺失比例低于5%,这没问题;但很多真实数据集缺失率超过20%,强行删除会丢掉大量时间信息。
更稳妥的处理是插值。pandas内置的interpolate()方法会按线性方式填补缺失值,适用于短时间缺失;时间稍长的缺失,我会用前向填充法ffill(),拿上一时刻的值补进来,这在PM2.5浓度变化平缓时效果不错。代码示例:
# 处理缺失值:先看缺失比例 missing_ratio = df.isnull().sum() / len(df) print(missing_ratio) # 策略1:短时间连续缺失用线性插值 df['pm25'] = df['pm25'].interpolate(method='linear') # 策略2:长时间缺失用前向填充 df['temp'] = df['temp'].ffill() # 策略3:实在缺得厉害就删除该行 df.dropna(subset=['pm25'], inplace=True)逻辑说明和参数说明:interpolate(method='linear')会沿索引顺序在缺失点两端取平均值填补,适合传感器短暂失灵的情况。ffill()取上一有效值延续填充,适合浓度缓慢变化的场景。这里的核心判断标准是缺失时间长度——缺失超过连续6小时,线性插值的可信度会大幅下降,需要考虑删除对应时段。
3.3 风向和类别特征的数值化处理
这是最容易忽略的一步。原始数据里风向是文本,比如“NW”代表西北风。直接把文本扔给线性回归会报错,所以必须编码。常见做法是独热编码,把风向变成多个0/1列:
# 一股脑对字符列做独热编码,drop_first=True可以避免共线性 df = pd.get_dummies(df, columns=['wind_dir'], drop_first=True)注意,drop_first=True这个参数很关键。如果不设,四个风向会生成四列,但它们之间存在完全共线性——知道前三列就知道第四列,矩阵会变成奇异矩阵,线性回归的系数计算会不稳定。我在自己项目里做过对比:不设drop_first时,模型系数出现正负交替的大数值,这就是共线性导致的数值病态。处理之后,系数回归正常,R²也稳了。
除了独热编码,还有一种思路是角度化。风向本身是0到360度的圆周变量,可以用numpy把风向拆成sin和cos两个连续特征,能保留方向之间的相似性。但对线性回归来说,独热编码的业绩通常更好,因为不需要为sin/cos的周期性关系额外建模。
4. 模型训练与可视化评估:从源码到可解释的预测结果
4.1 用scikit-learn训练线性回归模型的最小实现
这是整个项目的核心代码段。一份合格的源码应该让你看到完整的训练、预测、评估三步,而不是只给一个fit()调用。最小实现如下:
from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score import numpy as np # 初始化模型 model = LinearRegression() # 训练模型:fit的过程就是最小化均方误差 model.fit(X_train, y_train) # 预测 y_pred = model.predict(X_test) # 评估指标 rmse = np.sqrt(mean_squared_error(y_test, y_pred)) mae = mean_absolute_error(y_test, y_pred) r2 = r2_score(y_test, y_pred) print(f"RMSE: {rmse:.2f} 微克/立方米") print(f"MAE: {mae:.2f} 微克/立方米") print(f"R²: {r2:.4f}")逻辑说明与参数说明:LinearRegression()默认开启fit_intercept=True,也就是说模型会自动学习偏置项b,一般不需要关掉。核心参数只有normalize——在新版scikit-learn中已被弃用,所以不用设置。评价指标中,RMSE的量纲和PM2.5浓度一致,可以直接直观地理解误差水平;R²代表模型解释了目标值多少方差,超过0.8算是及格线。
如果你在源码里看到类似coef_的属性输出,不要忽略它。coef_是每个特征的权重系数,它告诉你“滞后1小时的PM2.5每升高1微克,当前浓度平均升高多少”。这个值的大小、正负可以直接用来做业务解释,也是线性回归相比黑匣子模型最大的优势。
4.2 特征系数分析:看懂模型在学什么
模型训练完之后,别急着交差。打印特征系数,你能发现数据深处的信息:
feature_names = X_train.columns coefficients = model.coef_ # 按系数的绝对值从大到小排序 coef_df = pd.DataFrame({'feature': feature_names, 'coef': coefficients}) coef_df['abs_coef'] = np.abs(coef_df['coef']) coef_df.sort_values('abs_coef', ascending=False, inplace=True) print(coef_df.head(10))运行结果通常显示:lag_1(前1小时浓度)的系数最大,可能在0.8左右,这说明PM2.5浓度具有很强的短时惯性;滑动平均的系数次之;气象因子的系数相对较小。如果某个特征的系数绝对值异常大(超过5),且正负符号不符合物理直觉,多半是特征共线性或数据泄漏的问题。我在项目里见过温度系数为正——温度越高PM2.5越高?仔细排查后发现,数据里温度列和日期列没有对齐,时间偏移导致虚假的相关。这是血泪经验,拿到结果先怀疑数据,再怀疑模型。
4.3 预测结果可视化:一眼看出模型在哪失效
matplotlib画图是这个项目里最能体现工作量的部分。预测值和实际值的时间序列对比图,能让你直观看到模型在哪些时段失效。完整代码:
import matplotlib.pyplot as plt # 设置图幅,避免时间轴太挤 plt.figure(figsize=(12, 5)) # 画实际值曲线 plt.plot(test_df['date'], y_test, label='Actual', color='black', linewidth=1.5) # 画预测值曲线,alpha控制透明度 plt.plot(test_df['date'], y_pred, label='Predicted', color='red', linewidth=1.2, alpha=0.8) plt.xlabel('Date') plt.ylabel('PM2.5 Concentration (μg/m³)') plt.title('PM2.5 Prediction Result: Actual vs Predicted') plt.legend() plt.xticks(rotation=45) plt.tight_layout() plt.show()这个图的典型特征是:预测曲线比实际曲线“平滑”,峰值被压低。原因很直白,线性回归在训练时以最小化均方误差为目标,而对离群的大峰值,模型倾向于预测到一个折中值,因为峰值样本少,惩罚又大。如果图上出现晚间预测值明显偏低,大概率是训练集中夜间数据不足。看图的技巧是:先看整体趋势是否一致,再看峰值时刻的离差幅度,最后看是否存在持续偏移——如果预测整体高于实际,说明有系统偏差,需要检查数据是否有时间对齐问题。
5. 避坑:线性回归做PM2.5预测的常见翻车点
5.1 数据泄漏:最隐蔽的“满分模型幻觉”
现象:测试集上R²高达0.98,RMSE低得离谱,但模型部署到新数据上效果一塌糊涂。
原因:数据划分阶段没有按时间切分,或者特征构造时混入了未来信息。最常见的坑是——用目标值做滑动平均时,滚动窗口包含了当前时刻之后的数据。pandas的rolling(window=6)默认中心对齐,window=6时第3行就会吃到第4、5、6行的“未来”数据。
解决:构造滞后特征和滑动平均特征之后,一定要检查行级别的数据流。用shift(-1)做“未来信息”测试——如果模型拟合度极好,而你发现任何一列带有shift(-1)的痕迹,那就是泄漏了。正确姿势是按时间顺序划分,且所有特征必须在预测时刻是可得的。
5.2 缺失值直接删除导致时间序列断裂
现象:训练出来的模型预测值总是偏低,尤其在夜晚时段。
原因:数据里夜间监测缺失率高,源码直接用dropna删掉了这些行,导致训练数据里白天样本占绝对多数,模型变成了“白天预测器”。
解决:先统计各小时段的缺失比例,如果某个时段缺失超过30%,不要直接删除,用ffill或interpolate补齐。如果缺失呈聚集性(比如连续3天设备故障),那就删除该时段而不是逐行删除。
5.3 特征尺度差异导致系数不可解释
现象:模型预测准了,但系数数值怪异,气压系数是0.0001,风速系数是-56,没法向别人解释。
原因:不同特征的单位尺度差异大,气压是1000量级,风速是5量级,线性回归的系数会被拉得极不平滑。
解决:训练前做标准化处理,用StandardScaler把每个特征缩放到均值为0、方差为1:
from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test)注意:transform测试集时要用训练集拟合好的scaler,不能重新fit,否则数据分布信息泄露。这个操作不影响R²或RMSE的数值大小,但让系数变成“该特征变化一个标准差时目标值变化多少”,解释起来舒服得多。
5.4 只看R²不看残差图
现象:R²=0.85,自信满满地部署,结果预测误差在某些区间特别大。
原因:R²是全局指标,掩盖了局部失效。PM2.5浓度低于50时误差小,但浓度超过200的重污染时段,模型误差急剧放大。
解决:画残差图——横轴是预测值,纵轴是真实值减预测值。如果残差随预测值增大而增大,呈现“喇叭形”,说明模型存在异方差性问题。线性回归此时已经到极限,你需要增加重污染时段的特征(比如在滞后特征里加入“过去24小时浓度峰值”)或者试试分位数回归。用残差图做验证“后悔药”,省得部署后被业务方找上门。
5.5 源码包的路径问题与环境兼容性
现象:解压后运行main.py,报错FileNotFoundError: 'data/pm25.csv' not found。
原因:源码作者在Windows上写死了相对路径,你在别的系统解压后目录结构变了。这是“免费python源码大全”里最常见的通病。
解决:用绝对路径或动态获取当前路径,改一处就够了:
import os # 获取当前文件所在目录,再拼接数据路径 base_dir = os.path.dirname(os.path.abspath(__file__)) data_path = os.path.join(base_dir, 'data', 'pm25.csv') df = pd.read_csv(data_path)参数说明:os.path.abspath(file)拿到脚本的完整路径,然后用os.path.join拼接子目录。这套写法在任何机器上都能定位到文件,不会因为工作目录不同而报错。顺便说一句,如果你的文件是中文路径,pandas读取时可能遇到编码问题,用pd.read_csv(data_path, encoding='gbk')或encoding='utf-8',根据原始文件编码判断。
6. 进阶:从单步预测到多步预测的验证思路
做完单步预测只是入门。线性回归最好的实践场景其实是多步预测——预测未来6小时、24小时的PM2.5浓度。两种思路供你选:第一种是递归预测,把预测值作为下一步的输入,不断滚动;第二种是直接多输出,让线性回归一次输出未来多个时间点的浓度数组。
递归预测的问题在于误差累积——第1小时预测不准,第2小时输入就是错的,误差像滚雪球。直接多输出的问题在于训练数据构造复杂,要搞清每行的标签是未来第几小时的值。我的建议是:先做24小时直接多输出,比较每个时间点的RMSE,画出“误差随时间步长增长曲线”。如果误差在8小时之后急剧上升,说明模型的短期惯性特征已经耗尽,该系统只能做短时预测,不能做长时预报。
最后一个教训:我做过很多次类似的预测项目,结论都会回归到一句话——在数据质量面前,算法选择没那么重要。这份源码的价值不在LinearRegression这个模型本身,而在它帮你串起的数据清洗、特征构造、时序划分、残差诊断这一整套流程。拿到新数据,先画时间序列图,再做滞后相关分析,最后才动手训练。养成这个习惯,比调任何参数都管用。希望帮到你。
本文还有配套的精品资源,点击获取