ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python空气质量数据挖掘与机器学习可视化分析系统实战

Python空气质量数据挖掘与机器学习可视化分析系统实战 简介本资源面向环境科学、数据挖掘与机器学习方向的学习者与研究者提供一套基于Python的空气质量数据可视化分析系统源码及配套数据可用于城市群划分、污染传输网络构建与传播过程探索等课题实践。项目采用BS架构前端整合HTML、CSS、JavaScript及D3、ECharts、Mapbox等可视化库后端基于Python与Flask搭建main.py为服务入口dataManager.py负责数据模块files目录存放原始数据与挖掘结果templates下index.html为前端入口。压缩包共2000个文件以1295个json数据文件和697个py脚本为主另含少量txt、xml与md说明文档整体约76.43MB结构清晰便于按模块查阅。目前已有252人学习下载适合希望掌握降维聚类、粒子输运与相关分析、多维空间变换渐进式探索等方法并需要完整可运行案例的读者参考。1. 空气质量数据挖掘系统从一份 CSV 到能跑通的分析闭环空气质量数据本身不稀缺各地环保部门每天都会公开 AQI、PM2.5、PM10、SO2、NO2、CO、O3 这些指标真正卡住人的是拿到数据之后那一步字段对不上、时间戳乱、缺失值成片、模型跑出来 R² 只有 0.3可视化图表画出来自己都不想看。这套「Python 基于数据挖掘与机器学习的空气质量数据可视化分析系统」要解决的正是这条链路——把原始监测数据清洗成可建模的结构用机器学习做污染物浓度预测或空气质量等级分类再用可视化把结论讲清楚。它适合两类人一类是正在做课程设计或毕业设计的学生需要一套能跑通、能改、能写进论文的完整流程另一类是想把数据分析能力落到环保、城市治理场景的工程师需要知道特征怎么选、模型怎么调、图表怎么组织。下面按数据获取、清洗、特征工程、建模、可视化、避坑的顺序把每个环节的参数和代码都摊开讲。2. 数据获取与字段理解先把数据源和字段含义吃透2.1 空气质量数据的常见来源与字段结构做这个系统第一步不是写代码是搞清楚数据长什么样。常见的空气质量数据来源有三类一是各地环境监测总站公开的逐小时或逐日数据通常以 CSV 或 Excel 形式提供二是通过公开 API 获取的实时数据返回 JSON 格式三是竞赛平台或教学数据集字段已经整理好但需要自己理解含义。不管哪种来源核心字段基本一致城市名、监测站点、日期时间、AQI 值、空气质量等级、PM2.5、PM10、SO2、NO2、CO、O3 这几项污染物浓度。其中 AQI 是综合指数由六项污染物分指数取最大值得到PM2.5 和 PM10 通常是影响 AQI 的主要因子。理解字段之后要确认两件事一是单位PM2.5、PM10、SO2、NO2、CO、O3 的浓度单位一般是 μg/m³CO 有时用 mg/m³混用会导致数值差三个数量级二是时间粒度逐小时数据和逐日数据的建模策略完全不同逐小时数据要考虑昼夜周期和滞后效应逐日数据更适合做趋势分析和等级分类。我一般会先写一段探查代码把字段类型、缺失比例、取值范围一次性打出来避免后面反复返工。import pandas as pd import numpy as np # 读取原始数据注意编码国内公开数据常见 gbk 或 utf-8-sig df pd.read_csv(air_quality_raw.csv, encodingutf-8-sig) # 基础探查字段类型、缺失率、描述统计 print(df.dtypes) print(df.isnull().mean().sort_values(ascendingFalse)) print(df.describe().T[[mean, std, min, max]]) # 检查时间字段是否能正确解析 df[datetime] pd.to_datetime(df[datetime], errorscoerce) print(时间解析失败条数:, df[datetime].isnull().sum())这段代码做三件事dtypes看字段类型判断数值列有没有被读成字符串isnull().mean()算每列缺失比例超过 30% 的列要重点处理describe()看均值和极值如果 PM2.5 最大值出现 9999 这种数基本是异常标记值需要替换成 NaN。时间字段用errorscoerce强制转换解析失败的会变成 NaT方便后续统计。2.2 用 Pandas 做缺失值处理与异常值识别空气质量数据缺失是常态传感器故障、通信中断、维护校准都会导致整段缺失。处理方式取决于缺失比例和缺失模式缺失比例低于 5% 且随机分布直接删掉或均值填充影响不大缺失比例在 5% 到 30% 之间建议用前后值插值或按小时均值填充超过 30% 的列要么放弃该字段要么用模型预测填补但后者容易引入偏差。异常值识别常用 3σ 原则和 IQR 四分位距法空气质量数据更适合 IQR因为浓度分布本身右偏3σ 会把大量真实高值误判为异常。# 缺失值处理按时间排序后做线性插值限制最大连续填充长度 df df.sort_values(datetime).reset_index(dropTrue) df[PM2.5] df[PM2.5].interpolate(methodlinear, limit6) df[PM10] df[PM10].interpolate(methodlinear, limit6) # 异常值处理IQR 法超出上下界的替换为边界值 def cap_outliers(series): q1, q3 series.quantile(0.25), series.quantile(0.75) iqr q3 - q1 lower, upper q1 - 1.5 * iqr, q3 1.5 * iqr return series.clip(lower, upper) for col in [PM2.5, PM10, SO2, NO2, CO, O3]: df[col] cap_outliers(df[col]) print(处理后缺失情况:, df[[PM2.5, PM10]].isnull().sum().to_dict())interpolate的limit6表示最多连续填充 6 个点超过就保留 NaN避免用插值掩盖长时间缺失。clip把超出 IQR 上下界的值压到边界而不是直接删除这样保留样本量的同时降低极端值对模型的干扰。如果数据里存在 9999 这类标记值要在插值前先用replace(9999, np.nan)替换掉否则插值结果会被污染。3. 特征工程与模型选型让机器学习真正学到东西3.1 时间特征与污染物交互特征的构造原始数据只有时间戳和浓度值直接丢给模型效果通常一般因为空气质量有强时间规律早晚高峰 PM2.5 和 NO2 升高午后 O3 升高冬季供暖期 PM2.5 整体偏高。把这些规律显式构造成特征模型更容易学到。常用做法是从时间戳拆出小时、星期、月份、是否周末、是否供暖季再对 PM2.5 和 PM10 做滞后特征因为当前时刻的浓度和前一小时高度相关。# 时间特征 df[hour] df[datetime].dt.hour df[weekday] df[datetime].dt.weekday df[month] df[datetime].dt.month df[is_weekend] (df[weekday] 5).astype(int) # 滞后特征前一小时和前三小时的 PM2.5 df[PM2.5_lag1] df[PM2.5].shift(1) df[PM2.5_lag3] df[PM2.5].shift(3) # 交互特征PM2.5 与 PM10 的比值反映颗粒物构成 df[pm_ratio] df[PM2.5] / (df[PM10] 1e-6) # 删除因滞后产生的空值行 df df.dropna().reset_index(dropTrue) print(特征构造后样本量:, len(df))shift(1)生成前一小时值注意这会在第一行产生 NaN所以最后要dropna。pm_ratio加1e-6是防止 PM10 为 0 时除零。如果做的是逐日数据滞后特征可以改成前一天和前三天。构造完特征后建议用热力图看相关性PM2.5 和 PM10 相关性通常超过 0.8如果同时放进线性模型要考虑共线性树模型则不太敏感。3.2 回归与分类模型的选型对比这个系统通常做两类任务一是预测 PM2.5 或 AQI 的具体数值属于回归二是预测空气质量等级优、良、轻度污染等属于分类。回归常用线性回归、随机森林、XGBoost、LightGBM分类常用逻辑回归、随机森林、SVM、XGBoost。选型时不要一上来就上深度学习空气质量数据样本量通常几千到几万条特征维度几十维树模型在这个规模上表现稳定且调参成本低。我一般先用随机森林跑基线再用 XGBoost 或 LightGBM 对比如果提升不明显就保留随机森林因为可解释性更好。from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, r2_score import xgboost as xgb features [PM10, SO2, NO2, CO, O3, hour, weekday, month, is_weekend, PM2.5_lag1, PM2.5_lag3, pm_ratio] X df[features] y df[PM2.5] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42) rf RandomForestRegressor(n_estimators200, max_depth12, min_samples_leaf3, random_state42) rf.fit(X_train, y_train) pred_rf rf.predict(X_test) print(RF MAE:, mean_absolute_error(y_test, pred_rf), R2:, r2_score(y_test, pred_rf)) xgb_model xgb.XGBRegressor(n_estimators300, learning_rate0.05, max_depth6, subsample0.8, colsample_bytree0.8, random_state42) xgb_model.fit(X_train, y_train) pred_xgb xgb_model.predict(X_test) print(XGB MAE:, mean_absolute_error(y_test, pred_xgb), R2:, r2_score(y_test, pred_xgb))随机森林的n_estimators200是树的数量太少欠拟合太多训练慢且收益递减max_depth12控制单棵树深度空气质量数据一般 8 到 15 之间比较合适min_samples_leaf3防止叶子节点样本过少导致过拟合。XGBoost 的learning_rate0.05配合n_estimators300是常见组合学习率低就需要更多树subsample和colsample_bytree都是 0.8表示每棵树随机用 80% 样本和 80% 特征增强泛化。如果 R² 低于 0.7优先检查滞后特征是否构造正确、时间字段有没有排序错。4. 可视化落地用 ECharts 和 Matplotlib 把结论讲清楚4.1 趋势图、热力图与污染物相关性矩阵可视化不是把数据画出来就完事是要让看图的人三秒内抓到重点。空气质量场景最常用的四类图时间趋势折线图看 AQI 和 PM2.5 随时间变化日历热力图看全年污染分布一眼能看出冬季高发相关性热力图看六项污染物之间的关联城市或站点对比柱状图看区域差异。Python 端用 Matplotlib 或 Pyecharts 生成前端用 ECharts 渲染Pyecharts 的优势是直接输出 HTML方便嵌入 Web 系统。from pyecharts.charts import Line, HeatMap from pyecharts import options as opts import pandas as pd # 按天聚合画 AQI 和 PM2.5 趋势 daily df.set_index(datetime).resample(D).agg( {AQI: mean, PM2.5: mean}).dropna().reset_index() daily[date_str] daily[datetime].dt.strftime(%Y-%m-%d) line (Line() .add_xaxis(daily[date_str].tolist()) .add_yaxis(AQI, daily[AQI].round(1).tolist(), is_smoothTrue) .add_yaxis(PM2.5, daily[PM2.5].round(1).tolist(), is_smoothTrue) .set_global_opts( title_optsopts.TitleOpts(titleAQI 与 PM2.5 日趋势), datazoom_optsopts.DataZoomOpts(range_start0, range_end30), yaxis_optsopts.AxisOpts(name浓度 μg/m³))) line.render(aqi_trend.html)resample(D)按天聚合如果原始是逐小时数据这一步把 24 条压成 1 条趋势更清晰。is_smoothTrue让折线平滑但要注意平滑会掩盖极端值做污染过程分析时建议关掉。DataZoomOpts默认只显示前 30% 数据用户可拖动查看全时段避免 x 轴标签挤成一团。相关性矩阵用df[[PM2.5,PM10,SO2,NO2,CO,O3]].corr()算出来再用 HeatMap 渲染重点看 PM2.5 和 PM10 是否高度相关如果超过 0.9建模时可以考虑只保留一个。4.2 把可视化结果接进 Web 系统的三种方式如果这套系统要交付成 Web 应用可视化结果有三种接法一是 Pyecharts 生成独立 HTML用 iframe 嵌入二是后端返回 JSON前端 ECharts 直接渲染三是用 Flask 或 FastAPI 做接口前端按需请求。第一种最省事适合课程设计快速出效果第二种最灵活适合需要交互筛选的场景第三种最接近生产环境但要多写接口和前端逻辑。我一般先用第一种把图表跑通确认数据没问题再改成第二种。from flask import Flask, jsonify import pandas as pd app Flask(__name__) app.route(/api/trend) def trend(): daily df.set_index(datetime).resample(D).agg( {AQI: mean, PM2.5: mean}).dropna().reset_index() result { dates: daily[datetime].dt.strftime(%Y-%m-%d).tolist(), aqi: daily[AQI].round(1).tolist(), pm25: daily[PM2.5].round(1).tolist() } return jsonify(result) if __name__ __main__: app.run(debugTrue, port5000)这个接口返回 JSON前端拿到后传给 ECharts 的setOption。debugTrue只在开发时开部署时要关掉。如果数据量大接口里加缓存或分页避免每次请求都重新聚合。前端 ECharts 配置里xAxis.type设为categoryyAxis.type设为valueseries里两个对象分别对应 AQI 和 PM2.5tooltip.trigger设为axis让鼠标悬停显示同一天两个值。5. 避坑与排查那些跑通之后才会暴露的问题5.1 时间字段排序错误导致滞后特征失效现象模型 R² 看起来还行但预测值和真实值错位滞后特征和当前值对不上。原因读取 CSV 后没有按时间排序就直接shift数据行顺序是乱的shift(1)取到的不是前一小时而是上一行。解决在任何特征构造之前先df.sort_values(datetime).reset_index(dropTrue)并且检查时间字段有没有重复值重复时间戳会让滞后关系错乱。5.2 缺失值插值把整段缺失填成直线现象趋势图上某段时间 PM2.5 是一条完美直线明显不真实。原因interpolate没有限制limit连续缺失几十个点被线性填充两端值一拉就成直线。解决设置limit6或更小超过限制的保留 NaN后续建模时要么删掉这些行要么用模型预测填补。如果缺失段正好是污染过程插值会严重低估峰值。5.3 训练集和测试集随机划分导致时间泄漏现象测试集 R² 高得离谱换一批数据就崩。原因用train_test_split随机划分测试集里的时间点可能夹在训练集中间滞后特征把未来信息泄漏给了训练。解决时间序列数据要按时间切分比如前 80% 做训练、后 20% 做测试或者用TimeSeriesSplit做交叉验证。这个坑在课程设计里特别常见答辩时被问到很难解释。5.4 可视化图表中文乱码现象Matplotlib 生成的图表标题和坐标轴中文显示成方框。原因默认字体不支持中文。解决在绘图前设置plt.rcParams[font.sans-serif] [SimHei]和plt.rcParams[axes.unicode_minus] False。Pyecharts 和 ECharts 一般没这个问题因为渲染在浏览器里字体由系统提供。如果服务器端生成图片要确认服务器装了中文字体。5.5 模型特征重要性高但业务上说不通现象XGBoost 输出特征重要性排第一的是一个看起来无关的字段。原因可能是该字段和标签存在间接泄漏比如用 AQI 的某个分项去预测 AQI或者用了未来才会知道的字段。解决逐个检查高重要性特征的业务含义确认它在预测时刻是否真的可得。空气质量预测里当前时刻的 PM2.5 对预测下一时刻有用但当前时刻的 AQI 等级如果是由 PM2.5 算出来的拿它预测 PM2.5 就是循环论证。6. 进阶技巧用 SHAP 解释模型并做特征筛选模型跑通之后真正拉开差距的是能不能解释「为什么预测这个值」。SHAP 是目前解释树模型最实用的工具它能给出每个样本每个特征的贡献值既能做全局特征重要性也能看单个预测的归因。我一般会先用 SHAP 做一轮特征筛选把贡献接近零的特征去掉再重新训练往往能在保持精度的同时减少特征数量推理速度也更快。import shap # 用训练好的 XGBoost 模型做 SHAP 解释 explainer shap.TreeExplainer(xgb_model) shap_values explainer.shap_values(X_test) # 全局特征重要性 shap.summary_plot(shap_values, X_test, plot_typebar) # 单个样本归因看第 0 个测试样本各特征贡献 shap.force_plot(explainer.expected_value, shap_values[0], X_test.iloc[0], matplotlibTrue)TreeExplainer对 XGBoost、LightGBM、随机森林都适用计算速度比 KernelExplainer 快很多。summary_plot的 bar 图按平均绝对 SHAP 值排序能直接看出哪些特征重要。force_plot看单个样本红色推高预测值蓝色拉低能解释为什么这个时刻预测 PM2.5 偏高。如果发现滞后特征 SHAP 值远高于其他特征说明模型主要靠自相关预测这时候要考虑加入更多气象特征温度、湿度、风速、气压来提升泛化能力因为纯靠滞后特征在污染过程突变时容易失效。特征筛选的具体做法跑完 SHAP 后把平均绝对 SHAP 值低于阈值的特征列出来逐个删掉再训练观察验证集 MAE 变化。如果删掉后 MAE 上升不超过 2%就保留删除模型更简洁。我自己的习惯是每轮只删一个特征避免一次性删太多导致性能骤降后无法定位原因。这套流程走下来一个能跑通、能解释、能交付的空气质量分析系统基本就成型了。希望帮到你。本文还有配套的精品资源点击获取
返回列表