☰
气象数据小波分析MATLAB实战:从预处理到出图的完整避坑指南
2026/10/16 23:01:45 网站建设 项目流程

简介:这份MATLAB代码资源面向气象、水文及环境科学领域的学生与研究人员,用于对非平稳气象信号开展小波分析。代码围绕小波基选择、小波系数生成、小波方差计算、小波模及模平方求解等环节展开,并以暴雨量数据为例演示时间-频率局部化分析流程,帮助使用者识别不同时间尺度上的极端降水事件与能量分布特征。资源包共2个文件,包含1个m脚本与1个mat数据文件,压缩包约2KB,脚本负责核心分析逻辑,数据文件提供可复现的暴雨量样本,便于直接运行与调试。目前已有5384人学习下载,适合希望将小波理论落地到气象数据处理、提升MATLAB编程与结果解读能力的读者参考。

1. 气象数据小波分析到底在分析什么

做气象数据处理的人迟早会撞上这样一个场景:手里攥着一段气温、风速或降水的长序列,想做趋势项和周期项的分离,FFT 一上去就发现频谱糊成一团——因为气象序列几乎都是非平稳的,季节波动、年际振荡、突变过程叠在一起,傅里叶变换那套「全局频率」的假设直接失效。这时候气象数据小波分析就成了绕不开的工具,而 MATLAB 因为自带 Wavelet Toolbox、矩阵操作顺手、绘图即出图,成了很多人落地这套方法的第一选择。

这篇笔记面向两类人:一类是刚拿到气象站观测数据、想快速跑出小波功率谱和显著性检验的新手;另一类是已经用过cwt但被边界效应、红噪声检验、尺度参数折腾过的熟手。我会把从数据预处理到出图的完整链路拆开讲,重点放在参数怎么设、坑在哪、结果怎么读,而不是复述小波变换的数学定义。整套流程用 MATLAB 原生函数加少量自写脚本就能跑通,不需要额外工具箱之外的依赖。

2. 从原始气象序列到可分析的小波输入

2.1 为什么气象序列必须先做这几步预处理

小波分析对输入序列的「干净程度」比 FFT 更敏感,原因是它同时在时间轴和尺度轴上做局部化,任何一个缺测值或异常跳变都会在多个尺度上产生虚假能量。常见的气象数据来源无非是站点观测、再分析资料、卫星反演三类,它们共同的毛病是:缺测(NaN 或哨兵值如 -999)、时间轴不均匀(尤其早期人工观测)、量纲不统一(气温 ℃ 与 K、风速 m/s 与 knot)。

我一般按这个顺序处理:先统一时间轴到等间隔(月尺度或日尺度),再处理缺测,最后去趋势。去趋势这一步很多人跳过,结果小波功率谱在最大尺度上翘起来,误以为是长周期信号,其实是趋势项泄漏。去趋势用线性拟合或滑动平均都行,关键是要在论文或报告里写清楚用了哪种,因为它直接影响低频段的解释。

% 假设原始数据为两列:datenum 和 value raw = readmatrix('station_data.csv'); t = raw(:,1); % MATLAB datenum x = raw(:,2); % 1) 缺测值替换:哨兵值 -999 转 NaN x(x == -999) = NaN; % 2) 时间轴等间隔化:重采样到月尺度 t0 = datenum('1980-01-01'); t1 = datenum('2020-12-01'); tq = (t0:31:t1)'; % 近似月步长,实际项目建议用 calmonths xq = interp1(t, x, tq, 'linear', NaN); % 3) 缺测插补:线性插值 + 首尾外推 nanIdx = isnan(xq); xq(nanIdx) = interp1(tq(~nanIdx), xq(~nanIdx), tq(nanIdx), 'linear', 'extrap'); % 4) 去趋势:减去最小二乘线性趋势 p = polyfit(tq, xq, 1); xdetr = xq - polyval(p, tq); % 5) 标准化:零均值单位方差,便于跨变量比较 xn = (xdetr - mean(xdetr)) / std(xdetr);

这段代码里几个参数值得说清楚。interp1的'linear'是最保守的选择,气象序列不建议用样条插值,因为样条会在缺测段制造出虚假的平滑振荡,小波一分析就露馅。polyfit的阶数固定为 1,如果你研究的是降水这种本身没有明显线性趋势的量,可以跳过第 4 步,但气温、海温这类有增暖背景的变量必须去趋势。标准化不是必须的,但如果你要对比不同站点的功率谱,标准化能让纵轴量纲统一,省去后面反复解释。

