Correcting matplotlib colorbar ticks
Solution 1
You are suffering from an off-by-one error. You have 10 ticklabels spread among 11 colors. You might be able to correct the error by using np.linspace instead of np.arange. Using np.linspace the third argument is the number of values desired. This reduces the amount of mental gymnastics needed to avoid the off-by-one error:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.cm as cm
import matplotlib.colors as mcolors
def colorbar_index(ncolors, cmap):
cmap = cmap_discretize(cmap, ncolors)
mappable = cm.ScalarMappable(cmap=cmap)
mappable.set_array([])
mappable.set_clim(-0.5, ncolors+0.5)
colorbar = plt.colorbar(mappable)
colorbar.set_ticks(np.linspace(0, ncolors, ncolors))
colorbar.set_ticklabels(range(ncolors))
def cmap_discretize(cmap, N):
"""Return a discrete colormap from the continuous colormap cmap.
cmap: colormap instance, eg. cm.jet.
N: number of colors.
Example
x = resize(arange(100), (5,100))
djet = cmap_discretize(cm.jet, 5)
imshow(x, cmap=djet)
"""
if type(cmap) == str:
cmap = plt.get_cmap(cmap)
colors_i = np.concatenate((np.linspace(0, 1., N), (0.,0.,0.,0.)))
colors_rgba = cmap(colors_i)
indices = np.linspace(0, 1., N+1)
cdict = {}
for ki,key in enumerate(('red','green','blue')):
cdict[key] = [ (indices[i], colors_rgba[i-1,ki], colors_rgba[i,ki])
for i in xrange(N+1) ]
# Return colormap object.
return mcolors.LinearSegmentedColormap(cmap.name + "_%d"%N, cdict, 1024)
fig, ax = plt.subplots()
A = np.random.random((10,10))*10
cmap = plt.get_cmap('YlGnBu')
ax.imshow(A, interpolation='nearest', cmap=cmap)
colorbar_index(ncolors=11, cmap=cmap)
plt.show()

Solution 2
You can control the placement and the labels by hand. I'll start with a linear cmap generated from cmap_discretize on the page you linked:
import numpy as np
import pylab as plt
# The number of divisions of the cmap we have
k = 10
# Random test data
A = np.random.random((10,10))*k
c = cmap_discretize('jet', k)
# First show without
plt.subplot(121)
plt.imshow(A,interpolation='nearest',cmap=c)
plt.colorbar()
# Now label properly
plt.subplot(122)
plt.imshow(A,interpolation='nearest',cmap=c)
cb = plt.colorbar()
labels = np.arange(0,k,1)
loc = labels + .5
cb.set_ticks(loc)
cb.set_ticklabels(labels)
plt.show()

Related videos on Youtube
urschrei
Updated on July 09, 2022Comments
-
urschrei over 1 yearI've placed a color bar alongside a choropleth map. Because the data being plotted are discrete rather than continuous values, I've used a LinearSegmentedColormap (using the recipe from the scipy cookbook), which I've initialised with my max counted value + 1, in order to show a colour for 0. However, I now have two problems:

-
The tick labels are incorrectly spaced (except for 5, more or less) – they should be located in the middle of the colour they identify; i.e. 0 - 4 should be moved up, and 6 - 10 should be moved down.
-
If I initialise the colorbar with
drawedges=True, so that I can style itsdividersproperties, I get this:

I'm creating my colormap and colorbar like so:
cbmin, cbmax = min(counts), max(counts) # this normalises the counts to a 0,1 interval counts /= np.max(np.abs(counts), axis=0) # density is a discrete number, so we have to use a discrete color ramp/bar cm = cmap_discretize(plt.get_cmap('YlGnBu'), int(cbmax) + 1) mappable = plt.cm.ScalarMappable(cmap=cm) mappable.set_array(counts) # set min and max values for the colour bar ticks mappable.set_clim(cbmin, cbmax) pc = PatchCollection(patches, match_original=True) # impose our colour map onto the patch collection pc.set_facecolor(cm(counts)) ax.add_collection(pc,) cb = plt.colorbar(mappable, drawedges=True)So I'm wondering whether my converting the counts to a 0,1 interval is one of the problems.
Update :
Having tried what Hooked suggested, the 0-value is correct, but subsequent values are set progressively higher, to the the point where 9 is where 10 should be:

Here's the code I used:
cb = plt.colorbar(mappable) labels = np.arange(0, int(cbmax) + 1, 1) loc = labels + .5 cb.set_ticks(loc) cb.set_ticklabels(labels)And just to confirm,
labelsdefinitely has the correct values:In [3]: np.arange(0, int(cbmax) + 1, 1) Out[3]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])-
Hooked about 10 yearsIt seems like the problem must lie with the colorbar you essentially create "by hand" using a PatchCollection. Is there any reason not to use a linear discrete colormap like the scipy page you linked? -
urschrei about 10 yearsYou mean create the colormap, pass it PatchCollection as acmapargument, and thenpc.set_arrayto the counts? -
Hooked about 10 yearsNo I mean usecmap_discretizeas on the page wiki.scipy.org/Cookbook/Matplotlib/ColormapTransformations . This is how I generated my example which doesn't suffer from the strange placement.
-
-
urschrei about 10 yearsHmm, it's still not correct, but in a different way. See edit.