I have a matrix A:
A = np.array([[ 0. , 0.00066748, -0.00097412, -0.00748846, 0.00305338],
[-0.00157652, 0. , 0.0048117 , 0.01069083, -0.0137888 ],
[-0.00713212, -0.00170574, 0. , 0.00096385, 0.00212367],
[-0.00186541, 0.00351104, -0.00590608, 0. , -0.00448311],
[-0.00929146, 0.00157808, 0.01300444, -0.00078593, 0. ]])
With the following code I create a heatmap that assigns blank (white) to zero values, and green for positive values and red for negative ones:
import matplotlib.pyplot as plt
import seaborn as sns
rdgn = sns.diverging_palette(h_neg=10, h_pos=130, s=99, l=55, sep=3, as_cmap=True)
fig = plt.figure()
sns.heatmap(A, mask=(A == 0), cmap=rdgn, center=0)
plt.xticks(np.arange(0, 5, 1) + 0.5, [i + 1 for i in range(5)])
plt.yticks(np.arange(0, 5, 1) + 0.5, [i + 1 for i in range(5)], rotation=0)
plt.tick_params(axis='both', which='major', labelsize=10, labelbottom=False, bottom=False, top=False, labeltop=True)
plt.show()
I obtain this:
Now my goal is to convert this picture to greyscale only. One way would be to assign darker colors to reds and lighter to greens, but I would like to highlight the transition from negative to positive making the zero values very different from others, ideally keeping them blank. If I try with
fig = plt.figure()
sns.heatmap(A, mask=(A == 0), cmap = 'gray', center = 0)
plt.xticks(np.arange(0, 5, 1) + 0.5, [i + 1 for i in range(5)])
plt.yticks(np.arange(0, 5, 1) + 0.5, [i + 1 for i in range(5)], rotation=0)
plt.tick_params(axis='both', which='major', labelsize=10, labelbottom=False, bottom=False, top=False, labeltop=True)
plt.show()
I do not get what I want. With mask==0
I can keep zeros to blank, but this is not reflected in the colorbar on the right. What I would like to do is basically "split" the colors into 2: from pitch black to "half" grey for negatives (from the farthest away to the closest to zero), white for zeros and from white to "half" grey again for positive (from the closest to farthest away from zero). Is there a way to achieve this? I am open to any suggestion on how to tackle the problem
The idea of having a gradient of color is to be able to compare the values directly. It might be difficult to compare the absolute values if different shades of grey are used for negative/positive.
Instead, why not plot the absolute value and add an additional information to tell negative and positive values apart?
sns.heatmap(abs(A), annot=np.where(A<0, '−', ''), fmt='', cmap = 'gray_r', vmin=0)
Or:
sns.heatmap(abs(A), annot=np.select([A<0, A>0], ['−', '+'], ''),
fmt='', cmap = 'gray_r', vmin=0)
You can use unicode symbols for easier visualization:
sns.heatmap(abs(A), annot=np.where(A<0, '▽', ''), fmt='',
cmap = 'gray_r', vmin=0, annot_kws={'size': 30})
Output:
You can also add hatches if you want as shown here:
sns.heatmap(abs(A), cmap = 'gray_r', vmin=0)
zm = np.ma.masked_greater_equal(A, 0)
plt.pcolor(np.arange(A.shape[0])+0.5,
np.arange(A.shape[1])+0.5,
zm, hatch='//', alpha=0, zorder=3)
Output: