新疆历史降水量数据可视化分析:从数据清洗到机器学习建模实战
2026/9/13 20:25:18 网站建设 项目流程

简介:一套基于Python的新疆历史降水量可视化分析资料包,面向毕业设计、课程设计及数据挖掘入门者。项目使用Pandas读取并清洗1980—2018年降水数据,借助Matplotlib、Seaborn绘制历史趋势折线图、年度总降水量条形图、月度平均降水量饼图等,清晰呈现雨季(6—8月)与旱季(10—11月)的分布规律;同时对比监测站平均降水量,并基于ARIMA模型完成月度降水量预测与结果验证。压缩包共4个文件,包含Excel数据集、可运行Notebook代码、HTML结果展示页及依赖环境说明txt,整体仅5.16MB。Notebook代码按步骤划分,包含数据预处理、异常年份识别、年度/月度分析、季节性对比及模型构建与评估等模块,注释清晰,便于学习者理解每个环节的分析思路和实现方法。目前已有149人学习,适合需要快速搭建完整数据分析流程、理解时间序列建模与可视化表达的学生参考。

1. 基于大数据的新疆历史降水量数据可视化分析:从数据挖掘到机器学习建模的完整落地路径

气象数据是典型的海量时序数据,新疆作为典型干旱半干旱区,其降水数据具有极强的地域特征与稀疏性,直接套用通用时间序列模型往往效果惨淡。这个标题看起来是毕业设计,实际踩中了数据挖掘完整链路:从原始气象站点数据出发,经过清洗、降维、特征工程,再到机器学习模型构建,最后落到可视化大屏呈现,几乎覆盖了数据分析从业者的核心日常。本文就是把这条链路拆开,给出可复现的数据处理方案、模型选型与调参思路,以及基于 ECharts 的企业级数据可视化实现。无论你是正在准备大数据毕业设计,还是需要处理区域气象数据做业务分析,这些步骤和参数都值得直接抄走。

2. 数据获取与数据清洗:新疆降水数据的多源整合与异常值处理策略

2.1 多源数据获取的常见做法:站点数据优先,再考虑再分析资料

历史降水量数据的来源主要有三类:国家气象信息中心的地面气象站点逐日数据、中国区域高分辨率降水融合产品(如 CLDAS),以及再分析数据集(如 ERA5)。对于毕设或中小规模分析,最可靠的是站点数据——它可以直接落地,不需要处理卫星反演和同化系统带来的偏差。新疆范围约 166 万平方公里,站点数量约 200 余个,对单机分析而言体量不大,属于典型的小数据大价值场景,不涉及分布式集群部署,一台普通开发机即可搞定。

我一般会先用爬虫脚本从公开气象数据平台抓取站点信息表,再根据经纬度匹配各站点的逐日降水记录。这里要特别注意的是,新疆站点稀疏且分布极不均匀,天山山区和昆仑山北坡站点密度明显高于盆地,后续建模时应将站点空间分布作为重要背景变量引入。

import pandas as pd # 假设已抓取原始站点降水数据 df = pd.read_csv('xinjiang_precip_raw.csv') # 数据列: station_id, date, precip(mm), lat, lon, elev df['date'] = pd.to_datetime(df['date']) df = df.sort_values(['station_id', 'date']) print(df.shape) print(df.isnull().sum())

读取后建议先按站点维度统计空值比例。这里有一个新疆数据常见的坑:冬季部分高海拔站点会因为设备冻结而出现连续数日空值,不能直接按整站删除,否则会破坏时间连续性。空值处理策略我一般分三级:连续缺失不超过 3 天,用线性插值;3 到 10 天,用该站同期多年平均值填充;超过 10 天,直接置为该站历年该日平均值的八成,并在数据集中追加标记列,方便建模时识别。

2.2 异常值识别:聚类与四分位距结合的方法

降水数据的异常值和一般业务数据不太一样,极大的值——比如新疆夏季天山山区单日暴雨——往往不是噪声,而恰恰是气候研究的关键对象。直接按 3 倍标准差剔除会把极端降水事件全部删光,这在气象分析里是不被接受的。我建议采用 IQR 结合局地聚类的方式来区分真正的仪器错误与极端气候事件。

