A different way to visualize classification results | by Nils Flaschel | Towards Data Science

I love good data visualizations. Back in the days when I did my PhD in particle physics, I was stunned by the histograms my colleagues built and how much information was accumulated in one single plot.
It is really challenging to improve existing visualization methods or to transport methods from other research fields. You have to think about the dimensions in your plot and the ways to add more of them. A good example is the path from a boxplot to a violinplot to a swarmplot. It is a continuous process of adding dimensions and thus information.
The possibilities of adding information or dimensions to a plot are almost endless. Categories can be added with different marker shapes, color maps like in a heat map can serve as another dimension and the size of a marker can give insight to further parameters.
When it comes to machine learning, there are many ways to plot the performance of a classifier. There is an overwhelming amount of metrics to compare different estimators like accuracy, precision, recall or the helpful MMC.
All of the common classification metrics are calculated from true positive, true negative, false positive and false negative incidents. The most popular plots are definitely ROC curve, PRC, CAP curve and the confusion matrix.
I won’t get into detail of the three curves, but there are many different ways to handle the confusion matrix, like adding a heat map.
For many cases, this is probably sufficient and easy to pick up all relevant information, but for a multi class problem, it can get much harder to do so.
While reading some papers, I stumbled across:
Jakob Raymaekers, Peter J. Rousseeuw, Mia Hubert. Visualizing classification results. arXiv:2007.14495 [stat.ML]
and from there to
Friendly, Michael. “Mosaic Displays for Multi-Way Contingency Tables.” Journal of the American Statistical Association, vol. 89, no. 425, 1994, pp. 190–200. JSTOR, www.jstor.org/stable/2291215. Accessed 13 Aug. 2020.
The authors propose a mosaic diagram to plot discrete values. We can transport this idea to the field of machine learning with the predicted classes as the discrete values.
In a multi class environment, such a plot would look like the following:
It has several advantages over a classical confusion matrix. One can easily see the predicted classes on the y-axis and the number proportion of each class on the x-axis. The big difference from a simple bar plot is the width of the bars, which are giving an idea of the class imbalance.
You can find the code for such a plot fed with a confusion matrix here:
Note: This is not my idea, it is inspired by:
Jakob Raymaekers, Peter J. Rousseeuw, Mia Hubert. Visualizing classification results. arXiv:2007.14495 [stat.ML]
and
Friendly, Michael. "Mosaic Displays for Multi-Way Contingency Tables." Journal of the American Statistical Association, vol. 89, no. 425, 1994, pp. 190–200. JSTOR, www.jstor.org/stable/2291215. Accessed 13 Aug. 2020.
In short:
This is an approach of visualizing the results of a multiclass estimator by taking the class distribution into account. The predicted classes are plotted along the y-axis and the class size on the x-axis. That way, one can immediatly see the class distribution in the data set. That makes it easier to judge the performance of the estimator.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
from statsmodels.graphics.mosaicplot import mosaic
from matplotlib.patches import Patch
import itertools
from collections import deque

def nclass_classification_mosaic_plot(n_classes, results):
    """
    build a mosaic plot from the results of a classification
    
    parameters:
    n_classes: number of classes
    results: results of the prediction in form of an array of arrays
    
    In case of 3 classes the prdiction could look like
    [[10, 2, 4],
     [1, 12, 3],
     [2, 2, 9]
    ]
    where there is one array for each class and each array holds the
    predictions for each class [class 1, class 2, class 3].
    
    This is just a prototype including colors for 6 classes.
    """
    class_lists = [range(n_classes)]*2
    mosaic_tuples = tuple(itertools.product(*class_lists))
    
    res_list = results[0]
    for i, l in enumerate(results):
        if i == 0:
            pass
        else:
            tmp = deque(l)
            tmp.rotate(-i)
            res_list.extend(tmp)
    data = {t:res_list[i] for i,t in enumerate(mosaic_tuples)}

    fig, ax = plt.subplots(figsize=(11, 10))
    plt.rcParams.update({'font.size': 16})

    font_color = '#2c3e50'
    pallet = [
        '#6a89cc', 
        '#4a69bd', 
        '#1e3799', 
        '#0c2461',
        '#82ccdd',
        '#60a3bc',
    ]
    colors = deque(pallet[:n_classes])
    all_colors = []
    for i in range(n_classes):
        if i > 0:
            colors.rotate(-1)
        all_colors.extend(colors)

    props = {(str(a), str(b)):{'color':all_colors[i]} for i,(a, b) in enumerate(mosaic_tuples)}

    labelizer = lambda k: ''

    p = mosaic(data, labelizer=labelizer, properties=props, ax=ax)

    title_font_dict = {
        'fontsize': 20,
        'color' : font_color,
    }
    axis_label_font_dict = {
        'fontsize': 16,
        'color' : font_color,
    }

    ax.tick_params(axis = "x", which = "both", bottom = False, top = False)
    ax.axes.yaxis.set_ticks([])
    ax.tick_params(axis='x', which='major', labelsize=14)

    ax.set_title('Classification Report', fontdict=title_font_dict, pad=25)
    ax.set_xlabel('Observed Class', fontdict=axis_label_font_dict, labelpad=10)
    ax.set_ylabel('Predicted Class', fontdict=axis_label_font_dict, labelpad=35)

    legend_elements = [Patch(facecolor=all_colors[i], label='Class {}'.format(i)) for i in range(n_classes)]
    ax.legend(handles=legend_elements, bbox_to_anchor=(1,1.018), fontsize=16)

    plt.tight_layout()
    plt.show()

n_classes = 4 # number of classes
results = [
    [50, 4, 1, 2], # predictions for class 1
    [1, 40, 4, 3], # predictions for class 2
    [3, 2, 30, 1], # predictions for class 3
    [1, 3, 1, 60], # predictions for class 4
]

nclass_classification_mosaic_plot(n_classes, results)

Have fun plotting your next classification results!