气象数据处理

3684 字
18 分钟
气象数据处理

数据处理方法参考#

本文档汇总了气象气候数据分析中常用的数据处理方法,包括 NetCDF 文件读写、xarray 数据集操作和常用统计计算。


一、NetCDF 文件读取#

1.1 使用 netCDF4 库#

from netCDF4 import Dataset, num2date
f = Dataset('sst.mnmean.nc') # 打开文件
sst = f.variables['sst'][:] # 读取变量数组
lon = f.variables['lon'][:] # 读取经度
lat = f.variables['lat'][:] # 读取纬度
time = f.variables['time'] # 获取时间变量对象
time = num2date(time[:], time.units, calendar='standard')
# 将数值时间转为datetime对象

关键函数参数说明:

函数参数说明
Dataset(path)pathNetCDF 文件路径(字符串)
f.variables[name]name变量名,可通过 f.variables.keys() 查看所有变量
num2date(times, units, calendar)times时间数值数组
units时间单位,如 'days since 1800-01-01'
calendar日历类型:'standard', '360_day', 'noleap'

注意事项:

  • [:] 将所有数据加载到内存,大文件可能耗时
  • num2date 返回的是 datetime.datetime 对象列表,可转为 [dt.datetime(x.year,x.month,x.day) for x in time]

1.2 使用 xarray 库(推荐)#

import xarray as xr
ds = xr.open_dataset('sst.mnmean.nc') # 打开数据集
sst = ds['sst'] # 获取 DataArray
lon = ds['lon'] # 坐标自动关联
lat = ds['lat']

