ARTICLE DETAIL

资讯详情

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

Django+ECharts城市PM2.5空气质量数据可视化分析实战

Django+ECharts城市PM2.5空气质量数据可视化分析实战 简介这是一套基于Python与Django框架的城市PM2.5空气质量数据可视化分析项目源码面向计算机相关专业学生、课程设计或期末大作业开发者也适合希望入门Django与数据分析的小白实战练习。资源包共64个文件约12.38MB包含17个py源码文件、24个csv数据文件、3个html模板以及sql数据库脚本、xml配置等覆盖数据导入、模型定义、视图渲染与前端展示的完整链路。项目围绕北京、上海、广州、成都、沈阳五座城市六年PM2.5数据展开涵盖月份变化、季度逐年、时间序列以及PM2.5与温度、露点、大气压、相对湿度、风向等要素的关联分析并配有登录注册页面与可视化首页。目前已有328人学习下载代码完整可直接运行可作为高分课程设计参考帮助读者快速理解Django项目结构、数据清洗与可视化呈现思路。1. 从一份 95 分课设说起Django PM2.5 可视化到底在做什么每年毕业季城市空气质量数据可视化都是被翻牌最多的题目之一。标题里这份「Python 基于 Django 城市 PM2.5 空气质量数据可视化分析源码」本质是一套完整的 Web 数据应用后端用 Django 承接数据存储与查询接口前端用 ECharts 把 PM2.5、AQI、PM10 等指标画成折线、柱状、热力图和地图中间夹着一层数据清洗与统计逻辑。它解决的不是「怎么画一张图」而是「一堆城市逐小时的空气质量记录怎么变成能点、能筛、能对比的交互页面」。适合两类人一是要交课程设计、需要能跑起来且结构完整的同学二是想拿一个真实数据集练 Django 查询、模板渲染和 ECharts 联调的新手。下面按「数据怎么进 → 接口怎么出 → 图怎么画 → 坑怎么躲」的顺序拆开讲每一步都能照着复现。2. 数据层怎么搭从原始 CSV 到 Django 模型2.1 先想清楚数据长什么样再决定模型字段空气质量数据的典型结构是「城市 时间 多项污染物浓度 AQI」。常见字段包括 city、datetime、pm25、pm10、so2、no2、co、o3、aqi、quality_level。很多新手一上来就建一张宽表把所有字段塞进一个模型结果查询时又慢又难扩展。我一般会拆成两张表一张城市维度表存城市名、省份、经纬度一张事实表存逐小时或逐天的监测值用外键关联城市。这样后面做「按城市筛选」「按区域聚合」时不用反复写字符串匹配。# models.py from django.db import models class City(models.Model): name models.CharField(max_length50, uniqueTrue, verbose_name城市名) province models.CharField(max_length50, blankTrue) longitude models.FloatField(nullTrue, blankTrue) latitude models.FloatField(nullTrue, blankTrue) def __str__(self): return self.name class AirQuality(models.Model): city models.ForeignKey(City, on_deletemodels.CASCADE, related_namerecords) record_time models.DateTimeField(db_indexTrue, verbose_name监测时间) pm25 models.FloatField(nullTrue) pm10 models.FloatField(nullTrue) so2 models.FloatField(nullTrue) no2 models.FloatField(nullTrue) co models.FloatField(nullTrue) o3 models.FloatField(nullTrue) aqi models.IntegerField(nullTrue) quality_level models.CharField(max_length20, blankTrue) class Meta: indexes [models.Index(fields[city, record_time])]逻辑说明City 表用 unique 约束城市名避免导入时重复插入AirQuality 表把 record_time 设为 db_index因为后面几乎所有图表都按时间范围过滤。Meta 里再建一个联合索引覆盖「某城市某时间段」这种最高频查询。参数上浓度字段用 FloatField 并允许 null是因为原始数据里常有缺测值强行设非空会导致导入中断。2.2 用 management command 做数据导入别写一次性脚本把 CSV 导入数据库很多人直接在视图里读文件或者写个独立脚本手动跑。更稳的做法是自定义 management command这样能复用 Django 的 ORM 和配置也方便重复执行。# management/commands/import_air.py import csv from django.core.management.base import BaseCommand from air.models import City, AirQuality from django.utils.dateparse import parse_datetime class Command(BaseCommand): help 导入空气质量 CSV 数据 def add_arguments(self, parser): parser.add_argument(csv_path, typestr) def handle(self, *args, **options): path options[csv_path] city_cache {} batch [] with open(path, encodingutf-8-sig) as f: reader csv.DictReader(f) for row in reader: city_name row[city].strip() if city_name not in city_cache: city_cache[city_name], _ City.objects.get_or_create(namecity_name) batch.append(AirQuality( citycity_cache[city_name], record_timeparse_datetime(row[datetime]), pm25float(row[pm25]) if row[pm25] else None, pm10float(row[pm10]) if row[pm10] else None, aqiint(row[aqi]) if row[aqi] else None, )) if len(batch) 1000: AirQuality.objects.bulk_create(batch) batch.clear() if batch: AirQuality.objects.bulk_create(batch) self.stdout.write(self.style.SUCCESS(导入完成))逻辑说明用 utf-8-sig 读取是为了处理 Excel 导出的 BOM 头否则第一列字段名会带隐藏字符导致 KeyError。city_cache 做进程内缓存避免每条记录都查一次城市表。bulk_create 每 1000 条落一次库比逐条 save 快一个数量级。参数上batch 大小可以按内存调整1000 到 5000 都常见如果数据量超过百万行建议改用 PostgreSQL 的 copy 或分文件导入。2.3 查询接口用 values 聚合别在模板里循环查库数据可视化最怕 N1 查询。图表要「某城市近 30 天 PM2.5 日均值」如果先取记录再在 Python 里按天分组数据一多就卡。正确做法是用 annotate 配合 TruncDate 在数据库层聚合。from django.db.models.functions import TruncDate from django.db.models import Avg from air.models import AirQuality def daily_pm25(city_id, start, end): qs (AirQuality.objects .filter(city_idcity_id, record_time__range(start, end)) .annotate(dayTruncDate(record_time)) .values(day) .annotate(avg_pm25Avg(pm25)) .order_by(day)) return list(qs)逻辑说明filter 先缩小时间范围走联合索引TruncDate 把时间戳截断到天values annotate 让数据库直接返回「日期 均值」Python 侧只做序列化。参数上start 和 end 用带时区的 datetime避免跨时区时日期错位。返回的 list 可以直接丢给 JsonResponse前端 ECharts 拿到的就是干净的 [{day, avg_pm25}] 结构。3. 接口与前端联调Django 出 JSONECharts 接数据3.1 用 JsonResponse 写轻量 API路径和参数要固定课设项目不必上 DRF原生 JsonResponse 足够。关键是接口路径和参数命名要稳定前端才好写。# views.py from django.http import JsonResponse from django.views.decorators.http import require_GET from datetime import datetime require_GET def api_daily_pm25(request): city_id request.GET.get(city_id) start request.GET.get(start) end request.GET.get(end) if not all([city_id, start, end]): return JsonResponse({code: 400, msg: 缺少参数}, status400) data daily_pm25(int(city_id), datetime.fromisoformat(start), datetime.fromisoformat(end)) return JsonResponse({code: 0, data: data})逻辑说明require_GET 限制方法避免被 POST 误触参数缺失时返回 400 而不是空数组前端能明确区分「没数据」和「请求错了」。参数用 ISO 格式字符串如 2024-01-01T00:00:00fromisoformat 解析前后端约定一致即可。返回结构统一成 code data后面加接口不用改前端解析逻辑。3.2 ECharts 折线图的最小可用配置前端拿到 [{day, avg_pm25}] 后直接映射成 x 轴和 series。下面是最小可用配置重点看数据映射和 tooltip。// static/js/pm25_chart.js fetch(/api/daily_pm25/?city_id1start2024-01-01T00:00:00end2024-01-30T00:00:00) .then(res res.json()) .then(res { if (res.code ! 0) return; const days res.data.map(item item.day); const values res.data.map(item item.avg_pm25); const chart echarts.init(document.getElementById(pm25Chart)); chart.setOption({ tooltip: { trigger: axis }, xAxis: { type: category, data: days }, yAxis: { type: value, name: PM2.5 (μg/m³) }, series: [{ name: 日均 PM2.5, type: line, smooth: true, data: values, markLine: { silent: true, data: [{ yAxis: 75, name: 国标二级限值 }] } }] }); });逻辑说明map 两次分别取日期和数值保证顺序一致markLine 画一条 75 的参考线这是 PM2.5 日均二级标准限值能让图立刻有分析味。参数上smooth 设为 true 让折线更顺但数据点少时反而失真可按需关掉。tooltip trigger 用 axis鼠标悬停能同时看到日期和数值。3.3 多城市对比一次请求返回多条 series只画一个城市太单薄课设通常要求对比。做法是接口接收 city_ids 逗号分隔返回按城市分组的字典。require_GET def api_compare(request): city_ids request.GET.get(city_ids, ).split(,) start request.GET.get(start) end request.GET.get(end) result {} for cid in city_ids: if not cid: continue result[cid] daily_pm25(int(cid), datetime.fromisoformat(start), datetime.fromisoformat(end)) return JsonResponse({code: 0, data: result})逻辑说明循环里复用同一个 daily_pm25 函数避免重复代码。返回结构是 {city_id: [...]}前端遍历生成多个 series。参数上city_ids 用逗号分隔比数组更省事URL 里不用编码。注意每个城市查询都会走一次数据库城市多时建议改成一次查询按 city 分组这里为了可读性保留循环。4. 避坑与排查那些让课设卡半天的细节4.1 时间字段时区错乱图表日期整体偏移一天现象数据库里存的是 2024-01-01 00:00:00前端显示成 2023-12-31。原因Django 默认 USE_TZTrue存 UTC模板或序列化时没转本地时区。解决在 settings 里设 TIME_ZONE Asia/Shanghai、USE_TZ True序列化时用 localtime 转换或者干脆在查询后用 timezone.localtime 处理。最省事的做法是导入时就统一转成带时区的 datetime。4.2 bulk_create 后拿不到自增 ID关联写入失败现象先 bulk_create 城市再 bulk_create 记录记录里 city_id 是 None。原因bulk_create 在部分数据库后端不返回主键。解决城市数据量小用 get_or_create 逐条写入只有记录表用 bulk_create。或者先查一次城市字典用内存映射拿 ID本文 2.2 的 city_cache 就是这个思路。4.3 ECharts 容器高度为 0图死活不显示现象接口返回正常控制台无报错页面空白。原因ECharts 初始化时容器 div 没有显式高度默认高度为 0。解决给容器设固定高度如 styleheight:400px;或者用 CSS 类写死。另外确保 echarts.init 在 DOM 加载完成后执行脚本放在 body 末尾或用 DOMContentLoaded。4.4 中文城市名在 URL 里乱码现象按城市名筛选时后端收到 %E5%8C%97%E4%BA%AC 之类。原因URL 编码未解码。解决前端用 encodeURIComponent 编码Django 侧 request.GET.get 会自动解码不要手动再 decode 一次否则二次解码报错。更稳的做法是接口只传 city_id不传中文名。4.5 数据缺测导致图表断线或报 NaN现象折线图中间断开或者 y 轴出现 NaN。原因原始数据有 null前端 map 后直接塞给 ECharts。解决后端聚合时用 Avg 会自动忽略 null但如果某天全空则返回 None前端在 map 时把 null 转成 - 或做插值。ECharts 对 null 的处理是断点对 - 是跳过按展示需求选。5. 让课设从「能跑」到「能讲」一个加分技巧如果只做到上面这些图能出来、筛选能用拿个及格没问题。但标题里写着「95 分以上」差距往往在「有没有分析味」。我的习惯是加一个 AQI 等级分布环形图再配一个「污染天数占比」的指标卡。具体做法在 daily_pm25 的基础上按 aqi 区间统计天数用 ECharts 的 pie 画环形中心显示「优良天数占比」。这个技巧的好处是答辩时你能指着图说「该城市近 30 天优良率 63%主要污染集中在 PM2.5」而不是只会说「这是折线图」。def aqi_distribution(city_id, start, end): qs (AirQuality.objects .filter(city_idcity_id, record_time__range(start, end)) .annotate(dayTruncDate(record_time)) .values(day) .annotate(avg_aqiAvg(aqi))) buckets {优: 0, 良: 0, 轻度污染: 0, 中度及以上: 0} for row in qs: aqi row[avg_aqi] if aqi is None: continue if aqi 50: buckets[优] 1 elif aqi 100: buckets[良] 1 elif aqi 150: buckets[轻度污染] 1 else: buckets[中度及以上] 1 return buckets逻辑说明先按天聚合 AQI再按国标区间分桶。参数上50/100/150 是 AQI 分级阈值写死即可不需要配置化。返回的字典直接给 pie 的 data前端 map 成 [{name, value}]。这个函数和折线接口可以合并成一次请求减少往返。验证方法很简单拿一个已知污染较重的城市和一段冬季数据跑一遍看环形图里「中度及以上」是否明显变大再换夏季数据对比。如果比例反了多半是时间范围或城市 ID 传错。我踩过的坑是忘了 TruncDate 之前先 filter导致全表聚合数据量一大就超时。后来养成习惯任何聚合查询第一行先写 filter。最后说个习惯这套代码我一般会留一个seed命令能一键生成模拟数据方便换电脑或演示时快速恢复环境。真实数据导入慢、字段脏演示前用模拟数据把流程跑通再换真数据能省下大量现场翻车的时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表