很多做水文气象的人可能都有过这个经历:手里握着一串几十年甚至上百年的降水数据,导师或者审稿人开口就问,这地方的降雨量到底有没有趋势?周期是多少年?如果你只回答“看起来增加了”“好像有个8年周期”,那基本过不了关。这时候,出镜率最高的两个工具就是降雨量MK检验和Morlet小波分析。MK检验用来判断趋势有没有统计意义,Morlet小波分析用来定位周期成分在什么时候出现、什么时候消失。我自己刚开始用这两个方法时也走过不少弯路,这篇就从一个能直接跑的Matlab脚本出发,把原理、代码、参数选择和最容易踩的坑一起说清楚,适合刚接触时间序列分析的研究生,也适合想快速复核结论的从业者。
1. 先把结论说清楚:MK检验和小波分析分别解决什么问题
1.1 MK检验:不假设正态,只看趋势是否显著
Mann-Kendall检验是一个非参数趋势检验方法,它不要求数据服从正态分布,对少数异常值也不那么敏感,这是它在水文气象领域特别受欢迎的原因。降水量数据经常出现偏态分布,比如某个年份来一场极端暴雨,实测值比常年大好几倍,如果用普通的线性回归去做趋势检验,一个极值就可能把斜率拽偏。MK检验不比较数值本身,而是比较序列里两两数据的大小关系,所以它受极端值的影响要小得多。
它的原假设是序列没有单调趋势,备择假设是存在单调上升或下降趋势。输出结果里最关键的两个值是Z统计量和p值。Z的绝对值越大,趋势越强;p小于0.05通常认为趋势显著,在论文里写“年降水量呈显著增加趋势”时才站得住脚。
不过MK检验只能回答“有没有趋势”,不能回答“趋势有多大”。所以实际使用中,我一般会再配一个Sen斜率估计,用所有相邻点对之间的斜率中位数来代表单位时间变化量,单位通常是mm/年。这样MK检验给显著性,Sen斜率给变化速率,一篇文章里关于趋势部分就完整了。
1.2 Morlet小波:找到序列里的“隐藏周期”
Morlet小波是连续小波变换里最常用的复值小波,它的特点是既能看信号在时间上的变化,又能看频率上的周期成分。和傅里叶变换不同,小波变换会把“时间窗”收缩或拉伸,在短尺度上时间分辨率高、周期分辨率低,在长尺度上周期分辨率高、时间分辨率低。这个特性恰好适合分析降雨量这种非平稳序列。
什么叫非平稳?就是说它的统计性质会随时间变化。比如某地区上世纪60年代到80年代存在明显的3年周期,90年代以后这个周期消失了。如果只用傅里叶变换,你只能得到整段时间的平均周期,3年周期和5年周期可能混在一起,看不出变化的节点。Morlet小波分析会给出一个时频图,横轴是年份,纵轴是周期,颜色深浅代表这一时间点上某个周期的强度。周期在哪几年增强、哪几年减弱,一目了然。
用Morlet小波还有一个好处:它是复值小波,能同时提取实部和虚部,进而得到小波功率、小波相位,对研究周期背后的物理机制也有帮助。论文里常见的“降雨量存在2~4年和8~12年周期”这类结论,很多就是来自这种分析。
1.3 为什么这两者经常成对出现
趋势和周期是降水序列分析里两个最基础的问题,但很多初学者会把它们混在一起讨论。有人拿到小波功率谱图看到某段颜色很深,就说“这地方有显著周期”,结果MK检验显示序列根本没有上升或下降趋势。其实这并不矛盾,因为周期震荡不等于单调趋势。MK检验和小波分析正好是互补的:MK检验处理的是序列整体漂移,小波分析处理的是围绕某个平均状态的波动结构。
实际文章里,我习惯这样安排:先用MK检验给出趋势是否显著、方向如何、速率多大,再用Morlet小波分析给出周期成分和时频特征。这两个结果放进同一篇“降雨量变化特征”的分析里,逻辑上是完整的。审稿人也不会再追问“你的趋势有多显著”或者“周期从哪年开始”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法原理与关键参数,别只会套代码
2.1 MK检验的统计量是怎么来的
MK检验的核心是统计量S。设有n个按时间排序的观测值x1, x2, ..., xn,对所有i<j的数据对,计算符号函数之和:
S = Σ_{i=1}^{n-1} Σ_{j=i+1}^{n} sign(xj - xi)
其中sign函数当xj大于xi时取1,小于时取-1,相等时取0。如果序列整体上升,绝大多数数据对都是后大于前,S为正;整体下降,S为负;没有趋势时,S的期望是0。
S的方差需要修正,因为降雨量数据里经常出现相同值,比如旱季连续几个月降水量为0,并列值会让方差变小。变量方差Var(S)为:
Var(S) = [n(n-1)(2n+5) - Σ tp(tp-1)(2tp+5)] / 18
这里的tp是第p个并列组的样本数。当n大于10时,可以构造标准化统计量:
Z = (S - 1) / sqrt(Var(S)),当S > 0时;
Z = 0,当S = 0时;
Z = (S + 1) / sqrt(Var(S)),当S < 0时。
Z近似服从标准正态分布,所以双边p值可以算出来。我在代码里直接用erfc函数计算p值,不依赖统计工具箱,这个细节后面会再说。
2.2 Morlet小波变换的核心公式
Morlet小波本质上是一个由复指数函数和高斯窗函数相乘形成的波包,时域形式可以写成:
ψ0(η) = π^{-1/4} exp(iω0η) exp(-η2/2)
其中η是无量纲时间参数,ω0是中心频率,通常取6。取6的好处是波形在时域和频域之间比较均衡,而且它的傅里叶周期和尺度s之间有一个近似关系:傅里叶周期约等于1.033s。如果换成ω0=8,频率分辨率会更好,但时间分辨率会变差,参数要在具体数据上权衡。
连续小波变换的定义是:
Wn(s) = √(dt/s) Σ xn' ψ0*((n'-n)dt/s)
这里的星号表示共轭,dt是采样间隔,s是尺度。如果一个地区60年降水序列的dt=1,最小尺度s0取2,那么小波尺度按照s0乘以2的幂次等对数步长展开,最终得到一组从短周期到长周期的小波系数。
实际计算时,直接在时域对每个尺度做卷积很慢,所以代码里用FFT在频域实现。先对序列做傅里叶变换,再乘上子小波的傅里叶变换,最后逆变换回时间域,速度会快得多。这个做法的前提是采用复值Morlet小波,频域形式很简单,只保留正频率部分即可。
2.3 显著性检验为什么不能省
很多初学者只看小波功率图的颜色深浅,颜色越红就认为周期越显著,这是最常见的一个误区。小波功率高只说明这个时段的波动能量强,但能量强不一定意味着周期在统计意义上显著,因为纯随机序列也可能会在某个频段偶然出现较高的功率。
正规做法是把小波功率谱和某种背景噪声谱做比较。水文气象里通常假设背景过程是一阶自回归过程AR(1),也就是红噪声过程,因为降水时间序列往往存在自相关性。显著性检验的大致逻辑是:在某个周期上,如果观测到的小波功率超过背景谱95%置信水平对应的阈值,就认为该周期是显著的。阈值计算里用到卡方分布,自由度取2,也就是Morlet小波功率谱的自度。
另外还要注意边界效应。小波在序列两端附近会“碰到边界”,因为没有足够的数据来计算完整卷积,高频部分影响较小,低频部分在边界处的假高值非常明显。所以小波功率图里都会画一条“锥形影响线”COI,COI以外的区域结果受边界效应影响,不能直接用来下结论。
3. 可直接运行的Matlab代码实现
3.1 MK检验和Sen斜率函数
先给出MK检验函数,代码不依赖任何额外工具箱,把下面的内容保存成mk_test.m就可以直接调用。
matlab复制function [h, p, Z, S, tau] = mk_test(x, alpha)
% MK检验:Mann-Kendall趋势检验
% 输入:
% x - 按时间排列的序列(列向量或行向量)
% alpha - 显著性水平,默认0.05
% 输出:
% h - 是否拒绝原假设(1=趋势显著)
% p - 双边p值
% Z - 标准化统计量
% S - 原始MK统计量
% tau - Kendall tau相关系数
if nargin < 2, alpha = 0.05; end
x = x(:);
n = length(x);
if n < 8
warning('样本量太小,MK检验结果仅供参考。');
end
S = 0;
for i = 1:n-1
S = S + sum(sign(x(i+1:end) - x(i)));
end
% 计算方差,考虑并列值(tie)的影响
xs = sort(x);
tie = [];
cnt = 1;
for i = 2:n
if xs(i) == xs(i-1)
cnt = cnt + 1;
else
if cnt > 1
tie(end+1) = cnt; %#ok<SAGROW>
end
cnt =