xarray 的优势:

  • 自动管理维度坐标,数据自带经纬度和时间标签
  • 支持标签索引(.sel(), .loc[], .isel()
  • 支持分组运算(.groupby()
  • 惰性加载,节省内存

二、数据选取与切片#

2.1 xarray 时间选取#

# 按时间范围选取(字符串切片)
sst = ds['sst'].sel(time=slice('1950', '2020'))
# 按具体日期选取
sst.loc['1997-12-01', :, :] # 选取某一时刻
# 按时间范围选取(等间距切片)
nino34.loc['1950-12-01':'2019-12-01':12] # 每隔12步取一次

2.2 xarray 空间选取#

# 按经纬度范围选取
nino34 = ds.sst.loc[:, 5:-5, 190:240] # lat: 5S~5N, lon: 190~240
TNA = ds.sst.loc[:, 25:5, 290:350] # lat: 5N~25N, lon: 290~350
sst_trop = sst.loc[30:-30, 120:300] # 热带区域

2.3 垂直层级选取#

对于四维数据 (time, level, lat, lon),如位势高度场,需先选取特定气压层:

hgt500 = hgt.sel(level=500) # 选择 500 hPa 层
参数说明
level气压层级,常见值:1000, 850, 500, 200, 100 hPa 等

三维数据 (time, lat, lon) 如 SST、T2m 无此维度,跳过此步骤。

2.4 xarray 按月份/季节选取#

# 方法一:使用 time.month 布尔判断
hgt_jan = hgt_range.sel(time=hgt_range['time.month'] == 1)
# 方法二:使用 dt 访问器(推荐)
hgt_jan = hgt_range.sel(time=hgt_range.time.dt.month == 1)
# 方法三:跨月筛选(如冬季 12, 1, 2 月)
import numpy as np
SEASON = [12, 1, 2]
months = hgt500['time'].dt.month
winter_mask = np.isin(months, SEASON)
hgt_winter = hgt500.sel(time=winter_mask)
# 方法四:选取夏季并按年求平均
summer_months = [6, 7, 8]
summer_t2m = t2m.sel(time=t2m['time.month'].isin(summer_months))
summer_t2m = summer_t2m.groupby('time.year').mean(dim='time')
方法关键函数说明
['time.month'] == 1直接索引按月份属性做布尔判断
.time.dt.monthdt 访问器更规范的写法,IDE 支持自动补全
np.isin(months, [12,1,2])numpy isin跨月筛选,适合跨年季节

2.5 numpy 切片技巧#

# 等间距抽取(每12个月取一次,从第12个元素开始)
nino34_DJF = (np.array(nino34[11:-11:12]) + # 12月 (索引11开始)
np.array(nino34[12::12]) + # 1月 (索引12开始)
np.array(nino34[13::12])) / 3 # 2月 (索引13开始)

2.6 最近邻格点选取#

选取距离指定经纬度最近的格点值:

z55_20 = hgt500_jan.sel(lat=55, lon=20, method='nearest')
参数说明
lat=55, lon=20目标坐标
method='nearest'选取最近邻格点(还支持 'pad', 'bfill', 'ffill' 等)

2.7 时间插值与多变量对齐#

当多个变量的时间轴不完全一致时,需要对齐:

# 插值到新时间坐标(将 hgt 插值到 index 的时间轴上)
hgt500 = hgt500.interp(time=index_da['time'])
# 按精确时间轴对齐两个 DataArray
hgt_winter, wp_winter = xr.align(hgt_winter, wp_winter, join='exact')
# 取时间交集
start_time = max(wp_da['time'].values[0], hgt500['time'].values[0])
end_time = min(wp_da['time'].values[-1], hgt500['time'].values[-1])
wp_da = wp_da.sel(time=slice(start_time, end_time))
hgt500 = hgt500.sel(time=slice(start_time, end_time))
函数参数说明
.interp(time=...)time目标时间坐标,将数据插值到新时间轴上
xr.align(a, b, join)a, b需要对齐的 DataArray
join='exact'只保留时间完全相同的部分;'outer'取并集,'inner'取交集

三、常用统计计算#

3.1 气候态与距平#

方法一:自定义 annave 函数(xarray)#

def annave(var):
var = var.groupby('time.month') # 按月份分组
var_clim = var.mean(dim='time') # 逐月气候态平均
var_anom = var - var_clim # 距平 = 原始值 - 气候态
return var_anom, var_clim
步骤函数/方法参数说明
分组var.groupby('time.month')'time.month'按月份分组(1~12月)
均值.mean(dim='time')dim='time'沿时间维求均值,得到12个月的气候态
距平var - var_climxarray 自动按月份广播匹配

方法二:使用 sacpy 库#

import sacpy as scp
sst = scp.get_anom(sst, method=1)
参数说明
sst输入 DataArray(维度为 time × lat × lon)
method=1方法1:逐月去气候态(去季节循环)

3.2 区域性平均(指数计算)#

# Nino3.4 指数:5°S–5°N, 170°W–120°W (即 190°E–240°E)
nino34 = sst.loc[:, 5:-5, 190:240].mean(dim=['lat', 'lon'])
# Nino1+2 指数:0°–10°S, 90°W–80°W (即 270°E–280°E)
nino12 = sst.loc[:, 10:0, 270:280].mean(dim=['lat', 'lon'])
# TNA 指数(热带北大西洋):5°N–25°N, 70°W–10°W (即 290°E–350°E)
TNA = sst.loc[:, 25:5, 290:350].mean(dim=['lat', 'lon'])

.mean() 参数说明:

参数说明
dim=['lat', 'lon']沿经度和纬度方向求平均,得到一维时间序列

3.3 气候态极值#

clim_mean = nino34.groupby('time.month').mean('time') # 逐月气候态均值
clim_max = nino34.groupby('time.month').max('time') # 逐月最大值
clim_min = nino34.groupby('time.month').min('time') # 逐月最小值
yerr_upper = clim_max - clim_mean # 误差条上限
yerr_lower = clim_mean - clim_min # 误差条下限

3.4 季节平均(处理跨年问题)#

冬季 DJF(12月、1月、2月)跨越两个年份,不能用 groupby('time.season') 简单处理。正确做法:

# 方法一:手动索引(推荐,最清晰)
nino34_DJF = (np.array(nino34.loc['1950-12-01':'2019-12-01':12]) +
np.array(nino34.loc['1951-01-01':'2020-01-01':12]) +
np.array(nino34.loc['1951-02-01':'2020-02-01':12])) / 3
# 方法二:按位置索引(需假设数据从1月开始,无缺测)
nino34_DJF = (np.array(nino34[11:-11:12]) + # 12月
np.array(nino34[12::12]) + # 1月
np.array(nino34[13::12])) / 3 # 2月
# 方法三:混合 groupby(取不同年份的月份再平均)
nino34_DJF_2 = (np.array(nino34.loc['1950':'2019'].groupby('time.month')[12]) +
np.array(nino34.loc['1951':'2020'].groupby('time.month')[1]) +
np.array(nino34.loc['1951':'2020'].groupby('time.month')[2])) / 3

注意 方法二对索引位置有严格要求,方法一和方法三更稳健。

3.5 时间平均#

# xarray 方式
sst_mean = sst.mean(dim='time')
# numpy 方式
sst = np.mean(sst, axis=0) # 沿第0轴(时间)求均值

3.6 纬度加权(EOF 面积权重)#

地球是球面,高纬度的格点面积更小,EOF 分析需进行面积加权:

coslat = np.cos(np.deg2rad(lat)).to_numpy()
wgts = np.sqrt(coslat)[..., np.newaxis] # 权重矩阵 (lat, 1)
solver = Eof(ssta, weights=wgts) # 传入权重
步骤说明
np.deg2rad(lat)纬度转弧度
np.cos(...)计算余弦权重
np.sqrt(...)开方(因为 EOF 会对变量本身做内积)
[..., np.newaxis]增加一维以便广播 (lat) → (lat, 1)

3.7 纬向平均#

仅沿经度方向求平均,常用于分析物理量的南北分布:

zonal_mean = hgt.mean(dim='lon') # 结果维度 (time, lat)

3.8 标准化 (z-score)#

将时间序列减去均值后除以标准差,使序列均值为 0、标准差为 1:

index_std = (index - index.mean(dim='time')) / index.std(dim='time')
步骤说明
index.mean(dim='time')时间均值
index.std(dim='time')时间标准差
(x - mean) / std标准化公式

3.9 DataArray 四则运算#

xarray 的 DataArray 之间可以直接进行加减乘除,维度自动对齐:

# 组合指数计算(如 EU 遥相关指数)
EU_index = -0.25 * z55_20 + 0.50 * z55_75 - 0.25 * z40_145
# 距平也可以直接用减法
hgt_anom = hgt - hgt.mean(dim='time')
# 纬向偏差
zonal_anom = hgt - hgt.mean(dim='lon')

四、线性回归#

4.1 手动实现(最小二乘法)#

import numpy as np
def LinearRegression(var, ts):
s = var.shape # (time, lat, lon)
var = np.array(var).reshape(s[0], -1) # → (time, npts)
mask = np.isnan(np.mean(var, axis=0)) # 掩膜 NaN 点位
X = np.vstack((ts, np.ones(len(ts)))).T # 设计矩阵 (time, 2)
r, *res = np.linalg.lstsq(X, var[:, ~mask], rcond=None) # 最小二乘
reg_map = np.zeros((2, s[1] * s[2])) * np.nan
reg_map[:, ~mask] = r # 结果回填
reg_map = np.reshape(reg_map, (2, s[1], s[2])) # → (2, lat, lon)
return reg_map # [0]=斜率, [1]=截距

核心函数 np.linalg.lstsq 参数说明:

参数说明
X设计矩阵,形状 (n_samples, n_features),第一列为指数,第二列为全1
var[:, ~mask]目标变量(已去除 NaN 格点),形状 (n_samples, n_points)
rcond=None奇异值截断阈值,None 使用默认机器精度

返回值: r[0, :] 为回归系数(斜率),r[1, :] 为截距。

注意:线性回归 Y = aX + bX 的第一列为 tsr[0] 是斜率 a

4.2 使用 sacpy 库#

import sacpy as scp
LinReg = scp.LinReg(nino34, sst)
slope = LinReg.slope # 回归系数场
p_value = LinReg.p_value # p 值场

4.3 多元线性回归#

X = np.vstack((nino34, TNA, np.ones(len(nino34)))).T # 两个自变量
r, *res = np.linalg.lstsq(X, y[:, ~mask], rcond=None)
# r[0] = Nino34 的回归系数, r[1] = TNA 的回归系数, r[2] = 截距

4.4 使用 scipy.stats.linregress 逐点回归#

linalg.lstsq 更简洁,一步返回斜率、截距、相关系数、p 值、标准误:

from scipy import stats
def linregress_1d(y, x):
a, b, r, p, std = stats.linregress(x, y)
return np.array([a, b, r, p, std])
返回值说明
a回归系数(斜率)
b截距
r相关系数
pp 值(双尾)
std斜率的标准误差

4.5 使用 xr.apply_ufunc 逐格点批量回归#

apply_ufunc 是 xarray 推荐的逐格点应用函数方式,支持 dask 并行:

reg = xr.apply_ufunc(
linregress_1d,
hgt_anom, index_da,
input_core_dims=[['time'], ['time']], # 沿 time 维做回归
output_core_dims=[['stat']], # 输出新维度 stat
vectorize=True, # 逐格点向量化
dask='parallelized', # dask 并行(可选)
output_dtypes=[float],
output_sizes={'stat': 5}, # stat 维度长度=5
)
beta = reg.sel(stat=0) # 回归系数(斜率)
corr = reg.sel(stat=2) # 相关系数
pval = reg.sel(stat=3) # p 值
sig = pval < 0.05 # 显著性检验
参数说明
linregress_1d自定义的格点回归函数
hgt_anom输入场 (time, lat, lon)
index_da回归指数 (time,)
input_core_dims指定沿哪些维度做回归,[['time'], ['time']] 表示第一个参数 hgt_anom 沿 time,第二个参数 index 也沿 time
output_core_dims输出新维度名
vectorize=True自动循环 lat, lon 维度
output_sizes新维度的长度

五、合成分析与显著性检验#

5.1 合成分析(Composite Analysis)#

from scipy import stats
el_nino_years = [1983, 1987, 1992, 1995, 1998, 2010, 2016]
la_nina_years = [1985, 1989, 1999, 2000, 2008, 2011]
# 提取夏季数据
summer_months = [6, 7, 8]
summer_t2m = t2m.sel(time=t2m['time.month'].isin(summer_months)) \
.groupby('time.year').mean(dim='time')
# 挑选年份并计算合成平均
el_nino_composite = summer_t2m.sel(year=el_nino_years).mean(dim='year')
la_nina_composite = summer_t2m.sel(year=la_nina_years).mean(dim='year')

5.2 单样本 t 检验 — stats.ttest_1samp#

检验合成距平是否显著偏离 0(零假设:总体均值为 0):

from scipy import stats
_, p_values = stats.ttest_1samp(composite_data, popmean=0.0, axis=0)
alpha = 0.05
significant = p_values < alpha

stats.ttest_1samp 参数说明:

参数说明
composite_data样本数据,形状 (n_samples, lat, lon)
popmean零假设的总体均值,检验距平时通常设为 0.0
axis沿哪个轴做检验,axis=0 表示在第一个维度(样本)上检验

返回值:

返回值类型说明
statisticndarrayt 统计量
pvaluendarrayp 值(双尾),形状与输入除去 axis 一致

5.3 双样本 t 检验 — stats.ttest_ind#

比较两组样本的差异是否显著(如 El Nino vs La Nina 合成场):

_, p_values = stats.ttest_ind(group1, group2, axis=0)
参数说明
group1第一组样本,形状 (n1, lat, lon)
group2第二组样本,形状 (n2, lat, lon)
axis沿哪个维度做检验

5.4 显著性可视化(地图打点)#

lon2d, lat2d = np.meshgrid(lon, lat)
ax.scatter(lon2d[significant], lat2d[significant],
color='grey', s=2, transform=ccrs.PlateCarree())

六、数据重塑与掩膜#

6.1 reshape 二维数据#

sst = sst.to_numpy()
y = np.reshape(sst, (sst.shape[0], -1)) # (time, lat*lon)
# 回归计算后...
map = np.reshape(map, (sst.shape[1], sst.shape[2])) # 恢复为 (lat, lon)

6.2 NaN 掩膜#

mask = np.isnan(np.mean(y, axis=0)) # 找出含 NaN 的格点
r, *res = np.linalg.lstsq(X, y[:, ~mask], rcond=None) # 只对有效格点回归
# 创建全 NaN 数组,回填计算结果
reg_map = np.zeros((2, y.shape[1])) * np.nan
reg_map[:, ~mask] = r

6.3 条件掩膜(显著性过滤)#

# 将不显著的格点设为 NaN(绘图时不显示)
sst_tmp = np.where(map_sst.p_value >= 0.05, np.nan, map_sst.slope)
uwnd_tmp = np.where(map_uwnd.p_value >= 0.10, np.nan, map_uwnd.slope)
参数说明
condition条件表达式
np.nan满足条件时的替换值
map_sst.slope不满足条件时的原值

6.4 xarray 转 numpy 及回构#

# xarray → numpy
sst_np = sst.to_numpy()
# numpy → xarray(保留原维度坐标)
sst_xr = xr.DataArray(map, dims=sst.dims, coords=sst.coords)

七、pandas 日期生成#

from pandas import date_range
time_year = pd.date_range(start='1951-01-01', end='2020-01-01', freq='YS')
参数说明
start起始日期
end结束日期
freq='YS'频率:'YS'=年初, 'MS'=月初, 'D'=每日 等

八、EOF 分析#

8.1 使用 eofs 库#

from eofs.standard import Eof
solver = Eof(ssta, weights=wgts) # 初始化
pt = solver.eofsAsCovariance(neofs=4) # 空间模态(协方差)
pc = solver.pcs(npcs=4, pcscaling=1) # 时间序列 PC
pcvar = solver.varianceFraction(neigs=4) * 100 # 解释方差百分比(%)
函数/方法参数说明
Eof(data, weights)data输入距平场 (time, lat, lon)
weights纬度权重,形状 (lat, 1)
eofsAsCovariance(neofs)neofs返回前 N 个模态(协方差形式)
pcs(npcs, pcscaling)npcs返回前 N 个 PC
pcscaling=1PC 标准差归一化
varianceFraction(neigs)neigs前 N 个模态的解释方差占比

8.2 使用 sacpy 库#

import sacpy as scp
eof = scp.EOF(ssta)
eof.solve()
pc = -eof.get_pc(npt=3) # PC(取负号可翻转符号,因人而异)
pt = -eof.get_pt(npt=3) # EOF 空间模态
pcvar = eof.get_varperc(npt=3) * 100 # 解释方差百分比(%)

九、时间处理与筛选#

import datetime as dt
# 时间范围筛选(netCDF4 方式)
def sel_time(year1, year2, time, dat):
start_time = dt.datetime(year1, 1, 1)
end_time = dt.datetime(year2, 12, 31)
time_mask = (np.array(time) >= start_time) & (np.array(time) <= end_time)
dat = dat[time_mask, :, :]
time_dat = np.array(time)[time_mask]
return time_dat, dat

温度单位转换#

dat = dat - 273.15 # 开尔文 → 摄氏度

风场强度计算#

wind_speed = np.sqrt(uwnd**2 + vwnd**2)

十、sacpy 库使用汇总#

sacpy 是复旦大学大气科学研究院开发的气象专用 Python 工具包,封装了距平计算、线性回归、EOF 分析等常用功能。

10.1 安装#

Terminal window
pip install sacpy

10.2 核心功能一览#

模块/类功能对应章节
scp.get_anom()计算距平(去气候态)3.1
scp.LinReg()线性回归(同步输出斜率+p值)4.2
scp.EOF()EOF 分析(支持纬度加权)8.2
sacpy.Map地图绘图增强(scontourf, squiver, sig_plot 等)cartopy.md / matplotlib.md

10.3 距平计算 — scp.get_anom#

import sacpy as scp
ssta = scp.get_anom(sst, method=1)
参数说明
sstxarray DataArray,维度 (time, lat, lon)
method=1方法1:逐月去气候态(减去各月多年平均),详见 3.1 节

10.4 线性回归 — scp.LinReg#

LinReg = scp.LinReg(nino34, sst)
slope = LinReg.slope # 回归系数场 (lat, lon)
p_value = LinReg.p_value # p 值场 (lat, lon)

对比手动回归:

特点手动 np.linalg.lstsqsacpy.scp.LinReg
同步输出斜率+p值需手算自动提供
NaN 自动处理需手动掩膜自动处理
使用复杂度

详细用法见 4.2 节。

10.5 EOF 分析 — scp.EOF#

eof = scp.EOF(ssta) # 初始化
eof.solve() # 求解
pc = eof.get_pc(npt=3) # 获取 PC (npt 个模态)
pt = eof.get_pt(npt=3) # 获取特征向量(空间模态)
pcvar = eof.get_varperc(npt=3) * 100 # 解释方差百分比(%)
方法参数说明
get_pc(npt)npt返回前 npt 个 PC,形状 (npt, time)
get_pt(npt)npt返回前 npt 个空间模态,形状 (npt, lat, lon)
get_varperc(npt)npt返回前 npt 个模态的方差占比(小数,需 ×100 得百分比)

注意:PC 和 pattern 的符号可同时取反而不影响物理意义,这取决于求解器内部约定。详细用法见 8.2 节。

10.6 sacpy vs 手动实现 对比#

功能手动实现sacpy
距平计算groupby('time.month').mean() → 相减scp.get_anom(data, method=1)
线性回归np.linalg.lstsq + 手算p值scp.LinReg(index, field)
EOF分析Eof(data, weights) 逐步骤操作scp.EOF(data).solve()

快速参考速查表#

需求代码
打开 NC 文件xr.open_dataset(path)Dataset(path)
时间切片ds.sel(time=slice('1950','2020'))
空间切片ds.loc[:, lat_min:lat_max, lon_min:lon_max]
垂直层级选取hgt.sel(level=500)
最近邻格点sel(lat=55, lon=20, method='nearest')
月份筛选 (dt)sel(time=data.time.dt.month == 1)
跨月筛选np.isin(data.time.dt.month, [12,1,2])
时间插值.interp(time=target_time)
多变量对齐xr.align(a, b, join='exact')
去气候态 (手动)sst.groupby('time.month').mean() → 相减
去气候态 (sacpy)scp.get_anom(sst, method=1)
区域平均sst.loc[:, ...].mean(dim=['lat','lon'])
纬向平均data.mean(dim='lon')
标准化 (z-score)(x - x.mean()) / x.std()
DataArray 运算-0.25*a + 0.50*b - 0.25*c
线性回归 (手动)np.linalg.lstsq(X, y, rcond=None)
线性回归 (sacpy)LinReg = scp.LinReg(index, field)
逐点回归 (linregress)stats.linregress(x, y) 返回 (a,b,r,p,std)
逐点回归 (apply_ufunc)xr.apply_ufunc(func, data, index, input_core_dims=[['time'],['time']], vectorize=True)
EOF 分析 (eofs)Eof(ssta, weights=wgts)eofsAsCovariance()
EOF 分析 (sacpy)scp.EOF(ssta).solve().get_pc() / .get_pt()
单样本 t 检验stats.ttest_1samp(data, popmean=0.0, axis=0)
双样本 t 检验stats.ttest_ind(group1, group2, axis=0)
数据重塑np.reshape(arr, (time, -1))
NaN 掩膜np.isnan(np.mean(y, axis=0))
条件替换np.where(condition, nan_val, orig_val)
日期范围pd.date_range(start, end, freq='YS')

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

气象数据处理
http://liuhuangzao.top/posts/python/数据处理/
作者
硫磺皂
发布于
2026-07-15
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
硫磺皂
干嘛
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
分类
标签
站点统计
文章
8
分类
2
标签
5
总字数
13,914
运行时长
0
最后活动
0 天前
站点信息
构建平台
Local
博客版本
Firefly v6.13.10
文章许可
CC BY-NC-SA 4.0