ARTICLE DETAIL

资讯详情

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

基于Python的空气污染数据可视化系统:数据预处理是关键

基于Python的空气污染数据可视化系统:数据预处理是关键 简介这是一套完整的空气污染数据可视化分析系统项目整合了Python源码、数据样本与设计报告主要面向高校计算机、自动化、电子信息、环境等相关专业学生可用于毕业设计、课程设计、课题研究或日常数据分析练习也适合入门者进阶。资源内共289个文件既包含CSV空气污染数据、Jupyter Notebook分析流程、Python脚本也包含可视化展示用的HTML页面及配套CSS样式、字体资源另有PDF/PPT设计文档、Markdown说明和一段MP4演示视频整体大小约104.47MB目录结构清晰便于按数据、代码、文档分块查阅。目前已有43人学习下载。通过项目可完整学习数据读取、清洗、统计建模、交互式可视化等流程设计报告有助于快速理解功能模块与实现思路在此框架上还能扩展其他城市或污染源的分析。若部署运行遇到问题也可联系作者远程指导。1. 基于 Python 的空气污染数据可视化系统值得跑的预处理脚本比画图更关键处理空气污染数据时最反直觉的一点是真正决定分析质量的不是后面的可视化代码而是最前面那几十行数据预处理。这套基于 Python 的空气污染数据可视化分析系统把 Pandas 清洗、matplotlib 出图、Flask ECharts 看板串成了一条完整链路。代码量不大但踩坑点集中在时间解析、缺失值填充和文件编码上。适合正在做课程设计、期末大作业或者学完 Python 基础想找一个真实数据分析项目复现的读者。下载后按 requirements.txt 装好环境再换一份空气质量 CSV 就能完整跑起来。2. 数据预处理把 PM2.5/PM10 时序数据洗到可用的三个关键步骤空气污染分析里数据清洗至少占了 80% 的工作量后面画图反而是体力活。这套源码里 preprocess.py 的核心逻辑就是下面三件事先把时间列解析成 datetime再处理缺失值和异常值最后按天或按小时重采样。顺序不能乱时间解析做不对后面 resample 和 set_index 全都会跟着出问题。2.1 先看类型再动手read_csv 与 datetime 解析拿到空气污染数据后第一步不要急着画图。先读进来把列名和类型打印出来看一眼。常见的数据文件是从环保站点导出或爬虫抓下来的 CSV列名可能是中文也可能是 PM2.5、PM10、SO2、NO2、CO、O3、AQI 这种约定俗成的英文缩写同一个站点在一段时间内偶尔还会改表头。import pandas as pd df pd.read_csv(data/air_quality.csv, encodingutf-8-sig) print(df.columns.tolist()) print(df.dtypes) print(df.head())逻辑说明这段代码只做了两个动作——把 CSV 读进 DataFrame然后打印列名、类型和头部样例。目的不是马上处理而是确认三件事文件编码是否正常、日期列叫什么名字、哪些指标列被读成了 object。空气污染数据的日期列如果读进来是 object后面 resample 和设置索引都会出问题。参数说明encoding 参数在 utf-8-sig 和 gbk 之间切换是因为从 Excel 导出的 CSV 通常有 BOM 头或使用 GBK 编码utf-8-sig 能剥掉 BOM如果打印出来是乱码就改成 encodinggbk 再跑一次。用 print 而不是直接进 Jupyter 里看是为了让脚本在纯命令行环境下一次跑通不依赖 IDE 的变量查看窗口。接下来处理时间列和数值列。时间列的格式一般是 2023-01-05 10:00:00 或 2023/1/5用 to_datetime 做一步解析就能覆盖绝大多数情况。df[datetime] pd.to_datetime(df[datetime]) df df.sort_values(datetime).set_index(datetime) value_cols [PM2.5, PM10, SO2, NO2, CO, O3, AQI] for col in value_cols: if col in df.columns: df[col] pd.to_numeric(df[col], errorscoerce)逻辑说明第一步把时间字符串转成 Timestamp 类型再按时间排序并设为索引。排序这步很多人会漏但原始 CSV 里经常混着几行乱序不排序的话后面画折线会出现横轴倒退的怪异曲线。第二步把指标列统一转成数值类型errorscoerce 会把无法解析的值置为 NaN脚本不会中断缺失值留到下一步统一处理。参数说明sort_values 按列名排序这里用的是刚转换出来的 datetime 列set_index 之后 DataFrame 的索引变成时间后面 resample 才有意义。value_cols 里的字段如果不存在用 if col in df.columns 兜底换数据源时不会因为缺一列就整体崩掉。提示如果监测数据原本就是乱序的月份set_index 之后必须再调用一次 sort_index()否则后面 resample 的结果会对不上真实时间线。2.2 缺失值填充与异常值过滤两种策略怎么选空气污染数据缺失几乎是必然的设备检修、通信中断、点位停测都会留空缺。不先看缺失量就直接处理大概率会把一个连续缺了半个月的数据集插值成一段漂亮的假曲线。先统计缺失的规模和占比再决定策略。print(df.isna().sum()) missing_ratio df[PM2.5].isna().mean() if missing_ratio 0.3: print(PM2.5 缺失比例超过 30%建议补充数据源或删除这段区间)逻辑说明isna().sum() 按列统计缺失数量。missing_ratio 是 PM2.5 这一列的缺失占比如果超过 30%插值已经救不回来应直接删除该区间或在设计报告里注明数据缺口避免影响后续趋势判断。参数说明0.3 这个阈值不是固定的如果分析的是月均值30% 缺失还可以接受如果做逐小时分析超过 10% 就值得警惕。缺失占比越高插值的可信度越低宁可少一段数据也不要补一段编出来的曲线。缺失值处理有两种常用策略。短缺口连续缺失三五个小时用插值长缺口删除区间或在报告中注明。下面这段是短缺口场景的常见写法。df df.interpolate(methodtime, limit_directionboth)逻辑说明interpolate 会沿着索引方向补值。methodtime 是按时间间隔做线性插值比普通线性插值更贴合监测数据的物理变化过程。limit_directionboth 表示首尾两侧都允许向里面补如果数据开头就缺了一段不加这个参数会补不进去。参数说明如果数据里的连续缺失超过 6 个小时建议先观察是否真的有这么长一段空窗。插值适合短缺口长缺口用 limit_areainside 反而更好因为首尾缺失通常对应站点没启用或已经停测不该用模拟值填。异常值这里用 IQR四分位距过滤因为空气监测数据中偶发仪器故障导致的尖峰和真实的重污染极端值很难靠阈值区分IQR 对长尾数据更稳健。q1, q3 df[PM2.5].quantile(0.25), df[PM2.5].quantile(0.75) iqr q3 - q1 low, high q1 - 1.5 * iqr, q3 1.5 * iqr mask (df[PM2.5] low) (df[PM2.5] high) df_clean df[mask].copy()逻辑说明Q1 和 Q3 分别是 25% 和 75% 分位数IQR 是两者之差。1.5 倍 IQR 是经典异常值判定边界落在边界之外的数值视为仪器噪声或录入错误。mask 是布尔索引保留原始索引结构便于后续溯源哪条数据被过滤掉了。参数说明如果数据整体分布集中可以把 1.5 放宽到 2.0减少误杀真实重污染事件。过滤后建议再用 describe() 看一眼各列 max 值是否还在合理范围内比如 PM2.5 的日均值超过 300 就要考虑是不是站点数据本身有问题。2.3 重采样小时数据聚合到日均小时级别的浓度数据波动太大画出来像一条锯齿做趋势分析要先聚合成日均。pandas 的 resample 是按时间索引聚合的入口这一步也是这套系统里 preprocess.py 的最后一道工序。daily df_clean.resample(1D).agg({ PM2.5: mean, PM10: mean, SO2: mean, NO2: mean, CO: mean, O3: mean, AQI: max })逻辑说明resample(1D) 按天切分数据agg 对每一列指定聚合方式。AQI 用 max因为空气质量指数一天内只要出现一次重污染对当天的健康影响就不可忽略浓度指标用 mean 更稳定能反映整体水平。如果后面要画逐小时变化把 1D 改成 1H但前提是原始数据本身是小时粒度。参数说明除了 1D 和 1H还有 1W、1M 可切周和月。课程设计里经常把日均和月均放一起对比冬夏差异。聚合完再看一眼 isna().sum()如果还有零散缺失再补一次 ffill 或往前填充即可。提示resample 会按完整日历分组如果原始数据中间缺了几天聚合结果里就会出现空行这是正常行为不是代码 bug。3. 可视化与看板matplotlib 静态图到 Flask ECharts 交互数据洗干净之后出图本身并不复杂真正扎心的是中文字体。matplotlib 默认字体不支持中文不设置的话标题和坐标轴全部渲染成方块。很多第一次跑这套系统的人图是出来了但全是乱码就是这个原因。3.1 中文字体与基础趋势图画图前先把字体和负号问题一次性设置好。这个写在 analysis.py 的最前面保证后面每张图的中文都能正常显示。import matplotlib import matplotlib.pyplot as plt matplotlib.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, Arial Unicode MS] matplotlib.rcParams[axes.unicode_minus] False逻辑说明font.sans-serif 指定了一个字体候选列表。SimHei 黑体在 Windows 上基本都有macOS 靠 Arial Unicode MS 兜底Linux 服务器可以补 Noto Sans CJK SC。axes.unicode_minus 设置为 False解决坐标轴负号显示成方块的问题。设置这两行之后标题、标签、图例、负号全部正常。注意如果在 Jupyter 里设置了中文字体还乱码通常是把字体名写错或系统没有对应字体。Linux 上用 fc-list :langzh 查可用中文字体查到什么就填什么。画日均 PM2.5 趋势图用 pandas 的 plot 方法可以快速出图但要输出文件给设计报告用savefig 的参数也得注意。daily[PM2.5].plot(figsize(12, 4), titlePM2.5 日均浓度变化) plt.ylabel(μg/m³) plt.savefig(output/pm25_trend.png, dpi150, bbox_inchestight) plt.close()逻辑说明pandas 的 plot 本质是封装了 matplotlib 的 plot 接口直接对 Series 调用即可。figsize 控制长宽比12:4 适合时序长图。savefig 输出到 output 目录与原始数据目录分开。参数说明dpi150 足够放进 Word 报告再高也没有实际用处bbox_inchestight 会自动裁掉图片周围的白边避免插入文档后四周空白过大。plt.close() 必须写否则在循环里画多张图时内存会被旧图对象一直占用。3.2 相关性热力图从图表里找污染来源线索空气污染的六个监测指标之间往往存在强相关相关性热力图是分析报告里必备的一张图。这套源码里没有额外依赖 seaborn直接用 matplotlib 的 imshow 也能画成清晰的热力图。import numpy as np corr daily[[PM2.5, PM10, SO2, NO2, CO, O3]].corr() fig, ax plt.subplots(figsize(8, 6)) im ax.imshow(corr, cmapcoolwarm, vmin-1, vmax1) ax.set_xticks(range(len(corr))) ax.set_xticklabels(corr.columns, rotation45, haright) ax.set_yticks(range(len(corr))) ax.set_yticklabels(corr.columns) for i in range(len(corr)): for j in range(len(corr)): ax.text(j, i, f{corr.iloc[i, j]:.2f}, hacenter, vacenter, fontsize9) plt.colorbar(im) plt.tight_layout() plt.savefig(output/corr_heatmap.png, dpi150) plt.close()逻辑说明corr() 默认计算 Pearson 相关系数返回一个 6×6 的 DataFrame。imshow 把相关矩阵映射成颜色vmin/vmax 固定为 -1 到 1这样不同批次图片的色标统一。两层 for 循环往每个格子里填数值报告里引用时可以直接说PM2.5 与 PM10 相关系数 0.91说明两者同源概率较高。参数说明cmapcoolwarm 让负相关偏蓝、正相关偏红。相关系数接近 0.9 的指标对在分析结论里可以归因于同类污染源NO2 与 PM2.5 的相关性如果明显通常可以推测与机动车尾气排放有关。这里只讲图表呈现逻辑具体归因要结合当地工业布局和气象条件。3.3 Flask 数据接口与 ECharts 渲染静态图适合放在报告里交互看板适合答辩演示。这套系统的 Web 部分用 Flask 提供接口前端用 ECharts 渲染。先造一个返回 JSON 的接口前端的折线图从 /api/daily 拿数据。from flask import Flask, jsonify, render_template app Flask(__name__) app.route(/api/daily) def daily_api(): data daily.reset_index()[[datetime, PM2.5, PM10, AQI]].dropna() return jsonify({ dates: data[datetime].dt.strftime(%Y-%m-%d).tolist(), pm25: data[PM2.5].round(1).tolist(), pm10: data[PM10].round(1).tolist(), aqi: data[AQI].round(1).tolist() }) app.route(/) def index(): return render_template(index.html) if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)逻辑说明先 reset_index 把时间索引变成普通列然后 dropna 保证接口里没有 NaN否则 Python 的 float nan 被 JSON 序列化成 null前端 ECharts 拿到 null 后不会报错但图会断线。dt.strftime 把时间统一格式化成字符串前端不必处理时区。round 保留一位小数控制接口传输的数据量。参数说明host0.0.0.0 允许局域网内其他设备访问演示时用手机或另一台电脑连同一个 WiFi 就能打开看板如果只在本机看改成 127.0.0.1 更安全。debugFalse 是演示环境的稳妥选择开着调试模式容易被陌生人触发 reloader。前端模板在 templates/index.html 里核心逻辑是 fetch 接口并把数据映射到折线图。div idchart stylewidth:100%;height:420px;/div script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script script fetch(/api/daily) .then(res res.json()) .then(data { const chart echarts.init(document.getElementById(chart)); chart.setOption({ xAxis: { type: category, data: data.dates }, yAxis: { type: value, name: μg/m³ }, series: [ { name: PM2.5, type: line, data: data.pm25 }, { name: PM10, type: line, data: data.pm10 } ] }); }); /script逻辑说明前端只有一个容器 div 和一个 fetch 流程。ECharts 初始化后把后端返回的 dates 数组赋给 xAxispm25/pm10 分别给两条 line 系列。JS 里没有任何清洗逻辑所有数据规整都在 Python 侧完成前后端只靠一个 JSON 结构通信。参数说明如果部署环境访问不了 CDN就把 echarts.min.js 下载到 static 目录然后改成script src{{ url_for(static, filenameecharts.min.js) }}。series 里加 smooth: true 可以让折线变成平滑曲线答辩演示效果更好。4. 源码包拆解设计报告、模块边界与二次开发入口这套资源下载下来之后先别急着双击运行把目录结构和模块职责理清楚后面改自己的数据时能少走很多弯路。源码在设计上遵循一条非常朴素的原则各管一段不做大泥球。4.1 preprocess / analysis / app 三个模块怎么切这套系统的源码拆成 preprocess.py、analysis.py、app.py 和维护 HTML 的 templates 目录。边界划分的规则很简单preprocess.py 只负责读数据和清洗analysis.py 只负责出图app.py 只负责 Web 服务和接口。这样划分的原因是Flask 请求里不应该做耗时预处理清洗结果提前算好接口直接读结果。air_pollution_system/ ├── preprocess.py # 读 CSV、清洗、重采样 ├── analysis.py # matplotlib 与 pyecharts 出图 ├── app.py # Flask 接口与页面路由 ├── templates/ │ └── index.html # ECharts 页面 ├── data/ │ └── air_quality.csv # 原始监测数据 ├── output/ # 生成的 png / html / csv ├── requirements.txt └── 设计报告.docx逻辑说明目录层级本身不是强制的但按这个结构改起来最快。output 目录存放所有生成结果与 data 原始数据分开避免跑一次流程就把数据目录弄乱。requirements.txt 里固定核心依赖pandas、numpy、matplotlib、flask需要地图可视化时再加 pyecharts。参数说明preprocess.py 里的 load() 函数建议设计成可传参数的形式例如 load(pathdata/air_quality.csv)。这样后面写日报脚本或换数据源时不用改动函数体只改参数即可。analysis.py 里的图表函数也建议全部接受 DataFrame 作为入参方便在脚本里直接复用。4.2 设计报告怎么用到开发里zip 里这份设计报告在课程设计场景里基本可以充当文档模板。它一般包含需求分析、系统设计、数据源设计、功能实现、测试结果这几块。真正值钱的不是凑字数的理论章节而是关于数据来源和字段含义的表格代码里所有列名都是从这些表对应过来的。| 设计报告章节 | 对应代码位置 | 开发阶段 | | 需求分析 | preprocess.py 的数据字段定义 | 确定统计口径与缺失策略 | | 系统设计 | analysis.py 的图表函数清单 | 明确画哪几张图 | | 数据源设计 | preprocess.py 的 read_csv 与清洗 | 确定列名和时间格式 | | 功能实现 | app.py templates/index.html | 接口字段与页面渲染 | | 测试结果 | output/ 下生成的 png | 核对图表和预期是否一致 |这个映射关系的意思是写完代码后再回填报告时可以直接按表格把代码里的技术点对应回文档章节。答辩被问到数据清洗怎么做照着 preprocess.py 里 interpolate 部分往下讲比背概念要有说服力得多。提示设计报告里的功能实现章节通常要求配运行截图。从 output/ 目录挑三张代表性图片放进去一张清洗前后对比图、一张趋势图、一张接口返回 JSON 的截图。页面截图放首页即可。4.3 换自己的数据源时必改的三个点很多读者下载这套系统后第一件事是换上自己下载的空气质量 CSV。直接改文件名大概率跑不动因为列名不一定对得上。必须改的位置基本集中在三处。第一处是 read_csv 之后的列名映射。不同来源的列名五花八门PM2.5 可能写成 PM25、PM2.5_QC日期可能叫 time 或 date还有可能叫监测时间。COLUMN_MAP { 日期: datetime, 时间: datetime, 监测时间: datetime, PM25: PM2.5, PM2.5_QC: PM2.5, 可吸入颗粒物: PM10 } df df.rename(columnsCOLUMN_MAP)逻辑说明rename 会把原始列名统一成标准列名。这里用字典映射而不是逐个改 CSV 文件是因为代码可复用。换个数据源时只需要往字典里加一行映射函数体不用动。参数说明如果列名带空格或特殊字符比如 PM2.5映射后记得 strip 一下。时间列映射成 datetime 之后后面的 to_datetime 才能统一处理。第二处是时间格式。某些网站的日期是 2023-01-05 而不是带时分秒的完整时间to_datetime 能自动识别但如果是 2023年1月5日 这种中文格式就得补 format%Y年%m月%d日。第三处是缺失策略。新数据源的缺失率如果更高插值和删除的平衡就要调整。先打印各列缺失比例超过 30% 的那列建议直接删掉而不是硬保留下来让相关性矩阵出现整行 NaN。5. 避坑排查环境装不上、中文乱码、图表空白的常见问题5.1 环境装不上的两类典型场景现象运行 python app.py 后第一行就报 ModuleNotFoundError: No module named flask 或 pandas明明装过依赖却找不到模块。原因电脑里通常同时存在 Anaconda 和官方 Python命令行默认进入的是 A 环境pip install 却装进了 B 环境也可能是 pip 安装失败后没排查。在 vscode 里跑这套代码最容易踩的坑是右下角选择的解释器和终端里 which python 指向的不是同一个环境。解决统一用 python -m pip install -r requirements.txt 安装-m 保证 pip 和 python 属于同一个解释器。装完在 vscode 终端里执行 python -c import pandas; print(pandas.version)能打印出版本号才算真正装好不要凭感觉认为装过就没问题。现象项目运行的时候每次打开终端都要重新激活 conda 环境否则 flask 命令找不到。原因Anaconda 的环境变量没有写入 shell或者代码里写死了绝对路径项目一换目录就崩。解决在 app.py 里用相对路径引用 data/ 和 output/并保证入口脚本在项目根目录执行。如果必须用绝对路径用 os.path.join(os.path.dirname(file), ...) 构造不要手写死路径。5.2 图表和数据相关的三个经典翻车点现象matplotlib 出的图中文全是方块标题、图例一个都认不出来。原因matplotlib 默认字体 DejaVu Sans 不含中文字形rcParams 配置执行晚了或者执行完没有重启解释器。解决把 rcParams 设置放在 import matplotlib 之后、任何绘图调用之前。如果 SimHei 缺失用 fc-list :langzh 查系统里的中文字体名Linux 服务器上常见的是 Noto Sans CJK SC。设置改完要重启内核或重新跑一遍脚本rcParams 只在当前进程中生效。现象read_csv 报 UnicodeDecodeError还没开始分析就倒在第一步。原因CSV 文件实际是 GBK/GB2312 编码而 read_csv 默认按 utf-8 解码。解决把 encodingutf-8-sig 改成 encodinggbk。工程上更稳的做法是在读取前先探测编码用 chardet 判断后自动选择但这套资源里手改 encoding 一个参数就够了。修改后打印 df.head() 抽查确认中文列没有乱码再继续。现象Flask 页面能打开ECharts 图却一片空白浏览器 Network 里 /api/daily 返回了 JSON但 series 没数据。原因后端返回的字段名与前端 JS 里的 key 不一致比如 Python 返回 pm25前端写 pm2.5或者数据里有 NaNJSON 序列化后变成 nullECharts 的折线在 null 处断开。解决先在浏览器地址栏直接访问 /api/daily肉眼核对返回字段和前端 fetch 代码里的字段是否完全一致。字段名不一致就改前端 JS 的 key有 NaN 就在接口里统一 dropna()。现象重采样后画折线横轴日期乱序中间还多出一段折线。原因原始 CSV 时间没排序set_index 后没调用 sort_indexresample(1D) 后缺失日期产生空行pandas 的 plot 会把这些空值直接连成直线。解决set_index 后立刻执行 sort_index()。聚合后如果缺失依然存在用 dropna() 清掉避免把缺口补成一条笔直的长线。6. 进阶把分析流程固化成每天早晨自动输出日报跑通分析流程只是第一步实际使用时我更习惯单独写一个 run_report.py只做数据清洗和汇总不启动 Flask。这样在服务器或本机定时任务里每天固定输出一张汇总 CSV 和几张关键趋势图不需要浏览器参与。6.1 日报脚本与定时任务的挂法# run_report.py import preprocess import matplotlib matplotlib.use(Agg) from analysis import TrendChart df preprocess.load(data/air_quality.csv) summary df.resample(1D).agg({ PM2.5: mean, PM10: mean, O3: max, AQI: max }) summary.to_csv(output/daily_report.csv, encodingutf-8-sig)逻辑说明matplotlib.use(Agg) 是无人值守环境下的关键设置。Agg 是不需要显示窗口的渲染后端不加这一行在无图形界面的服务器上运行时会直接报 TclError 或找不到 display。to_csv 保存成带 BOM 的 utf-8-sig 编码Excel 双击打开不会乱码。参数说明summary 里 AQI 和 O3 用 max浓度指标用 mean和前面预处理阶段的口径保持一致。如果还想输出前一天与前一周的对比多写一行 summary.tail(7).mean() 就能拼进报表。Linux 定时任务写在 crontab 里0 8 * * * cd /path/air_pollution_system /usr/bin/python3 run_report.py output/cron.log 21参数说明0 8 表示每天 8:00 执行。cd 先切到项目目录保证相对路径能正确解析。21 把报错也写入日志之后数据出问题时直接看 cron.log 定位。Windows 上是任务计划程序触发器设为每天 8:00操作指向 python.exe参数填脚本绝对路径。从那以后我每次拿到新的空气污染 CSV都会强制先跑一遍 run_report.py 再开 Flask因为日报脚本会暴露数据的真实形态页面接口只是把已经验证过的结果拿出来展示。这个顺序帮我躲过不少数据质量的暗坑希望帮到你。本文还有配套的精品资源点击获取
返回列表