2.2 采样间隔和序列长度怎么定才不出错

小波分析的尺度范围直接受序列长度和采样间隔约束。一个经验规则:最大可分析尺度不要超过序列长度的三分之一,否则边界效应会吃掉大半结果。比如 40 年月度数据(480 个点),最大尺度控制在 160 个月以内比较稳妥,再往上功率谱两端的可信度就明显下降。

采样间隔决定了你能分辨的最小尺度。月度数据理论上能分辨 2 个月,但实际中 2 个月尺度的能量往往被噪声淹没,我一般从 3 个月起画。日尺度数据可以下探到 2 天,但要注意日气象序列有强烈的 1 天周期,这个周期会在小波谱上形成一个贯穿所有尺度的竖条,分析前最好先做日平均或带阻滤波去掉。

还有一个容易被忽略的点:序列长度最好是 2 的整数次幂附近,不是硬性要求,但用 FFT 加速的cwt在长度接近 2^n 时速度明显更快。480 个点不算快,如果你有 1000 个站点要批量跑,把序列截断到 512 或补零到 512 能省不少时间。补零要谨慎,它会在两端引入虚假能量,截断比补零安全。

3. 用 MATLAB 跑通连续小波变换的最小命令集

3.1 cwt 和 wcoherence 该选哪个

MATLAB 里做小波分析主要有两条路:cwt做单序列的连续小波变换,wcoherence做两个序列的交叉小波和相干性分析。选哪个取决于你的科学问题。如果只是想知道单个气象要素有哪些周期,用cwt;如果想看两个要素(比如气温和降水、海温和季风指数)在哪些时段哪些尺度上同步,用wcoherence。

cwt的默认小波是 Morse 小波,比老版本的 Morlet 更灵活,但如果你要跟文献对比,很多经典气象小波论文用的是 Morlet,这时候需要显式指定。指定方法是在cwt里传'Wavelet'参数。Morse 小波的默认参数(3, 20)对大多数气象序列够用,但如果你关注的是窄带信号(比如 ENSO 的 3-7 年),Morlet 的时间分辨率更好,代价是频率分辨率下降。

% 连续小波变换:单序列 Fs = 1; % 采样频率,月度数据取 1(单位:1/月) wname = 'morl'; % 或 'morse',跟文献对比时用 morl cwt(xn, Fs, wname); % 指定尺度范围(单位:月) scales = 3:1:160; cwt(xn, scales, wname); % 交叉小波相干:两个序列 % y 需与 x 等长、同采样 wcoherence(xn, yn, Fs);

cwt直接调用会出图,但做批量分析时我一般用输出参数拿数据自己画,因为默认图的配色和标注在报告里往往要改。cwt的输出包括系数矩阵wt、频率向量f、以及用于显著性检验的coi(影响锥)和sig95(95% 置信水平)。这三个东西是后面出图的核心,缺一个图就不完整。

wcoherence的输出是相干谱和相位谱,相位谱用箭头表示两个序列的领先滞后关系,读图时箭头向右表示同相,向左表示反相,向上表示 y 领先 x 四分之一周期。这个相位读法是交叉小波最容易搞错的地方,很多人把箭头方向理解反了,结论就全拧了。

3.2 尺度序列怎么设:线性还是对数

尺度序列的设置直接影响图的纵轴分布。线性尺度(比如 3:1:160)在纵轴上等间距,低频段被压缩,高频段被拉伸;对数尺度(logspace)让纵轴按对数分布,低频段展开,高频段压缩。气象文献里两种都有,但周期分析通常用对数尺度,因为周期本身是倍频关系,对数轴更能体现 2 年、4 年、8 年这种倍频结构。

% 对数尺度:从 3 个月到 160 个月,共 60 个尺度 scales = logspace(log10(3), log10(160), 60); % 对应的实际周期(月) periods = scales; % 对 Morlet,周期约等于尺度 % 画图时纵轴用 log2 刻度更直观 figure; wt = cwt(xn, scales, 'morl'); imagesc(tq, log2(scales), abs(wt)); set(gca, 'YDir', 'normal', 'YTick', log2([3 6 12 24 48 96 160]), ... 'YTickLabel', {'3','6','12','24','48','96','160'}); ylabel('周期 (月)'); xlabel('时间'); colorbar;

