1

I have figured out how to print a matplotlib scatterplot and have the points be clicked to give graphs of other data, based on the metadata of the clicked point. I now want to apply similar thinking to a seaborn heatmap. The trouble is that the logic I used for the matplotlib plot seems to be unique to scatterplots. The loop featured here and written below would not apply to a heatmap.

import matplotlib.pyplot as plt

class custom_objects_to_plot:
    def __init__(self, x, y, name):
        self.x = x
        self.y = y
        self.name = name

a = custom_objects_to_plot(10, 20, "a")
b = custom_objects_to_plot(30, 5, "b")
c = custom_objects_to_plot(40, 30, "c")
d = custom_objects_to_plot(120, 10, "d")

def on_pick(event):
    print(event.artist.obj.name)

fig, ax = plt.subplots()
for obj in [a, b, c, d]:
    artist = ax.plot(obj.x, obj.y, 'ro', picker=5)[0]
    artist.obj = obj

fig.canvas.callbacks.connect('pick_event', on_pick)

plt.show()

To give a bit more detail, I have five subjects, each of whom rate something on ten parameters, for a total of 50 ratings. In addition to the "subject", "parameter", and "rating" columns in my data frame, there is a column for comments. I want these comments to print out when I click the heatmap that has subjects along the horizontal axis and parameters along the vertical axis.

It seems like I should be able to use the position in the heatmap to identify the subject-parameter combination and look up what comments there are by that subject for that parameter, but extracting the subject and parameter from the clicked cell eludes me, and the reference code with the custom_objects_to_plot seems not to apply to a heatmap. While I have my thoughts on how to execute this task, if only I could get the subject and parameter from the clicked cell, I welcome answers that use different approaches but still give me a clickable seaborn heatmap.

Dave
  • 314
  • 2
  • 13

1 Answers1

1

You could try mplcursors, and use a dummy image (because the QuadMesh created by sns.heatmap isn't supported). With hover=True, the information is shown in an annotation box while hovering. With hover=False (the default) this only happens when clicking. If you just want to print something out, you can set sel.annotation.set_visible(False) and print something (or update the statusbar).

Here is an example:

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

def show_annotation(sel):
    x = int(sel.target[0])
    y = int(sel.target[1])
    sel.annotation.set_text(f's:{subjects[y]} p:{parameters[x]} r:{df_rating.iloc[y, x]}\n'
                            f'{df_comment.iloc[y, x]}')
    sel.annotation.get_bbox_patch().set(alpha=0.9)

subjects = np.arange(1, 6)
parameters = [*'ABCDEFGHIJ']
df = pd.DataFrame({'subject': np.repeat(subjects, len(parameters)),
                   'parameter': np.tile(parameters, len(subjects)),
                   'rating': np.random.randint(1, 11, len(subjects) * len(parameters)),
                   'comment': [f'comment subj {i} param {j}' for i in subjects for j in parameters]})
df_rating = df.pivot('subject', 'parameter', 'rating')
df_comment = df.pivot('subject', 'parameter', 'comment')
sns.set_style('white')
ax = sns.heatmap(df_rating, annot=True, lw=2)
dummy_image = ax.imshow(df_rating.to_numpy(), zorder=-1, aspect='auto')
cursor = mplcursors.cursor(dummy_image, hover=True)
cursor.connect('add', show_annotation)
plt.show()

sns.heatmap with mplcursors

JohanC
  • 71,591
  • 8
  • 33
  • 66
  • Awesome! I want to make sure this works when I integrate it with my real heatmap that has some complexities that I did not include here, but I think it will. – Dave Jan 05 '22 at 14:34
  • I do not have `mplcursors` and do not have permission to install it, but this answer works to the extent that I can test it, so +15. – Dave Jan 11 '22 at 14:06
  • mplcursors is just a [single file](https://github.com/anntzer/mplcursors/blob/master/lib/mplcursors/_mplcursors.py). Instead of installing it, you can just download it and call the functions directly. – JohanC Jan 11 '22 at 14:15