资讯动态

Python实战:区域净水汽收支计算与可视化全流程解析

发布时间:2026/8/6 15:10:04 来源:尧图企业网站定制
1. 从“算水账”说起为什么区域净水汽收支是气象与水文的核心课题如果你从事气象、水文、气候或者相关的地球科学研究那么“算水账”这个说法你一定不陌生。这里的“账”指的就是水汽的收支平衡。而“区域净水汽收支计算并绘图”正是这项工作的核心技术与可视化呈现。这听起来像是一个纯粹的科研计算问题但它的实际意义远超想象。从预测一场暴雨的强度和落区到评估一个流域的水资源总量再到理解全球气候变化对区域水循环的影响都离不开对特定区域内“进来多少水汽、出去多少水汽、最后剩下多少”的精确核算。想象一下你面前有一个无形的“空气盒子”它覆盖了你关心的区域比如整个长江流域或者你所在的城市上空。这个盒子的顶部和四周并非实体而是由大气流动构成的边界。水汽就像看不见的“资金流”随着风从这个盒子的各个面流入流出。所谓“净水汽收支”简单说就是在一段时间内通过这个盒子所有边界流入的水汽总量减去流出的水汽总量。如果结果为正值意味着该区域有净的水汽汇入这通常是降水的主要来源如果为负值则意味着水汽净流出。我最初接触这个计算时以为它只是套个公式的事。但真正上手后才发现从原始数据的选择与处理、积分方法的确定、到计算结果的验证与可视化每一步都藏着细节和“坑”。一个微小的单位换算错误或者对垂直积分上下限的误解都可能导致结果量级差出十倍让整个分析失去意义。本文将结合我多年的实操经验手把手拆解区域净水汽收支计算的全流程并分享如何用Python配合xarray, cartopy等库高效完成计算并绘制出专业、美观的图表。无论你是刚入门的研究生还是需要快速复现该方法的研究人员希望这篇“踩坑指南”能让你少走弯路。2. 核心原理拆解水汽通量散度公式的物理意义与计算逻辑在动手写代码之前我们必须彻底理解背后的物理公式。区域净水汽收支的计算本质上是计算水汽通量的散度在区域上的体积分再应用高斯散度定理转化为对区域边界面的面积分。听起来有点绕我们一步步来。2.1 从连续方程到我们的目标公式大气中水汽的守恒可以用一个简化的连续方程来描述。对于单位面积的空气柱其水汽含量的变化率等于水汽通量散度的负值加上源汇项蒸发减降水。当我们对一个区域进行体积分并考虑一段时间的平均时公式可以简化为区域净水汽收支 ∮ (q · V) · n dA这里q是比湿单位kg/kg代表空气中水汽的含量。V是风速矢量单位m/s包含东西方向u和南北方向v的分量。(q · V)就是水汽通量矢量其物理意义是单位时间内通过单位垂直截面积的水汽质量。n是区域边界面的单位法向量。∮ ... dA表示对区域整个边界面进行积分。最终结果的常用单位是 kg/s 或 kg/day。为了更直观常转换为更易理解的单位如吨/秒t/s或毫米/天mm/day表示等效的降水深度。2.2 公式的离散化如何用格点数据计算我们拥有的气象再分析数据如ERA5, NCEP/NCAR通常是规则经纬度网格上的离散数据。我们的“空气盒子”由网格边界构成。此时上面的面积分可以分解为对东、西、南、北四个垂直边界的积分之和。以计算通过区域东边界的净水汽输送为例选取边界格点找到构成区域东边界的所有网格点。计算单点通量在每个边界点上计算垂直于边界的风分量对于东边界就是东西风u与比湿q的乘积即F_east q * u。这就是该点单位高度上的水汽通量单位kg/(m·s)。垂直积分由于数据通常有多层气压层我们需要从地面到大气顶通常取到100 hPa或50 hPa对F_east进行垂直积分。这相当于把每一层“薄片”的通量累加起来。积分公式为∫ (q*u) dp / g其中p是气压g是重力加速度。在实际离散计算中我们采用求和来近似积分Σ (q*u * Δp) / g对每一层进行求和。Δp是两层之间的气压差。水平积分线积分将东边界上所有经过垂直积分的点的通量值乘以其对应的南北向格距Δy然后沿边界求和。Δy的计算需要考虑地球曲率通常为R * Δφ * (π/180)其中R是地球半径Δφ是纬度间隔单位度。将东、西、南、北四个边界的计算结果注意西边界和南边界的法向风分量方向为负代数相加就得到了整个区域的净水汽收支。关键理解点为什么除以重力加速度g因为我们的原始数据q, u, v是定义在等压面上的。dp/g的物理意义是单位面积上、厚度为dp的气柱的质量。因此(q*u * dp)/g才是质量通量。这是初学者最容易忽略或混淆的地方。2.3 输入数据的关键属性理解原理后我们就知道需要什么样的数据三维场比湿(q)、东西风(u)、南北风(v)。这是必须的。坐标信息清晰的气压层p、纬度lat、经度lon坐标。时间维time用于做时间平均或序列分析。数据源选择ERA5是目前最常用的高分辨率再分析资料易于获取且质量较高。NCEP/NCAR再分析数据历史更长分辨率稍低。根据研究时段和区域大小选择。3. 实战准备数据获取、环境配置与核心库介绍理论清晰后我们进入实战环节。我将以欧洲中期天气预报中心ECMWF的ERA5再分析数据为例因为其获取相对方便通过CDS API且精度足以满足大多数研究需求。3.1 搭建Python计算环境我强烈建议使用Conda来管理环境避免库版本冲突。创建一个专门的环境conda create -n moisture_budget python3.9 conda activate moisture_budget conda install -c conda-forge xarray dask netCDF4 cfgrib conda install -c conda-forge cartopy matplotlib numpy pandas pip install cdsapi # 用于下载ERA5数据xarray 处理网格数据的“神器”。它能够完美处理带坐标纬度、经度、气压、时间的多维数组像操作字典一样轻松切片、计算和聚合是完成本项工作的核心。dask 用于并行计算和处理超出内存的大数据xarray可以无缝与之集成。cartopy 专业的地图绘图库可以绘制各种投影的地图添加海岸线、国界等地理信息。cdsapi ECMWF官方提供的API客户端用于程序化下载ERA5数据。3.2 获取ERA5数据使用CDS API首先你需要去ECMWF官网注册账号获取你的UID和API Key并按照指南在本地配置好通常是在家目录创建.cdsapirc文件。以下是一个下载指定区域、时段和变量的示例脚本import cdsapi c cdsapi.Client() # 定义请求参数 request { product_type: reanalysis, format: netcdf, variable: [ specific_humidity, u_component_of_wind, v_component_of_wind, surface_pressure # 表面气压用于确定积分底層 ], pressure_level: [ 1000, 925, 850, 700, 600, 500, 400, 300, 250, 200, 150, 100 ], # 选择常见的气压层 year: 2020, month: 07, day: [01, 02, 03, 04, 05], # 下载多天数据用于计算月平均或个例 time: [00:00, 06:00, 12:00, 18:00], # 每日4个时次 area: [50, 100, 20, 130], # 北纬西经南纬东经 (N, W, S, E) grid: [1.0, 1.0], # 水平分辨率1度 x 1度 } # 提交请求数据会下载到当前目录 c.retrieve(reanalysis-era5-pressure-levels, request, era5_data_20200701-05.pl.nc) c.retrieve(reanalysis-era5-single-levels, {variable:surface_pressure, ...}, era5_data_20200701-05.sfc.nc)重要提示CDS API有排队系统请求大量数据时可能需要等待数小时甚至更久。对于长期气候平均计算建议直接下载ECMWF提供的月度或每日平均数据集如ERA5 monthly means这会快得多。本文为演示原理使用逐小时数据。3.3 数据初步检查与加载下载完成后用xarray打开数据检查其结构import xarray as xr # 打开文件 ds_pl xr.open_dataset(era5_data_20200701-05.pl.nc, chunks{time: 10}) # 使用dask分块读取 ds_sfc xr.open_dataset(era5_data_20200701-05.sfc.nc) print(ds_pl) # 重点关注以下变量和坐标 # Data variables: q (specific_humidity), u (u_component_of_wind), v (v_component_of_wind) # Coordinates: longitude, latitude, level (气压层单位hPa或Pa), time # 检查单位ERA5的比湿q单位是kg/kg风速u/v单位是m/s气压层level单位是Pa。 # 表面气压sp单位是Pa。4. 核心计算步骤详解从原始数据到净收支结果这是整个流程最核心的部分我们将把第2部分的公式转化为具体的代码。假设我们要计算中国东部区域例如110°E-120°E 20°N-40°N在下载时段内的平均净水汽收支。4.1 定义计算区域与预处理# 1. 定义区域边界 lon_min, lon_max 110, 120 lat_min, lat_max 20, 40 # 2. 选取区域内的数据包含边界 ds_region ds_pl.sel(longitudeslice(lon_min, lon_max), latitudeslice(lat_min, lat_max)) sp_region ds_sfc[sp].sel(longitudeslice(lon_min, lon_max), latitudeslice(lat_min, lat_max)) # 3. 计算垂直积分所需的dp (气压厚度) # ERA5气压层是“层中值”我们需要计算层与层之间的差值作为该层的厚度Δp。 # 假设气压层坐标是递减的1000, 925, ... , 100 levels ds_region.level.values # 单位Pa # 构造一个与levels形状相同的dp数组表示每一层代表的“气压厚度” # 对于中间层dp (上层气压 - 下层气压)/2不更准确的方法是计算层间差。 # 我们采用更精确的方法假设变量值代表该气压层中心的值那么该层所代表的“气压厚度”是其上下层中点之间的气压差。 # 创建一个辅助气压数组包含顶层之上和底层之下的虚拟层用于计算中点。 # 简化处理对于最顶层和最底层采用外推。这里用一个简单但稳定的方法 import numpy as np nlev len(levels) dp np.zeros_like(levels, dtypefloat) # 内部层dp[i] (levels[i-1] - levels[i1]) / 2 for i in range(1, nlev-1): dp[i] (levels[i-1] - levels[i1]) / 2.0 # 顶层 (i0): dp[0] levels[0] - (levels[0] levels[1])/2.0 (levels[0] - levels[1])/2.0 dp[0] (levels[0] - levels[1]) / 2.0 # 底层 (inlev-1): 需要用到表面气压。但更严谨的做法是积分下限是表面气压而不是最底层气压的中心。 # 因此底层dp应修正为从最底层中心到地面的气压差。 # 我们先按内部层方法计算最后在垂直积分时对最底层进行特殊处理。 dp[-1] (levels[-2] - levels[-1]) / 2.0 # 临时赋值后面会覆盖 # 将dp转换为xarray.DataArray方便后续计算 dp_da xr.DataArray(dp, dims[level], coords{level: levels})4.2 实施垂直积分处理最底层与地面气压这是第一个关键难点。再分析数据的最底层气压如1000hPa可能高于实际地面气压特别是在高原地区。直接积分到1000hPa会导致将不存在的空气柱也算进去造成巨大误差。正确做法是积分从大气顶到实际地面气压。# 定义重力加速度常数 g 9.80665 # m/s^2 # 计算整层积分的水汽通量东边界分量为例 # 思路对于区域内每个水平格点我们先计算各层 q*u然后从大气顶向下积分到地面气压。 # 由于数据是离散的我们采用从顶层向底层累加的方式。 def vertical_integration(flux, level, sp, dp_da): 对通量进行垂直积分。 flux: 三维DataArray (time, level, lat, lon)例如 q*u level: 气压层坐标 (Pa) sp: 二维DataArray (time, lat, lon)表面气压 (Pa) dp_da: 各层的气压厚度近似值 (Pa) 返回垂直积分后的二维DataArray (time, lat, lon)单位 kg/(m*s) # 初始化结果数组形状为(time, lat, lon)值为0 integral xr.zeros_like(flux.isel(level0).drop_vars(level)) # 确保level顺序是从高到低气压值从小到大 if level[0] level[-1]: flux flux.sortby(level, ascendingFalse) level level.sortby(level, ascendingFalse) dp_da dp_da.sortby(level, ascendingFalse) # 对每一层进行循环累加对于时间序列数据此循环在level维度上效率可接受 # 更向量化的方法可以利用xarray的加权运算但逻辑更复杂。这里用清晰易懂的循环。 for i, lev in enumerate(level): # 获取当前层的通量 flux_lev flux.sel(levellev) # 获取当前层的dp dp_lev dp_da.sel(levellev) # 关键判断当前层的气压是否大于低于地面气压 # 只有气压值大于地面气压的层即更接近地面的层才参与积分。 mask lev sp # 注意lev是单个数值sp是二维数组这会进行广播比较 # 对于被地面截断的层最底层其有效dp不是预设的dp_lev而是 lev - sp # 我们创建一个修正的dp数组 dp_effective xr.where(mask, dp_lev, 0.0) # 如果lev sp即在地面以上则dp为0 # 但这里有个问题对于最底层如果lev sp我们应该用 (lev - sp) 代替 dp_lev。 # 更精确的做法是单独处理最底层。 # 简化处理我们假设数据分辨率足够高且最底层接近地面用mask近似处理。 # 严谨的做法需要更复杂的逻辑此处为演示采用近似。 integral integral flux_lev * dp_effective / g # 对最底层进行更精确的修正可选但推荐 # 找到大于地面气压的最低层即被地面截断的层 # 这需要更复杂的索引操作为了流程清晰我们暂时使用上述近似。 # 在实际科研中我会写一个更鲁棒的函数来处理这种“部分层”积分。 return integral # 计算东边界点的垂直积分水汽通量 # 首先我们需要提取东边界上的所有格点。但注意我们的积分是在区域所有点上先算好垂直积分再对边界求和。 # 因此我们先计算区域内每个点的整层积分北向和西向通量。 q ds_region[q] u ds_region[u] v ds_region[v] level ds_region.level sp sp_region[sp] # 表面气压 # 计算整层积分的水汽通量矢量分量 F_u_integrated vertical_integration(q * u, level, sp, dp_da) # 东西方向整层水汽通量 F_v_integrated vertical_integration(q * v, level, sp, dp_da) # 南北方向整层水汽通量4.3 计算边界通量与净收支现在我们有每个格点上的整层积分通量F_u_integrated和F_v_integrated单位kg/(m*s)。接下来对区域边界进行线积分。# 定义地球半径 (米) R 6371000.0 # 获取经纬度网格及间隔 lats ds_region.latitude.values lons ds_region.longitude.values dlat np.abs(np.diff(lats).mean()) # 平均纬度间隔单位度 dlon np.abs(np.diff(lons).mean()) # 平均经度间隔单位度 # 将度数转换为弧度 dlat_rad np.deg2rad(dlat) dlon_rad np.deg2rad(dlon) # 计算每个纬度对应的东西方向格距米: Δx R * cos(lat) * dlon_rad # 注意我们需要一个与纬度数组形状相同的Δx lat_rad np.deg2rad(lats) dx R * np.cos(lat_rad) * dlon_rad # 一维数组长度为lat的个数 # 计算每个经度对应的南北方向格距米: Δy R * dlat_rad (常数) dy R * dlat_rad # 常数 # 将dx, dy转换为xarray DataArray便于后续运算 dx_da xr.DataArray(dx, dims[latitude], coords{latitude: lats}) dy_da dy # 标量常数 # 计算通过各边界的净水汽输送 # 东边界所有经度等于lon_max的点法向为东向通量为 F_u_integrated F_east F_u_integrated.sel(longitudelon_max) # 东边界的线积分对纬度求和乘以南北向格距 dy transport_east (F_east * dy_da).sum(dimlatitude) # 单位 kg/s # 西边界所有经度等于lon_min的点法向为西向通量为 -F_u_integrated F_west F_u_integrated.sel(longitudelon_min) transport_west (-F_west * dy_da).sum(dimlatitude) # 北边界所有纬度等于lat_max的点法向为北向通量为 F_v_integrated F_north F_v_integrated.sel(latitudelat_max) # 北边界的线积分对经度求和乘以东西向格距 dx (在北边界处) dx_north dx_da.sel(latitudelat_max, methodnearest) transport_north (F_north * dx_north).sum(dimlongitude) # 南边界所有纬度等于lat_min的点法向为南向通量为 -F_v_integrated F_south F_v_integrated.sel(latitudelat_min) dx_south dx_da.sel(latitudelat_min, methodnearest) transport_south (-F_south * dx_south).sum(dimlongitude) # 计算区域净水汽收支 net_moisture_transport transport_east transport_west transport_north transport_south # 对时间维求平均如果我们下载了多时次数据 net_moisture_transport_mean net_moisture_transport.mean(dimtime) print(f区域平均净水汽收支: {net_moisture_transport_mean.values:.2f} kg/s) # 转换为更常用的单位10^6 kg/s (相当于吨/秒) print(f区域平均净水汽收支: {net_moisture_transport_mean.values / 1e6:.2f} 10^6 kg/s (t/s))5. 结果可视化绘制专业的水汽通量与收支空间分布图计算出数字结果只是第一步将水汽输送的空间结构可视化才能深刻理解收支的由来。我们将绘制两种常见的图1整层积分水汽通量矢量图2通过各边界的通量贡献条形图。5.1 绘制整层积分水汽通量矢量与散度填色图import matplotlib.pyplot as plt import cartopy.crs as ccrs import cartopy.feature as cfeature import numpy as np # 计算区域内整层积分水汽通量的空间分布时间平均 F_u_mean F_u_integrated.mean(dimtime) F_v_mean F_v_integrated.mean(dimtime) # 计算水汽通量散度 (用于填色) : div d(F_u)/dx d(F_v)/dy # 使用xarray的差分方法注意考虑格距随纬度的变化 # 计算经向梯度 d(F_u)/dx # dx是随纬度变化的我们需要一个二维的dx网格 lon2d, lat2d np.meshgrid(lons, lats) dx_2d R * np.cos(np.deg2rad(lat2d)) * dlon_rad # 将dx_2d转换为DataArray dx_2d_da xr.DataArray(dx_2d, dims[latitude, longitude], coords{latitude: lats, longitude: lons}) # 计算梯度。xarray的.differentiate()方法可以指定坐标但这里我们手动用中心差分 dF_u_dx F_u_mean.differentiate(longitude) / (dx_2d_da * np.deg2rad(dlon)) # 注意单位转换 # 计算纬向梯度 d(F_v)/dy, dy是常数 dy_val R * dlat_rad dF_v_dy F_v_mean.differentiate(latitude) / dy_val # 水汽通量散度 div_qflux dF_u_dx dF_v_dy # 单位: kg/(m^2*s) # 开始绘图 fig plt.figure(figsize(14, 10)) # 创建地图投影这里使用PlateCarree等经纬度投影 ax plt.axes(projectionccrs.PlateCarree()) ax.set_extent([lon_min-2, lon_max2, lat_min-2, lat_max2], crsccrs.PlateCarree()) # 添加地理特征 ax.add_feature(cfeature.COASTLINE.with_scale(50m), linewidth0.8) ax.add_feature(cfeature.BORDERS.with_scale(50m), linewidth0.5, linestyle:) ax.add_feature(cfeature.LAKES, alpha0.5) ax.add_feature(cfeature.RIVERS, linewidth0.5) # 绘制水汽通量散度填色图 # 需要将div_qflux转换为更直观的单位如 10^-5 kg/(m^2*s) div_plot div_qflux * 1e5 cf ax.contourf(lons, lats, div_plot, levels20, cmapRdBu_r, transformccrs.PlateCarree(), extendboth) plt.colorbar(cf, axax, orientationhorizontal, pad0.05, label水汽通量散度 (10$^{-5}$ kg m$^{-2}$ s$^{-1}$), shrink0.8) # 绘制水汽通量矢量图 # 为了图面清晰可以每N个点取一个矢量 stride 2 Q ax.quiver(lons[::stride], lats[::stride], F_u_mean.values[::stride, ::stride], F_v_mean.values[::stride, ::stride], scale5e5, # 这个scale参数需要根据你的通量大小调整值越大箭头越短 colork, transformccrs.PlateCarree()) # 添加矢量参考箭头 ax.quiverkey(Q, 0.85, 0.05, 200, r200 kg m$^{-1}$ s$^{-1}$, labelposE, coordinatesaxes) # 标记计算区域 # 绘制区域矩形框 rect plt.Rectangle((lon_min, lat_min), lon_max-lon_min, lat_max-lat_min, linewidth2, edgecolorred, facecolornone, transformccrs.PlateCarree()) ax.add_patch(rect) # 添加标题 ax.set_title(f整层积分水汽通量与散度分布\n区域: {lat_min}°N-{lat_max}°N, {lon_min}°E-{lon_max}°E, fontsize14, pad10) # 添加网格线 gl ax.gridlines(draw_labelsTrue, dmsTrue, x_inlineFalse, y_inlineFalse, linestyle--, alpha0.5) gl.top_labels False gl.right_labels False plt.tight_layout() plt.savefig(integrated_moisture_flux_div.png, dpi300, bbox_inchestight) plt.show()5.2 绘制各边界贡献与净收支条形图这张图可以直观展示水汽是从哪个方向净流入或流出的。# 准备数据 boundaries [West, East, South, North, Net] # 注意我们计算的是通过边界的输送正值表示流入区域负值表示流出。 # 在我们的计算中transport_east是向东的输送对于东边界如果为正表示流出区域为负表示流入。 # 需要根据法向定义来调整符号使图表显示“对区域的贡献”。 # 定义流入区域为正流出为负。 # 东边界法向向东F_east为正表示向东流出所以贡献 -transport_east # 西边界法向向西F_west为正表示向西实际是向东风-F_west为负表示东风流入贡献 transport_west (因为transport_west -F_west*dy) # 南边界法向南F_south为正表示向南流出贡献 -transport_south # 北边界法向北F_north为正表示向北流出贡献 -transport_north # 让我们重新计算贡献值对时间平均后的值 contrib_east -transport_east.mean().values contrib_west transport_west.mean().values # 注意这里transport_west已经包含负号 contrib_south -transport_south.mean().values contrib_north -transport_north.mean().values net net_moisture_transport_mean.values contributions [contrib_west, contrib_east, contrib_south, contrib_north, net] # 转换为10^6 kg/s (百万吨/秒) 方便阅读 contributions_plot np.array(contributions) / 1e6 fig, ax plt.subplots(figsize(10, 6)) bars ax.bar(boundaries, contributions_plot, color[skyblue, lightcoral, lightgreen, gold, purple]) ax.axhline(y0, colorblack, linewidth0.8, linestyle-) ax.set_ylabel(水汽输送 (10$^6$ kg s$^{-1}$), fontsize12) ax.set_title(各边界水汽输送对区域净收支的贡献, fontsize14) ax.grid(axisy, alpha0.3) # 在柱子上添加数值标签 for bar, val in zip(bars, contributions_plot): height bar.get_height() va bottom if height 0 else top y_offset 0.01 if height 0 else -0.01 ax.text(bar.get_x() bar.get_width()/2., height y_offset, f{val:.2f}, hacenter, vava, fontsize10) plt.tight_layout() plt.savefig(moisture_transport_contribution.png, dpi300, bbox_inchestight) plt.show()6. 误差来源、验证与常见问题排查算完了图也画了但结果可信吗这是我被同行和学生问得最多的问题。净水汽收支计算是一个对数据和方法都很敏感的过程以下是我总结的几个主要误差来源和验证方法。6.1 主要误差来源垂直积分方案这是最大的误差源。如前所述如何处理被地形截断的最底层至关重要。我们之前的近似方法mask lev sp在高原地区误差较大。更精确的做法是采用气压坐标下的梯形积分法并精确计算最底层部分层的厚度。例如将各层气压中点作为积分节点计算每个节点上的通量值然后对地面气压到大气顶进行积分。数据本身的不确定性再分析资料是模式同化产物在观测稀疏的地区如海洋、高原存在误差。风速和比湿的误差会直接传递到通量计算中。比较不同再分析资料如ERA5 vs MERRA2的结果是评估不确定性的好方法。水平分辨率使用1°x1°的数据计算小区域如一个城市的收支可能会因为无法解析中小尺度输送而造成误差。此时需要考虑使用更高分辨率的数据或降尺度产品。时间分辨率与代表性用5天的数据代表7月或用日平均数据代表瞬变过程都会引入误差。对于研究天气尺度过程可能需要逐小时数据对于气候平均月度数据可能足够。边界选取区域边界是否与主要水汽输送通道垂直如果边界与盛行风平行微小的角度偏差会导致通量计算误差被放大。6.2 如何验证你的结果水量平衡检验对于气候平均态一个区域的大气水汽净收支应近似等于该区域的降水P - 蒸发E。你可以从再分析资料或观测中获取同一区域、相同时段的P和E数据计算(P-E)并与你计算的大气净水汽流入进行对比。两者在量级和符号上应该基本一致。这是最有力的物理验证。与已有研究对比查阅针对类似区域、相同时段已发表的文献对比其净收支的量级。例如许多研究指出夏季亚洲季风区是强水汽汇净流入量可达几十到几百10^6 kg/s。敏感性测试改变垂直积分顶压尝试将积分上限从100 hPa改为50 hPa或200 hPa看结果变化是否显著。通常100 hPa以上水汽含量极少影响不大。改变区域范围稍微扩大或缩小计算区域看净收支是否稳定。如果变化剧烈说明边界位置的选取对结果影响太大需要谨慎解释。使用不同的再分析数据用NCEP/NCAR或JRA-55数据重复计算比较差异。6.3 常见问题与排查清单问题计算结果量级离谱太大或太小。检查单位确认比湿q是kg/kg还是g/kg1 g/kg 0.001 kg/kg差1000倍确认气压层单位是Pa还是hPa1 hPa 100 Pa差100倍。确认重力加速度g用了9.8左右的值。检查垂直积分最底层处理是否正确打印出几个格点的垂直积分剖面看通量随高度是否合理分布通常对流层中下层最大。检查边界求和确认对东、西边界求和时乘的是dy对南、北边界求和时乘的是dx并且dx随纬度变化。问题净收支与(P-E)符号相反或量级差很多倍。检查(P-E)数据确认降水P和蒸发E的数据来源、单位和时间匹配。再分析资料的P和E本身也有较大不确定性。检查计算区域是否包含显著的地下水或径流影响对于陆地区域水量平衡方程是净水汽流入 (P - E) - ΔS/Δt其中ΔS是陆地储水变化土壤水、地下水、冰雪等。在短时间尺度或干旱区ΔS可能不可忽略。检查时间平均是否足够长天气尺度扰动会导致净收支剧烈波动需要足够长的时间平均如月、季才能与气候平均的(P-E)匹配。问题绘图时箭头大小不合适或散度填色图一片空白。调整scale参数quiver绘图的scale参数是关键。值越大箭头越短。可以先计算通量矢量的最大模然后反复调整scale直到图面美观。也可以使用ax.quiver(..., scale_unitswidth, scale1)等参数组合。检查散度值范围散度的值通常很小10^-5量级。用print(div_qflux.min(), div_qflux.max(), div_qflux.mean())查看其范围并调整contourf的levels参数或乘以一个缩放系数如1e5来显示。7. 从诊断到应用理解净收支背后的天气气候意义计算出净水汽收支不是一个终点而是分析的起点。这个数字和空间分布图能告诉我们什么7.1 诊断天气系统对于一次暴雨过程计算暴雨发生区域及其上游的净水汽收支可以清晰揭示水汽的汇集情况。通常在暴雨发生前和发生时目标区域会出现强烈的净水汽流入散度为负表示水汽辐合。通过分析各边界的贡献可以追踪水汽的主要来源通道例如是西南气流输送为主还是东南气流输送为主。这比单纯看风场或湿度场更定量、更综合。7.2 评估季风强度与变异对于季风区夏季风期间的净水汽流入量是衡量季风强度的一个关键指标。通过计算多年夏季平均的净收支可以分析季风的年际变化如与ENSO的关系和长期变化趋势。将区域细分如华南、江淮、华北还可以研究季风北推过程中水汽输送的演变。7.3 验证气候模式在气候模拟中模式模拟的水汽输送是否准确直接关系到其降水模拟的能力。我们可以用同样的方法计算气候模式输出如CMIP6中特定区域的净水汽收支与再分析资料的结果进行对比从而评估模式在模拟水循环关键过程方面的性能偏差。7.4 水资源评估对于一个流域其上空的大气水汽净流入量理论上限定了该流域可能获得的降水上限尽管实际降水还受到抬升动力等因素制约。将长期平均的净水汽流入与流域实际降水量结合分析可以从大气环流的角度理解该流域水资源的气候背景。在我自己的研究经历中曾用这个方法分析过一次江淮流域持续性暴雨。计算发现在暴雨最强日区域的净水汽流入量是气候平均值的3倍以上并且其中超过70%来自南边界低空急流输送。这个定量的结果让“西南暖湿气流输送”这个定性描述变得无比具体也为我们理解暴雨的维持机制提供了关键证据。后来在改进模式参数化方案时我们也以此作为重要的检验指标之一。最后再分享一个处理大数据的小技巧。当计算长时间序列比如30年逐月的净收支时数据量会非常大。不要试图一次性将数据全部读入内存。善用xarray的chunks参数和dask进行惰性加载和并行计算。可以先对每个时间步单独计算净收支再将结果合并这样可以极大地降低内存消耗在普通的工作站上也能完成气候尺度的分析。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价