这里有个细节:Morlet 小波的「尺度」和「周期」在数值上近似相等,但严格来说有一个转换系数,约 1.03。做定性分析可以忽略,做定量周期估计时最好乘上这个系数,或者直接用cwt输出的频率向量f取倒数得到周期,避免手算误差。imagesc画图时纵轴用log2(scales)而不是scales,这样倍频周期在图上等间距,读图时 2 年、4 年、8 年的位置一目了然。

4. 显著性检验和影响锥:小波分析最容易翻车的地方

4.1 红噪声背景谱怎么算

小波功率谱的显著性检验和 FFT 一样,需要一个背景谱作为零假设。气象序列的默认零假设是红噪声(AR(1) 过程),因为大多数气象要素有自相关性,白噪声假设太强,会把大量真实信号误判为显著。红噪声背景谱由序列的 lag-1 自相关系数决定,这个系数必须从去趋势后的序列算,用原始序列算会偏大。

% 计算 lag-1 自相关系数 xlag = xn(1:end-1); xcur = xn(2:end); r = corrcoef(xlag, xcur); r1 = r(1,2); % 红噪声背景谱(简化版,Morlet) % 完整实现需按 Torrence & Compo (1998) 的公式 n = length(xn); dt = 1; freq = 1 ./ scales; alpha = (2 + r1) / (1 + r1); % 红噪声谱指数 Pk = (1 - r1^2) / (1 - 2*r1*cos(2*pi*freq*dt) + r1^2);

这段代码是简化版,完整的红噪声检验需要按 Torrence & Compo 的公式算每个尺度上的 95% 置信阈值,再和实际功率谱比较。MATLAB 的cwt输出里已经带了sig95,直接用就行,不用自己重算。但你要知道sig95是怎么来的,否则图上看不懂为什么有些区域被判定显著、有些没有。

r1的计算有个坑:如果序列本身有强周期(比如明显的年循环),lag-1 自相关会被周期信号污染,算出来的红噪声背景偏高,显著性检验偏保守。这时候可以先做带阻滤波去掉年循环再算r1,或者改用更复杂的背景谱模型。我一般会同时画出去年循环和不去年循环两个版本,对比看结论是否稳健。

4.2 影响锥为什么必须画在图上

影响锥(Cone of Influence, COI)是小波分析区别于 FFT 的核心概念之一。小波变换在序列两端需要补零或镜像延拓,导致两端的大尺度结果不可信。COI 就是标出这个不可信区域的边界线,通常画成一条从两端向中间收拢的弧线,弧线以外的区域功率谱被边界效应污染,不能用于物理解释。

很多新手出的图不画 COI,审稿人一眼就能看出来。更隐蔽的错误是画了 COI 但没把 COI 以外的区域遮掉或打阴影,读者容易误读。我习惯用半透明阴影把 COI 以外区域盖住,或者直接把那部分功率谱设为 NaN 不显示。

% cwt 输出自带 coi [wt, f, coi] = cwt(xn, scales, 'morl'); % 画图时叠加 COI figure; pcolor(tq, log2(scales), abs(wt)); shading interp; hold on; plot(tq, log2(coi), 'w--', 'LineWidth', 1.5); % 把 COI 以外区域打阴影 for i = 1:length(tq) if ~isnan(coi(i)) fill([tq(i) tq(i) tq(min(i+1,end)) tq(min(i+1,end))], ... [log2(coi(i)) max(log2(scales)) max(log2(scales)) log2(coi(i))], ... [0.5 0.5 0.5], 'FaceAlpha', 0.3, 'EdgeColor', 'none'); end end

COI 的形状取决于小波类型和尺度序列。Morlet 的 COI 在两端收拢得比较快,Morse 稍慢。如果你的结论严重依赖 COI 边缘附近的信号,那这个结论本身就不够稳健,应该缩短分析时段或换更长的序列重做。

5. 避坑与排查:气象小波分析里最常见的五个翻车点

5.1 现象:功率谱在最大尺度上翘起,误判为长周期信号

原因:序列没去趋势,或者去趋势不彻底。线性趋势在小波谱上表现为最大尺度处的能量堆积,看起来像一个超长周期。另一个可能是序列两端有突变(比如仪器更换导致的跳变),小波在两端补零时把这个跳变放大。

解决:先画原始序列确认有没有趋势或跳变,有趋势就做线性或多项式去趋势,有跳变就分段处理或做均一化校正。去趋势后重跑,如果最大尺度能量明显下降,说明之前是趋势泄漏。

5.2 现象:显著性区域连成一片,几乎所有尺度都显著

