Add annotations to heatmap with slider

Hi,

I’m currently working on a project where I want to display multiple confusion matrices over several steps to check whether a ML model is drifting or not.

I’ve successfully used the figure_factory.annotated_heatmap function to create a static heatmap doing the following:

import plotly.figure_factory as ff

conf_matrix = [[1.0, 0.0, 0.0], 
                   [0.0, 1.0, 0.0], 
                   [0.5, 0.0, 0.5]]

labels = ["Negative", "Neutral", "Positive"]
labels_r = labels.copy()
labels_r.reverse()

fig = ff.create_annotated_heatmap(conf_matrix, x=labels, y=labels_r, colorscale='Blues')
fig.update_layout(
        title={
            "text": "Normalized confusion matrix",
            "xanchor": 'center',
            "yanchor": 'top',
            "y": 0.9,
            "x": 0.5,
        }
    )

fig.show()

I’ve tried to create multiple heatmaps and add a slider but it doesn’t display any of the annotations on it. Here’s how I’ve created an animated heatmap without annotations so far:

labels = ["Negative", "Neutral", "Positive"]
labels_r = labels.copy()
labels_r.reverse()

conf_matrices = []
for i in range(10):
    conf_matrices.append([[random.random(), random.random(), random.random()],
                          [random.random(), random.random(), random.random()],
                          [random.random(), random.random(), random.random()]])

animated_fig = go.Figure()

for conf_matrix in conf_matrices:
    conf_matrix.reverse()

    fig = ff.create_annotated_heatmap(conf_matrix, x=labels, y=labels_r, colorscale='Blues')

    # Adding the trace, but not the annotations
    animated_fig.add_trace(fig.data[0])
    
    # How can I also add the annotations here?


# Making the last trace visible
animated_fig.data[len(animated_fig.data)-1].visible = True

# Create and add slider
steps = []
for i in range(len(animated_fig.data)):
    step = dict(
        method="update",
        args=[{"visible": [False] * len(animated_fig.data)},
              {"title": "Normalized confusion matrix"}],
    )
    step["args"][0]["visible"][i] = True
    steps.append(step)

sliders = [dict(
    active=len(animated_fig.data)-1,
    currentvalue={"prefix": "Step: "},
    pad={"t": 50}, # Not really understanding this parameter but I'll sort it out later
    steps=steps
)]

animated_fig.update_layout(
    sliders=sliders
)

animated_fig.show()

How can I add annotations from the ff.create_annotated_heatmap figure to my graph with sliders?

Thanks!

Hi Khreas,

I’m trying to do a similar task with Confusion matrices for ML training. Were you able to figure out how to display the annotations with the slider?

Thanks!

So not sure if you are still looking for an annotated confusion matrix with slider.
After a lot of struggle I manage to create it in a very simple form:

import plotly.express as px
import numpy as np
import pandas as pd
import random

y_prob = np.arange(0.00,0.96, 0.02).tolist()
y_true = np.where(np.asarray([random.uniform(-0.4, 0.4) + x for x in y_prob]) > 0.5, 1 ,0)

# Couldn't find another way than this to add threshold values to the slider
import xarray as xr

cms = []

threshold_list = np.arange(0.0, 1.05, 0.05)

for threshold in threshold_list:
    y_pred = [1 if x > threshold else 0 for x in y_prob]

    df = pd.DataFrame({'y_true': y_true, 'y_pred': y_pred})

    # predicted / actual 
    trfa = len(df[(df['y_pred'] == 1) & (df['y_true'] == 0)])
    trtr = len(df[(df['y_pred'] == 1) & (df['y_true'] == 1)])
    fafa = len(df[(df['y_pred'] == 0) & (df['y_true'] == 0)])
    fatr = len(df[(df['y_pred'] == 0) & (df['y_true'] == 1)])

    z = [[trtr, trfa],
            [fatr, fafa]]  

    cms.append(z)

# Round to 2 decimals (purely visually)
threshold_list = [round(i,2) for i in threshold_list]

# convert to xarray
da = xr.DataArray(cms, coords=[threshold_list, ['1', '0'], ['1', '0']], dims=['Threshold', 'Predicted', 'Actual'])

# Create figure
fig = px.imshow(da, 
                title='Confusion Matrix',
                animation_frame='Threshold',
                text_auto=True,
                width=750, height=750, 
                labels=dict(animation_frame="Threshold"))

# set default slider value to 0.5
fig.layout.sliders[0]['active'] = 10  
fig.update_traces(z=fig.frames[10].data[0].z)

# Update axis names and tick distance (otherwise 0.5 and 1.5 would also be shown)
fig.update_layout(
    xaxis = dict(
        title='Actual',
        dtick=1,
    ),
    yaxis = dict(
        title='Predicted',
        dtick=1,
    )
)

# Remove the play and stop button
fig["layout"].pop("updatemenus")

fig.show()

From the original poster’s code (@Khreas) it’s obvious that he did not want to animate the confusion matrices, as long as he set in
slider definition, method=“update” , not “animate”!!!
Now ff_create_annotated_heatmap is obsolete. We can create an annotated heatmap, simply, setting in a go.Heatmap definition, text and texttemplate:

import plotly.graph_objects as go
import numpy as np
labels = ["Negative", "Neutral", "Positive"]
conf_matrix = [[1.0, 0.0, 0.0], 
               [0.0, 1.0, 0.0], 
               [0.5, 0.0, 0.5]]

labels = ["Negative", "Neutral", "Positive"]
labels_r = labels.copy()
labels_r.reverse()
fig = go.Figure(data=go.Heatmap(x=labels, y=labels_r,
                    z=conf_matrix,
                    text=conf_matrix,
                    texttemplate="%{text}",
                    colorscale="Blues"))
fig.update_layout(width=500, height=500,
                  xaxis_type="category", 
                  yaxis_type="category", 
                  xaxis_side="top",
                  title_text= "Normalized confusion matrix", 
                  title_x=0.5
                  )
conf_matrices = []
for i in range(10):
    conf_matrices.append((np.round(np.random.rand(9), 2)).reshape((3,3)))

steps=[]
for conf_matrix in conf_matrices:
    steps.append(dict(method = "restyle",
                 args = [{'z': [ conf_matrix ], #in the initial fig update z and text
                       'text': [conf_matrix]}]))
sliders = [dict(
    active=0,
    currentvalue={"prefix": "Step: "},
    pad={"t": 50}, sort it out later
    steps=steps
)]

fig.update_layout(sliders=sliders)