4

I'm trying to make a heatmap with the colour bar on the side using seaborn. However, in my real application case, I have long column names that I rotate. This requires the use of plt.subplots_adjust, otherwise the labels do not fit in the image:

plt.setp(g.ax_heatmap.get_xticklabels(), rotation=90)
plt.subplots_adjust(bottom=0.5)

Using the following minimal example, I found out that this last command was cancelling the colour bar relocation:

#!/usr/bin/env python3

import seaborn as sns
import matplotlib.pyplot as plt

iris = sns.load_dataset("iris")
species = iris.pop("species")
# Make long column names
iris.columns = ["________".join(label.split("_")) for label in iris.columns]


g = sns.clustermap(iris, col_cluster=False, yticklabels=False)
# Relocate colour bar on the side
g.cax.set_position([.15, .2, .03, .45])
plt.setp(g.ax_heatmap.get_xticklabels(), rotation=90)
plt.savefig("/tmp/unadjusted.png")

plt.cla()

g = sns.clustermap(iris, col_cluster=False, yticklabels=False)
g.cax.set_position([.15, .2, .03, .45])
plt.setp(g.ax_heatmap.get_xticklabels(), rotation=90)
# Adjust to have the column labels not cut out
plt.subplots_adjust(bottom=0.5)
plt.savefig("/tmp/adjusted.png")

This results in the following images:

Without plt.subplots_adjust:

unadjusted

With plt.subplots_adjust:

adjusted

Why does this happen?

Is this a bug?

What can I do to have the colour bar where I want and still be sure that my rotated colour labels will not be cut?

bli
  • 7,549
  • 7
  • 48
  • 94
  • 1
    `subplots_adjust` adjusts the subplot positions, as the name suggests this would hence be expected. Isn't it just that you want to first adjust the subplots, and then set the bar's position? Changing the order of the commands should give the desired result. – ImportanceOfBeingErnest Nov 17 '17 at 13:26
  • Thanks, that helped. I had to use a different way to position the colour bar, though. I will post this as an answer. – bli Nov 17 '17 at 13:56

1 Answers1

3

Based on a suggestion by @ImportanceOfBeingErnest, I tried to adjust before moving the colour bar. This resulted in the following image:

adjusted_before

Since I actually don't want the dendrogram, but just the clustering, I can obtain a more pleasing result by using the dendrogram box to put the colour bar:

#!/usr/bin/env python3

import seaborn as sns
import matplotlib.pyplot as plt

iris = sns.load_dataset("iris")
species = iris.pop("species")
# Make long column names
iris.columns = ["________".join(label.split("_")) for label in iris.columns]

g = sns.clustermap(iris, col_cluster=False, yticklabels=False)
plt.setp(g.ax_heatmap.get_xticklabels(), rotation=90)
# Adjust to have the column labels not cut out
plt.subplots_adjust(bottom=0.5)
g.cax.set_position([.15, .2, .03, .45])
plt.savefig("/tmp/adjusted_before.png")

plt.cla()

g = sns.clustermap(iris, col_cluster=False, yticklabels=False)
plt.setp(g.ax_heatmap.get_xticklabels(), rotation=90)
# Adjust to have the column labels not cut out
plt.subplots_adjust(bottom=0.5)
# Remove the dendrogram (https://stackoverflow.com/a/42418968/1878788)
g.ax_row_dendrogram.set_visible(False)
# Use the dendrogram box to reposition the colour bar
dendro_box = g.ax_row_dendrogram.get_position()
dendro_box.x0 = (dendro_box.x0 + 2 * dendro_box.x1) / 3
g.cax.set_position(dendro_box)
# Move the ticks to the left (https://stackoverflow.com/a/36939552/1878788)
g.cax.yaxis.set_ticks_position("left")
# If we add a label to the colour bar
# (cbar_kws={"label" : "the_label"} in clustermap arguments)
# we also need to move the label
# g.cax.yaxis.set_label_position("left")
plt.savefig("/tmp/adjusted_before_using_row_dendrogram_box.png")

This generates the following image:

adjusted_before_using_row_dendrogram_box


Edit: The same issue occurs with plt.tight_layout.

Further developing my heatmap, I wanted to add row colours with legends (using an approach proposed in this answer). In my real application case, the legend takes enough horizontal space to be cut out of the image.