# 按月-站组合计算IQR,标记异常候选点 df['year_month'] = df['date'].dt.to_period('M') q1 = df.groupby(['station_id', 'year_month'])['precip'].transform('quantile', 0.25) q3 = df.groupby(['station_id', 'year_month'])['precip'].transform('quantile', 0.75) iqr = q3 - q1 df['outlier_candidate'] = (df['precip'] > q3 + 3 * iqr) | (df['precip'] < q1 - 3 * iqr) # 查看候选异常值分布 candidate = df[df['outlier_candidate'] == True] print(candidate.groupby('station_id').size().sort_values(ascending=False))

这里倍数取 3 而不是常见的 1.5,因为降水数据本身偏态分布,尾部天然厚重,1.5 倍会误杀大量正常强降水。筛选出候选异常值后,再看同一天周边 3 个以上站点是否同时有降雨记录,如果有,则大概率是真实天气过程,保留;如果仅单站剧烈偏离,再人工核对原始记录,确定为设备故障后剔除。这个流程比纯统计阈值合理得多,并且中间产物可以直接写入数据挖掘报告作为方法论支撑。

2.3 降水数据的重采样与融合:从逐日到多时间尺度的特征覆盖

降水量分析通常不止看逐日值,月尺度、季节尺度、年尺度的统计特征对趋势分析更有价值。重采样时建议同时产出三套数据:逐日序列、逐月汇总(总量、最大日值、降水日数、连续无降水日数)、逐年汇总。这里有个关键点,新疆降水集中且季节性强,直接用 pandas 的 resample 默认参数会忽略无降水月份的存在,导致降水集中度计算失真。

# 以某站为例,逐月聚合 station = df[df['station_id'] == '51053'] monthly = station.set_index('date')['precip'].resample('M').agg( total='sum', max_daily='max', rainy_days=lambda x: (x > 0.1).sum(), dry_days=lambda x: (x < 0.1).sum() ) monthly['consecutive_dry_max'] = station['precip'].rolling(30).apply( lambda x: (x < 0.1).sum(), raw=True )

这个聚合过程实际上在做数据挖掘里最基础的特征提取工作。rainy_days的阈值取 0.1mm 而不是 0,是因为气象观测中微量降水(小于 0.1mm)通常只记录痕迹,不好直接算作有效降水日。后续做机器学习模型时,这些月度特征比原始逐日数据更有预测能力和解释性,在新疆这种降水稀少的区域尤为明显。

3. 图表选型与 ECharts 可视化大屏:新疆降水空间分布与趋势预测的核心呈现

3.1 可视化技术选型:ECharts 在气象降水场景中的适用边界

在数据可视化选型上,气象领域常见的是 Panoply、GrADS、NCL 等专业工具,但作为大数据分析项目展示,Web 端交互式大屏的表现力和传播性远胜传统静态图。ECharts 作为国内生态最成熟的图表库,在数据可视化大屏场景下有三点明显优势:内置地图组件支持 GeoJSON 自定义轮廓,热力图与等值线图对网格化数据渲染性能不错,以及 tooltip 联动在时序巡检中体验很顺。企业级数据可视化大屏往往要求同时呈现概览、趋势和分布三个维度,ECharts 的多图联动正好对应这种布局。

新疆降水可视化核心在于空间插值——站点数据是散布的,但人眼理解需要连续色块。我会用 Kriging 插值把站点雨量映射到 0.1°×0.1° 网格(约 1000 行代码的数据矩阵),再用 ECharts 的visualMap分段展示。这里有一个关键参数:visualMapsplitNumber不宜过大,降水色阶建议分为 6 段左右,因为新疆大部分区域月降水量集中在 0 到 30mm,分段过细反而看不出地域差异。

3.2 地理热力地图的实现:天山北坡与南疆盆地的空间呈现

以下是基于插值结果绘制新疆降水热力地图核心代码片段:

var precipitationData = []; // 从后端接口获取的插值网格数据 var geoJson = await fetch('./xinjiang.json').then(res => res.json()); echarts.registerMap('xinjiang', geoJson); var chart = echarts.init(document.getElementById('precip-map')); var option = { tooltip: { trigger: 'item', formatter: function(params) { return params.name + '<br/>月降水量:' + params.value.toFixed(1) + 'mm'; } }, visualMap: { min: 0, max: 30, splitNumber: 6, inRange: { color: ['#d9f0d9', '#b8e2b8', '#8fca8f', '#5aa75a', '#2f7f2f'] }, text: ['多雨', '少雨'] }, geo: { map: 'xinjiang', roam: true }, series: [{ type: 'map', map: 'xinjiang', geoIndex: 0, data: precipitationData }] }; chart.setOption(option);

