diff --git a/visualisation_model.py b/visualisation_model.py new file mode 100644 index 0000000000000000000000000000000000000000..0d2e61d61ff2af4e320d1a764f8d5135ebf95a6e --- /dev/null +++ b/visualisation_model.py @@ -0,0 +1,167 @@ +import numpy as np +import pandas as pd +import matplotlib.pyplot as plt +from matplotlib.gridspec import GridSpec + +class visualisation_model: + x_end = 2*np.pi + spaced_labels=[] + spaced_y_1=[] + spaced_y_0=[] + spaced_x_angles=[] + x_angles=[] + x_positions=[] + n=0 + width=0 + def __init__(self, labels,y,y_1,y_0,IGQRs,group_1,group_0,title,path,group_1_colour='yellow',group_0_colour='midnightblue',at_least_t_colour='lightblue',lower_t_colour='magenta',quality_threshold=0.8,IGQR_threshold=0.8,optimised_performance_measure='Accuracy'): + self.labels=labels + self.n = len(labels) + self.width = 2*np.pi/(2*self.n) + self.y=y + self.y_1=y_1 + self.y_0=y_0 + self.IGQRs=IGQRs + self.group_1=group_1 + self.group_0=group_0 + self.title = title + self.path = path + self.quality_threshold = quality_threshold + self.IGQR_threshold=IGQR_threshold + self.group_1_colour=group_1_colour + self.group_0_colour=group_0_colour + self.at_least_t_colour=at_least_t_colour + self.lower_t_colour=lower_t_colour + self.optimised_performance_measure = optimised_performance_measure + + + def create_final_visualisation(self): + self.spacing_and_positioning() + fig,axs = self.axs_creator() + axs[0],axs[1]=self.create_circular_barplots(fig, axs[0], axs[1]) + plt.savefig(self.path) + plt.show() + + + + def spacing_and_positioning(self): + ''' + Spaces between the bars are added and the visual hint of the optimised measure is created + + ''' + self.spaced_labels=list(np.zeros(2*self.n)) + self.spaced_y_1=list(np.zeros(2*self.n)) + self.spaced_y_0=list(np.zeros(2*self.n)) + for i in range(0,self.n): + self.spaced_y_1[2*i]=self.y_1[i] + self.spaced_y_0[2*i]=self.y_0[i] + self.spaced_labels[2*i-1]='' + if self.labels[i]== self.optimised_performance_measure: + self.labels[i]='★ ' +self.labels[i] + self.spaced_labels[2*i]=self.labels[i] + self.x_positions= np.linspace(0, self.x_end,2*self.n,endpoint=False) + self.spaced_x_angles = [i * self.width for i in range(0,2*self.n)] + self.x_angles= [self.spaced_x_angles[2*i] for i in range(0,self.n)] + + def axs_creator(self): + ''' + Creation of the parallel layout of the sun plot and the IGQR plot + + ''' + fig = plt.figure(figsize=(10,5),layout='constrained') + fig.suptitle(self.title) + axs=[] + gs = GridSpec(1, 2, figure=fig,hspace=0.0,wspace=0.1) + axs.append(fig.add_subplot(gs[0, 0],projection='polar')) + axs.append(fig.add_subplot(gs[0, 1],projection='polar')) + maxs_value = 1.0 + for ax in axs: + ax.grid(visible=True, axis='y', linewidth=0.75) + ax.set_xticks(self.x_positions,self.spaced_labels) + ax.tick_params(axis='x',pad=10) + ax.tick_params(axis='y',top=True) + ax.set_rlim(0, maxs_value) + ax.set_rlabel_position(180) + ax.set_rorigin(-0.25) + ax.set_theta_zero_location('S') + return fig,axs + + def create_circular_barplots(self,fig,ax1,ax2): + xmins = [self.spaced_x_angles[i]-self.width/2 for i in range(0,2*self.n)] + xmaxs = [xmins[i]+self.width for i in range(0,2*self.n)] + ax1=self.create_quality_circular_barplot(xmins,xmaxs,ax1) + ax2=self.create_IGQR_circular_barplot(xmins,xmaxs,ax2) + return ax1,ax2 + + def create_quality_circular_barplot(self,xmins,xmaxs,ax1): + ax1.set_title('Quality Measures',pad=20) + #Creation of the red dashed threshold line + aimed_qualities = [self.quality_threshold for i in range(0,2*self.n)] + ax1.hlines(y=aimed_qualities,xmin=xmins,xmax=xmaxs,color='red',linewidth=2,linestyle='--') + #Creation of the star plots + ax1.plot(self.x_angles, self.y_0, alpha=0.2, color=self.group_0_colour, linewidth=0.5, linestyle='solid') + ax1.fill(self.x_angles, self.y_0, self.group_0_colour, alpha=0.25) + ax1.plot(self.x_angles, self.y_1, alpha=0.2, color=self.group_1_colour, linewidth=0.5, linestyle='solid') + ax1.fill(self.x_angles, self.y_1, self.group_1_colour, alpha=0.25) + + #Creation of the overlapping bars + for i in range(0,self.n): + #Encoding of invalid values as red and bold ticks; No bar is created + if self.spaced_y_0[2*i]==-1 or self.spaced_y_1[2*i]==-1: + ax1.get_xticklabels()[2*i].set_color('red') + ax1.get_xticklabels()[2*i].set_fontweight('bold') + + else: + #Creation of the overlapping impression by building the smaller bar normally and + #stacking the difference of both values on the smaller bar + if self.spaced_y_0[2*i]>=self.spaced_y_1[2*i]: + ax1.bar(self.spaced_x_angles[2*i],self.spaced_y_1[2*i],width=self.width,color=self.group_1_colour,edgecolor='black') + ax1.bar(self.spaced_x_angles[2*i],self.spaced_y_0[2*i]-self.spaced_y_1[2*i],bottom=self.spaced_y_1[2*i],width=self.width,color=self.group_0_colour,edgecolor='black') + else: + ax1.bar(self.spaced_x_angles[2*i],self.spaced_y_0[2*i],width=self.width,color=self.group_0_colour,edgecolor='black') + ax1.bar(self.spaced_x_angles[2*i],self.spaced_y_1[2*i]-self.spaced_y_0[2*i],bottom=self.spaced_y_0[2*i],width=self.width,color=self.group_1_colour,edgecolor='black') + #Legend + legend_labels = {self.group_1_colour: self.group_1, self.group_0_colour: self.group_0} + legend_handles = [plt.Line2D([0], [0], marker='o', color='w', label=label, markerfacecolor=color, markersize=10) + for color, label in legend_labels.items()] + threshold_line_handle = plt.Line2D([0], [0], color='red', lw=2, linestyle='--', label='quality threshold: '+str(self.quality_threshold)) + legend_handles.append(threshold_line_handle) + ax1.legend(handles=legend_handles,loc='upper center',bbox_to_anchor=(.25 + np.cos(np.deg2rad(247.5))/2, .5 + np.sin(np.deg2rad(247.5))/2),frameon=False) + return ax1 + + def create_IGQR_circular_barplot(self,xmins,xmaxs,ax2): + ax2.set_title('IGQR Values',pad=20) + #Creation of the red dashed threshold line + border_IGQRs = [self.IGQR_threshold for i in range(0,2*self.n)] + ax2.hlines(y=border_IGQRs,xmin=xmins,xmax=xmaxs,color='red',linewidth=2,linestyle='--') + #Creation of the IGQR bars and the color encoding + for i in range(0,self.n): + IGQR_impact = self.IGQRs[i] + if IGQR_impact>=0.8: + ax2.bar(self.spaced_x_angles[2*i],IGQR_impact,width=self.width,color=self.at_least_t_colour,edgecolor='black') + else: + #Encoding of invalid values as red and bold ticks; No bar is created + if IGQR_impact==-1: + ax2.get_xticklabels()[2*i].set_color('red') + ax2.get_xticklabels()[2*i].set_fontweight('bold') + + else: + ax2.bar(self.spaced_x_angles[2*i],IGQR_impact,width=self.width,color=self.lower_t_colour,edgecolor='black') + #Legend + legend_labels = {self.at_least_t_colour: 'IGQR ≥ '+ str(self.IGQR_threshold), self.lower_t_colour: 'IGQR < '+str(self.IGQR_threshold)} + legend_handles = [plt.Line2D([0], [0], marker='o', color='w', label=label, markerfacecolor=color, markersize=10) + for color, label in legend_labels.items()] + threshold_line_handle = plt.Line2D([0], [0], color='red', lw=2, linestyle='--', label='IGQR threshold: '+str(self.IGQR_threshold)) + legend_handles.append(threshold_line_handle) + ax2.legend(handles=legend_handles,loc='upper center',bbox_to_anchor=(.25 + np.cos(np.deg2rad(247.5))/2, .5 + np.sin(np.deg2rad(247.5))/2),frameon=False) + return ax2 + + def get_table(self): + ''' + Representation of the visualisation data as table plus the general quality measure values + ''' + table = pd.DataFrame(columns=self.labels,index=['total',self.group_1,self.group_0,'IGQR']) + table.loc['total']=[round(self.y[j],2) for j in range(self.n)] + table.loc[self.group_1]=[round(self.y_1[j],2) for j in range(self.n)] + table.loc[self.group_0]=[round(self.y_0[j],2) for j in range(self.n)] + table.loc['IGQR']=[round(IGQR_impact,2) for IGQR_impact in self.IGQRs] + return table