在数据分析领域,长时间序列数据无处不在——气象领域的逐年气温变化、水文领域的河流流量波动、生态领域的植被覆盖演变、环境领域的污染物浓度变化,甚至是经济领域的月度营收走势,都属于典型的长时间序列数据。对于这类数据,核心需求之一就是判断其是否存在明显的上升、下降趋势,以及这种趋势是否具有统计学意义,而SEN-MK方法,正是解决这一问题的“黄金标准”。
SEN-MK并非单一方法,而是由两种非参数统计方法——Sen斜率估计(Theil-Sen Median)与Mann-Kendall(MK)显著性检验——结合而成的组合方法。它之所以被广泛应用于各类长时间序列分析,核心优势在于无需假设数据服从正态分布、对异常值和缺失值不敏感,能稳健捕捉数据的长期趋势,完美适配现实中杂乱无章的真实数据场景,这也是它区别于传统线性回归分析的关键所在。
一、SEN-MK方法核心原理:两个“好搭档”的分工协作
SEN-MK的核心逻辑的是“分工明确、协同发力”:Sen斜率负责“量化趋势强度”,判断数据是上升还是下降、变化速度有多快;MK检验负责“验证趋势可靠性”,判断这种趋势是真实存在的,还是随机波动导致的。两者结合,既能给出趋势的具体方向和速率,又能保障结果的统计学可信度。
1. Sen斜率估计:不被异常值干扰的“趋势标尺”
Sen斜率估计(Theil-Sen Median)是一种稳健的非参数趋势估计方法,其核心思想非常简单:通过计算时间序列中所有两两数据点连线的斜率,再取这些斜率的中位数,作为整个序列的趋势斜率(简称Sen斜率)。
具体来说,假设我们有一组长时间序列数据(n为数据长度,对应n个时间点),任意两个不同时间点i和j(i < j)之间的斜率计算如下:
我们会计算所有可能的(总共个),然后取这些斜率的中位数,即为Sen斜率。
Sen斜率的解读非常直观:
•若:数据呈现<>上升趋势<>,数值越大,上升速度越快;
•若:数据呈现<>下降趋势<>,数值越小(绝对值越大),下降速度越快;
•若:数据无明显趋势,基本处于平稳状态。
与传统线性回归的斜率估计相比,Sen斜率的最大优势的是“抗干扰能力强”。线性回归容易被异常值(如极端高温、突发洪水、异常营收)带偏,而Sen斜率通过取中位数,能有效过滤极端值的影响,更贴合数据的真实长期趋势,这也是它适合长时间序列分析的核心原因之一。
2. Mann-Kendall(MK)检验:判断趋势是否“靠谱”
有了Sen斜率,我们知道了数据的趋势方向和速度,但还不能确定这种趋势是真实存在的,还是随机波动导致的——比如某几年的气温波动,可能只是偶然现象,而非长期趋势。这时候,就需要MK检验来“把关”,判断趋势的显著性。
MK检验同样是一种非参数方法,无需假设数据服从正态分布,也不受异常值和缺失值的影响,其核心是通过比较所有两两数据点的大小关系,计算统计量来判断趋势是否显著。
MK检验的核心步骤可以简化为3步:
1.定义符号函数:当时,取值为1;当时,取值为-1;当时,取值为0(i < j);
2.计算统计量S:将所有两两数据点的符号函数结果求和,
,S的正负反映趋势方向(正为上升,负为下降);
3.计算标准化统计量Z:根据S的方差,将S标准化为Z值,Z值的绝对值越大,趋势越显著。
MK检验的显著性判断标准(常用置信水平):
•当:趋势通过99%置信水平检验,<>极显著<>;
•当:趋势通过95%置信水平检验,<>显著<>;
•当:趋势通过90%置信水平检验,<>弱显著<>;
•当:趋势不显著,可能是随机波动导致。
简单来说,Sen斜率告诉我们“趋势是什么样的”,MK检验告诉我们“这个趋势可信吗”,两者结合,才是完整的SEN-MK趋势分析流程。
3、Python实现SEN-MK长时间序列趋势性分析,我们用Python实现SEN-MK分析。本次将使用两个常用库:pymannkendall(快速实现MK检验和Sen斜率计算,简化代码)、numpy(数据处理)、matplotlib(结果可视化)。
import numpy as npimport matplotlib.pyplot as pltimport pymannkendall as mktime = np.arange(1993, 2023) # 时间范围:1993-2022年# 模拟NDVI数据(含轻微上升趋势+随机噪声,贴近真实数据)np.random.seed(42) # 固定随机种子,保证结果可复现trend = 0.02 * np.arange(len(time)) # 上升趋势(斜率0.02)noise = np.random.normal(0, 0.05, len(time)) # 随机噪声ndvi_data = 0.5 + trend + noise # 使用pymannkendall库直接计算Sen斜率和MK检验结果# original_test方法返回结果包含:趋势方向、p值、Z值、Sen斜率等mk_result = mk.original_test(ndvi_data)# 提取关键结果trend_direction = mk_result.trend # 趋势方向('increasing'/'decreasing'/'no trend')p_value = mk_result.p # p值(与Z值等价,p<0.05表示显著)z_value = mk_result.z # Z值(判断显著性)sen_slope = mk_result.slope # Sen斜率(趋势速率)# ---------------------- 3. 结果解读 ----------------------print("=" * 50)print("SEN-MK长时间序列趋势分析结果")print("=" * 50)print(f"时间序列长度:{len(time)}个时间点")print(f"Sen斜率:{sen_slope:.4f}")print(f"趋势方向:{trend_direction}")print(f"MK检验Z值:{z_value:.4f}")print(f"MK检验p值:{p_value:.4f}")# 根据Z值判断显著性if abs(z_value) >= 2.58: significance = "极显著(99%置信水平)"elif 1.96 <= abs(z_value) < 2.58: significance = "显著(95%置信水平)"elif 1.645 <= abs(z_value) < 1.96: significance = "弱显著(90%置信水平)"else: significance = "不显著"print(f"趋势显著性:{significance}")print("=" * 50)# ---------------------- 4. 结果可视化(直观展示趋势)----------------------plt.rcParams['font.sans-serif'] = ['SimHei'] # 解决中文显示问题plt.figure(figsize=(12, 6))# 绘制原始时间序列plt.plot(time, ndvi_data, 'o-', color='#1f77b4', label='原始NDVI数据', linewidth=1.5, markersize=4)trend_line = sen_slope * (time - time[0]) + ndvi_data[0]plt.plot(time, trend_line, '-', color='#ff7f0e', linewidth=2, label=f'Sen趋势线(斜率={sen_slope:.4f})')# 添加显著性标记plt.text(time[0] + 2, np.max(ndvi_data) - 0.05, f'趋势:{trend_direction}\n显著性:{significance}\nSen斜率:{sen_slope:.4f}', bbox=dict(boxstyle='round', facecolor='lightgray', alpha=0.8), fontsize=10)# 图表美化plt.xlabel('年份', fontsize=12)plt.ylabel('NDVI植被指数', fontsize=12)plt.title('长时间序列NDVI数据SEN-MK趋势分析(1993-2022)', fontsize=14, pad=20)plt.legend(loc='best', fontsize=10)plt.grid(True, alpha=0.3)plt.tight_layout()# 保存图片(可选)plt.savefig('sen_mk_trend_analysis.png', dpi=300, bbox_inches='tight')plt.show()# ---------------------- 补充:手动实现Sen斜率(可选,理解原理)----------------------def calculate_sen_slope(data): """手动计算Sen斜率""" n = len(data) slopes = [] # 遍历所有两两数据点,计算斜率 for i in range(n): for j in range(i + 1, n): slope = (data[j] - data[i]) / (j - i) slopes.append(slope) # 返回斜率的中位数 return np.median(slopes)# 手动计算Sen斜率(与pymannkendall结果一致)manual_sen_slope = calculate_sen_slope(ndvi_data)print(f"手动计算Sen斜率:{manual_sen_slope:.4f}")

在GIS应用领域,基于SEN-MK方法原理,可通过ArcGIS的Python脚本工具(ArcPy)编写专属工具箱,实现栅格数据(尤其是遥感影像时序数据,如NDVI时序数据集)的批量、自动化趋势分析。笔者已亲测该工具箱可正常运行,无需手动编写复杂代码,只需在ArcGIS界面中导入时序栅格数据,设置相关参数(如置信水平、输出路径),即可自动完成逐像元的Sen斜率计算与MK显著性检验,最终将Sen斜率栅格、趋势显著性分级栅格等结果直接输出至ArcGIS界面,生成可视化成果,极大提升了GIS领域时序趋势分析的效率。
如需GIS计算工具的可关注公众号留言,小编会一一回复。后续会更新GEE相关计算代码,感兴趣的小伙伴可以在评论区留言交流,也可以将一些算法难题进行分享,咱们一起讨论学习。