# Python实战:5分钟搞定泰勒图绘制(附完整代码与数据格式详解)
你是否曾在评估模型性能时,面对一堆相关系数、标准差和均方根误差(RMSE)感到无从下手?散点图只能展示两个维度,而我们需要的是一个能同时呈现多个精度指标的“仪表盘”。泰勒图(Taylor Diagram)正是为此而生。它巧妙地将相关系数、标准差和RMSE这三个关键指标融合在一张二维极坐标图上,让模型间的优劣对比一目了然。对于数据分析师和科研人员来说,这不仅是展示结果的利器,更是深入理解模型行为的窗口。然而,当你兴冲冲地搜索教程,准备用Python实现时,却可能被繁琐的坐标轴转换、复杂的skill_metrics库配置,或是令人头疼的数据格式要求劝退。别担心,这篇文章就是为你准备的。我们将绕过那些冗长的理论推导和复杂的库文档,直击核心:**用最短的时间、最清晰的代码,让你亲手绘制出专业级的泰勒图**。我会分享一个经过实战检验的“代码模板”,并详细拆解每一步的数据准备要点,帮你避开那些新手常踩的“坑”。无论你是刚接触模型评估,还是想优化现有的可视化流程,接下来的内容都将让你有所收获。
## 1. 理解泰勒图:你的模型“体检报告”
在动手写代码之前,花几分钟理解泰勒图到底在画什么,能让你后续的调整和解读事半功倍。你可以把泰勒图想象成一份为模型定制的“综合体检报告”。
传统的评估方法像是单项检查:相关系数告诉你模型和观测值的变化趋势是否一致(相关性),标准差反映两者自身波动幅度的大小(变异性),而RMSE则衡量了整体误差的规模。泰勒图的精妙之处在于,它将这三项检查结果,整合到了一张图上。图中每一个点代表一个模型(或一次模拟),它的位置由三个信息共同决定:
* **径向距离(点到原点的距离)**:代表该模型预测值的**标准差**。距离原点越远,说明模型的预测值自身波动越大。
* **极角(点与正东方向的夹角)**:代表该模型预测值与观测值之间的**相关系数**。角度越小(越靠近正东方向),相关系数越接近1,表示趋势一致性越好。
* **到参考点的距离**:参考点(通常位于x轴上,代表观测值)到模型点的**直线距离**,在特定刻度下,可以代表**均方根误差(RMSE)**。距离参考点越近,RMSE越小,模型整体精度越高。
因此,一个“完美”的模型点,应该落在参考点上(RMSE=0),或者尽可能靠近它。在实际图中,我们通常会看到一组代表不同模型或不同配置的散点,通过它们与参考点的相对位置,我们能迅速判断孰优孰劣。
为了更直观地理解这三个指标在泰勒图上的几何关系,可以参考下面的对照表:
| 评估指标 | 在泰勒图中的体现 | 几何意义 | 理想情况 |
| :--- | :--- | :--- | :--- |
| **相关系数 (r)** | 点的极角(方位角) | 趋势一致性 | 角度为0°(r=1),指向正东 |
| **标准差 (σ)** | 点到原点的径向距离 | 自身波动幅度 | 与参考点(观测值)的径向距离相同 |
| **均方根误差 (RMSE)** | 点到参考点的直线距离 | 综合误差大小 | 距离为0(与参考点重合) |
> **提示**:泰勒图假设观测值是“真理”,因此参考点固定在x轴上,其标准差定义了坐标系的基准。所有模型点都围绕这个基准进行比较。
理解了这些,你就知道我们写代码的目标是什么:计算出每个模型的这三个统计量,然后按照上述规则,将它们准确地映射到极坐标图上。接下来,我们就进入实战环节。
## 2. 环境准备与核心工具选择
工欲善其事,必先利其器。为了高效绘制泰勒图,我们需要一个稳定且功能强大的Python环境。这里我推荐使用 **Anaconda** 来管理环境,它能很好地解决包依赖问题。
### 2.1 创建专属的绘图环境
打开你的终端(Windows用户请使用Anaconda Prompt),执行以下命令来创建一个新的、干净的虚拟环境,避免与已有项目的包版本冲突。
```bash
# 创建一个名为 taylor_plot 的新环境,并指定 Python 版本
conda create -n taylor_plot python=3.9 -y
# 激活这个环境
conda activate taylor_plot
```
### 2.2 安装必备的Python库
激活环境后,我们需要安装几个核心库。这里有一个关键选择:是使用现成的第三方库(如 `skill_metrics`)来快速出图,还是基于 `matplotlib` 和 `numpy` 从头构建以获得最大灵活性?
对于追求“5分钟搞定”的目标,我强烈推荐使用 `skill_metrics` 库。它是一个专门为气象、海洋等领域模型评估设计的工具箱,其中包含一个非常成熟的 `taylor_diagram` 函数,能处理大部分繁琐的绘图细节。当然,我们也会安装数据分析的黄金搭档。
使用pip一键安装所有所需库:
```bash
pip install numpy pandas matplotlib scipy skill_metrics openpyxl
```
* `numpy`, `pandas`: 数据计算和处理的基石。
* `matplotlib`: 绘图的核心引擎。
* `scipy`: 用于计算统计量(如相关系数),`skill_metrics` 库可能会依赖它。
* `skill_metrics`: 今天的“主角”,它提供了绘制泰勒图的直接函数。
* `openpyxl`: 用于读取Excel格式的数据文件。
安装完成后,你可以在Python中导入它们以验证是否成功:
```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import skill_metrics as sm
print("所有库已就绪!")
```
如果没有任何报错,那么你的绘图武器库就已经装备完毕了。
## 3. 数据准备:构建正确的输入格式
数据格式是新手最容易出错的地方。`skill_metrics` 库的 `taylor_diagram` 函数需要一组特定的统计量数组作为输入,而不是原始数据。我们需要先计算出每个模型的 **标准差(SDEV)**、**中心化均方根误差(CRMSD)** 和 **相关系数(CCOEF)**。
### 3.1 理解核心统计量的计算
假设我们有一组观测值 `ref` 和 N 个模型的预测值 `pred_1`, `pred_2`, ..., `pred_N`。对于每一个模型,我们需要计算相对于观测值的三个量:
1. **标准差 (SDEV)**: 即预测值序列自身的标准差。`σ_pred = np.std(pred)`
2. **相关系数 (CCOEF)**: 预测值与观测值之间的皮尔逊相关系数。`r = np.corrcoef(pred, ref)[0, 1]`
3. **中心化均方根误差 (CRMSD)**: 这是去除了均值偏差后的RMSE,其平方等于 `CRMSD² = σ_pred² + σ_ref² - 2 * σ_pred * σ_ref * r`。它衡量的是去除系统偏差后的模式误差。
`skill_metrics` 库提供了一个便捷函数 `taylor_statistics` 来一次性计算这些值。它的工作方式是:**成对处理**。你给它一对 `(预测序列, 观测序列)`,它返回一个包含 `sdev`, `crmsd`, `ccoef` 等键的字典。
### 3.2 Excel数据组织实战
让我们通过一个具体的Excel文件例子来理解。假设你有一个名为 `model_comparison.xlsx` 的文件,里面记录了某个变量(如温度、流量)在不同时间步长的观测值和三个不同模型的预测值。
你的Excel表格应该这样组织:
| time_step | ref (观测值) | model_A | model_B | model_C |
| :--- | :--- | :--- | :--- | :--- |
| 1 | 15.2 | 14.8 | 15.5 | 16.1 |
| 2 | 16.5 | 16.1 | 17.0 | 15.9 |
| 3 | 17.8 | 17.5 | 18.2 | 17.0 |
| ... | ... | ... | ... | ... |
**关键点**:
* `ref` 列是基准,必须存在。
* `model_A`, `model_B`, `model_C` 等是你需要评估的预测序列。列名可以自定义。
* 确保没有缺失值(NaN),否则计算统计量时会出错。
### 3.3 从数据到绘图数组的代码转换
有了格式正确的数据文件,我们就可以用Python读取并转换为 `taylor_diagram` 函数需要的数组格式。下面这段代码你可以直接作为模板使用:
```python
import pandas as pd
import numpy as np
import skill_metrics as sm
# 1. 读取数据
df = pd.read_excel('model_comparison.xlsx') # 替换为你的文件路径
# 假设你的列名是 'ref', 'model_A', 'model_B', 'model_C'
obs = df['ref'].values
pred_models = ['model_A', 'model_B', 'model_C'] # 列出所有需要评估的模型列名
# 2. 初始化空列表,用于收集每个模型的统计量
all_sdev = [np.std(obs)] # 第一个是观测值的标准差
all_crmsd = [0.0] # 观测值与自身的CRMSD为0
all_ccoef = [1.0] # 观测值与自身的相关系数为1
# 3. 循环计算每个模型的统计量
for model_name in pred_models:
pred = df[model_name].values
# 使用 skill_metrics 库的函数计算统计量
stats = sm.taylor_statistics(pred, obs, 'data')
# stats['sdev'] 是一个包含 [观测值标准差, 预测值标准差] 的列表
all_sdev.append(stats['sdev'][1])
all_crmsd.append(stats['crmsd'][1])
all_ccoef.append(stats['ccoef'][1])
# 4. 转换为 numpy 数组,这是绘图函数要求的输入格式
sdev_array = np.array(all_sdev)
crmsd_array = np.array(all_crmsd)
ccoef_array = np.array(all_ccoef)
print("标准差数组:", sdev_array)
print("CRMSD数组:", crmsd_array)
print("相关系数数组:", ccoef_array)
```
运行这段代码后,`sdev_array`, `crmsd_array`, `ccoef_array` 这三个数组就是绘制泰勒图的最终输入数据。数组的第一个元素总是对应观测值(参考点),后续元素对应各个模型。
## 4. 核心绘图与深度定制
万事俱备,只欠绘图。`skill_metrics.taylor_diagram` 函数是核心,但默认的图形可能不符合你的报告或论文要求。下面我们从基础绘图开始,逐步深入到个性化定制。
### 4.1 绘制你的第一张泰勒图
使用上一节准备好的三个数组,绘制基础泰勒图只需要一行代码:
```python
import matplotlib.pyplot as plt
from matplotlib import rcParams
# 可选:设置图形全局参数,让图更美观
rcParams['figure.figsize'] = [8, 8] # 图形大小
rcParams['font.size'] = 12
rcParams['axes.linewidth'] = 1.5
# 核心绘图函数
sm.taylor_diagram(sdev_array, crmsd_array, ccoef_array)
# 添加标题
plt.title('Model Performance Comparison - Taylor Diagram', fontsize=16, pad=20)
plt.show()
```
执行后,你应该能看到一张标准的泰勒图。坐标轴上有标准差的刻度,从原点发出的射线是相关系数线,而那些以参考点为圆心的虚线弧则是等RMSE线。你的模型点会以默认的标记(如圆圈)显示在图上。
### 4.2 个性化定制:让图表会说话
默认图表可能有些简陋。`taylor_diagram` 函数提供了大量参数供我们定制。下面是一个综合性的定制示例,展示了如何修改颜色、标记、标签等:
```python
# 准备标签:顺序需与数组顺序一致(第一个是观测值‘REF’)
model_labels = ['REF', 'Model A', 'Model B', 'Model C']
fig = plt.figure(figsize=(10, 10))
sm.taylor_diagram(
sdev_array,
crmsd_array,
ccoef_array,
# 标记与样式
markerLabel = model_labels, # 为每个点添加标签
markerLegend = 'on', # 显示图例
markerColor = ['r', 'g', 'b', 'm'], # 为每个点指定颜色:红、绿、蓝、洋红
markerSize = 12, # 标记大小
markerSymbol = ['o', 's', '^', 'D'], # 标记形状:圆、方、三角、菱形
# 坐标轴与刻度
tickSTD = np.arange(0, 2.5, 0.5), # 设置标准差的刻度范围:0到2.5,步长0.5
axismax = 2.0, # 坐标轴最大显示范围(基于标准差)
# 等RMSD线(虚线弧)
tickRMS = np.arange(0.2, 1.6, 0.4), # 设置RMSD刻度
colRMS = 'gray', # RMSD线颜色
styleRMS = '--', # RMSD线样式:虚线
widthRMS = 1.5, # RMSD线宽
titleRMS = 'on', # 显示RMSD刻度标签
# 相关系数射线
tickCOR = [0.1, 0.3, 0.5, 0.7, 0.9, 0.95, 0.99], # 设置相关系数刻度
colCOR = 'lightblue', # 相关系数线颜色
styleCOR = ':', # 相关系数线样式:点线
widthCOR = 1.0,
# 标准差轴(实线圆)
colSTD = 'black',
styleSTD = '-',
widthSTD = 2.0,
)
# 进一步使用matplotlib美化
plt.title('Customized Taylor Diagram: Model Evaluation', fontsize=18, fontweight='bold', pad=25)
plt.grid(True, alpha=0.3, linestyle='-') # 添加浅色网格
plt.tight_layout() # 自动调整布局
plt.show()
```
通过调整这些参数,你可以让泰勒图完全匹配你的出版物或演示文稿的风格要求。
### 4.3 进阶技巧:处理多组数据与子图
有时我们需要比较多个实验场景或不同时间段的结果。将多个泰勒图并排展示是很好的选择。我们可以利用matplotlib的子图功能来实现:
```python
# 假设我们有两组数据,已经计算好数组:group1_arrays, group2_arrays
# (sdev1, crmsd1, ccoef1), (sdev2, crmsd2, ccoef2)
labels = ['REF', 'Model X', 'Model Y']
fig, axes = plt.subplots(1, 2, figsize=(16, 8), subplot_kw={'projection': 'polar'}) # 创建1行2列的子图
# 绘制第一个泰勒图到左轴
sm.taylor_diagram(sdev1, crmsd1, ccoef1,
markerLabel=labels,
markerLegend='on',
tickSTD=np.arange(0, 3, 0.5),
axismax=2.5,
title='Scenario 1: Baseline',
ax=axes[0]) # 关键:指定axes[0]作为绘图区域
# 绘制第二个泰勒图到右轴
sm.taylor_diagram(sdev2, crmsd2, ccoef2,
markerLabel=labels,
markerLegend='on',
tickSTD=np.arange(0, 3, 0.5),
axismax=2.5,
title='Scenario 2: With Optimization',
ax=axes[1]) # 指定axes[1]
plt.suptitle('Comparative Analysis of Two Scenarios', fontsize=20, y=1.05)
plt.tight_layout()
plt.show()
```
关键点在于 `ax` 参数,它允许我们将泰勒图绘制到指定的matplotlib坐标轴上,从而实现灵活的排版。
## 5. 避坑指南与常见问题排查
即使按照步骤操作,你也可能会遇到一些问题。这里我总结了一些常见的“坑”及其解决方案,希望能帮你快速排雷。
### 5.1 安装与导入问题
* **问题:`ModuleNotFoundError: No module named 'skill_metrics'`**
* **原因**:最常见的原因是库没有正确安装,或者你在一个没有安装该库的Python环境中运行代码。
* **解决**:
1. 确认你已激活正确的Conda环境(`conda activate taylor_plot`)。
2. 在终端中运行 `pip list | grep skill` 查看是否已安装。
3. 如果未安装,尝试使用 `pip install skill_metrics --upgrade`。如果还不行,可以尝试从GitHub源码安装:`pip install git+https://github.com/PeterRochford/skill_metrics.git`
* **问题:导入skill_metrics时警告或报错**
* **原因**:可能是库的版本与你的Python或numpy版本不兼容。
* **解决**:尝试创建一个新的Python 3.8或3.9环境重新安装。老版本的库对新版Python支持可能不佳。
### 5.2 数据与计算问题
* **问题:绘制的图上点全部挤在角落或位置明显不对**
* **原因**:输入给 `taylor_diagram` 的三个数组 `sdev`, `crmsd`, `ccoef` 的顺序或内容有误。
* **排查**:
1. **检查顺序**:确保三个数组是一一对应的,并且第一个元素都是观测值(REF)。
2. **检查数值范围**:打印出数组看看。相关系数 `ccoef` 应在[-1, 1]之间,标准差 `sdev` 应为正数。如果 `ccoef` 绝对值大于1,说明计算可能出错。
3. **检查计算函数**:确保你使用的是 `sm.taylor_statistics(pred, obs, 'data')` 并正确提取了索引为 `[1]` 的预测值统计量。
* **问题:`taylor_statistics` 函数返回NaN值**
* **原因**:输入的数据序列 `pred` 或 `obs` 中存在缺失值(NaN)或所有值都相同(标准差为0)。
* **解决**:
1. 在计算前清洗数据:`df.dropna(inplace=True)`
2. 检查数据是否恒定不变:`if np.std(pred) == 0: print("警告:预测值标准差为0!")`
### 5.3 图形显示与保存问题
* **问题:图形上的文字标签重叠或显示不全**
* **解决**:
1. 调整图形大小:`plt.figure(figsize=(12,12))`
2. 调整标签位置:`taylor_diagram` 函数有些版本支持 `markerLabelColor` 和 `markerLabelWeight`,但更直接的方法是使用matplotlib的 `text` 函数事后手动调整,不过这需要更精细的控制。
3. 最简单的办法是减少标签数量,或者只标注关键模型。
* **问题:如何保存高清图片?**
* **解决**:在 `plt.show()` 之前使用 `plt.savefig()`。
```python
plt.savefig('taylor_diagram.png', dpi=300, bbox_inches='tight', facecolor='white')
```
* `dpi=300` 设置高分辨率。
* `bbox_inches='tight'` 自动裁剪白边。
* `facecolor='white'` 确保背景为白色。
> **注意**:`skill_metrics` 库的不同版本,函数参数可能略有差异。如果你遇到参数未识别的错误,可以查看库的源码或帮助文档(`help(sm.taylor_diagram)`)来确认当前版本支持的参数名。
掌握了这些核心步骤和避坑技巧,你应该可以独立完成从数据准备到生成定制化泰勒图的全过程。整个流程的核心在于理解数据格式要求和统计量的计算逻辑,一旦打通这个环节,绘图本身只是一行函数调用的事。在实际项目中,我习惯将数据计算和绘图封装成函数,这样只需要准备好DataFrame,就能一键生成评估图表,极大提升了分析效率。