这段代码里有几个实际运营场景中容易被忽略的细节:GeoJSON 需要从标准新疆地图矢量数据转换而来,坐标需要 WGS84 坐标,和站点经纬度对齐才不会有偏移;visualMapinRange颜色建议从浅到深表达降水量递增,顺序颠倒会影响阅读直觉;roam: true允许大屏端用户拖拽放大地图,避免局部区域站点过密时信息重叠。如果后端返回的插值数据量大,建议用 Web Worker 预处理,避免主线程阻塞导致地图拖动卡顿。

3.3 时序趋势图与降水集中度分析的有效搭配

大屏上除了地理热力分布,时序展示同样重要。新疆降水的一个显著特征是集中在夏季以及降水呈逐年微弱波动,直接画全序列折线图信息量很少。我通常用双轴图:柱状展示逐年总量,折线展示降水集中度指数(PCI),计算公式为各月降水的平方除以年总量平方和,PCI 大于 80 表示降水高度集中。这样一张图既能看出整体年际趋势,又能看出季节分配的集中特征,对后续机器学习建模时的季节性解释也有直接支撑。

// 垂直布局时速览图 var trendChart = echarts.init(document.getElementById('trend-chart')); trendChart.setOption({ xAxis: { type: 'category', data: yearList }, yAxis: [ { type: 'value', name: '年降水总量 (mm)' }, { type: 'value', name: 'PCI指数', min: 50, max: 100 } ], series: [{ name: '年总量', type: 'bar', barWidth: 12, itemStyle: { color: '#2d8cf0' } }, { name: 'PCI', type: 'line', yAxisIndex: 1, lineStyle: { width: 2 } }] });

这里PCI定义有很多种,我在项目里习惯用 Oliver 1980 年提出的修正公式,它对零降水月份的处理在实际观测数据中表现稳定。若直接输出 PCI 原始值,需要保留两位小数并在 tooltip 里加说明,不然大屏观众很难快速理解 75 和 85 之间的差异。

4. 机器学习在降水预测中的应用:特征重要性排序与几种模型对比

4.1 输入特征与目标变量的定义:月尺度预测是最可行方案

降水量预测在气象学中属于时序预报范围,要做逐日精确预测需要气象物理模型或大尺度环流因子,这在毕设场景里不现实。常见做法是把目标定义成次月降水量,特征使用过去 12 个月的月度降水序列、季节均值、厄尔尼诺指数、站点海拔与经纬度。选择月尺度有三个实质理由:维数不过高,数据质量更可靠;在新疆降水高度季节化背景下,月份是天然强特征;后期可视化展示时月尺度折线和大屏时间轴搭起来更顺手。

在特征工程上,我强烈建议加入降水的滞后特征和滚动均值特征,这样能捕捉降水自身的时间依赖性。新疆降水变化跟全球气候驱动因子有一定关联,但直接拿原始海温数据做特征并不合适,需要降维后使用,例如 EOF 分析提取前三个主模态。

# 构造机器学习数据集 features = [] for st in station_groups: df_st = station_groups[st] df_st = df_st.sort_values('date') # 过去12个月的连续无降水日数特征 for lag in range(1, 13): df_st[f'precip_lag_{lag}'] = df_st['precip'].shift(lag) # 滚动均值:最近3、6、12个月 df_st['rolling_mean_3'] = df_st['precip'].rolling(3).mean() df_st['rolling_mean_6'] = df_st['precip'].rolling(6).mean() df_st['rolling_mean_12'] = df_st['precip'].rolling(12).mean() features.append(df_st) dataset = pd.concat(features) dataset = dataset.dropna() X = dataset[['precip_lag_1', 'precip_lag_2', 'precip_lag_3', 'rolling_mean_6', 'rolling_mean_12', 'lat', 'lon', 'elev', 'month']] y = dataset['precip'].shift(-1) # 目标:下月降水量

这个特征集并不追求大而全,而是抓住气象序列的周期性和空间背景。precip_lag_1precip_lag_2通常贡献了半数以上的模型解释力。month特征以数值形式使用意味着 12 月与 1 月在数值上相距很远,这不符合实际,因此需要做三角函数编码,把“月份相邻”这一事实嵌入模型输入。同样,latlon保留也可以,但需要确认它们与海拔没有共线性,否则模型的特征重要性会被空间变量挤占。