I wanted to use plt.tight_layout to adjust the image. This again interferes with the colour bar relocation (and doesn't even solve the cut legend issue...), as illustrated with the following examples.

#!/usr/bin/env python3

import seaborn as sns
import matplotlib.pyplot as plt

iris = sns.load_dataset("iris")
species = iris.pop("species")
# Make long column names
iris.columns = ["________".join(label.split("_")) for label in iris.columns]


# Associating colours with species
species_list = species.unique()
label_colours = dict(zip(species_list, sns.color_palette("colorblind", len(species_list))))
row_colours = species.map(label_colours)

g = sns.clustermap(iris, col_cluster=False, yticklabels=False, row_colors=row_colours, cbar_kws={"label" : "measure"})
# Adding row colour legend
# https://stackoverflow.com/a/27992943/1878788
for (label, colour) in label_colours.items():
    # Make long label
    g.ax_col_dendrogram.bar(0, 0, color=colour, label="Iris___________________{}".format(label))
g.ax_col_dendrogram.legend(loc="center", ncol=3)
# Rotating column labels
plt.setp(g.ax_heatmap.get_xticklabels(), rotation=90)
# Adjust to have the column labels not cut out
plt.subplots_adjust(bottom=0.5)
# Remove the dendrogram (https://stackoverflow.com/a/42418968/1878788)
g.ax_row_dendrogram.set_visible(False)
# Use the dendrogram box to reposition the colour bar
dendro_box = g.ax_row_dendrogram.get_position()
dendro_box.x0 = (dendro_box.x0 + 2 * dendro_box.x1) / 3
g.cax.set_position(dendro_box)
# Move the ticks and labels to the left (https://stackoverflow.com/a/36939552/1878788)
g.cax.yaxis.set_ticks_position("left")
g.cax.yaxis.set_label_position("left")
plt.savefig("/tmp/with_row_colour_lengend.png")

plt.cla()

g = sns.clustermap(iris, col_cluster=False, yticklabels=False, row_colors=row_colours, cbar_kws={"label" : "measure"})
# Adding row colour legend
# https://stackoverflow.com/a/27992943/1878788
for (label, colour) in label_colours.items():
    # Make long label
    g.ax_col_dendrogram.bar(0, 0, color=colour, label="Iris___________________{}".format(label))
g.ax_col_dendrogram.legend(loc="center", ncol=3)
# Rotating column labels
plt.setp(g.ax_heatmap.get_xticklabels(), rotation=90)
# Adjust to have the column labels not cut out
plt.subplots_adjust(bottom=0.5)
# Remove the dendrogram (https://stackoverflow.com/a/42418968/1878788)
g.ax_row_dendrogram.set_visible(False)
# Use the dendrogram box to reposition the colour bar
dendro_box = g.ax_row_dendrogram.get_position()
dendro_box.x0 = (dendro_box.x0 + 2 * dendro_box.x1) / 3
g.cax.set_position(dendro_box)
# Move the ticks and labels to the left (https://stackoverflow.com/a/36939552/1878788)
g.cax.yaxis.set_ticks_position("left")
g.cax.yaxis.set_label_position("left")
plt.tight_layout()
plt.savefig("/tmp/tight_after.png")

plt.cla()

g = sns.clustermap(iris, col_cluster=False, yticklabels=False, row_colors=row_colours, cbar_kws={"label" : "measure"})
# Adding row colour legend
# https://stackoverflow.com/a/27992943/1878788
for (label, colour) in label_colours.items():
    # Make long label
    g.ax_col_dendrogram.bar(0, 0, color=colour, label="Iris___________________{}".format(label))
g.ax_col_dendrogram.legend(loc="center", ncol=3)
plt.tight_layout()
# Rotating column labels
plt.setp(g.ax_heatmap.get_xticklabels(), rotation=90)
# Adjust to have the column labels not cut out
plt.subplots_adjust(bottom=0.5)
# Remove the dendrogram (https://stackoverflow.com/a/42418968/1878788)
g.ax_row_dendrogram.set_visible(False)
# Use the dendrogram box to reposition the colour bar
dendro_box = g.ax_row_dendrogram.get_position()
dendro_box.x0 = (dendro_box.x0 + 2 * dendro_box.x1) / 3
g.cax.set_position(dendro_box)
# Move the ticks and labels to the left (https://stackoverflow.com/a/36939552/1878788)
g.cax.yaxis.set_ticks_position("left")
g.cax.yaxis.set_label_position("left")
plt.savefig("/tmp/tight_before.png")

This results in the following figures:

Without plt.tight_layout, the right-most label is cut (the species should be "virginica"):

large_row_colour_legend

Calling plt.tight_layout after relocating the colour bar:

tight_after

Calling plt.tight_layout before relocating the colour bar:

tight_before

bli
  • 7,549
  • 7
  • 48
  • 94