本项目旨在介绍气象预报检验中常用的空间检验指标——Fraction Skill Score (FSS)。本教程通过对比两种主流的 Python 实现方式(pySTEPS 库与 Meteva 库),演示如何科学地计算并可视化降水预报的空间技巧。
传统的“点对点”检验方法(如 RMSE、TS评分)在评估高分辨率数值模式时,经常会遇到“双重惩罚”问题:即预报的降水形态非常准确,仅因位置偏移了几个格点,评分就会大幅下降。
FSS (Fraction Skill Score) 这种邻域验证方法通过比较观测与预报在邻域窗口内发生的频率,允许一定程度的空间位移,从而更客观地评价模式的预报能力。
!pip install --upgrade meteva -i https://pypi.mirrors.ustc.edu.cn/simple/
Looking in indexes: https://pypi.mirrors.ustc.edu.cn/simple/
Requirement already satisfied: meteva in /opt/conda/lib/python3.9/site-packages (1.9.2.6)
Requirement already satisfied: importlib-resources>=5.2.1 in /opt/conda/lib/python3.9/site-packages (from meteva) (5.4.0)
Requirement already satisfied: pandas>=1.0.4 in /opt/conda/lib/python3.9/site-packages (from meteva) (1.2.4)
Requirement already satisfied: matplotlib>=3.2.2 in /opt/conda/lib/python3.9/site-packages (from meteva) (3.4.2)
Requirement already satisfied: netCDF4>=1.4.1 in /opt/conda/lib/python3.9/site-packages (from meteva) (1.5.8)
Requirement already satisfied: numpy>=1.12.1 in /opt/conda/lib/python3.9/site-packages (from meteva) (1.20.3)
Requirement already satisfied: httplib2>=0.12.0 in /opt/conda/lib/python3.9/site-packages (from meteva) (0.20.2)
Requirement already satisfied: scipy>=0.19.0 in /opt/conda/lib/python3.9/site-packages (from meteva) (1.7.2)
Requirement already satisfied: scikit-learn>=0.21.2 in /opt/conda/lib/python3.9/site-packages (from meteva) (0.24.2)
Requirement already satisfied: protobuf>=1.0.0 in /opt/conda/lib/python3.9/site-packages (from meteva) (3.15.8)
Requirement already satisfied: urllib3>=1.21.1 in /opt/conda/lib/python3.9/site-packages (from meteva) (1.26.5)
Requirement already satisfied: pyshp>=2.1.0 in /opt/conda/lib/python3.9/site-packages (from meteva) (2.1.3)
Requirement already satisfied: tables>=3.4.4 in /opt/conda/lib/python3.9/site-packages (from meteva) (3.6.1)
Requirement already satisfied: shapely>=1.8.0 in /opt/conda/lib/python3.9/site-packages (from meteva) (1.8.0)
Requirement already satisfied: pynverse>=0.1.4.6 in /opt/conda/lib/python3.9/site-packages (from meteva) (0.1.4.6)
Requirement already satisfied: xarray>=0.10.0 in /opt/conda/lib/python3.9/site-packages (from meteva) (0.20.1)
Requirement already satisfied: pyparsing!=3.0.0,!=3.0.1,!=3.0.2,!=3.0.3,<4,>=2.4.2 in /opt/conda/lib/python3.9/site-packages (from httplib2>=0.12.0->meteva) (2.4.7)
Requirement already satisfied: zipp>=3.1.0 in /opt/conda/lib/python3.9/site-packages (from importlib-resources>=5.2.1->meteva) (3.4.1)
Requirement already satisfied: cycler>=0.10 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=3.2.2->meteva) (0.10.0)
Requirement already satisfied: pillow>=6.2.0 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=3.2.2->meteva) (8.4.0)
Requirement already satisfied: python-dateutil>=2.7 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=3.2.2->meteva) (2.8.1)
Requirement already satisfied: kiwisolver>=1.0.1 in /opt/conda/lib/python3.9/site-packages (from matplotlib>=3.2.2->meteva) (1.3.1)
Requirement already satisfied: six in /opt/conda/lib/python3.9/site-packages (from cycler>=0.10->matplotlib>=3.2.2->meteva) (1.16.0)
Requirement already satisfied: cftime in /opt/conda/lib/python3.9/site-packages (from netCDF4>=1.4.1->meteva) (1.5.1.1)
Requirement already satisfied: pytz>=2017.3 in /opt/conda/lib/python3.9/site-packages (from pandas>=1.0.4->meteva) (2021.1)
Requirement already satisfied: threadpoolctl>=2.0.0 in /opt/conda/lib/python3.9/site-packages (from scikit-learn>=0.21.2->meteva) (2.1.0)
Requirement already satisfied: joblib>=0.11 in /opt/conda/lib/python3.9/site-packages (from scikit-learn>=0.21.2->meteva) (1.0.1)
Requirement already satisfied: numexpr>=2.6.2 in /opt/conda/lib/python3.9/site-packages (from tables>=3.4.4->meteva) (2.7.3)
本教程依赖 numpy 进行数据处理,matplotlib 进行绘图,并对比使用 pysteps 和 meteva。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from pysteps.verification.spatialscores import fss # pySTEPS 核心算法
import meteva.method as mem # Meteva 核心算法
# 设置随机种子以保证结果可复现
np.random.seed(42)
Pysteps configuration file found at: /opt/conda/lib/python3.9/site-packages/pysteps/pystepsrc
为了演示计算逻辑,我们构建了一组包含“位置偏差”的模拟降水场。
# 1. 观测场 (Obs):在中心设置一个 20x20 的强降水中心
obs = np.zeros((100, 100))
obs[30:50, 30:50] = 50.0
obs += np.random.rand(100, 100) * 2 # 添加噪声
# 2. 预报场 (Fcst):将降水中心向右偏移 5 个格点
fcst = np.zeros((100, 100))
fcst[30:50, 35:55] = 50.0
fcst += np.random.rand(100, 100) * 2
pySTEPS 的 fss 函数设计精简,需要通过循环来构建不同阈值和尺度的矩阵。
thresholds = [1.0, 5.0, 10.0]
scales = [1, 3, 5, 9, 15, 21] # 全窗口大小
fss_pysteps = np.zeros((len(thresholds), len(scales)))
for i, thr in enumerate(thresholds):
for j, sc in enumerate(scales):
fss_pysteps[i, j] = fss(fcst, obs, thr, sc)
# 结果展示
print("pySTEPS FSS 矩阵:")
print(fss_pysteps)
pySTEPS FSS 矩阵:
[[0.53525734 0.90795379 0.96137832 0.98442297 0.9927203 0.99553457]
[0.75 0.78488372 0.81521739 0.86594203 0.90088757 0.92160279]
[0.75 0.78488372 0.81521739 0.86594203 0.90088757 0.92160279]]
Meteva 针对业务流程进行了优化,支持通过 half_window_size_list 批量输出中间结果。
注意: Meteva 使用“半窗口”定义。若要对应 pySTEPS 的
Scale=21,则 Meteva 的Half_window应设为10(公式:)。
grade_list = [1.0, 5.0, 10.0]
half_window_list = [0, 1, 2, 4, 7, 10] # 对应 scale 1, 3, 5, 9, 15, 21
# 计算中间分量
result_3d = mem.fbs_pobfo(obs, fcst, grade_list=grade_list,
half_window_size_list=half_window_list)
# 根据公式计算 FSS: 1 - fbs / (pob + pfo)
pob, pfo, fbs = result_3d[:,:,0], result_3d[:,:,1], result_3d[:,:,2]
fss_meteva = (1 - fbs / (pob + pfo + 1e-30)).T # 转置以匹配 (阈值, 尺度)
标准的 FSS 评价通常采用“评分随空间尺度变化曲线”。
def plot_fss_curves(scales, fss_matrix, thresholds):
fig, ax = plt.subplots(figsize=(10, 6), dpi=100)
markers = ['o', 's', '^']
for i, thr in enumerate(thresholds):
ax.plot(scales, fss_matrix[i, :], marker=markers[i], label=f'Threshold ≥ {thr} mm')
# 基准参考线:FSS=0.5
ax.axhline(y=0.5, color='grey', linestyle='--', alpha=0.8)
ax.text(scales[-1], 0.52, 'Skillful Threshold (0.5)', color='grey', ha='right')
ax.set_ylim(0, 1.05)
ax.set_xticks(scales)
ax.set_xlabel('Neighborhood Scale (grid points)', fontweight='bold')
ax.set_ylabel('FSS Score', fontweight='bold')
ax.set_title('Fraction Skill Score vs. Spatial Scale', fontsize=14)
ax.legend(loc='lower right')
ax.grid(True, linestyle=':', alpha=0.6)
plt.show()
# 执行绘图
plot_fss_curves(scales, fss_pysteps, thresholds)

