Seaborn heatmap fit annotation text to cell

Viewed 454

I have this code that displays a confusion matrix. In each cell, first the accuracy is displayed then under it the number of correct predicted samples/total samples. Now I want to display all the text inside each cell. First cell for exemple should displays 186/208 under the accuracy. How can I show the whole text of annotations inside the cell ? I tried to reduce the font size but it did not work.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

def cm_analysis(cm, labels, figsize=(20,15)):
    cm_sum = np.sum(cm, axis=1, keepdims=True)
    cm_perc = cm / cm_sum.astype(float)
    annot = np.empty_like(cm).astype(str)
    nrows, ncols = cm.shape
    for i in range(nrows):
        for j in range(ncols):
            c = cm[i, j]
            p = cm_perc[i, j]
            if i == j:
                s = cm_sum[i]
                annot[i, j] = '%.2f%%\n%d/%d' % (p, c, s)
            elif c == 0:
                annot[i, j] = ''
            else:
                annot[i, j] = '%.2f%%\n%d' % (p, c)
    cm = pd.DataFrame(cm, index=labels, columns=labels)
    cm.index.name = 'Groundtruth labels'
    cm.columns.name = 'Predicted labels'
    fig, ax = plt.subplots(figsize=figsize)
    ax.axhline(color='black')

    g =sns.heatmap(cm, cmap="BuPu", annot_kws={"weight": "bold"}, annot=annot, fmt='', ax=ax, cbar_kws={'label': 'Number of samples'}, linewidths=0.1, linecolor='black')
    g.set_xticklabels(g.get_xticklabels(), rotation = 45)
    sns.set(font_scale=1.1)
    plt.savefig("filename.png")

normalised_confusion_matrix  = np.array(
[[186,3,0,1,2,0,3,3,7,1,2,0,0],
 [5,9,1,0,3,0,0,0,0,0,0,0,1],
 [0,0,49,3,0,0,0,0,1,0,0,0,6],
 [1,0,6,89,0,0,0,0,1,1,1,0,1],
 [3,7,0,0,50,0,0,0,6,0,1,0,0],
 [1,0,0,0,0,9,0,1,0,0,0,0,0],
 [3,0,1,0,0,0,54,0,0,0,3,0,0],
 [2,0,0,0,0,0,2,7,0,0,0,0,0],
 [3,0,0,0,2,1,2,0,53,2,4,0,0],
 [0,0,0,1,0,1,0,0,1,7,0,1,0],
 [1,1,0,0,1,0,1,0,3,0,52,0,0],
 [1,0,0,0,0,0,0,0,1,0,0,5,0],
 [0,0,11,2,0,0,0,0,0,0,0,0,26]]
)

classes = ['Assemble system','Consult sheets','Picking in front','Picking left','Put down component','Put down measuring rod','Put down screwdriver','Put down subsystem','Take component','Take measuring rod','Take screwdriver','Take subsystem','Turn sheets']

    
cm_analysis(cm= normalised_confusion_matrix, labels = classes)

enter image description here

1 Answers

The principal problem is creating the annot array as type str instead of object (so, annot = np.empty_like(cm).astype(object)). Having it of type str leads to strange errors, as numpy strings have some maximum length built-in. (See also this post.)

As you only use one index in cm_sum[i], it's better to not "keep the dimensions" in cm_sum = np.sum(cm, axis=1, keepdims=False) (docs).

Also, note that for a percentage, you need to multiply with 100. (The modern way to created formatted strings would use f-strings: annot[i, j] = f'{p*100:.2f}%\n{c}/{s}').

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

def cm_analysis(cm, labels, figsize=(20, 15)):
    cm_sum = np.sum(cm, axis=1, keepdims=False)
    cm_perc = cm / cm_sum.astype(float)
    annot = np.empty_like(cm).astype(object)
    nrows, ncols = cm.shape
    for i in range(nrows):
        for j in range(ncols):
            c = cm[i, j]
            p = cm_perc[i, j]
            if i == j:
                s = cm_sum[i]
                annot[i, j] = f'{p*100:.1f}%\n{c}/{s}'
            elif c == 0:
                annot[i, j] = ''
            else:
                annot[i, j] = f'{p*100:.1f}%\n{c}'
    cm = pd.DataFrame(cm, index=labels, columns=labels)
    cm.index.name = 'Groundtruth labels'
    cm.columns.name = 'Predicted labels'
    fig, ax = plt.subplots(figsize=figsize)
    ax.axhline(color='black')

    g = sns.heatmap(cm, cmap="BuPu", annot_kws={"weight": "bold"}, annot=annot, fmt='', ax=ax,
                    cbar_kws={'label': 'Number of samples'}, linewidths=0.1, linecolor='black')
    g.set_xticklabels(g.get_xticklabels(), rotation=45)
    sns.set(font_scale=1.1)
    plt.savefig("filename.png")

normalised_confusion_matrix = np.array(
    [[186, 3, 0, 1, 2, 0, 3, 3, 7, 1, 2, 0, 0],
     [5, 9, 1, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1],
     [0, 0, 49, 3, 0, 0, 0, 0, 1, 0, 0, 0, 6],
     [1, 0, 6, 89, 0, 0, 0, 0, 1, 1, 1, 0, 1],
     [3, 7, 0, 0, 50, 0, 0, 0, 6, 0, 1, 0, 0],
     [1, 0, 0, 0, 0, 9, 0, 1, 0, 0, 0, 0, 0],
     [3, 0, 1, 0, 0, 0, 54, 0, 0, 0, 3, 0, 0],
     [2, 0, 0, 0, 0, 0, 2, 7, 0, 0, 0, 0, 0],
     [3, 0, 0, 0, 2, 1, 2, 0, 53, 2, 4, 0, 0],
     [0, 0, 0, 1, 0, 1, 0, 0, 1, 7, 0, 1, 0],
     [1, 1, 0, 0, 1, 0, 1, 0, 3, 0, 52, 0, 0],
     [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 5, 0],
     [0, 0, 11, 2, 0, 0, 0, 0, 0, 0, 0, 0, 26]]
)

classes = ['Assemble system', 'Consult sheets', 'Picking in front', 'Picking left', 'Put down component',
           'Put down measuring rod', 'Put down screwdriver', 'Put down subsystem', 'Take component',
           'Take measuring rod', 'Take screwdriver', 'Take subsystem', 'Turn sheets']

cm_analysis(cm=normalised_confusion_matrix, labels=classes)

corrected heatmap

Related