Search code examples
pythonmatplotlibcolorbar

How to use a custom colormap with irregular intervals?


I'm trying to use a custom colorbar in matplotlib with irregular intervals. But when following the tutorial and using the colorbar, it gets used as a regular interval colorbar.

How do I construct/use a colorbar with irregular intervals?

MWE below:

I'm plotting various data with plt.matshow(), like this:

testdf = pd.DataFrame([
    (7, 7.1, 8 , 9),
    (0, 1, 1.5, 2),
    (2.001, 3, 3.5, 4),
    (4.001, 5, 6, 6.9999),
], index=[0, 1, 2, 3], columns=('A', 'B', 'C', 'D'),)

and

plt.matshow(testdf)

default matrix

However, I want only certain numbers highlighted and I want to group others, i.e. I want a discrete, custom colorbar, instead of the default continuous one.

Luckily, the matplotlib documentation has just what I need. So, lets set up this colorbar:

fig, ax = plt.subplots(figsize=(6, 1))
fig.subplots_adjust(bottom=0.5)

cmap = (mpl.colors.ListedColormap(['red', 'green', 'blue', 'cyan'])
        .with_extremes(over='0.25', under='0.75'))

bounds = [1, 2, 4, 7, 8]
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
fig.colorbar(
    mpl.cm.ScalarMappable(cmap=cmap, norm=norm),
    cax=ax,
    boundaries=[0] + bounds + [13],  # Adding values for extensions.
    extend='both',
    ticks=bounds,
    spacing='proportional',
    orientation='horizontal',
    label='Discrete intervals, some other units',
)

custom colorbar

Looks great! Numbers from 1 to 2 in red and 7 to 8 in blue and two large groups for all the uninteresting stuff between 2 and 7.

So, let's use it.

plt.matshow(testdf, cmap=cmap)
plt.colorbar()

custom matrix

...and that's not what I expected. The colorbar should look like the one I just constructed before, not regularly spaced and thus lines 0 and 1 should contain a black/grey box for over/under, line 2 should be all green and line 3 all blue.

How do I fix this? What am I missing?


Solution

  • As Jody Klymak and JohanC have pointed out in the comments, norm also needs to be passed into matshow, i.e. plt.matshow(testdf, cmap=cmap, norm=norm).

    However, this doesn't work for stuff where I can't pass further arguments with my colormap (or where I can't figure out how to do so…), e.g. in sns.clustermap.

    A possible workaround is to define a colormap with regular intervals, with many following intervals being of the same color:

    fig, ax = plt.subplots(figsize=(6, 1))
    fig.subplots_adjust(bottom=0.5)
    
    cmap = (mpl.colors.ListedColormap(['red',
                                       'green', 'green',
                                       'blue', 'blue', 'blue', 'blue',
                                       'cyan'])
                                       .with_extremes(over='0.25', under='0.75'))
    
    bounds = [1, 2, 3, 4, 5, 6, 7, 8]
    norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
    fig.colorbar(
        mpl.cm.ScalarMappable(cmap=cmap, norm=norm),
        cax=ax,
        boundaries=[0] + bounds + [13],  # Adding values for extensions.
        extend='both',
        ticks=bounds,
        spacing='proportional',
        orientation='horizontal',
        label='Discrete intervals, some other units',
    )
    

    Results in

    Colorbar

    and following that

    matrix

    As you can see, it's still different from what I expected in the question, but turns out that the limits are different from what I expected them to be, i.e. the interval 1, 2 means >1, <2which is easily fixable, if you know/expect this behaviour.