1

I have a heatmap in Seaborn via sns.heatmap. I now want to white out the bottom row and right column but keep the values.

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

np.random.seed(2021)
df = pd.DataFrame(np.random.normal(0, 1, (6, 4))
df = df.rename(columns = {0:"a", 1:"b", 2:"c", 3:"d"})
df.index = [value for key, value in {0:"a", 1:"b", 2:"c", 3:"d", 4:"e", 5:"f"}.items()]
sns.heatmat(df, annot = True)
plt.show

I thought I had to include a mask argument in my sns.heatmap call, but I am not having success giving a proper mask, and the mask removes the annotation. I also need to preserve the text indices of my data frame d. How can I get those cells whited out while preserving the text indices?

Dave
  • 314
  • 2
  • 13

1 Answers1

1

Here is an approach:

  • use the original data for annotation (annot=data)
  • create a "norm" using the original data, to be used for coloring
  • create a copy of the colormap and assign an "over" color as "white"
  • create a copy of the data, and fill the right column and lower row with a value higher than the maximum of the original data (np.inf can't be used, because then no annotation will be placed); use this copy for the coloring; seaborn will magically use the appropriate color for the annotation
  • to use the dataframe's column and index names in the heatmap, just use sns.heatmap(..., xticklabels=df.columns, yticklabels=df.index)
  • if you don't have a recent seaborn version installed, you might consider using one of matplotlib's standard colormaps, or create one via matplotlib.colors.ListedColormap(). Maybe cmap = ListedColormap(sns.color_palette('rocket', 256))?

In example code:

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

np.random.seed(2021)
df = pd.DataFrame(np.random.normal(0, 1, (6, 4)), columns=[*"abcd"], index=[*"abcdef"])
data = df.to_numpy()
data_for_colors = data.copy()
data_for_colors[:, -1] = data.max() + 10
data_for_colors[-1, :] = data.max() + 10
norm = plt.Normalize(data[:-1, :-1].min(), data[:-1, :-1].max())
# cmap = sns.color_palette('rocket', as_cmap=True).copy()
cmap = copy(plt.get_cmap('RdYlGn'))
cmap.set_over('white')
sns.set_style('white')
sns.heatmap(data=data_for_colors, xticklabels=df.columns, yticklabels=df.index,
            annot=data, cmap=cmap, norm=norm)
plt.show()

sns.heatmap with one row and one column whitened

JohanC
  • 71,591
  • 8
  • 33
  • 66
  • This is clever, but the `as_cmap` is not working for me: `color_palette() got an unexpected keyword argument`. Any idea why? // I will edit the original question to reflect this, but I also need the 0-5 and 0-3 to be the text indices from my data frame. Is there a way to preserve those or reintroduce them to the plot? – Dave Oct 26 '21 at 18:46
  • Maybe a very old seaborn version? Latest is 0.11.2. You can always use a different one of the standard matplotlib colormaps. Or create your own from a list of colors. – JohanC Oct 26 '21 at 19:31
  • I updated the answer with your new requirements. – JohanC Oct 26 '21 at 20:01
  • I am getting an error with the `cmap = plt.get_cmap('RdYlGn').copy()` line: `AttributeError: 'LinearSegmentedColormap' object has no attribute 'copy'`. If I get rid of the `.copy()`, I get a plot similar to the plot you show, except with the bottom row and right column in green and the color bar ranging from 0 (really more like -2) to 12. Any idea what's going on? – Dave Oct 26 '21 at 23:58
  • You're running a really old matplotlib version. Please consider upgrading. The developers dedicate a lot of effort in improving and extending the library. In older versions, one would use `from copy import copy` and `cmap = copy(plt.get_cmap('RdYlGn'))`. It also would work directly with `cmap = plt.get_cmap('RdYlGn')`, although that isn't recommended, as `set_over` then would change a global colormap. I updated the code. – JohanC Oct 27 '21 at 05:52
  • This almost works, but I am getting a green border instead of white. This has happened on more than one computer. Thoughts? – Dave Oct 28 '21 at 12:10
  • Are you running the latest matplotlib 3.4.3 and seaborn 0.11.2? Where do you get the green border (all cells, only the white cells, ...)? Did you make changes to matplotlib's settings? Did you try `sns.heatmap(..., lw=0, edgecolor='white')`? Could you edit your post and add an image of the undesired plot? – JohanC Oct 28 '21 at 16:55
  • Perhaps I just have old versions of `matplotlib` and `seaborn`, but I never got this to work 100%. However, I managed to get something to work for me that I will eventually type up as a self-answer. Thank you for your help. Your ideas set me on the right path to do what I needed to do. – Dave Nov 03 '21 at 22:03