跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Python算法

基于 Python 的栅格数据 Theil-Sen 与 Mann-Kendall 趋势分析及显著性检验

基于 Python 的栅格数据 Theil-Sen 与 Mann-Kendall 趋势分析及显著性检验。该方法利用中位数斜率评估时间序列长期趋势,并通过统计量判断显著性水平,适用于气候、生态等非正态分布数据的稳健分析。教程涵盖原理公式、完整代码实现(含进度条可视化)、结果重分类及 GeoTIFF 保存流程,可直接用于 MODIS 等遥感产品的长时间序列变化检测。

涅槃凤凰发布于 2026/3/23更新于 2026/10/25106 浏览
基于 Python 的栅格数据 Theil-Sen 与 Mann-Kendall 趋势分析及显著性检验

文章配图

在气候变化、生态环境等研究中,如何准确分析长时间序列数据的趋势至关重要。Theil-Sen Median 趋势分析(Sen 分析)和 Mann-Kendall 显著性检验(MK 检验)是两种常用的非参数方法,特别适合处理含有异常值或缺失值的遥感数据。本文将梳理这两种方法的原理,并提供完整的 Python 代码实现。

核心方法解析

1. Theil-Sen Median 趋势分析(Sen 分析)

这是一种稳健的非参数统计方法。不同于传统的最小二乘法,Sen 分析通过计算数据的中位数斜率来评估时间序列的趋势,对异常值和极端值不敏感。

具体计算以蒸散发(ET)为例: 文章配图

其中,ETi 和 ETj 分别为时间序列中不同时间点的数据值,Sen 为中位数斜率。若 Sen > 0,表示上升趋势;若 Sen < 0,表示下降趋势。

2. Mann-Kendall 显著性检验(MK 检验)

MK 检验用于判断时间序列的趋势是否显著,不要求数据服从特定分布。它通过对每对数据点进行比较,计算趋势的符号,并利用统计量评估显著性。

基本步骤如下:

  1. 比较所有数据点对,得到符号(升序为 1,降序为 -1,相等为 0);
  2. 计算统计量 S,反映总体趋势;
  3. 根据 S 及其方差计算 Z 值,判断显著性。

文章配图

Z 值越大,趋势越显著。符号函数累加和得到统计量 S: 文章配图

3. MK 显著性结果划分

在 0.05 置信水平下,依据 Z 值判断:

  • 显著变化:上升趋势(Z > 1.96)或下降趋势(Z < -1.96)
  • 不显著变化:-1.96 ≤ Z ≤ 1.96
4. 方法优势
  • 稳健性强:有效处理含异常值或缺失值的数据。
  • 显著性检验:确保趋势结果具有统计学意义。
  • 适用范围广:适用于无法满足正态分布假设的时间序列数据。

代码实现

以下代码实现了逐像元栅格进度可视化,只需修改标注位置即可运行。代码逻辑上优化了空值处理和内存占用,适合长时间序列分析。

 os
 rasterio
 numpy  np
 tqdm  tqdm
 matplotlib.pyplot  plt
 pathlib  Path


base_path = os.getcwd()


data_path = os.path.join(base_path, )
result_path = os.path.join(base_path, )


os.makedirs(result_path, exist_ok=)


start_year = 
end_year = 
cd = end_year - start_year +   

