Search code examples
pythonmatplotlibseabornvisualizationheatmap

Discrete Heatmap, change the cell opacity depending on variable (seaborn?)


I'd like to make an heatmap featuring both the continuous variable and the categorical data the sample came with. The goal is to have the hue coming from the category and the opacity (or transparency, saturation) from the continuous values.

A toy dataset would be like:

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

names = ['Berthe', 'Paul', 'Francis', 'Jane']

time_spent_together = [
    [ 1.0, 0.2, 0.7, 0.5 ],
    [ 0.2, 1.0, 0.8, 0.5 ],
    [ 0.7, 0.8, 1.0, 0.1 ],
    [ 0.5, 0.5, 0.1, 1.0 ],
]

type_of_relationship = [
    [ 'id', 'friends', 'coworkers', 'nemesis' ],
    [ 'friends', 'id', 'family', 'family' ],
    [ 'coworkers', 'family', 'id', 'friends' ],
    [ 'nemesis', 'family', 'friends', 'id' ],
]

df_times = pd.DataFrame(data=time_spent_together, index=names, columns=names)
df_relationships = pd.DataFrame(data=type_of_relationship, index=names, columns=names)

And the result would 'alter' the discrete plot:

plt.figure(figsize=(3,3))
value_to_int = {j:i for i,j in enumerate(pd.unique(df_relationships.values.ravel()))}
n = len(value_to_int)
cmap = sns.color_palette("tab10", n) 
sns.heatmap(df_relationships.replace(value_to_int), cmap=cmap)

enter image description here

With the continuous one

plt.figure(figsize=(3,3))
sns.heatmap(df_times, cmap='Greys',annot=True, 
            annot_kws={"size": 7}, vmin=0.25, vmax=1)

enter image description here

As you can see, I used seaborn and pyplot. I struggle to override the basic behavior. Being able to directly set a cell's color can be the right path ?

Thanks in advance for your answers, Cheers!


Solution

  • Here is an approach using multiple mono-color colormaps, together with a legend:

    import matplotlib.pyplot as plt
    from matplotlib.cm import ScalarMappable
    import seaborn as sns
    import pandas as pd
    import numpy as np
    
    fig, ax = plt.subplots(figsize=(6, 6))
    
    cmaps = ['Blues', 'Oranges', 'Greens', 'Reds', 'Purples']
    norm = plt.Normalize(vmin=0.25, vmax=1)
    handles = []
    for rel, cmap in zip(np.unique(df_relationships.values), cmaps):
        sns.heatmap(df_times, mask=df_relationships.values != rel, cmap=cmap, norm=norm, annot=True, cbar=False)
        handles.append(plt.Rectangle((0, 0), 0, 0, color=plt.get_cmap(cmap)(0.55), lw=0, label=rel))
    
    plt.colorbar(ScalarMappable(cmap='Greys', norm=norm), ax=ax)
    ax.legend(handles=handles, ncol=len(handles), bbox_to_anchor=(0, 1.01), loc='lower left', handlelength=0.7)
    plt.tight_layout()
    plt.show()
    

    sns.heatmap with multiple color ranges