Python | SST | 高通滤波 | EOF | 论文复现

文摘   2024-08-20 08:00   北京  

前言

最近有粉丝在后台私信问我:"空间场,海温场这些如何滤波?","一直以为时间序列才需要滤波"。很久之前做过相关的工作,今天就分享给大家。

高通滤波

今天我们以高通滤波举例,高通滤波(High-pass filtering) 是一种信号处理技术,用于移除信号中频率较低的成分(即长时间尺度的波动),保留频率较高的成分(即短时间尺度的波动)。

对于海温等变量,存在陆地掩膜或者缺测值填充的情况,我们今天就以sst变量举例,对三维变量场的高通滤波的代码如下:

def high_pass_filter(data: xr.DataArray | np.array,
                     cutoff: np.array
                     )
 -> np.array:

    """
    Apply a high-pass filter to remove frequencies lower than the cutoff.
    
    Parameters:
    - data: Input data array (time, lat, lon)
    - cutoff: Cutoff period in years
    
    Returns:
    - Filtered data array
    """

    if isinstance(data, xr.DataArray):
        data = data.values
    fft_data = fft(data, axis=0)
    freqs = np.fft.fftfreq(data.shape[0])
    high_pass_mask = np.abs(freqs) > 1 / cutoff
    filtered_data = ifft(fft_data * high_pass_mask[:, NoneNone], axis=0)
    return np.real(filtered_data)

对于以上代码的检验,我们今天以论文复现的方式来检验,请大家继续往下看。

论文复现

今天复现的论文是《秋季北大西洋马蹄型海温异常与初冬我国气温年际变化的联系》。

该论文主要关注秋季北大西洋马蹄型海温异常冬季NAO对流层亚洲极涡等大气环流异常的影响。

论文中的图(1)如下:

1951~2020 年秋季北大西洋海温异常年际变率 EOF 分析第一模态(a)空间分布(b)时间系数

该图是我们今天需要复现的图,接下来我们来看数据在论文中的如何处理的:

  • 英国气象局哈德莱中心(The UK Meteorological Office Hadley Center)提供的月平均海表温度(sea surface temperature,SST)资料(Rayner et al., 2003),水平分辨率为 1°×1°,时间段为1951~2020 年,数据下载地址:https://www.metoffice.gov.uk/hadobs/hadisst/.
  • 文中秋季指的是 9 月、10 月和 11 月三个月平均,初冬指的是 12 月。
  • 研究主要关注年际变率,因此所有资料均去除 9 年以上的年代际变率线性趋势

有了以上处理信息,那么就很简单了。从文中的图片风格来看,作者使用了NCL来绘图,下面我们使用Python来复现这张图。

Python代码复现

前期准备

import numpy as np
import xarray as xr
from eofs.standard import Eof
from matplotlib import pyplot as plt
import cartopy.mpl.ticker as cticker
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import os 
from scipy.fftpack import fft, ifft

def high_pass_filter(data: xr.DataArray | xr.Dataset,
                     cutoff: np.array
                     )
 -> np.array:

    """
    Apply a high-pass filter to remove frequencies lower than the cutoff.
    
    Parameters:
    - data: Input data array (time, lat, lon)
    - cutoff: Cutoff period in years
    
    Returns:
    - Filtered data array
    """

    if isinstance(data, xr.DataArray):
        data = data.values
    fft_data = fft(data, axis=0)
    freqs = np.fft.fftfreq(data.shape[0])
    high_pass_mask = np.abs(freqs) > 1 / cutoff
    filtered_data = ifft(fft_data * high_pass_mask[:, NoneNone], axis=0)
    return np.real(filtered_data)

读取数据

Extend = [-80,0,10,70]
Lat_Range = slice(7010)
Lon_Range = slice(-800)
d = xr.open_dataset('HadISST_sst.nc').sel(latitude = Lat_Range, longitude = Lon_Range, time = slice("1951""2020"))
lat = d.latitude
lon = d.longitude
sst = d.sst

9年高通滤波

cutoff_period = 9 * 12
sst = high_pass_filter(sst, cutoff_period)

去除季节循环和去除全球趋势

去除季节循环是为了:
  • 去除季节性影响,突出长期变化
  • 避免季节性变化主导 EOF 模式
  • 降低噪声,提高鲁棒性
sst = xr.DataArray(sst,dims=['time','latitude','longitude'], coords=[d.time, lat, lon])
Meansst= sst.groupby("time.month").mean()
ssta = (sst.groupby("time.month") - Meansst).where(lambda m: m.time.dt.month.isin([91011])).groupby('time.year').mean("time")
ssta = ssta - ssta.weighted(np.cos(np.deg2rad(lat))).mean(('latitude','longitude'))

EOF

lat = np.array(lat)
coslat = np.cos(np.deg2rad(lat))
wgts = np.sqrt(coslat)[..., np.newaxis]
ssta = np.array(ssta)
solver = Eof(ssta, weights=wgts,center = True)
eof1 = solver.eofsAsCorrelation().squeeze() 
pc1 = solver.pcs(pcscaling=1).squeeze()
var = solver.varianceFraction()[0]
pc1 = pc1[:,0]

绘图

plt.figure(figsize=(10,4),facecolor = 'w',dpi = 300)
plt.subplots_adjust(left=0.05, right=0.98, top=0.9, bottom=0.1, wspace=0.1)
proj = ccrs.PlateCarree()
leftlon, rightlon, lowerlat, upperlat = (Extend[0],Extend[1],Extend[2],Extend[3])
lon_formatter = cticker.LongitudeFormatter()
lat_formatter = cticker.LatitudeFormatter()

ax = plt.subplot(121, projection=ccrs.PlateCarree())

ax.set_extent([leftlon, rightlon, lowerlat, upperlat], crs=ccrs.PlateCarree())
ax.add_feature(cfeature.COASTLINE.with_scale('50m'))
ax.add_feature(cfeature.LAKES, alpha=0.5)
ax.set_xticks(np.arange(leftlon,rightlon+10,20), crs=ccrs.PlateCarree())
ax.set_yticks(np.arange(lowerlat,upperlat+10,20), crs=ccrs.PlateCarree())
ax.xaxis.set_major_formatter(lon_formatter)
ax.yaxis.set_major_formatter(lat_formatter)
C = plt.contourf(lon,lat, eof1[0,:,:], levels=np.arange(-1,1.01,0.05), transform=ccrs.PlateCarree(), cmap=plt.cm.RdBu_r)
Cf = plt.contour(lon,lat, eof1[0,:,:], levels=np.arange(-1,1.01,0.2),
            colors = 'k',transform=ccrs.PlateCarree())
ax.coastlines(color="#888888",lw = 2)
plt.title('(a) EOF1',loc='left',fontsize =15)
plt.title( '%.2f%%' % (var*100),loc='right',fontsize =15)

cax = plt.axes([0.06, -0.02, 0.42, 0.03])
plt.colorbar(C,  cax =cax, orientation="horizontal", ticks = [-1, -0.5, 0, 0.5, 1])

ax2 = plt.subplot(122)

plt.title('(b) PC1',loc='left',fontsize =15)

rects2 = plt.bar(np.arange(1951,2021),np.where(pc1>0,pc1,np.nan),color='red',
                    width = 1,edgecolor='k'
                   )
rects3 = plt.bar(np.arange(1951,2021),np.where(pc1<0,pc1,np.nan),color='blue',
                    width = 1,edgecolor='k'
                   )
plt.xlim(1950, 2020)
plt.show()

Output

1951~2020 年秋季北大西洋海温异常年际变率 EOF 分析第一模态(a)空间分布(b)时间系数
论文原图:1951~2020 年秋季北大西洋海温异常年际变率 EOF 分析第一模态(a)空间分布(b)时间系数

第一模态贡献率为20.72% ,论文中则是 20.5% ,可以说是相当接近了。

完整代码

import numpy as np
import xarray as xr
from eofs.standard import Eof
from matplotlib import pyplot as plt
import cartopy.mpl.ticker as cticker
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from scipy.fftpack import fft, ifft

def high_pass_filter(data: xr.DataArray | np.array,
                     cutoff: np.array
                     )
 -> np.array:

    """
    Apply a high-pass filter to remove frequencies lower than the cutoff.
    
    Parameters:
    - data: Input data array (time, lat, lon)
    - cutoff: Cutoff period in years
    
    Returns:
    - Filtered data array
    """

    if isinstance(data, xr.DataArray):
        data = data.values
    fft_data = fft(data, axis=0)
    freqs = np.fft.fftfreq(data.shape[0])
    high_pass_mask = np.abs(freqs) > 1 / cutoff
    filtered_data = ifft(fft_data * high_pass_mask[:, NoneNone], axis=0)
    return np.real(filtered_data)


Extend = [-80,0,10,70]
Lat_Range = slice(7010)
Lon_Range = slice(-800)
d = xr.open_dataset('HadISST_sst.nc').sel(latitude = Lat_Range, longitude = Lon_Range, time = slice("1951""2020"))

lat = d.latitude
lon = d.longitude
sst = d.sst

# 9年高通滤波
cutoff_period = 9 * 12
sst = high_pass_filter(sst, cutoff_period)

# 去季节循环
sst = xr.DataArray(sst,dims=['time','latitude','longitude'], coords=[d.time, lat, lon])
Meansst= sst.groupby("time.month").mean()
ssta = (sst.groupby("time.month") - Meansst).where(lambda m: m.time.dt.month.isin([91011])).groupby('time.year').mean("time")
ssta = ssta - ssta.weighted(np.cos(np.deg2rad(lat))).mean(('latitude','longitude'))

# EOF
lat = np.array(lat)
coslat = np.cos(np.deg2rad(lat))
wgts = np.sqrt(coslat)[..., np.newaxis]
ssta = np.array(ssta)
solver = Eof(ssta, weights=wgts,center = True)
eof1 = solver.eofsAsCorrelation().squeeze() 
pc1 = solver.pcs(pcscaling=1).squeeze()
var = solver.varianceFraction()[0]
pc1 = pc1[:,0]

plt.figure(figsize=(10,4),facecolor = 'w',dpi = 300)
plt.subplots_adjust(left=0.05, right=0.98, top=0.9, bottom=0.1, wspace=0.1)
proj = ccrs.PlateCarree()
leftlon, rightlon, lowerlat, upperlat = (Extend[0],Extend[1],Extend[2],Extend[3])
lon_formatter = cticker.LongitudeFormatter()
lat_formatter = cticker.LatitudeFormatter()

ax = plt.subplot(121, projection=ccrs.PlateCarree())

ax.set_extent([leftlon, rightlon, lowerlat, upperlat], crs=ccrs.PlateCarree())
ax.add_feature(cfeature.COASTLINE.with_scale('50m'))
ax.add_feature(cfeature.LAKES, alpha=0.5)
ax.set_xticks(np.arange(leftlon,rightlon+10,20), crs=ccrs.PlateCarree())
ax.set_yticks(np.arange(lowerlat,upperlat+10,20), crs=ccrs.PlateCarree())
ax.xaxis.set_major_formatter(lon_formatter)
ax.yaxis.set_major_formatter(lat_formatter)
C = plt.contourf(lon,lat, eof1[0,:,:], levels=np.arange(-1,1.01,0.05), transform=ccrs.PlateCarree(), cmap=plt.cm.RdBu_r)
Cf = plt.contour(lon,lat, eof1[0,:,:], levels=np.arange(-1,1.01,0.2),
            colors = 'k',transform=ccrs.PlateCarree())
ax.coastlines(color="#888888",lw = 2)
plt.title('(a) EOF1',loc='left',fontsize =15)
plt.title( '%.2f%%' % (var*100),loc='right',fontsize =15)

cax = plt.axes([0.06-0.020.420.03])
plt.colorbar(C,  cax =cax, orientation="horizontal", ticks = [-1-0.500.51])

ax2 = plt.subplot(122)

plt.title('(b) PC1',loc='left',fontsize =15)

rects2 = plt.bar(np.arange(1951,2021),np.where(pc1>0,pc1,np.nan),color='red',
                    width = 1,edgecolor='k'
                   )
rects3 = plt.bar(np.arange(1951,2021),np.where(pc1<0,pc1,np.nan),color='blue',
                    width = 1,edgecolor='k'
                   )
plt.xlim(19502020)
plt.show()

References

  • 李忠贤, 王庭轩, 曾刚, 等. 2024. 秋季北大西洋马蹄型海温异常与初冬我国气温年际变化的联系 [J]. 大气科学, 48(3): 1131−1143. LI Zhongxian, WANG Tingxuan, ZENG Gang, et al. 2024. Characteristics of North Atlantic Horseshoe Sea Surface Temperature Anomaly in Autumn and Relationship with Interannual Variation in Early Winter Temperature in China [J]. Chinese Journal of Atmospheric Sciences (in Chinese), 48(3): 1131−1143. doi:10.3878/j.issn.1006-9895.2209.22106

往期回顾

·投稿或转载请联系·
小编微信
长按可关注


气python风雨
主要发一些涉及大气科学的Python文章与个人学习备忘录
 最新文章