泰勒图(Taylor_diagram)

最近开始要对模式数据和模型结果进行评估。泰勒图(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图。详细的参数设置可以见我前面的文章:

泰勒图绘制-评估_matplotlib 泰勒图-优快云博客

但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的标准差

结果如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值