最近开始要对模式数据和模型结果进行评估。泰勒图(Taylor_diagram)是一种很好的展现数据的std(标准差)、r(相关系数)以及(RMSE)均方根误差的一种图。
泰勒图怎么看
1. 坐标系:图中使用极坐标系表示不同模型与参考数据之间的相似性。
2. 标准差(std):每个点的半径表示模型输出的标准差,越远离原点表示标准差越大。
3. 相关系数(r):点与原点之间的夹角表示模型输出与参考数据之间的相关系数,越接近0°表示相关性越强。
4. 均方根误差(RMSE):点与参考点的距离表示模型输出与参考数据之间的CRMSE(中心均方根误差),距离越短表示模型表现越好。
需要注意的是,之前一直没注意到,Taylor图里的RMSE代表的是中心均方根误差(Centering RMSE, CRMSE)、它并未考虑两者间的总体偏差。RMSE公式如下:
而实际上的均方根误差(Root Mean Square Error, RMSE)和中心均方根误差(Centering RMSE, CRMSE)的关系为:
这其中,CRMSE可以由观测值的标准差、模型的标准差以及模型与观测值的相关系数算出,公式如下:
而Bias代表整体偏差,表示为:
由以上公式可以推导得出:
sklearn.metrics库
Python有一个自带的sklearn.metrics库可以很简单的画出Taylor图。详细的参数设置可以见我前面的文章:
但SM库无法对各个图例进行设置,参数的设置比较少,于是打算自己写一个绘制的代码:
具体参考了一部分 https://zenodo.org/records/5548061
代码部分
代码如下:
import matplotlib.pyplot as plt
import numpy as np
#定义字体大小
plt.rcParams['font.family'] = 'Times New Roman'
plt.rcParams['font.size'] = 14
# 定义Taylor图类
class TaylorDiagram:
def __init__(self, refstd, fig=None, rect=111, label='Reference', srange=(0, 2),
linestyle1=None,linestyle2=None,extend=False, **kwargs):
self.refstd = refstd#参考模型的标准差
self.fig = fig or plt.figure()#创建fig
self.ax = self.fig.add_subplot(rect, polar=True, label=label, **kwargs)#子图
self.srange=srange#图形边界
self.rect=rect
if extend:#定义是否有r为负值的情况
self.tmax=np.pi
else:
self.tmax=np.pi/2
# 限制角度范围为四分之一圆 (0 到 90 度, 也就是 π/2 弧度)
self.ax.set_ylim(0, srange[1]*self.refstd) # 0-2
self.ax.set_xlim(0, self.tmax) # 限制到四分之一圆
# 手动输入需要确定相关系数(若有负相关需要再次设置)
corr_ticks = [1.47,1.369,1.266,1.159,1.047,0.927,0.795,0.6435,0.451,0.3176,0.1415,0]
corr_labels = np.round(np.cos(corr_ticks), 2) # cos(theta) 对应相关系数
self.ax.set_thetagrids(np.degrees(corr_ticks), labels=corr_labels)
# 单独设置x网格线样式
self.ax.xaxis.grid(color='k', linestyle=linestyle1, linewidth=1)
# 单独设置y网格线样式
self.ax.yaxis.grid(color='k', linestyle=linestyle2, linewidth=1)
# 设置标准差刻度
self.ax.set_rgrids(np.arange(srange[0]*self.refstd, (srange[1]+0.1)*self.refstd,
0.5*self.refstd), angle=0)
list=np.round(np.arange(srange[0]+0.5, srange[1]+0.1, 0.5)*self.refstd,2)
for i in range(4):
self.ax.text(np.pi/2, np.round(0.5*(i+1)*self.refstd,2), f'{list[i]}\n', ha='center', va='center',
fontsize=14,rotation=90)
#自定义CRMSE标值
a=[0.4,0.79,1.1,1.43]
b=[1.25,1.4,1.65,1.88]
rot=[0,5,15,25]
for i in range(4):
self.ax.text(a[i], np.round(b[i]*self.refstd,2), f'{list[i]}\n', ha='center', va='center',
fontsize=14,rotation=rot[i],c='red',fontweight='bold')
# 添加"Correlation Coefficient"标签
text_n = 'Correlation Coefficient' # 标签文本
n_text = len(text_n)
theta = np.linspace(np.pi / 8 * 3, np.pi / 8, n_text) # 改为60度范围
r = np.full_like(theta, self.refstd + 1.2*self.refstd) # 稍微向外移动标签
for i in range(n_text):
self.ax.text(theta[i], r[i], text_n[i], ha='center', va='center',
rotation=np.degrees(-theta[n_text-1-i]), fontsize=16, fontweight='bold')
# 添加"CRMSE"标签
text_n = 'CRMSE' # 标签文本
n_text = len(text_n)
thetaa =[0.4476,0.5325,0.565,0.5735,0.56]
r=[0.58,0.68,0.785,0.895,0.99]
theta_n=[1.1,0.86,0.5925,0.532,0.4005]
for i in range(n_text):
self.ax.text(thetaa[i], r[i]*self.refstd, text_n[i], ha='center', va='center',
rotation=np.degrees(theta_n[i]), fontsize=16, fontweight='bold')
# 绘制1.0obs的标准弧线并加粗
r_1 = np.full(100, 1.0)*self.refstd # 半径为1.0
theta = np.linspace(0, np.pi/2, 100)
self.ax.plot(theta, r_1, 'b-', linewidth=1.5) # 设置弧线为红色,粗2个单位
self.ax.text(-0.2, 0.8*self.refstd, 'Observation', c='blue', fontweight='bold', font='Times New Roman', fontsize=14) # 设置弧线为红色,粗2个单位
def add_sample(self, stddev, corrcoef, marker=None, color=None, label=None,markersize=None):#打点
r = stddev
theta_plt = np.arccos(np.clip(corrcoef, -1, 1)) # 确保相关系数在[-1, 1]范围内
self.ax.plot(theta_plt, r, marker=marker, color=color, label=label,markersize=markersize)#打点模式
# 打点观测数据
self.ax.plot(np.arccos(1), self.refstd, marker='^', color='r', label='Obs',markersize=16,zorder=2)
def add_contours(self, **kwargs):#画RMSE
rs, ts = np.meshgrid(np.linspace(self.srange[0]*self.refstd,
self.srange[1]*self.refstd, 100), np.linspace(0, self.tmax, 100))
# rms = np.sqrt(1 + rs ** 2 - 2 * rs * np.cos(ts))
rms=np.sqrt(self.refstd**2 +rs**2-2*self.refstd*rs*np.cos(ts))
contour=self.ax.contour(ts, rs, rms, levels=np.round([0.5*float(self.refstd),1.0*float(self.refstd),
1.5*float(self.refstd),2.0*float(self.refstd)],2), **kwargs)
def __str__(self): # 提取模型参数
return f" ---------------Parameters--------------\n" \
f" Range:{self.tmax} STD(obs):{self.refstd} \n " \
f"Boundary:{self.srange} Ax_Position :{self.rect}"
# ----绘制Taylor图的函数,支持传入samples和观测std---
def plot_taylor_diagram(samples,obs_std):
fig = plt.figure(figsize=(8, 6))#创建绘图
#输入标准化后的std,即观测std=1
dia = TaylorDiagram(obs_std, fig=fig,linestyle1='-.',linestyle2='--')#refstd,fig
print(dia)#打印类的参数
# 添加样本数据
for stddev, corrcoef, marker, color, label in samples:
dia.add_sample(stddev, corrcoef, marker=marker, color=color, label=label)
# 添加等值线(设置RMSE线的ls)
dia.add_contours(colors='red', linestyles='--')#RMSE
# 添加图例,显示marker和颜色
handles = [plt.Line2D([0], [0], marker=marker, color='w', markerfacecolor=color, markersize=10, label=label)
for _, _, marker, color, label in samples]
# 在handles的第一个位置插入Obs图例
handles.insert(0, plt.Line2D([0], [0], marker='^', color='w', markerfacecolor='r', markersize=10,
label='Obs'))
# 调整图例位置 (1.42, 1.1)x和y位置
plt.legend(handles=handles, loc='upper right', bbox_to_anchor=(1.42, 1.1), frameon=False,fontsize=12)
#y轴标签
plt.ylabel('Standard Deviation',x=-0.1, fontweight='bold', font='Times New Roman', fontsize=14)
plt.tight_layout()
plt.show()
# # 示例:定义样本数据
# #std,cor,marker,markerfacecolor,model_name
# samples = [
# (0.8, 0.9, 'o', 'green', 'Sample 1'),
# (1.2, 0.95, 's', 'blue', 'Sample 2'),
# (1.5, 0.85, 'D', 'red', 'Sample 3'),
# (0.9, 0.7, '*', 'orange', 'Sample 4')
# ]
# # 执行绘图
# plot_taylor_diagram(samples,1.0)#输入:样本以及obs的标准差
结果展示
以上是示例数据的结果,同时我们还能对这个代码进行一个封装,并进行调用:
from taylor_diagram import plot_taylor_diagram
import numpy as np
obs_std=5.0
std=[6.2,3.5,4.4,6.3,5.2,4.2,6.9,2.2,2.9,4.6,3.7,5.3,6.6]
r=[0.80,0.77,0.69,0.89,0.57,0.88,0.85,0.74,0.72,0.66,0.55,0.76,0.83]
namelist=[f'Model {i+1}' for i in range(13)]
'''
# 参数设置参考
kind = ['+', 'o', 'x', 's', 'd', '^', 'v', 'p', 'h', '*']
colorm = ['r', 'b', 'g', 'c', 'm', 'y', 'k', 'gray']
'''
#自定义markermap和colormap
markermap=['o','s','d','*','v','o','^','p','h','d','D', 'v', 'p','^']
colormap=['g','b','r','orange','b', 'orange', 'c', 'm', 'y','gray','brown','purple','pink','k']
samples=[(std[i],r[i],markermap[i],colormap[i],namelist[i]) for i in range(len(r))]
# 执行绘图
plot_taylor_diagram(samples,np.round(obs_std,2))#样本以及obs的标准差
结果如下: