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

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

基于 Python 实现栅格时间序列的 Theil-Sen Median 趋势分析与 Mann-Kendall 显著性检验。该方法利用中位数斜率评估变化方向,结合 Z 值统计量判断显著性,有效处理异常值与非正态分布数据。代码涵盖数据加载、逐像元计算、结果重分类全流程,适用于植被、蒸散发等遥感指标在气候变化研究中的应用。

怪力乱神发布于 2026/3/29更新于 2026/6/826 浏览
Python 栅格数据 Theil-Sen 趋势分析与 Mann-Kendall 显著性检验

遥感时间序列趋势分析:Theil-Sen 与 Mann-Kendall

在气候变化、环境监测及生态演变研究中,准确识别长时间序列数据的趋势至关重要。Theil-Sen Median 趋势分析(Sen 分析)结合 Mann-Kendall 显著性检验(MK 检验)是处理此类问题的经典非参数方法组合。相比传统最小二乘法,该方法对异常值和缺失值具有更强的鲁棒性,且无需数据服从正态分布。

原理概述

1. Theil-Sen Median 趋势分析

Sen 分析通过计算所有点对斜率的中位数来评估趋势。对于时间序列 $ET_1, ET_2, ..., ET_n$,任意两点 $(i, j)$ 的斜率计算公式为:

文章配图

其中 $ET_i$ 和 $ET_j$ 分别为不同时间点的数据值,$Q$ 为中位数斜率。若 $Q > 0$ 表示上升趋势,$Q < 0$ 表示下降趋势。

2. Mann-Kendall 显著性检验

MK 检验用于判断 Sen 分析得出的趋势是否具有统计学意义。它通过比较时间序列中每对数据点的符号来计算统计量 $S$:

文章配图

其中 $sgn(ET_j - ET_i)$ 为符号函数。根据 $S$ 及其方差可进一步计算 $Z$ 值:

文章配图

3. 结果判读

在 0.05 置信水平下,依据 $Z$ 值判断显著性:

  • 显著上升:$Z > 1.96$
  • 显著下降:$Z < -1.96$
  • 不显著:$-1.96 \le Z \le 1.96$

代码实现

以下 Python 脚本实现了从数据加载、Sen 斜率计算、MK 检验到结果重分类的全流程。代码支持逐像元进度可视化,并针对空值进行了处理。

准备工作

确保已安装 rasterio, numpy, tqdm, matplotlib 等依赖库。数据文件夹结构建议如下:

文章配图

核心代码
import os
import 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
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
# 创建结果数组
0
# 优化:仅处理有效像素(至少有一个非空值的像素)
any
0
2
sum
# 计算 Sen's Slope 趋势
# 这是计算最耗时的部分,使用 tqdm 显示进度
print
"正在计算 Sen's Slope 趋势..."
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)}"
运行效果

程序执行过程中会显示详细的进度条,方便监控长耗时任务。最终生成的栅格文件可直接在 GIS 软件中查看。

文章配图

文章配图

文章配图

文章配图

文章配图

数据说明

本文示例使用了 MOD16A2H 2000-2020 年宁夏部分区域的蒸散发数据。实际使用时,请确保输入数据的时间序列完整且格式一致(如 GeoTIFF)。

目录

  1. 遥感时间序列趋势分析:Theil-Sen 与 Mann-Kendall
  2. 原理概述
  3. 1. Theil-Sen Median 趋势分析
  4. 2. Mann-Kendall 显著性检验
  5. 3. 结果判读
  6. 代码实现
  7. 准备工作
  8. 核心代码
  9. 获取当前工作目录
  10. 定义数据和结果路径
  11. 创建结果目录(如果不存在)
  12. 定义年份范围
  13. 运行效果
  14. 数据说明
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • C++ 性能分析工具全景与选型指南
  • 基于 Web 的红色旅游网站的设计与实现
  • 千寻智能融资近20亿,荣耀进军机器人,智平方成百亿独角兽,华为云发布具身智能平台
  • Python 多线程编程基础
  • AI Agent 新范式:基于 FastGPT 与 MCP 协议构建工具增强型智能体
  • 申请 Hugging Face 访问令牌(以 Meta-Llama-3.1-8B-Instruct 为例)
  • 人工智能 Gemini 2.5 Pro:深度解析技术突破与实战应用
  • Gemini 2.5 Pro 技术突破与实战应用深度解析
  • OpenClaw 中文发行版部署指南:npm/Docker/脚本三种方式
  • 费曼学习法知识助手的 AI 智能体实现案例
  • 基于 Web-Check 与内网穿透实现远程站点安全检测
  • FPGA Libero SoC 2024.2 安装与工程实战指南
  • C++ std::stringstream 详解
  • HY-Motion 1.0 实战:健身指导、AR 试衣与元宇宙 NPC 驱动
  • ClawX:OpenClaw 可视化桌面客户端,零门槛使用 AI 智能体
  • FAIR plus 机器人全产业链接会:聚焦具身智能与产业链协同
  • OpenAI Whisper 语音转文本实战指南
  • OpenAI Whisper 语音识别工具本地部署与使用
  • Effective Modern C++ 条款 36:必要时指定 std::launch::async 策略
  • C++ 性能分析工具全景与选型指南

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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