4.2 模型选型:随机森林、XGBoost 与 LightGBM 在稀疏数据上的表现

回归任务上,经典线性模型对这样的零膨胀分布很难拟合,LSTM 又需要更长序列才能发挥优势,月尺度样本量通常只有 200 到 400 条,纯粹使用深度学习容易过拟合。常见做法是使用集成学习树模型,随机森林、XGBoost 与 LightGBM 可以都在同一份数据集上做交叉验证,然后按 R² 和 MAE 选择最优。

from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error, r2_score X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, shuffle=False # 时间序列不能随机打乱 ) model = RandomForestRegressor( n_estimators=500, max_depth=8, min_samples_leaf=3, random_state=42 ) model.fit(X_train, y_train) y_pred = model.predict(X_test) print('MAE:', mean_absolute_error(y_test, y_pred)) print('R2:', r2_score(y_test, y_pred))

代码里最需要用心的是shuffle=False。时间序列数据一旦随机切分,训练集和测试集之间会混入未来信息,指标虚高。随机森林在这个任务里通常能达到 MAE 3 到 5mm 的水平,R² 在 0.5 到 0.7 左右。对比来看,LightGBM 若加上linear_tree=True,在稀疏降水数据上往往会有更好的拟合度和更快的训练速度。模型的可解释性是重点,报告里必须输出特征重要性排序。

特征重要性结果表明precip_lag_1monthrolling_mean_3通常排在前三位,这其实呼应了一个朴素机制:新疆月降水最强的预测因子就是“上个月下了多少”与“现在是几月”。把这两个特征加到模型后,几乎没有其他变量能提供同等量级的增量信息。

4.3 类别不平衡陷阱:降水量为 0 的月份如何处理

新疆许多内陆站的月降水为 0 或小于 0.1mm,在真实数据集里出现频率很高。直接训练回归模型会让预测结果偏向于“少雨”方向,这种均方误差驱动的收缩是大数据挖掘中极为常见的败坏模式。处理方式有两种:一是构建两阶段模型,先做分类判断是否为有效降水月,再在有效月份上做回归;二是在回归损失中提高非零样本的权重。第二种更好落地,而且可直接在 sklearn 里通过sample_weight实现。

# 两阶段方案中的第一阶段:有效降水月分类 from xgboost import XGBClassifier train_mask = dataset['precip_target'] >= 0.5 # 定义为有效月 clf = XGBClassifier(n_estimators=200, max_depth=6, learning_rate=0.1) clf.fit(X[train_mask], (y[train_mask] >= 0.5).astype(int))

阈值 0.5mm 的选取不是随意的,它对应新疆气象业务中“有量降水”的起始值。二分类器在这个任务上最好能达到 F1 值 0.85 以上,否则回归模型的误差主要来自于分类错误造成的误预测,而不是回归本身的精度。把两阶段模型的结果同时放入可视化大屏,还能提供“下月是否降水”和“降水量级”两层信息,展示效果和解释性都更完整。

5. 模型部署与可视化大屏落地:预测结果服务化和空间插值展示的关键跳转

5.1 用 Flask 将训练好的模型封装成预测接口

模型训练完成后要想真正驱动大屏展示,还需要在服务端部署推理接口。常见做法是用 Flask 写一个轻量 API,加载训练好的joblib模型文件,根据前端传入的站点经纬度和历史降水序列返回下一个月的预测值。这个方案适合毕设和轻量级应用,不需要引入复杂的模型服务平台。

import joblib from flask import Flask, request, jsonify import pandas as pd app = Flask(__name__) model = joblib.load('precip_model.joblib') @app.route('/predict', methods=['POST']) def predict(): data = request.get_json() # data包含: station_id, lat, lon, elev, month, hist_precip_list feat = build_features_from_history(data) pred = model.predict([feat])[0] return jsonify({'predicted_precip_mm': round(float(pred), 2)}) def build_features_from_history(data): hist = data['hist_precip_list'][-12:] feat = { 'precip_lag_1': hist[-1] if len(hist) >= 1 else 0, 'precip_lag_2': hist[-2] if len(hist) >= 2 else 0, 'rolling_mean_6': sum(hist[-6:]) / min(6, len(hist)), 'rolling_mean_12': sum(hist) / min(12, len(hist)), 'lat': data['lat'], 'lon': data['lon'], 'elev': data['elev'], 'month': data['month'] } return [feat['precip_lag_1'], feat['precip_lag_2'], feat['rolling_mean_6'], feat['rolling_mean_12'], feat['lat'], feat['lon'], feat['elev'], feat['month']]