:
    
    first_file = os.path.join(data_path, )
     rasterio.(first_file)  src:
        a = src.read()  
        transform = src.transform  
        metadata = src.meta.copy()  
        
        
        metadata.update({
            : ,
            : np.nan  
        })
        
        
        m, n = a.shape
        ()
        
        
        
        all_data = np.full((m, n, cd), np.nan)
        
        
        ()
         i, year  (tqdm((start_year, end_year + ), desc=)):
            filename = os.path.join(data_path, )
             rasterio.(filename)  src:
                all_data[:, :, i] = src.read()
        
        
        sen_result = np.full((m, n), np.nan)
        
        
        
        ()
        valid_pixels = 
        
        valid_mask = np.(all_data > , axis=)
        total_valid = np.(valid_mask)
        
         i  tqdm((m), desc=):
             j  (n):
                 valid_mask[i, j]:
                    data = all_data[i, j, :]
                     np.(~np.isnan(data))  np.(data) > :  
                        slopes = []
                         k1  (, cd):
                             k2  (k1):
                                
                                slope = (data[k1] - data[k2]) / (k1 - k2)
                                slopes.append(slope)
                        
                         slopes:
                            sen_result[i, j] = np.median(slopes)
                            valid_pixels += 
        
        ()
        
        
        sen_output_path = os.path.join(result_path, )
        
        
        sen_result = sen_result.astype()
        
        
         rasterio.(sen_output_path, , **metadata)  dst:
            dst.write(sen_result, )
        (, sen_output_path)
        
        
        ()
        mk_result = np.full((m, n), np.nan)
        
         i  tqdm((m), desc=):
             j  (n):
                 valid_mask[i, j]:
                    data = all_data[i, j, :]
                     np.(~np.isnan(data))  np.(data) > :
                        sgnsum = []
                         k1  (, cd):
                             k2  (k1):
                                
                                sgn = np.sign(data[k1] - data[k2])
                                sgnsum.append(sgn)
                        
                        mk_result[i, j] = np.(sgnsum)
        
        
        ()
        vars_mk = cd * (cd - ) * ( * cd + ) / 
        z_scores = np.full((m, n), np.nan)
        
        
        z_scores[~np.isnan(mk_result) & (mk_result == )] = 
        z_scores[~np.isnan(mk_result) & (mk_result > )] = (mk_result[~np.isnan(mk_result) & (mk_result > )] - ) / np.sqrt(vars_mk)
        z_scores[~np.isnan(mk_result) & (mk_result < )] = (mk_result[~np.isnan(mk_result) & (mk_result < )] + ) / np.sqrt(vars_mk)
        
        
        mk_output_path = os.path.join(result_path, )
        
        z_scores = z_scores.astype()
         rasterio.(mk_output_path, , **metadata)  dst:
            dst.write(z_scores, )
        (, mk_output_path)
        
        
        ()
        
        S2 = np.full((m, n), np.nan)
        M2 = np.full((m, n), np.nan)
        
        
        S2[np.isnan(sen_result)] = -
        S2[~np.isnan(sen_result) & (sen_result <= -)] = -
        S2[~np.isnan(sen_result) & (sen_result >= )] = 
        S2[~np.isnan(sen_result) & (sen_result > -) & (sen_result < )] = 
        
        
        M2[z_scores > ] = 
        M2[~np.isnan(z_scores) & (z_scores <= )] = 
        
        
        reclassify = (S2 * M2).astype(np.int16)
        
        
        reclass_output_path = os.path.join(result_path, )
        
        
        metadata.update({
            : ,
            : -
        })
        
        
         rasterio.(reclass_output_path, , **metadata)  dst:
            dst.write(reclassify, )
        (, reclass_output_path)
        
 Exception  e:
    ()