print("\n" + "="*50)
print(f"{'Thr/Scale':<10} | {'pySTEPS (Scale 21)':<20} | {'Meteva (Half 10)':<20}")
print("-"*50)
for i, thr in enumerate(thresholds):
print(f"{thr:<10.1f} | {fss_pysteps[i, -1]:<20.4f} | {fss_meteva[i, -1]:<20.4f}")
print("="*50)
==================================================
Thr/Scale | pySTEPS (Scale 21) | Meteva (Half 10)
--------------------------------------------------
1.0 | 0.9955 | 0.9957
5.0 | 0.9216 | 0.9216
10.0 | 0.9216 | 0.9216
==================================================
pySTEPS 与 Meteva 在相同物理定义下计算出的 FSS 值是完全一致的。scale。half_window_size。import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from pysteps.verification.spatialscores import fss as pysteps_fss
import meteva.method as mem
# ==========================================
# 1. 项目概况与模拟数据构建
# ==========================================
# 固定随机种子确保结果可重复
np.random.seed(42)
# 观测场 (Obs):100x100 区域,中心设置一个 20x20 的 50mm 强降水区
obs = np.zeros((100, 100))
obs[30:50, 30:50] = 50.0
obs += np.random.rand(100, 100) * 2# 添加噪声
# 预报场 (Fcst):将降水中心向右偏移 5 个格点 (模拟位置偏差)
fcst = np.zeros((100, 100))
fcst[30:50, 35:55] = 50.0
fcst += np.random.rand(100, 100) * 2
# 参数设置
thresholds = [1.0, 5.0, 10.0] # 降水阈值 (mm)
scales = [1, 3, 5, 9, 15, 21] # 全窗口尺寸 (Scale)
# ==========================================
# 2. 计算:方法一 使用 pySTEPS
# ==========================================
print("正在使用 pySTEPS 计算 FSS...")
fss_pysteps = np.zeros((len(thresholds), len(scales)))
for i, thr in enumerate(thresholds):
for j, sc in enumerate(scales):
# pySTEPS 接口: (pred, obs, thr, scale)
fss_pysteps[i, j] = pysteps_fss(fcst, obs, thr, sc)
print("pySTEPS 计算完成。")
# ==========================================
# 3. 计算:方法二 使用 Meteva
# ==========================================
print("\n正在使用 Meteva 计算 FSS...")
# 对应 pySTEPS 的 scale,计算 Meteva 所需的半窗口 (half_window_size)
# 公式: half = (scale - 1) / 2
half_window_list = [(s - 1) // 2for s in scales]
# 调用 Meteva 中间分量计算函数
# 返回 shape 为 (窗口数, 阈值数, 3)
result_3d = mem.fbs_pobfo(
obs,
fcst,
grade_list=thresholds,
half_window_size_list=half_window_list
)
# 计算 FSS: 1 - fbs / (pob + pfo)
pob = result_3d[:, :, 0]
pfo = result_3d[:, :, 1]
fbs = result_3d[:, :, 2]
# 转置以便与 pysteps 结果矩阵结构(阈值, 尺度)对齐
fss_meteva = (1 - fbs / (pob + pfo + 1e-30)).T
print("Meteva 计算完成。")
# ==========================================
# 4. 可视化:科研标准绘图
# ==========================================
def plot_fss_comparison(scales, matrix, thresholds, title_suffix=""):
fig, ax = plt.subplots(figsize=(10, 6), dpi=120)
markers = ['o', 's', '^', 'D']
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
for i, thr in enumerate(thresholds):
ax.plot(scales, matrix[i, :],
marker=markers[i % len(markers)],
color=colors[i % len(colors)],
linewidth=2, markersize=8,
label=f'Threshold $\geq$ {thr} mm')
# 基准参考线 FSS = 0.5
ax.axhline(y=0.5, color='grey', linestyle='--', linewidth=1.5, alpha=0.7)
ax.text(scales[-1], 0.52, 'Skillful Threshold (0.5)', color='grey', ha='right', fontsize=10)
# 坐标轴美化
ax.set_ylim(0, 1.05)
ax.set_xlim(scales[0], scales[-1])
ax.set_xticks(scales)
ax.set_xlabel('Neighborhood Scale (grid points)', fontsize=12, fontweight='bold')
ax.set_ylabel('FSS Score', fontsize=12, fontweight='bold')
ax.set_title(f'Fraction Skill Score Analysis {title_suffix}', fontsize=14, pad=15)
ax.legend(loc='lower right', frameon=True, shadow=True)
ax.grid(True, linestyle=':', alpha=0.6)
plt.tight_layout()
plt.show()
# 绘制 pySTEPS 结果图
plot_fss_comparison(scales, fss_pysteps, thresholds, "(via pySTEPS)")
# ==========================================
# 5. 结果打印对比
# ==========================================
print("\n" + "="*50)
print(f"{'Thr/Scale':<10} | {'pySTEPS (Scale 21)':<20} | {'Meteva (Half 10)':<20}")
print("-"*50)
for i, thr in enumerate(thresholds):
print(f"{thr:<10.1f} | {fss_pysteps[i, -1]:<20.4f} | {fss_meteva[i, -1]:<20.4f}")
print("="*50)
正在使用 pySTEPS 计算 FSS...
pySTEPS 计算完成。
正在使用 Meteva 计算 FSS...
Meteva 计算完成。

==================================================
Thr/Scale | pySTEPS (Scale 21) | Meteva (Half 10)
--------------------------------------------------
1.0 | 0.9955 | 0.9957
5.0 | 0.9216 | 0.9216
10.0 | 0.9216 | 0.9216
==================================================