2022-09-24

Add legend entry as label to each box of grouped and colored boxplot

I have a boxplot that groups the data on the x-axis and also by coloring them according to a categorical value. Since I have lot of categories that have to be differentiated I wanted to have a label on each box (instead of just having the legend and than trying to match colors to label). I eventually managed, but the solution seems a bit hacky and I wondered if there is a simple solution to that problem. My solution includes finding the position of each box, convert the data coordinates of the bboxes to axis coordinates. Then match the color of each box with the color of the legend and add this legend entry as a text at the found box position. Is there a more convenient / easier solution? Here is the code:

import random
import numpy as np
import pandas as pd
import seaborn as sns
from matplotlib import pyplot as plt
import matplotlib

#make a dataframe with some random data
values=random.choices(np.arange(0, 100), k=1000)
typus=random.choices([0,1,2,3], k=1000)
month=random.choices([0, 1,2,3,4,5,5,6,7,8,8,9,10, 11, 12], k=1000)

data=pd.DataFrame(list(zip(values, typus, month)), columns=['value', 'type', 'month'])

fig, ax = plt.subplots(figsize = (15, 10))
box_plot=sns.boxplot(data=data, x='type', y='value', hue='month',ax=ax)
plt.legend(bbox_to_anchor=(1.04, 1), loc="upper left")

#take the legend, and put label and color into a dictionary to look it up later
handles, labels = box_plot.get_legend_handles_labels()
color_label_dict={}
for i, label in enumerate(labels):
    color_label_dict.update({label:handles[i].get_facecolor()})

#loop through the boxes
for c in box_plot.get_children():
    if type(c) == matplotlib.patches.PathPatch:
        #get the color of a box and match it with the label from the legend
        label_of_box = list(color_label_dict.keys())[list(color_label_dict.values()).index(c.get_facecolor())]
        
        #add the matched label as a text close to the position of the box
        box_plot.text((c.get_extents().transformed(ax.transData.inverted()).extents[0]+c.get_extents().transformed(ax.transData.inverted()).extents[2])/2,
            c.get_extents().transformed(ax.transData.inverted()).extents[3]-4,label_of_box,
            horizontalalignment='center',size=8,color='w',weight='bold',rotation=90)

enter image description here



No comments:

Post a Comment