
简介本资源是一个基于Python与Django框架开发的大气污染预测Web系统面向环境科学、数据科学及Web开发方向的课程设计与项目实践者解决空气质量趋势分析与短期污染预警的实际问题。压缩包共13.6MB含完整Django项目结构主要文件类型包括Python后端逻辑views.py、models.py、时间序列建模代码ARIMA/SARIMA实现、HTML/CSS/JS前端页面及数据库迁移脚本支撑数据采集、模型训练、可视化预测与阈值报警等核心功能。已有113人学习下载适合中阶Python学习者通过可运行项目深入理解Web服务集成、时序建模落地与环境数据分析全流程。读者可直接部署调试复现PM2.5、NO2等多污染物预测效果掌握Statsmodels与Django协同开发的关键实践同时获得含数据预处理、模型评估、报告生成的完整工程化方案。1. 这不是另一个“天气预报网页”而是一套可落地的时间序列污染预测闭环系统你见过太多 Django 项目博客、商城、后台管理——但真正把 ARIMA/SARIMA 模型嵌进 Web 流程、让非统计背景的环保岗人员也能调参看图、导出报告、设阈值告警的完整闭环极少。这个项目不是 demo它默认加载了模拟的 PM2.5/NO₂/SO₂ 三类污染物日度数据含明显季节性与趋势项模型训练后能输出未来 7–30 天带置信区间的预测曲线并在 Django Admin 中暴露关键参数如 SARIMA 的 (p,d,q)(P,D,Q,s)、滑动窗口长度、报警阈值而非把模型黑箱打包成.pkl丢进views.py。它面向的是课程设计需体现“建模—部署—交互”全链路的学生也适合作为基层环境监测站快速验证本地化预测能力的原型底座。如果你正卡在“Python 做完预测怎么塞进网页”“Django 怎么安全传参给 statsmodels”“如何避免每次训练都重跑全量数据”这个源码包就是按真实工程节奏组织的——模型训练异步触发、结果缓存到数据库、前端用 Chart.js 渲染带误差带的多污染物对比图连requirements.txt里 statsmodels 版本都锁死在 0.13.5避开了 0.14 的 seasonal_decompose API 变更坑。2. 从原始 CSV 到 SARIMA 模型训练Django 中时间序列建模的标准化流水线2.1 数据预处理模块解决环境监测数据的三大顽疾环境监测数据常存在缺失值、异常突变点、单位不统一问题。该项目在pollution/utils.py中封装了clean_air_data()函数其核心逻辑并非简单fillna(methodffill)而是分层处理def clean_air_data(df, pollutants[PM25, NO2, SO2]): # 步骤1按小时/日粒度聚合若原始为分钟级 df df.resample(D, ondatetime).mean().reset_index() # 步骤2对每类污染物独立检测并修正异常值IQR法 领域知识约束 for p in pollutants: Q1 df[p].quantile(0.25) Q3 df[p].quantile(0.75) IQR Q3 - Q1 lower_bound max(0, Q1 - 1.5 * IQR) # 浓度不能为负 upper_bound Q3 1.5 * IQR df.loc[(df[p] lower_bound) | (df[p] upper_bound), p] np.nan # 步骤3使用插值 领域规则填充如PM2.5连续3天500视为设备故障不插值 for p in pollutants: if p PM25: # PM2.5 超 800 μg/m³ 视为无效保留 NaN 供后续标记 df.loc[df[p] 800, p] np.nan df[p] df[p].interpolate(methodtime, limit3) # 仅插值最多连续3天 return df.dropna(subsetpollutants)提示limit3是关键参数——环境数据中连续超过3天的缺失往往意味着设备停机强行插值会污染模型。该函数返回的 DataFrame 已确保时间索引连续datetime列转为pd.DatetimeIndex这是 SARIMA 训练的前提。2.2 SARIMA 模型配置与训练Django 管理后台驱动的参数实验平台模型参数未硬编码在视图中而是通过 Django Model 定义可配置的PredictionConfig# models.py class PredictionConfig(models.Model): pollutant models.CharField(max_length10, choices[(PM25,PM2.5),(NO2,NO2),(SO2,SO2)]) seasonal_period models.IntegerField(default365) # 年周期 arima_order models.CharField(max_length20, default(1,1,1)) # (p,d,q) seasonal_order models.CharField(max_length30, default(1,1,1,365)) # (P,D,Q,s) forecast_steps models.IntegerField(default14) alert_threshold models.FloatField(default75.0) # μg/m³ is_active models.BooleanField(defaultTrue)训练任务由管理后台的train_modelaction 触发实际执行在pollution/tasks.py中# tasks.py from statsmodels.tsa.statespace.sarimax import SARIMAX from django.core.cache import cache def train_sarima_model(pollutant, config_id): # 1. 从数据库读取最新3年有效数据避免全量扫描 end_date timezone.now().date() start_date end_date - timedelta(days1095) qs AirData.objects.filter( datetime__range(start_date, end_date), **{f{pollutant}__isnull: False} ).order_by(datetime).values(datetime, pollutant) df pd.DataFrame(list(qs)) df[datetime] pd.to_datetime(df[datetime]) df df.set_index(datetime).sort_index() # 2. 解析配置参数字符串转元组 config PredictionConfig.objects.get(idconfig_id) order eval(config.arima_order) # (1,1,1) seasonal_order eval(config.seasonal_order) # (1,1,1,365) # 3. 构建并训练模型启用 mle_approx 加速 model SARIMAX( df[pollutant], orderorder, seasonal_orderseasonal_order, enforce_stationarityFalse, enforce_invertibilityFalse, simple_differencingFalse ) fitted model.fit(dispFalse, methodlbfgs) # 避免 MLE 收敛失败 # 4. 缓存模型结果非模型本身只存预测值置信区间 forecast fitted.forecast(stepsconfig.forecast_steps) conf_int fitted.get_forecast(stepsconfig.forecast_steps).conf_int() cache_key fsarima_{pollutant}_{config_id} cache.set(cache_key, { forecast: forecast.tolist(), lower_bound: conf_int.iloc[:, 0].tolist(), upper_bound: conf_int.iloc[:, 1].tolist(), last_updated: timezone.now().isoformat() }, timeout3600) # 缓存1小时注意enforce_stationarityFalse和enforce_invertibilityFalse是必须设置的——环境数据常含强趋势和季节性强制平稳性会导致拟合失败methodlbfgs比默认lbfgs更稳定statsmodels 0.13.5 实测缓存策略存储的是预测结果而非模型对象规避了 Django 多进程下 pickle 模型的兼容性问题。2.3 模型评估自动化MAPE 与残差诊断双轨验证每次训练后系统自动计算 MAPE平均绝对百分比误差并生成残差图结果存入ModelEvaluation模型# evaluation.py def evaluate_model(fitted_model, actual_series, forecast_steps14): # 使用滚动预测验证非单次外推 history actual_series.copy() predictions [] actuals [] for i in range(forecast_steps): # 滚动窗口每次用最新历史数据重新拟合轻量版 window_size min(365, len(history)) window_data history[-window_size:] try: temp_model SARIMAX( window_data, orderfitted_model.specification[order], seasonal_orderfitted_model.specification[seasonal_order] ).fit(dispFalse, methodlbfgs) pred temp_model.forecast(steps1)[0] predictions.append(pred) actuals.append(history.iloc[-forecast_steps i]) except: break if len(predictions) forecast_steps // 2: return {mape: float(inf), residuals: []} mape np.mean(np.abs((np.array(actuals) - np.array(predictions)) / np.array(actuals))) * 100 residuals fitted_model.resid # 残差白噪声检验Ljung-Box lb_test acorr_ljungbox(residuals, lags[10], return_dfTrue) is_white_noise lb_test[lb_pvalue].iloc[0] 0.05 return { mape: round(mape, 2), residuals: residuals.tolist(), white_noise: is_white_noise, aic: round(fitted_model.aic, 2) }该函数被train_sarima_model调用后将mape、aic、white_noise写入数据库管理员可在/admin/pollution/modelevaluation/查看各污染物模型的历史评估记录直观判断是否需要调整arima_order。3. Django Web 层实现从 URL 路由到动态图表渲染的端到端细节3.1 RESTful API 设计分离模型计算与前端渲染项目未使用 Django Template 直接渲染图表而是提供/api/prediction/pollutant/接口返回 JSON# views.py from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.core.cache import cache csrf_exempt def prediction_api(request, pollutant): if request.method ! GET: return JsonResponse({error: Only GET allowed}, status405) config_id request.GET.get(config_id) if not config_id: # 默认取激活配置 config PredictionConfig.objects.filter(pollutantpollutant, is_activeTrue).first() if not config: return JsonResponse({error: No active config}, status404) config_id config.id cache_key fsarima_{pollutant}_{config_id} result cache.get(cache_key) if not result: # 触发异步训练Celery 可选此处简化为同步 from pollution.tasks import train_sarima_model train_sarima_model(pollutant, config_id) result cache.get(cache_key) or {error: Training failed} return JsonResponse(result)关键点csrf_exempt仅用于此只读 API无敏感操作避免前端 JS 调用时因 CSRF token 缺失报错cache.get()保证高并发下不重复训练config_id参数支持同一污染物多模型 A/B 测试。3.2 前端动态图表Chart.js 渲染带置信区间的多污染物对比templates/pollution/dashboard.html中初始化图表!-- 引入 Chart.js -- script srchttps://cdn.jsdelivr.net/npm/chart.js/script div classchart-container canvas idpollutionChart/canvas /div script // 获取当前污染物来自模板上下文 const pollutant {{ current_pollutant }}; const configId {{ config_id }}; // 动态请求预测数据 fetch(/api/prediction/${pollutant}/?config_id${configId}) .then(r r.json()) .then(data { if (data.error) throw new Error(data.error); // 构造时间轴未来14天 const now new Date(); const labels Array.from({length: data.forecast.length}, (_, i) { const d new Date(now); d.setDate(d.getDate() i 1); return d.toLocaleDateString(zh-CN, {month: short, day: numeric}); }); // 绘制主预测线 置信区间 const ctx document.getElementById(pollutionChart).getContext(2d); new Chart(ctx, { type: line, data: { labels: labels, datasets: [ { label: ${pollutant} 预测, data: data.forecast, borderColor: #36A2EB, fill: false, tension: 0.1 }, { label: ${pollutant} 置信下限, data: data.lower_bound, borderColor: rgba(54, 162, 235, 0.3), borderWidth: 0, fill: -1, pointRadius: 0 }, { label: ${pollutant} 置信上限, data: data.upper_bound, borderColor: rgba(54, 162, 235, 0.3), borderWidth: 0, fill: -1, pointRadius: 0 } ] }, options: { responsive: true, plugins: { title: { display: true, text: 未来14天 ${pollutant} 浓度预测 } }, scales: { y: { beginAtZero: false, title: { display: true, text: 浓度 (μg/m³) } } } } }); }); /script参数说明fill: -1实现置信区间填充Chart.js 3.x 语法tension: 0.1控制曲线平滑度避免过拟合锯齿pointRadius: 0隐藏置信区间端点保持视觉简洁。3.3 报警系统实现基于预测结果的阈值触发与邮件通知报警逻辑在pollution/alerts.py中定义# alerts.py from django.core.mail import send_mail from django.conf import settings def check_alerts(): 每日定时任务检查所有激活配置的预测结果 configs PredictionConfig.objects.filter(is_activeTrue) for config in configs: cache_key fsarima_{config.pollutant}_{config.id} result cache.get(cache_key) if not result: continue # 检查未来7天内是否有预测值超阈值 forecast result[forecast][:7] if any(val config.alert_threshold for val in forecast): # 发送告警邮件生产环境应接入企业微信/钉钉 subject f[空气污染预警] {config.pollutant} 将超限 message f根据预测{config.pollutant} 在未来7天内将达到 {config.alert_threshold} μg/m³ 以上请关注。\n\n预测峰值{max(forecast):.1f} μg/m³ send_mail( subjectsubject, messagemessage, from_emailsettings.DEFAULT_FROM_EMAIL, recipient_list[adminenv-monitor.local], # 替换为实际邮箱 fail_silentlyFalse, )该函数通过 Django-Q 或系统 cron 每日 8:00 执行确保预警时效性。邮件内容明确标注预测峰值避免模糊表述。4. 本地部署实操从 Python 环境搭建到 Django 迁移的零遗漏步骤4.1 Python 环境与依赖安装避开 statsmodels 兼容性雷区项目要求 Python 3.8–3.10statsmodels 0.13.5 不支持 3.11。推荐使用pyenv管理版本# Ubuntu/Debian 系统 sudo apt update sudo apt install -y make build-essential libssl-dev zlib1g-dev \ libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev \ libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev python-openssl git # 安装 pyenv curl https://pyenv.run | bash export PYENV_ROOT$HOME/.pyenv export PATH$PYENV_ROOT/bin:$PATH eval $(pyenv init -) # 安装并设为全局 pyenv install 3.9.18 pyenv global 3.9.18 # 创建虚拟环境避免污染系统 Python python -m venv venv source venv/bin/activate # 安装依赖注意 statsmodels 版本锁定 pip install --upgrade pip pip install -r requirements.txt # 若报错手动指定 statsmodels 版本 pip install statsmodels0.13.5关键点pyenv比conda更轻量适合课程设计场景pip install statsmodels0.13.5必须显式执行因为requirements.txt中的statsmodels0.13.0可能升级到 0.14 导致seasonal_decompose参数失效。4.2 Django 数据库迁移与初始数据加载项目使用 SQLite开箱即用但支持 PostgreSQL见settings.py注释# 初始化数据库 python manage.py migrate # 加载示例数据含3年模拟污染数据 python manage.py loaddata pollution/fixtures/initial_data.json # 创建超级用户用于登录 Admin python manage.py createsuperuser # 启动开发服务器 python manage.py runserver 0.0.0.0:8000initial_data.json包含1000 条AirData记录2021–2023 年日度 PM2.5/NO2/SO23 条PredictionConfig每种污染物一套默认参数1 条ModelEvaluation预训练结果访问http://127.0.0.1:8000/admin/可查看并修改配置。4.3 关键配置文件解析settings.py 中影响预测精度的 5 个参数settings.py中以下配置直接影响模型行为配置项默认值作用修改建议PREDICTION_CACHE_TIMEOUT3600模型预测结果缓存秒数生产环境可设为8640024小时避免频繁重训MAX_HISTORICAL_DAYS1095训练时读取的最大历史天数数据量大时可降至730加速训练FORECAST_STEPS_DEFAULT14默认预测天数根据业务需求改为7短期或30中期ALERT_CHECK_INTERVAL86400告警检查间隔秒数严格场景可设为3600每小时STATSMODELS_METHODlbfgsSARIMA 优化方法若出现收敛警告可尝试bfgs这些参数均在settings.py顶部集中定义便于课程设计时快速调整对比效果。5. 模型调优实战用 ACF/PACF 图确定 SARIMA 参数的现场诊断法5.1 在 Django Admin 中一键生成 ACF/PACF 图项目扩展了 Admin为AirData模型添加acf_pacf_plot动作# admin.py from django.contrib import admin from .models import AirData import matplotlib matplotlib.use(Agg) # 避免 GUI 后端错误 import matplotlib.pyplot as plt from io import BytesIO import base64 admin.action(description生成 ACF/PACF 图) def acf_pacf_plot(modeladmin, request, queryset): if queryset.count() ! 1: modeladmin.message_user(request, 请选择且仅选择一条记录) return obj queryset.first() # 获取该记录对应污染物的全部历史数据 pollutant PM25 # 示例实际根据字段动态获取 series AirData.objects.values_list(pollutant, flatTrue).order_by(datetime) series pd.Series(list(series)).dropna() # 生成 ACF/PACF 图 fig, (ax1, ax2) plt.subplots(1, 2, figsize(12, 4)) plot_acf(series, axax1, lags40) plot_pacf(series, axax2, lags40) ax1.set_title(ACF) ax2.set_title(PACF) # 转为 base64 嵌入 HTML buffer BytesIO() plt.savefig(buffer, formatpng, bbox_inchestight) buffer.seek(0) image_base64 base64.b64encode(buffer.read()).decode() plt.close() # 返回包含图片的响应简化为弹窗展示 modeladmin.message_user(request, fimg srcdata:image/png;base64,{image_base64} width800/, extra_tagssafe ) admin.register(AirData) class AirDataAdmin(admin.ModelAdmin): actions [acf_pacf_plot]操作流程进入 Admin → AirData → 勾选任意一条记录 → 选择 “生成 ACF/PACF 图” → 点击执行 → 页面弹出诊断图。ACF 拖尾、PACF 截尾处即为p值反之则为q值季节性周期s从 ACF 图中显著峰间距读取如 365 天。5.2 SARIMA 参数调试对照表不同污染物的典型配置根据项目内置数据测试三类污染物的推荐初始参数污染物arima_orderseasonal_order选择依据MAPE验证集PM2.5(1,1,1)(1,1,1,365)ACF 拖尾缓慢PACF 在 lag1 截尾年周期明显12.3%NO₂(2,1,0)(1,0,1,7)日周期突出交通潮汐PACF 在 lag2 截尾8.7%SO₂(0,1,2)(0,1,1,365)ACF 在 lag2 后截尾需更多移动平均项15.1%技巧在 Admin 中修改PredictionConfig的arima_order后点击对应配置的 “训练模型” 按钮系统自动重训并更新ModelEvaluation表中的 MAPE。对比不同参数组合的 MAPE选择最小值即可——无需理解所有统计原理用数据说话。5.3 预测失败排错清单5 种常见报错及现场修复命令当train_sarima_model报错时按顺序执行以下诊断报错信息根本原因修复命令验证方式ValueError: The computed initial AR coefficients are not stationaryenforce_stationarityTrue导致在tasks.py中确认enforce_stationarityFalse重启服务后重试训练ConvergenceWarning: Maximum number of iterations reached优化器未收敛将methodlbfgs改为methodbfgs查看fitted_model.mle_retvals[converged]是否为TrueKeyError: datetimeCSV 数据缺少时间列python manage.py shell中运行AirData.objects.all()[:5]检查字段修正loaddata的 fixture 或清洗脚本Cache key too longcache_key超过 250 字符在tasks.py中缩短cache_key如fsarima_{pollutant[:3]}_{config_id}cache.get(cache_key)返回非 NoneNo module named statsmodels.tsa.statespace.sarimaxstatsmodels 版本过低pip install --force-reinstall statsmodels0.13.5python -c from statsmodels.tsa.statespace.sarimax import SARIMAX所有修复均在代码层面完成无需重装 Python 或操作系统。本文还有配套的精品资源点击获取