气象数据处理
数据处理方法参考
本文档汇总了气象气候数据分析中常用的数据处理方法,包括 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) | path | NetCDF 文件路径(字符串) |
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'] # 获取 DataArraylon = 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~240TNA = ds.sst.loc[:, 25:5, 290:350] # lat: 5N~25N, lon: 290~350sst_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 npSEASON = [12, 1, 2]months = hgt500['time'].dt.monthwinter_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.month | dt 访问器 | 更规范的写法,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'])
# 按精确时间轴对齐两个 DataArrayhgt_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_clim | — | xarray 自动按月份广播匹配 |
方法二:使用 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 + b,X的第一列为ts,r[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 | 相关系数 |
p | p 值(双尾) |
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.05significant = p_values < alphastats.ttest_1samp 参数说明:
| 参数 | 说明 |
|---|---|
composite_data | 样本数据,形状 (n_samples, lat, lon) |
popmean | 零假设的总体均值,检验距平时通常设为 0.0 |
axis | 沿哪个轴做检验,axis=0 表示在第一个维度(样本)上检验 |
返回值:
| 返回值 | 类型 | 说明 |
|---|---|---|
statistic | ndarray | t 统计量 |
pvalue | ndarray | p 值(双尾),形状与输入除去 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.nanreg_map[:, ~mask] = r6.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 → numpysst_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) # 时间序列 PCpcvar = 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=1 | PC 标准差归一化 | |
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 安装
pip install sacpy10.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)| 参数 | 说明 |
|---|---|
sst | xarray 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.lstsq | sacpy.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') |
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!



