Search code examples
pythonseabornheatmapcolorbarclustermap

plotting a combined heatmap and clustermap, problems with adding two colorbars


I have two omics datasets which I woud like to compare. For this I plot one as a clustermap, and extract the order from it and plot the exact same genes as a heatmap, so that I can make a direct comparison between the two datasets. However, I would like to show the colorbars for both, and I cannot seem to get it to work.

  • the colorbar for the clustermap (left) is where I want it and the size I want it, however it is not showing y-ticks for the range of my data
  • the colorbar for the heatmap (right) is not where I want it, too large, and also not showing tickmarks. I would like it to be exactly like the first colorbar but on the right side instead.

I used this to combine the two plots, and this to adjust the colorbar of the clustermap, but this does not work as a function for the heatmap. I was playing around with cbar_kws but I don't think this allows me to do what I want, though I don't fully understand the potential there I think.

Using python in VScode;

import matplotlib.gridspec

#clustermap from averaged protein data!
g = sns.clustermap(hmprot, figsize=(8,12), col_cluster=False, yticklabels=True, cmap = 'viridis')

labels = [t.get_text() for t in g.ax_heatmap.yaxis.get_majorticklabels()]

g.gs.update(left=0.05, right=0.49)

#create new gridspec for the right part
gs2 = matplotlib.gridspec.GridSpec(1,1)

hmrna = hmrna.reindex(index=labels)

# create axes within this new gridspec
ax2 = g.fig.add_subplot(gs2[0])

# get position of heatmap
heatmap_bbox = g.ax_heatmap.get_position()
ax2.set_position([0.5, heatmap_bbox.y0, .35, heatmap_bbox.height])

# plot heatmap in the new axes
sns.heatmap(hmrna, ax=ax2, cmap = 'viridis', cbar=True, yticklabels=True, 
            cbar_kws= dict(use_gridspec=False,location = 'right', shrink= 0.5))


#change font size for the genes on the y-axis
g.tick_params(axis='y', labelsize=6, labelright = False, right=False)
g.tick_params(axis='x', labelbottom = True, bottom=False)
ax2.tick_params(axis='y', labelsize=8, labelright=True, left=False, labelleft=False, labelrotation = 0)
ax2.tick_params(axis='x', labelbottom = True, bottom=False)

#then adjusting the labels for each axis individually
# g.set_xlabel(' ') -> Doesn't work
ax2.set_xlabel(' ')
ax2.set_title('RNAseq', weight="bold") 
# ax2.set_title('Proteomics(C)', weight="bold")   -> Doesn't work

x0, _y0, _w, _h = g.cbar_pos
g.ax_cbar.set_position([0.01, 0.04, 0.02, 0.1])
g.ax_cbar.set_title('Z-score')
g.ax_cbar.tick_params(axis='x', length=10)

title = "Clustermap of Protein (left) & RNA (right) " + str(GO_term)

plt.suptitle(title, weight="bold", fontsize=20, y=0.85)
fig.tight_layout()

plt.show()

I have also tried the suggestion mentioned here, with the following code;

cbar_2_ax = fig.add_axes([0.95, 0.04, 0.02, 0.1])
cbar_2 = mp.colorbar(ax2, cax=cbar_2_ax)

Then I get an error :'Axes' object has no attribute 'get_array' I'm not sure how to proceed now, I'm probably not using the right functions to change this but I have not been able to find something that does work.

In addition, I managed to 'configure' the heatmap with its own title, and removed the 'group' label on the x-axis. I have not been able to figure out how to do the same for the clustermap, to give it it's own title, and remove this 'group' label. The same functions for the heatmap do not work here.

A dummy version of my code that replicates the issues for me. How do I fix the right colorbar??

Some dummy version of my code that replicates the issue:

df = pd.DataFrame(np.random.randint(0,100,size=(100, 4)), columns=list('ABCD'))
df2 = pd.DataFrame(np.random.randint(0,100,size=(100, 4)), columns=list('ABCD'))


#clustermap from df1
g = sns.clustermap(df, figsize=(12,18), col_cluster=False, yticklabels=True, cmap = 'viridis')

g.gs.update(left=0.05, right=0.49)

#create new gridspec for the right part
gs2 = matplotlib.gridspec.GridSpec(1,1)

# create axes within this new gridspec
ax2 = g.fig.add_subplot(gs2[0])

# get position of heatmap
heatmap_bbox = g.ax_heatmap.get_position()
ax2.set_position([0.5, heatmap_bbox.y0, .35, heatmap_bbox.height])
# plot boxplot in the new axes
sns.heatmap(df2, ax=ax2, cmap = 'viridis', cbar=True, yticklabels=True, 
            cbar_kws= dict(use_gridspec=False,location = 'right', shrink= 0.5)
            )