import
import
import
as
from
import
import
as
from
import
# 获取当前工作目录
# 定义数据和结果路径
'data'
'results'
# 创建结果目录(如果不存在)
True
# 定义年份范围
2000
2020
1
# 时间跨度
try
# 读取第一个栅格文件以获取元数据和尺寸信息
f'{start_year}.tif'
with
open
as
1
# 读取栅格数据
# 栅格的空间转换信息
# 获取元数据
# 修复:更新元数据中的数据类型为 float32,解决 dtype 不匹配问题
'dtype'
'float32'
'nodata'
# 设置空值为 NaN
# 获取栅格尺寸
print
f"栅格尺寸:{m} x {n}"
# 创建空数组来存储所有年份的数据
# 使用三维数组以避免展平和重塑操作
# 加载每一年的数据
print
"正在加载每一年的栅格数据..."
for
in
enumerate
range
1
"加载年度数据"
f'{year}.tif'
with
open
as
1
# 创建结果数组
# 使用 Sen's Slope 方法计算趋势
# 这是计算最耗时的部分,使用 tqdm 显示进度
print
"正在计算 Sen's Slope 趋势..."
0
# 优化:仅处理有效像素(至少有一个非空值的像素)
any
0
2
sum
for
in
range
"计算 Sen's Slope"
for
in
range
if
if
all
and
min
0
# 检查数据是否包含有效值
for
in
range
1
for
in
range
# 计算变化率
# 取中位数作为最终斜率值
if
1
print
f"有效像素数:{valid_pixels}/{total_valid} (共{m*n}像素)"
# 设置输出路径并将 Sen's Slope 结果保存为 GeoTIFF
'基于 sen 的 ET 变化趋势.tif'
# 确保结果为 float32 类型
'float32'
# 保存 Sen's Slope 结果
with
open
'w'
as
1
print
'Sen\'s Slope 处理完成!结果已保存至:'
# 计算 Mann-Kendall 检验
print
"正在计算 Mann-Kendall 检验..."
for
in
range
"计算 Mann-Kendall 检验"
for
in
range
if
if
all
and
min
0
for
in
range
1
for
in
range
# 计算符号差异
# 计算符号差异的总和
sum
# 计算 Z 值
print
"正在计算 Z 值..."
1
2
5
18
# 处理不同情况的 Z 值计算
0
0
0
0
1
0
0
1
# 保存 Mann-Kendall 检验结果
'MK 检验结果.tif'
'float32'
with
open
'w'
as
1
print
'Mann-Kendall 检验处理完成!结果已保存至:'
# 对 Sen's slope 和 MK 检验结果进行重分类
print
"正在进行重分类..."
# 创建重分类结果数组
# 重分类 Sen's slope 结果
9999
0.0005
1
0.0005
1
0.0005
0.0005
0
# 重分类 MK 检验结果
1.96
2
1.96
1
# 计算最终重分类结果
# 设置输出路径并将重分类结果保存为 GeoTIFF
'重分类结果.tif'
# 更新元数据中的数据类型为 int16
'dtype'
'int16'
'nodata'
9999
# 保存重分类结果
with
open
'w'
as
1
print
'重分类处理完成!结果已保存至:'
except
as
print
f"处理过程中发生错误:{str(e)}"

结果展示

运行上述脚本后,将生成三个主要结果文件:

  1. 基于 sen 的 ET 变化趋势.tif:记录每个像元的斜率值。
  2. MK 检验结果.tif:记录 Z 值,用于判断显著性。
  3. 重分类结果.tif:综合趋势方向和显著性的分类图。

示例数据使用了 MOD16A2H 2000-2020 年宁夏部分区域数据。可视化部分的代码建议以 Jupyter Notebook 的 .ipynb 文件形式展现,也可以复制到新的 .py 文件中运行。

目录

  1. 核心方法解析
  2. 1. Theil-Sen Median 趋势分析(Sen 分析)
  3. 2. Mann-Kendall 显著性检验(MK 检验)
  4. 3. MK 显著性结果划分
  5. 4. 方法优势
  6. 代码实现
  7. 获取当前工作目录
  8. 定义数据和结果路径
  9. 创建结果目录(如果不存在)
  10. 定义年份范围
  11. 结果展示

更多推荐文章

查看全部
  • 基于 Spring Boot 3.5.x + Sa-Token + MyBatis Flex 的企业级文件管理系统 Free-FS
  • AI 核心概念解析:从人工智能到大模型技术详解
  • Qwen3-0.6B-FP8 跨平台 AI 助手构建:Web/Telegram/Discord 统一后端
  • 前端开发必备技能:AI 辅助设计、工程实践与硬件优化
  • 大语言模型提示工程与高效交互指南
  • Umi 脚手架创建项目实战指南
  • OpenCode 开源 AI 编程助手:从入门到精通
  • VSCode 本地运行 DeepSeek 模型配置教程
  • C++ std::string 的弱点与潜在风险
  • C++ 引用、内联函数与 C++11 新特性详解
  • C++ 类的 6 个默认成员函数与运算符重载
  • OpenCode 开源 AI 编程助手使用指南
  • 前端面试核心知识点梳理与高频考点解析
  • 基于 Go 语言的命令行 AI 对话客户端开发实战
  • LeetCode 141:环形链表判断的两种经典解法
  • 使用 Dexie 操作前端 IndexedDB 数据库教程
  • AI 变现真相:为何掌握大量工具仍难获利
  • Web 全栈开发实战:从架构设计到部署运维
  • 基于 Java 与 Vue 的大学生理财记账贷款系统
  • 鸿蒙 HarmonyOS 开发技术入门与实战指南

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online