原因:红噪声背景谱的 lag-1 自相关系数算错了,或者序列本身有强自相关但没做预白化。还有一种可能是序列长度太短,自由度不足,显著性检验本身不可靠。

解决:检查r1是否在合理范围(气象序列一般 0.3-0.8),如果接近 1 说明序列接近随机游走,红噪声假设不适用。序列长度少于 100 个点时,显著性检验结果只能作参考,不要下强结论。

5.3 现象:交叉小波的相位箭头方向读反,领先滞后结论颠倒

原因:wcoherence的相位定义是 y 相对 x 的相位差,箭头向右表示同相,向上表示 y 领先 x 四分之一周期。很多人凭直觉把箭头方向理解成时间流向,结论就反了。

解决:拿两个已知相位关系的合成序列(比如 y = sin(t + pi/2))跑一遍wcoherence,看箭头方向,建立正确的直觉。实际分析时,相位结论最好和互相关函数或滞后回归交叉验证,不要只靠小波相位下结论。

5.4 现象:批量跑几百个站点时内存爆掉或速度极慢

原因:cwt对每个序列都重新计算小波基和尺度序列,批量循环时重复开销大。另外cwt默认输出完整系数矩阵,长序列加多尺度时矩阵很大。

解决:把尺度序列预先算好,循环里复用;只保留需要的输出(功率谱和 COI),不要保留完整系数矩阵;序列长度截断到 2 的整数次幂附近加速 FFT。如果站点超过 500 个,考虑用parfor并行,但要注意每个 worker 的内存占用。

5.5 现象:换一个小波类型或尺度范围,结论就变了

原因:小波分析的结果对参数敏感,这是方法本身的特性,不是 bug。Morlet 和 Morse 的时间-频率分辨率不同,线性尺度和对数尺度在图上呈现不同,结论自然会有差异。

解决:做敏感性测试,至少用两种小波类型和两种尺度范围各跑一遍,只报告在所有配置下都稳健的信号。如果某个周期只在特定参数下出现,那它很可能是方法 artifact,不是真实信号。这一步很多论文省略,但恰恰是审稿人最关注的。

6. 把结果做成能发表的图:配色、标注和批量出图技巧

出图这件事,做过的人都知道它是玄学重灾区。同样的数据,配色差一点、COI 画法差一点,审稿人的观感能差出两个档次。我踩过的坑包括:用jet配色被批「不专业」,功率谱用线性色标导致弱信号全看不见,COI 阴影盖住了显著区域。

配色上,功率谱推荐用感知均匀的色标,MATLAB 自带的parula或第三方cmocean里的thermal、balance都行。避免jet和hsv,这两个在功率谱上会产生虚假的边界感。色标范围用caxis手动设,一般取功率谱的 5% 到 95% 分位数,避免极端值把整体对比度压掉。

% 批量出图:每个站点一张,统一色标 stations = {'S001','S002','S003'}; % 虚构站点编号 clim = [0 1.5]; % 统一色标范围,需先统计所有站点 for k = 1:length(stations) fn = sprintf('%s_monthly.mat', stations{k}); S = load(fn); xn = S.xn; [wt, f, coi] = cwt(xn, scales, 'morl'); P = abs(wt).^2; figure('Visible','off','Position',[100 100 900 400]); pcolor(tq, log2(scales), P); shading interp; caxis(clim); colormap(cmocean('thermal')); hold on; plot(tq, log2(coi), 'k--', 'LineWidth', 1.2); set(gca, 'YDir', 'normal', 'YTick', log2([3 6 12 24 48 96 160]), ... 'YTickLabel', {'3','6','12','24','48','96','160'}, 'FontSize', 11); xlabel('年份'); ylabel('周期 (月)'); title(sprintf('站点 %s 小波功率谱', stations{k})); colorbar; print(gcf, sprintf('%s_cwt.png', stations{k}), '-dpng', '-r300'); close(gcf); end

批量出图的关键是统一色标和统一尺度范围,否则站点之间没法对比。clim要先跑一遍所有站点统计全局分位数再定,不能每个站点各设各的。Visible设为off避免弹窗,print用-r300保证分辨率够投稿。如果图要进 LaTeX 或 Word,建议同时输出 PDF 矢量图,print的-dpdf选项即可。

最后一个习惯:每张图旁边存一份参数记录,写清楚小波类型、尺度范围、去趋势方法、红噪声参数。半年后你回头看图,没有这份记录根本想不起来当时怎么设的。这个习惯帮我省过好几次返工,希望帮到你。

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

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

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

立即咨询