g.tick_params(axis='y', labelsize=6, labelright = False, right=False)
g.tick_params(axis='x', labelbottom = True, bottom=False)
ax2.tick_params(axis='y', labelsize=8, labelright=True, left=False, labelleft=False, labelrotation = 0)
ax2.tick_params(axis='x', labelbottom = True, bottom=False)

ax2.set_title('title', weight="bold")  # Set a custom title 


x0, _y0, _w, _h = g.cbar_pos
g.ax_cbar.set_position([0.01, 0.04, 0.02, 0.1])
g.ax_cbar.set_title('Z-score')
g.ax_cbar.tick_params(axis='x', length=10)

title = "Clustermap (left) & heatmap (right) " 
plt.suptitle(title, weight="bold", fontsize=20, y=0.85)
fig.tight_layout()
plt.show()

Below is my output figure from this code. I've highlighted the issues I'm hoping to fix.enter image description here This is my output figure from this code. I've highlighted the issues I'm hoping to fix.


Solution

  • Below you find some example code to adapt the layout and add the colorbars.

    Some remarks:

    • sns.clustermap has a parameter cbar_pos to directly set the position of the colobar
    • sns.clustermap also has a parameter dendrogram_ratio which define the space for the row and column dendrograms (the column dendrograms aren't used in this example, so the spacing can be set smaller)
    • g.tick_params changes the ticks of all the subplots. This probably causes the missing ticks of the colorbars. To only change the ticks of the clustermap's heatmap, you can use g.ax_heatmap.tick_params()
    • g.ax_heatmap.set_title() sets a title for the clustermap
    • g.ax_heatmap.set_xlabel('') removes the label (apparently labeled 'group' in the original image) of the clustermap
    • tight_layout() doesn't work when axes are created with fixed coordinates

    Something that is rather unclear, is that the clustermap changes the order of the rows (it places "similar" rows closer together). But the heatmap and its row labels at the right still seem to use their original order.

    To reorder the rows of the heatmap to the same order as the clustermap, the y-tick-labels of the clustermap can be extracted. Then, df2.reindex(...) reorders the rows of the heatmap. As the tick labels are strings, the example code below supposes the index of the dataframe are strings.

    import matplotlib.pyplot as plt
    import seaborn as sns
    import pandas as pd
    import numpy as np
    
    index = [f'r{i:02}' for i in range(50)]
    df = pd.DataFrame(np.random.randint(0, 100, size=(50, 4)), columns=list('ABCD'), index=index)
    df2 = pd.DataFrame(np.random.randint(0, 100, size=(50, 4)), columns=list('ABCD'), index=index)
    
    # clustermap from df1
    g = sns.clustermap(df, figsize=(12, 18), col_cluster=False, yticklabels=True, cmap='viridis',
                       dendrogram_ratio=(0.12, 0.04),  # space for the left and top dendograms
                       cbar_pos=[0.02, 0.04, 0.02, 0.1])
    g.ax_cbar.set_title('Z-score')
    g.ax_heatmap.set_xlabel('')  # remove possible xlabel
    g.ax_heatmap.set_title('clustermap title', weight="bold", fontsize=16)  # Set a custom title
    
    # extract the order of the y tick labels of the clustermap (before removing the ticks)
    new_index = [t.get_text() for t in g.ax_heatmap.get_yticklabels()]
    # remove right ticks and tick labels of the clustermap
    g.ax_heatmap.tick_params(axis='y', right=False, labelright=False)
    g.ax_heatmap.tick_params(axis='x', labelbottom=True, bottom=False)
    
    # get position of heatmap
    heatmap_bbox = g.ax_heatmap.get_position()
    # make space for the right heatmap by reducing the size of the clustermap's heatmap
    g.ax_heatmap.set_position([heatmap_bbox.x0, heatmap_bbox.y0, 0.49 - heatmap_bbox.x0, heatmap_bbox.height])
    
    ax2 = plt.axes([0.50, heatmap_bbox.y0, 0.38, heatmap_bbox.height])
    cbar_2_ax = plt.axes([0.94, 0.04, 0.02, 0.1])
    
    # plot heatmap in the new axes, reordering the rows similar as in the clustermap
    sns.heatmap(df2.reindex(new_index), cmap='viridis', cbar=True, yticklabels=True, ax=ax2, cbar_ax=cbar_2_ax)
    
    ax2.tick_params(axis='y', labelsize=8, labelright=True, left=False, labelleft=False, labelrotation=0)
    ax2.tick_params(axis='x', labelbottom=True, bottom=False)
    
    ax2.set_title('heatmap title', weight="bold", fontsize=16)  # Set a custom title
    cbar_2_ax.set_title('Z-score')
    
    # title = "Clustermap (left) & heatmap (right)"
    # plt.suptitle(title, weight="bold", fontsize=20)
    
    plt.show()
    

    heatmap next to clustermap