接口设计上要注意特征顺序必须与训练时完全一致,这是最容易出 Bug 的地方。建议在build_features_from_history函数里输出特征列名,并保存训练时的特征列表到 JSON 文件供服务端读取,避免硬编码顺序。由于预测任务每月跑一次即可,接口不需要高并发支撑,Flask 默认单进程模型足够稳定。对于可视化大屏,这个接口还可以暴露为/batch_predict,一次传入所有站点,批量返回预测结果,前端再根据经纬度做插值渲染。

5.2 利用服务端渲染或静态数据文件优化大屏加载性能

当站点数量在 200 左右时,批量预测的内存占用几乎可以忽略。为追求大屏首屏渲染速度,可以把过去 30 年的站点降水和预测数据事先聚合成一个monthly_data.json文件,前端直接 fetch 而不是每次从后端算。只有在大屏端主动点击“刷新最新预测”时才调用 Flask 接口,这样能显著减少等待时间,避免大屏冷启动时白屏时间过长。

# 生成前端静态数据文件 python export_map_data.py --year 2024 --output ./public/data/xinjiang_precip_2024.json

导出的 JSON 结构建议采用{ "stations": [...], "grid": [...], "prediction": [...] }三段式。其中grid是 Kriging 插值后生成的空间网格,prediction是模型返回的各站预测结果。地图热力层优先读取grid,而 tooltip 详细数据则从stations取,这样交互时不会因大范围重渲染而卡顿。如果网格数量超过 1 万个点,还可在前端开启动画过渡或采样显示,比如地图在低缩放级别只显示 5km 网格,放大到站点级别后再展示更精细的等值面。

ECharts 的setOption支持局部更新,不要每次都替换完整 option 对象。用chart.setOption({ series: [{ data: newData }] })只更新 series 数据,保留之前的 geo 和 visualMap 配置,这样渲染开销大幅下降。大屏自动轮播时利用setInterval每 5 秒切换月份数据,及时释放前一个图表实例,能避免长时间运行后内存累积导致页面崩溃。对于在现代浏览器上的运行期稳定性,可以用echarts.dispose()在切换页面时明确销毁实例。

5.3 特征鲁棒性与业务鲁棒性:遇到模型表现不稳定怎么办

模型上线训练和离线训练之间经常出现差异,主要原因是线上特征缺失或埋点不同。在气象场景下,API 收到的hist_precip_list如果来自不同数据源,可能会有批量缺失。补救方式是在build_features_from_history中统一回退策略:最近月份缺失时,用历史同月多年均值替代;连续缺失超过 3 个月,直接放弃该站预测并在数据集中标记缺测。把错误响应{'error': 'insufficient history'}明确暴露给前端,大屏端对该站点显示灰色,而不是用一个虚假数字继续渲染。这个处理比任何模型参数调整都重要——大屏上宁可缺测,也不能污染数据可信度。

在模型评估时除了 R² 与 MAE,建议增加一个业务消耗指标:在年降水量低于 150mm 的站点上单独计算误差。新疆南部站点如果模型的平均绝对误差达到 4mm,对标其半年总降水 50mm,这是一个不能接受的相对误差。建模时可以对这部分站点将sample_weight调高一倍,让模型在干区的表现优先得到优化。这样做对于真正需要用大屏做水资源调度研究的用户,远比追求全局 R² 更有价值。

在干燥区,降水序列里大量 0 值再叠加预测输出被压缩至接近 0,视觉上看上去一目了然,但这正是“零膨胀模型”的正常输出。应对办法是把目标变量从绝对降水量换成降水量百分位或对数值,在展示端再反变换回来。对数值变换在反变换时要做偏差修正,否则值域意义上的雨量会被系统性低估。实操上,我用np.log1p作为目标变换,输出后应用np.expm1还原,并对比反变换值和原始值,发现偏差在可接受范围内才可以交付大屏使用。这个流程虽然多花几步,却是数据挖掘项目从“看起来像研究”推进到“真正能说服现场专家”的分水岭。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询