8

I am creating a heatmap to be used in a publication. The publication is restricted to black and white printing, so I'm creating the heatmap in grayscale. The problem I have is that there are some squares in the heatmap which are "Not Applicable" which I want to visually differentiate from the other cells. My understanding is that this may(?) be possible using numpy's masked arrays if the heatmap is colored at both ends of the scale, and that masked fields may simply display as white. The problem is, I would like to use the full spectrum from white to black to illustrate the range of the non-NA data. Is there anyway to distinguish NA cells with some other visual mechanism, such as a strikethrough?

Below is a minimum example of grayscale with a masked array (adapted from here). The NA values are probably masked here, you just can't tell because it is using white which is already being used as the color on the high end of the valid spectrum.

import numpy as np
from pylab import *

z = rand(10, 25)
z = np.ma.masked_array(z,mask=z>0.8)

c = pcolor(z)
set_cmap('gray')
colorbar()
c = pcolor(z, edgecolors='w', linewidths=1)
axis([0,25,0,10])
savefig('plt.png')
show()

enter image description here

Community
  • 1
  • 1
Bryce Thomas
  • 10,479
  • 26
  • 77
  • 126

2 Answers2

17

A simple solution is to just hatch the background axes patch. E.g.:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
np.random.seed(1977)

data = np.random.random((10,25))
data = np.ma.masked_greater(data, 0.8)

fig, ax = plt.subplots()
im = ax.pcolormesh(data, cmap=cm.gray, edgecolors='white', linewidths=1,
                   antialiased=True)
fig.colorbar(im)

ax.patch.set(hatch='xx', edgecolor='black')

plt.show()

enter image description here

Note that if you'd prefer not to have the borders drawn in between empty cells, you can use pcolor instead of pcolormesh. For example, if we change the line:

im = ax.pcolormesh(data, cmap=cm.gray, edgecolors='white', linewidths=1,
                   antialiased=True)

to:

im = ax.pcolor(data, cmap=cm.gray, edgecolors='white', linewidths=1)

We'll get:

enter image description here

The difference is subtle -- Lines are not drawn between adjacent empty cells with pcolor. Which aesthetics you prefer is purely personal, but it highlights a key difference between pcolor and pcolormesh.

Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • Hi Joe! Using matplotlib version 1.4.3, I had to add `ax.patch.set_edgecolor('black')` to make the hatch marks visible. I'm not sure if that is needed in general or if it is just because my version is old. – unutbu Mar 10 '16 at 14:48
  • @unutbu - Thanks! It also needs to be set in 1.5, as well. I was implicitly relying on the patch's edgecolor being set, which is never a good thing. Better to explicitly set it, regardless of differences between versions. – Joe Kington Mar 10 '16 at 15:01
  • hello @JoeKington, thanks for your answer. What if I want to plot the same thing but for a huge matrix (100k lines, 200 columns) in order to have a general idea about where are the nans concentrated? – dark Mar 27 '16 at 00:52
  • @msalem - In that case, use `imshow` instead of `pcolor` or `pcolormesh`. You can either use the hatching trick, or the `set_bad` method of the colormap to set the NaNs to whatever color/style you'd like. Imshow is much faster that the other options for large arrays, though. – Joe Kington Mar 27 '16 at 01:47
3

I was not able to reproduce Joe's answer by adding a patch by ax.patch.set_hatch('x'). Instead I had to create the patch as a rectangle according to this question as

import numpy as np
import matplotlib.pyplot as plt

import matplotlib.cm as cm
import matplotlib.patches as patches

data = np.random.random((10,25))
data = np.ma.masked_greater(data, 0.8)

fig, ax = plt.subplots()
im = ax.pcolormesh(data, cmap=cm.gray, edgecolors='white', linewidths=0)
fig.colorbar(im)

# ax.patch.set_hatch('x')  replaced by:
p = patches.Rectangle((0,0), 25, 10, hatch='xx', fill=None,zorder=-10)
ax.add_patch(p)

plt.show()

Furthermore, pcolormesh seems to be sorted out by now, so one can use it here.

Community
  • 1
  • 1
carsten
  • 31
  • 2