尧图精选

基于Python的空气污染数据可视化分析系统实战:从数据清洗到ECharts大屏

🕒 发布时间:2026/10/2 3:30:51 📁 来源:尧图网络
简介这份资源是面向高校学生与Python初学者的数据分析可视化课程设计完整方案以空气污染数据为分析对象帮助解决期末大作业选题难、代码无从下手、报告不会撰写等问题适合作为Python语言、数据分析可视化类课程的期末项目或课程设计参考。压缩包共288个文件约104.33MB涵盖12个py源码文件、15个ipynb分析笔记、42个csv数据文件、34个html页面及配套css、js资源另附pdf报告、pptx答辩材料与mp4演示视频代码含注释部署简单新手也能看懂。目前已有1065人学习下载。项目围绕空气质量数据的清洗、统计与可视化展开包含多维度图表展示与交互页面读者可获得完整源码、分析报告、数据集与答辩素材直接用于课程设计提交或二次开发具备较高的实际应用与参考价值。1. 空气污染数据可视化系统从一份课程大作业到能写进简历的项目每年做 Python 数据分析大作业空气污染数据几乎是出现频率最高的选题之一。原因很直接数据公开、字段规整、时间序列特征明显而且天然适合做可视化。但真正动手之后你会发现大部分人的成品停留在「读 CSV、画折线图、截图贴报告」这个阶段代码跑完就丢报告里全是图却说不清数据背后的结论。这篇笔记要讲的是把一份空气污染数据可视化分析系统做成完整项目的路径——从数据获取、清洗、指标计算到 ECharts 可视化大屏和可复现的分析报告。适合正在做课程设计的学生也适合想用一个小项目补齐 pandas、matplotlib、pyecharts 这条工具链的入门者。核心不是画图而是让每一张图都能回答一个具体问题。2. 空气污染数据从哪来、字段怎么理解2.1 常见数据源与字段结构做空气污染分析第一步是拿到数据。常见的做法有三类一是用公开的空气质量历史数据接口按城市和日期拉取二是直接使用课程提供的 CSV 数据集三是用爬虫从公开监测平台采集。前两种更稳第三种容易因为页面结构变化而翻车新手不建议一上来就写爬虫。不管哪种来源空气污染数据的核心字段基本一致。以常见的城市日度数据为例字段通常包括字段名含义单位典型范围date日期—2018-01-01 起city城市名—字符串AQI空气质量指数—0500PM2.5细颗粒物浓度μg/m³0500PM10可吸入颗粒物μg/m³0600SO2二氧化硫μg/m³0200NO2二氧化氮μg/m³0200CO一氧化碳mg/m³010O3臭氧μg/m³0300理解字段的关键在于两点AQI 是综合指数不是浓度PM2.5 和 PM10 是质量浓度单位是微克每立方米。很多报告把 AQI 和 PM2.5 混着讲结论就会失真。AQI 的等级划分是固定的050 优51100 良101150 轻度污染151200 中度污染201300 重度污染300 以上严重污染。这个分级是后续所有可视化分色的依据。2.2 用 pandas 做一次完整的数据体检拿到数据后不要急着画图先做体检。下面这段代码是我每次拿到新数据集都会跑一遍的模板import pandas as pd import numpy as np # 读取数据注意编码中文数据常见 gbk 或 utf-8-sig df pd.read_csv(air_quality.csv, encodingutf-8-sig) # 1. 看形状和字段类型 print(数据形状:, df.shape) print(df.dtypes) # 2. 看缺失情况 missing df.isnull().sum() print(缺失值统计:\n, missing[missing 0]) # 3. 看数值分布判断有没有异常值 print(df[[AQI, PM2.5, PM10]].describe()) # 4. 日期字段转成 datetime方便后续按时间聚合 df[date] pd.to_datetime(df[date], errorscoerce) print(日期范围:, df[date].min(), 到, df[date].max()) # 5. 检查重复行 print(重复行数:, df.duplicated().sum())这段代码的逻辑是先确认数据规模再定位缺失和异常最后统一时间格式。参数上encoding要根据实际文件调整读进来乱码就换gbkerrorscoerce会把无法解析的日期变成 NaT避免直接报错中断。describe()重点看最大值如果 PM2.5 出现 9999 这种值基本是缺失标记要单独处理。体检之后通常会发现三类问题缺失值、异常值、单位不统一。缺失值如果占比低于 5%可以直接删占比高就要考虑插值。异常值不要无脑删先判断是不是真实的重污染事件。单位不统一最常见的是 CO有的数据是 mg/m³有的是 μg/m³差一千倍不统一后面所有对比都是错的。3. 清洗与特征工程让数据能支撑结论3.1 缺失值、异常值与单位统一清洗的目标不是把数据变干净而是让数据能支撑你要下的结论。下面是我一般会用的清洗流程# 1. 删除关键字段缺失的行 df df.dropna(subset[date, city, AQI]) # 2. 数值字段用中位数填充比均值更抗异常值 for col in [PM2.5, PM10, SO2, NO2, O3]: df[col] df[col].fillna(df[col].median()) # 3. 处理异常值把超过物理上限的值标记为缺失再填充 df.loc[df[PM2.5] 1000, PM2.5] np.nan df[PM2.5] df[PM2.5].fillna(df[PM2.5].median()) # 4. 统一 CO 单位到 mg/m³ if df[CO].max() 100: df[CO] df[CO] / 1000 # 5. 按 AQI 打等级标签 def aqi_level(aqi): if aqi 50: return 优 elif aqi 100: return 良 elif aqi 150: return 轻度污染 elif aqi 200: return 中度污染 elif aqi 300: return 重度污染 return 严重污染 df[level] df[AQI].apply(aqi_level)逻辑说明中位数填充比均值稳因为污染数据右偏严重均值容易被极端值拉高。异常值先转 NaN 再填充是为了保留其他字段的信息而不是整行删掉。CO 单位判断用max() 100这个阈值是因为正常 mg/m³ 的 CO 不会超过几十超过基本就是 μg/m³。等级标签是后面分色可视化的基础一定要在这一步做好。参数上物理上限可以按经验设PM2.5 超过 1000、PM10 超过 1500、SO2 超过 500 都值得怀疑。但要注意沙尘暴天气 PM10 确实可能很高所以异常值处理要结合业务判断不能一刀切。3.2 时间维度与城市维度的特征构造清洗完只是原料要出结论还得构造特征。空气污染分析最常用的两个维度是时间和城市。时间维度上我会构造这些字段df[year] df[date].dt.year df[month] df[date].dt.month df[season] df[month].map({ 12: 冬季, 1: 冬季, 2: 冬季, 3: 春季, 4: 春季, 5: 春季, 6: 夏季, 7: 夏季, 8: 夏季, 9: 秋季, 10: 秋季, 11: 秋季 }) # 按城市和月份聚合看月度趋势 monthly df.groupby([city, month])[AQI].mean().reset_index() # 按城市和季节聚合看季节差异 seasonal df.groupby([city, season])[AQI].mean().reset_index()城市维度上重点是横向对比。可以算每个城市的年均 AQI、超标天数占比、首要污染物。首要污染物的判断逻辑是比较各污染物浓度折算后的分指数取最大的那个。简化做法是直接比较 PM2.5、PM10、SO2、NO2、O3 的标准化值。# 计算每个城市的年均 AQI 和超标天数 city_stats df.groupby(city).agg( 年均AQI(AQI, mean), 超标天数(AQI, lambda x: (x 100).sum()), 总天数(AQI, count) ).reset_index() city_stats[超标占比] city_stats[超标天数] / city_stats[总天数]这一步的产出是后续所有图表的数据源。我的习惯是把清洗后的明细数据和聚合后的统计表分别存成 CSV画图时只读统计表避免每次重新计算。提示聚合前一定要确认时间范围一致。如果不同城市的数据起止时间不同直接对比年均值会失真要先对齐时间窗口。4. 可视化实现从静态图到可交互大屏4.1 matplotlib 与 seaborn 做探索性图表探索阶段用 matplotlib 和 seaborn 最快。它们适合快速看分布、看趋势不追求美观追求信息密度。下面是一个典型的探索组合import matplotlib.pyplot as plt import seaborn as sns # 设置中文字体否则中文会显示成方块 plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False fig, axes plt.subplots(2, 2, figsize(14, 10)) # 1. AQI 分布直方图 sns.histplot(df[AQI], bins50, axaxes[0, 0], color#4C72B0) axes[0, 0].set_title(AQI 分布) # 2. 各城市年均 AQI 柱状图 city_stats.sort_values(年均AQI).plot( xcity, y年均AQI, kindbarh, axaxes[0, 1], color#DD8452 ) axes[0, 1].set_title(城市年均 AQI 对比) # 3. 月度趋势折线图 for city in monthly[city].unique()[:5]: sub monthly[monthly[city] city] axes[1, 0].plot(sub[month], sub[AQI], labelcity) axes[1, 0].set_title(月度 AQI 趋势) axes[1, 0].legend() # 4. 污染物相关性热力图 corr df[[AQI, PM2.5, PM10, SO2, NO2, O3, CO]].corr() sns.heatmap(corr, annotTrue, fmt.2f, axaxes[1, 1], cmapcoolwarm) axes[1, 1].set_title(污染物相关性) plt.tight_layout() plt.savefig(explore.png, dpi150)逻辑说明font.sans-serif设成 SimHei 是中文环境的标准操作不设的话标题全是方块。subplots(2, 2)一次出四张图适合放进报告。相关性热力图能快速看出哪些污染物同源比如 PM2.5 和 PM10 通常高度相关。参数上bins50是分布图的常用值数据量大可以加到 100。dpi150是报告插图的最低要求再低打印会糊。figsize按报告排版调一般 14×10 够用。4.2 pyecharts 做可交互可视化大屏报告里放静态图可以但要做「系统」就得有交互。pyecharts 是目前最顺手的选择生成的 HTML 可以直接打开也方便截图放进报告。下面是一个组合大屏的核心代码from pyecharts import options as opts from pyecharts.charts import Bar, Line, Pie, Map, Grid from pyecharts.commons.utils import JsCode # 1. 城市年均 AQI 柱状图 bar ( Bar() .add_xaxis(city_stats[city].tolist()) .add_yaxis(年均AQI, city_stats[年均AQI].round(1).tolist()) .set_global_opts( title_optsopts.TitleOpts(title城市年均 AQI), visualmap_optsopts.VisualMapOpts( max_150, min_30, range_color[#50a3ba, #eac763, #d94e5d] ) ) ) # 2. 月度趋势折线图 line ( Line() .add_xaxis([str(m) for m in range(1, 13)]) .add_yaxis(PM2.5, monthly_pm25, smoothTrue) .add_yaxis(PM10, monthly_pm10, smoothTrue) .set_global_opts(title_optsopts.TitleOpts(title月度污染物趋势)) ) # 3. 污染等级占比饼图 level_count df[level].value_counts() pie ( Pie() .add(, [list(z) for z in zip(level_count.index, level_count.values)]) .set_global_opts(title_optsopts.TitleOpts(title污染等级占比)) .set_series_opts(label_optsopts.LabelOpts(formatter{b}: {d}%)) ) # 4. 组合成一张大屏 grid ( Grid(init_optsopts.InitOpts(width1200px, height600px)) .add(bar, grid_optsopts.GridOpts(pos_left5%, pos_right55%)) .add(line, grid_optsopts.GridOpts(pos_left55%, pos_right5%)) ) grid.render(dashboard.html)逻辑说明visualmap_opts让柱状图按数值自动分色比统一颜色直观。smoothTrue让折线平滑适合展示趋势。Grid用来把多张图拼到一张画布上通过pos_left、pos_right控制位置。render输出 HTML浏览器打开就是可交互的。参数上range_color建议用污染语义色低值偏蓝绿高值偏红。width和height按屏幕调大屏一般 1920×1080 起步。饼图的formatter{b}: {d}%会显示名称和百分比比默认标签清楚。注意pyecharts 的版本差异较大v1 和 v2 的 API 不完全兼容。装的时候锁定版本避免照着旧教程写出跑不通的代码。4.3 把图表串成一份能讲清结论的报告可视化做完报告才是最终交付物。我的报告结构一般是数据说明、清洗过程、整体概况、时间趋势、城市对比、污染物相关性、结论。每一节配一到两张图图下面写清楚这张图回答了什么。比如月度趋势图不要只写「从图中可以看出 AQI 有季节变化」而要写「冬季 AQI 明显高于夏季主要因为采暖期 PM2.5 浓度上升12 月和 1 月的月均 AQI 比 7 月高出约 40%」。结论要带数字数字来自前面的聚合表。报告里最容易出问题的是图表和结论对不上。常见情况是图画的是年均值结论却在讲某个月。写的时候养成习惯每张图先写一句「这张图的数据来源是 XX 表统计口径是 XX」再写结论。5. 避坑与排查那些让大作业翻车的细节5.1 中文乱码与字体缺失现象图表标题、坐标轴中文全部显示成方块。原因matplotlib 默认字体不含中文pyecharts 在部分环境也依赖系统字体。解决matplotlib 里设plt.rcParams[font.sans-serif] [SimHei]Linux 环境如果没有 SimHei换成WenQuanYi Micro Hei或Noto Sans CJK SC。pyecharts 生成的 HTML 如果中文异常检查浏览器编码一般是meta charsetutf-8缺失。5.2 日期解析失败导致聚合为空现象groupby之后结果为空或者时间范围显示 1970 年。原因日期字段格式不统一有的是2018/1/1有的是2018-01-01pd.to_datetime解析失败变成 NaT。解决先用errorscoerce看有多少 NaT再统一格式。可以用df[date].str.replace(/, -)预处理或者用formatmixed让 pandas 自动推断。5.3 单位不统一导致对比失真现象某城市 CO 浓度比其他城市高一千倍。原因数据源单位不同有的是 mg/m³有的是 μg/m³。解决画图前先看每个数值字段的describe()对量级明显异常的先判断单位。CO 正常范围是 010 mg/m³超过 100 基本就是 μg/m³除以 1000 即可。5.4 聚合口径不一致导致结论矛盾现象报告里年均 AQI 和月度趋势对不上。原因年均值用的是全量数据月度趋势用的是清洗后数据两次清洗规则不同。解决清洗只做一次把清洗后的明细存成 CSV所有聚合都从这份明细出发。不要在每个分析环节重新读原始数据。5.5 pyecharts 图表在报告里显示不全现象HTML 打开正常截图放进 Word 后右侧被裁掉。原因大屏宽度设得太大截图工具或 Word 页面装不下。解决截图前把InitOpts的width调到 1000px 左右或者用浏览器的响应式模式截图。报告里插图宽度建议不超过 15cm。6. 进阶技巧让这个项目从及格变成能拿得出手如果只做到前面几步项目能跑、报告能交但很难在简历里单独拿出来讲。真正拉开差距的是两件事一是把分析做成可复现的流程二是让可视化有明确的业务指向。先说可复现。我的习惯是把整个项目拆成三个脚本prepare.py负责读取和清洗输出clean.csvanalyze.py负责聚合和统计输出若干统计表visualize.py负责读统计表出图。三个脚本按顺序跑任何一步出问题都能单独排查。这样做的好处是别人拿到你的代码改一下数据路径就能复现全部结果而不是面对一个几百行的 notebook 无从下手。再说业务指向。空气污染分析最容易犯的错是图很多但结论很泛。改进方法是给每张图配一个「所以呢」。比如城市对比图不要停在「A 城市污染比 B 城市重」而要往下算一步A 城市超标天数占比多少主要超标污染物是什么如果要把超标天数压到某个目标哪个污染物需要优先控制。这一步不需要复杂模型用前面的聚合表就能算。还有一个实用技巧是加一个简单的预测。不用上 LSTM用statsmodels做季节性分解或者简单的移动平均就能出趋势线。下面是一个最小示例from statsmodels.tsa.seasonal import seasonal_decompose # 按日期聚合全国日均 AQI daily df.groupby(date)[AQI].mean().asfreq(D) daily daily.interpolate() # 季节性分解 result seasonal_decompose(daily, modeladditive, period365) result.plot() plt.savefig(decompose.png, dpi150)逻辑说明asfreq(D)把时间序列补齐成日频interpolate()填掉缺失日期。period365对应年周期。分解出来的趋势项能看出长期走向季节项能看出周期性波动。这张图放进报告比单纯画折线图更有分析深度。参数上model选additive还是multiplicative看波动幅度是否随均值变化。污染数据一般用加法模型就够。period按数据粒度设日数据用 365月数据用 12。最后说一个我自己的习惯项目做完之后把 README 当成报告摘要来写。不要写「本项目实现了数据可视化」而要写「本项目分析了 X 个城市 Y 年的空气质量数据发现冬季 AQI 比夏季高约 40%PM2.5 是首要污染物并给出了可交互的 HTML 大屏和完整清洗流程」。这样别人扫一眼就知道你做了什么、结论是什么。做课程设计也好放简历也好这种写法都比堆功能列表管用。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →