Python | 计算可降水量

文摘   2024-09-19 18:55   广东  

写在前面

在进行一些评估工作时,需要用到可降水量这个数据,Cmip6的模式中是可以输出的,即prw这个变量,定义为

Water Vapor Path [kg m-2]

但是,貌似ERA5-monthly数据中是没有直接叫可降水量的数据,以下列出了相关单位为[kg  ]的变量

搜了一下相关资料,感觉只有 TCWV  (total column of water vapour), 整层气柱水汽总量;TCW (total column of water), 整层气柱水含量; VIWV  (vertical integral of water vapour), 水汽垂直积分比较相关,其中,变量 TCWV (水汽总量)和 VIWV (水汽垂直积分)基本相同,但由不同的软件生成。

在ERA5-monthly-single level上是可以下载total column of water vapour数据以及比湿(specific_humidity)数据的,而比湿是可以计算得到可降水量的;所以,这里通过手动从比湿计算得到可降水量数据,与TCWV数据和cmip6中的一些模式数据进行对比,来验证手动计算可降水量的结果。

比湿计算可降水量公式

可降水量,定义为垂直积分的比湿(q),主要通过

  • 转换气压层单位为pa
  • 使用梯形积分计算整层水汽积分

其中,单位换算如下:

  • q 是比湿(单位为 kg kg
  • 是地表气压
  • 是顶层气压
  • g为重力加速度,单位:m/s

其中,pa单位可以换算为

= =

读取数据

先简单读取比湿数据,并转换经度从-180~180到0~360,将纬度从南到北排序

import xarray as xr
import numpy  as np
import cartopy.crs as ccrs
import matplotlib.pyplot as plt

sph = xr.open_dataset("I://sph.monthly.2010_2019.nc").q
def convert_lon(ds,lons='longitude',lats='latitude'):
    ds = ds.sortby(lats)
    lon_name = lons  #你的nc文件中经度的命名
    ds['longitude_adjusted'] = xr.where(ds[lon_name] < 0, ds[lon_name]%360,\
                        ds[lon_name])
    ds = (
    ds
    .swap_dims({lon_name: 'longitude_adjusted'})
    .sel(**{'longitude_adjusted': sorted(ds.longitude_adjusted)})
    .drop_vars(lon_name))
    ds = ds.rename({'longitude_adjusted': lon_name})

    return ds

sph_convert = convert_lon(sph)
sph_convert

  • 空间插值

为了方便后续计算效率,将数据插值到2x2分辨率,并看一下插值的空间分布图

sph_interp = sph_convert.sel(level=slice(100,1000)).interp(longitude=np.arange(0,360,2),latitude=np.arange(-40,42,2))
sph_interp 
fig, ax = plt.subplots(subplot_kw={'projection': ccrs.PlateCarree(180)}, dpi=200,figsize=(86))
contour = sph_interp.sel(level=850)[0].plot.contourf(ax=ax, levels=11, cmap='RdBu_r', extend='both', add_colorbar=False,
                                              transform=ccrs.PlateCarree())
ax.coastlines()
gl = ax.gridlines(draw_labels=True, linestyle='--')  # 添加经纬度网格并显示标签
gl.right_labels = False  # 关闭右侧标签
gl.top_labels = False    # 关闭顶部标签

# 添加横轴方向的colorbar
cbar = fig.colorbar(contour, ax=ax, orientation='horizontal', pad=0.1, shrink=0.9,aspect=40)
cbar.set_label('kg kg**-1')  
plt.show()

下面提供两种计算方法, 一种是xarray的,一种是numpy的,双重验证

xarray

def xarray_intergrated(sph):

    pressure = (sph.level * 100# 转换为 Pa
    print(pressure)
    # 定义重力加速度常数
    g = 9.81  # m/s²

    # 计算dp(差分),并取绝对值
    dp = pressure.diff(dim='level')
   
    # 计算垂直积分 (Column-integrated specific humidity)
    column_integrated_sph = (sph * dp).sum(dim='level') / g

    # 输出结果,单位为 kg/m²
    print(column_integrated_sph.min(), column_integrated_sph.max())
    return column_integrated_sph
sph_trap = xarray_intergrated(sph_interp)
sph_trap

简单绘图看看一下空间分布


fig, ax = plt.subplots(subplot_kw={'projection': ccrs.PlateCarree(180)}, dpi=200,figsize=(86))

contour = sph_trap[0].plot.contourf(ax=ax, levels=np.linspace(20,70,51), cmap='RdBu_r', extend='both', add_colorbar=False,
                                              transform=ccrs.PlateCarree())
ax.coastlines()
gl = ax.gridlines(draw_labels=True, linestyle='--')  # 添加经纬度网格并显示标签
gl.right_labels = False  # 关闭右侧标签
gl.top_labels = False    # 关闭顶部标签
ax.set_aspect(2)
# 添加横轴方向的colorbar
cbar = fig.colorbar(contour, ax=ax, orientation='horizontal', pad=0.1, shrink=0.9,aspect=40)
cbar.set_label('kg kg**-1')  

plt.show()
xarray

numpy

def numpy_intergrated(sph_interp):
    
    pressure_levels = sph_interp.level * 100  # 将 millibars 转换为 Pa

    # 重力加速度
    g = 9.81  # m/s²

    # 使用梯形积分法计算整层水汽积分
    sph_trap = np.trapz(sph_interp, pressure_levels, axis=1) / g
    print(sph_trap.min(),sph_trap.max())
    return sph_trap

sph_trap_numpy = numpy_intergrated(sph_interp)    

同样看一下空间分布

fig, ax = plt.subplots(subplot_kw={'projection': ccrs.PlateCarree(180)}, dpi=200,figsize=(86))

contour = ax.contourf(sph_trap_numpy[0], 
                      levels=np.linspace(20,70,51), 
                      
                      cmap='RdBu_r', extend='both'
                                              transform=ccrs.PlateCarree())
ax.coastlines()
gl = ax.gridlines(draw_labels=True, linestyle='--')  # 添加经纬度网格并显示标签
gl.right_labels = False  # 关闭右侧标签
gl.top_labels = False    # 关闭顶部标签
ax.set_aspect(2)
# 添加横轴方向的colorbar
cbar = fig.colorbar(contour, ax=ax, orientation='horizontal', pad=0.1, shrink=0.9,aspect=40)
cbar.set_label('kg kg**-1')  

plt.show()
numpy

从纬向平均的情况来看看是否有明显的差异

dataset = [sph_trap[0].mean('latitude'), np.nanmean(sph_trap_numpy[0],axis=0), sph_trap[0].mean('latitude')- np.nanmean(sph_trap_numpy[0],axis=0)]
data_title = ['Prw-Xarray''Prw-Numpy''diff']

colors = ['b''g''r']  

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
fig, ax = plt.subplots(dpi=200, figsize=(106))

# 绘制三个数据的纬向平均值在同一个图上
for i, data in enumerate(dataset):
    line = ax.plot(data, label=data_title[i], color=colors[i])

ax.set_title('纬向平均值')
ax.set_xlabel('经度')
ax.set_ylabel('值')
ax.legend()

plt.show()
两种计算方法的纬向平均结果

可以发现,总体上趋势还是一致的,基于Xarray的计算方法稍微比基于Numpy的结果要大一点,不影响总体结论

与CMIP6的空间patter对比

下面,找了几个CMIP6的prw月平均资料,选择同样的时间,同样的分辨率来看看空间分布

import xarray as xr
import matplotlib.pyplot as plt
import numpy as np
import cartopy.crs as ccrs

model_paths = [
    "I://EC-Earth3_historical_r1i1p1f1_1997-2014_interpolated.nc",
    "I://SAM0-UNICON_historical_r1i1p1f1_1997-2014_interpolated.nc",
    "I://UKESM1-0-LL_historical_r1i1p1f2_1997-2014_interpolated.nc",
]

model_names = ['EC-Earth3''SAM0-UNICON''UKESM1-0-LL']
# 读取所有模型数据
models = [xr.open_dataset(path).prw.sel(time='2010', lat=slice(-4040), lon=slice(0360))[0for path in model_paths]
print(models[0].min(),models[0].max())

fig, axes = plt.subplots(13, subplot_kw={'projection': ccrs.PlateCarree(180)}, dpi=200, figsize=(156))

for i, (model, ax) in enumerate(zip(models, axes)):

    model.plot.contourf(ax=ax, levels=np.linspace(207051), cmap='RdBu_r', extend='both',add_colorbar=False,transform=ccrs.PlateCarree())
    ax.coastlines()
    gl = ax.gridlines(draw_labels=True, linestyle='--')  # 添加经纬度网格并显示标签
    gl.right_labels = False  # 关闭右侧标签
    gl.top_labels = False    # 关闭顶部标签
    ax.set_title(f'{model_names[i]}')
    ax.set_aspect(2)

cbar = fig.colorbar(contour, ax=axes, orientation='horizontal', pad=0.1, shrink=0.9, aspect=60)
cbar.set_label('kg kg**-1')

# plt.show()

感觉和自己算的还是有的类似的,有意思的是对比ERA5数据,可以明显的发现,cmip6这个三个model在陆地上的模拟都存在明显的差别,难以模拟出陆地上的可降水量,在海上赤道中东太平洋的prw信号模拟也较弱。

与ERA5-TCWV的空间分布以及纬向平均结果是比较一致,可以用来与cmip6对比

读取下载的TCWV数据,并选择同样的时间,插值相同的分辨率与计算得到的结果进行对比

era5_path = r'I://download.nc'
era5_tcwv = xr.open_dataset(era5_path).sortby('latitude').tcwv.sel(time='2010'
            latitude=slice(-4042))
era5_tcwv_lon = convert_lon(era5_tcwv).interp(longitude=np.arange(03602),latitude=np.arange(-40422))
era5_tcwv_lon
print(era5_tcwv_lon.min(), era5_tcwv_lon.max())
dataset = [era5_tcwv_lon[0], sph_trap[0], era5_tcwv_lon[0] - sph_trap[0]]
data_title = ['ERA-5-TCWV''ERA-5-SPH''diff']
fig, axes = plt.subplots(13, subplot_kw={'projection': ccrs.PlateCarree(180)}, dpi=200, figsize=(156))

# 绘制前两个子图并共用一个颜色条
for i, (data, ax) in enumerate(zip(dataset[:2], axes[:2])):
    contour = data.plot.contourf(ax=ax,
                                 levels=np.linspace(207051),
                                 cmap='RdBu_r', extend='both', add_colorbar=False, transform=ccrs.PlateCarree())
    ax.coastlines()
    gl = ax.gridlines(draw_labels=True, linestyle='--')  # 添加经纬度网格并显示标签
    gl.right_labels = False  
    gl.top_labels = False    
    ax.set_title(f'{data_title[i]}')
    ax.set_aspect(2)

cbar = fig.colorbar(contour, ax=axes[:2], orientation='horizontal', pad=0.1, shrink=0.9, aspect=60)
cbar.set_label('kg kg**-1')

contour_diff = dataset[2].plot.contourf(ax=axes[2],
                                        levels=np.linspace(-44,21),
                                        cmap='RdBu_r', extend='both', add_colorbar=False, transform=ccrs.PlateCarree())
axes[2].coastlines()
gl = axes[2].gridlines(draw_labels=True, linestyle='--')  # 添加经纬度网格并显示标签
gl.right_labels = False  
gl.top_labels = False    
axes[2].set_title(f'{data_title[2]}')
axes[2].set_aspect(2)

cbar_diff = fig.colorbar(contour_diff, ax=axes[2], orientation='horizontal', pad=0.1, shrink=0.9, aspect=40)
cbar_diff.set_label('kg kg**-1')

plt.show()

可以发现的是,通过比湿计算得到的可降水量与TCWV在陆地上主要的偏差也在陆地上,很奇怪。在还要上的偏差倒是比较小

对于纬向平均和经向评价结果,也能得到类似的模拟情况,在纬向平均的35°E、280°E附近存在明显的差异,分布对应于南美和南非大陆,很有意思

dataset = [era5_tcwv_lon[0], sph_trap[0], era5_tcwv_lon[0] - sph_trap[0]]
data_title = ['ERA-5-TCWV''ERA-5-SPH''diff']

colors = ['b''g''r']  

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
fig, ax = plt.subplots(dpi=200, figsize=(106))

# 绘制三个数据的纬向平均值在同一个图上
for i, data in enumerate(dataset):
    line = data.mean('latitude').plot(ax=ax, label=data_title[i], color=colors[i])

ax.set_title('纬向平均值')
ax.set_xlabel('经度')
ax.set_ylabel('值')
ax.legend()

plt.show()

纬向平均
经向平均

总结

总的来说,通过从xarray和numpy的角度,基于比湿数据得到的可降水量数据与ERA5中的TCWV的空间分布以及纬向平均结果是比较一致,但是在陆地上存在明显的差异。当然, 这里测试的数据只是2010年的1月的数据,没有从更长时间范围上进行评估,只是简单验证一下计算方法的可靠性。 在后续评估中,感觉可以用TCWV来与cmip6的Prw进行背景场的评估。

相关数据和测试代码放到GitHub上

https://github.com/Blissful-Jasper/jianpu_record

https://confluence.ecmwf.int/pages/viewpage.action?pageId=56667102


给我点在看的人

越来越好看

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