Create own colormap using matplotlib and plot color scale
Asked Answered
C

4

96

I have the following problem, I want to create my own colormap (red-mix-violet-mix-blue) that maps to values between -2 and +2 and want to use it to color points in my plot. The plot should then have the colorscale to the right.

That is how I create the map so far. But I am not really sure if it mixes the colors.

cmap = matplotlib.colors.ListedColormap(["red","violet","blue"], name='from_list', N=None)
m = cm.ScalarMappable(norm=norm, cmap=cmap)


That way I map the colors to the values.

colors = itertools.cycle([m.to_rgba(1.22), ..])


Then I plot it:

for i in range(0, len(array_dg)):
  plt.plot(array_dg[i], markers.next(),alpha=alpha[i], c=colors.next())


My problems are:
1. I can't plot the color scale.
2. I am not completely sure if my scale is creating a continues (smooth) colorscale.

Chemotherapy answered 30/5, 2013 at 11:22 Comment(3)
Could you clarify your question a bit? For example, c= specifies the line color, while you are talking about points. You can only specify one markerfacecolor, scatter might be a better option if you really want points. And indeed ListedColormap is listed, not continuous, see LinearSegmentedColormap.Grundy
That is strange, it is supposed to be points and it looks like points.Chemotherapy
You can off course, but thats what you should clarify. We cant see what plot style you are using. If you use plt.plot(values, 'o'), you will plot only markers and no line, but the markers will have one fixed color which doesnt (and cant) vary by the value.Grundy
S
95

There is an illustrative example of how to create custom colormaps here. The docstring is essential for understanding the meaning of cdict. Once you get that under your belt, you might use a cdict like this:

cdict = {'red':   ((0.0, 1.0, 1.0), 
                   (0.1, 1.0, 1.0),  # red 
                   (0.4, 1.0, 1.0),  # violet
                   (1.0, 0.0, 0.0)), # blue

         'green': ((0.0, 0.0, 0.0),
                   (1.0, 0.0, 0.0)),

         'blue':  ((0.0, 0.0, 0.0),
                   (0.1, 0.0, 0.0),  # red
                   (0.4, 1.0, 1.0),  # violet
                   (1.0, 1.0, 0.0))  # blue
          }

Although the cdict format gives you a lot of flexibility, I find for simple gradients its format is rather unintuitive. Here is a utility function to help generate simple LinearSegmentedColormaps:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors


def make_colormap(seq):
    """Return a LinearSegmentedColormap
    seq: a sequence of floats and RGB-tuples. The floats should be increasing
    and in the interval (0,1).
    """
    seq = [(None,) * 3, 0.0] + list(seq) + [1.0, (None,) * 3]
    cdict = {'red': [], 'green': [], 'blue': []}
    for i, item in enumerate(seq):
        if isinstance(item, float):
            r1, g1, b1 = seq[i - 1]
            r2, g2, b2 = seq[i + 1]
            cdict['red'].append([item, r1, r2])
            cdict['green'].append([item, g1, g2])
            cdict['blue'].append([item, b1, b2])
    return mcolors.LinearSegmentedColormap('CustomMap', cdict)


c = mcolors.ColorConverter().to_rgb
rvb = make_colormap(
    [c('red'), c('violet'), 0.33, c('violet'), c('blue'), 0.66, c('blue')])
N = 1000
array_dg = np.random.uniform(0, 10, size=(N, 2))
colors = np.random.uniform(-2, 2, size=(N,))
plt.scatter(array_dg[:, 0], array_dg[:, 1], c=colors, cmap=rvb)
plt.colorbar()
plt.show()

enter image description here


By the way, the for-loop

for i in range(0, len(array_dg)):
  plt.plot(array_dg[i], markers.next(),alpha=alpha[i], c=colors.next())

plots one point for every call to plt.plot. This will work for a small number of points, but will become extremely slow for many points. plt.plot can only draw in one color, but plt.scatter can assign a different color to each dot. Thus, plt.scatter is the way to go.

Strachey answered 30/5, 2013 at 12:27 Comment(7)
Now I got a problem. I also would like to get a different marker symbol according to the color (I have 13 different colors). But the scatter plot allows only one marker per plot, or do I miss something?Chemotherapy
In that case you will need to call plt.scatter (or plt.plot) once for each color/marker combination.Strachey
Why can't I use a color map created with this awesome function in plt.set_cmap()? The error is very long, the last line is ValueError: Colormap CustomMap is not recognized.Prurigo
@Ilya: First register the colormap: plt.register_cmap(name=rvb.name, cmap=rvb) and then call plt.set_cmap(rvb).Strachey
any suggestions on how to use this with plt.cm? I was hoping to color my scatterplot by the ratio of x to y. I was previously using color = plt.cm.cool(x/y) and ax.scatter(x,y,c=colors)Briefing
@As3adTintin: rvb above is a full-fledge Colormap, just like plt.cm.cool. So they are fungible: color = rvb(x/y).Strachey
@Strachey oh that was right there... anywho, thanks so much for answering!Briefing
P
133

Since the methods used in other answers seems quite complicated for such easy task, here is a new answer:

Instead of a ListedColormap, which produces a discrete colormap, you may use a LinearSegmentedColormap. This can easily be created from a list using the from_list method.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors

x,y,c = zip(*np.random.rand(30,3)*4-2)

norm=plt.Normalize(-2,2)
cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", ["red","violet","blue"])

plt.scatter(x,y,c=c, cmap=cmap, norm=norm)
plt.colorbar()
plt.show()

enter image description here


More generally, if you have a list of values (e.g. [-2., -1, 2]) and corresponding colors, (e.g. ["red","violet","blue"]), such that the nth value should correspond to the nth color, you can normalize the values and supply them as tuples to the from_list method.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors

x,y,c = zip(*np.random.rand(30,3)*4-2)

cvals  = [-2., -1, 2]
colors = ["red","violet","blue"]

norm=plt.Normalize(min(cvals),max(cvals))
tuples = list(zip(map(norm,cvals), colors))
cmap = matplotlib.colors.LinearSegmentedColormap.from_list("", tuples)

plt.scatter(x,y,c=c, cmap=cmap, norm=norm)
plt.colorbar()
plt.show()

enter image description here

Pforzheim answered 16/10, 2017 at 20:16 Comment(14)
How would you now pass an own defined range to this, e.g. that red corresponds to -5, violet to 1 and blue to 100? I would very much appreciate if you could look at the question I asked here.Bentz
Using the vmin and vmax or the norm argument of the respective plotting method.Pforzheim
This might not be as flexible as the complete custom map in the accepted answer, but that is crazily complicated and this answer is exactly what most people need when they want a custom colormap I think.Moresque
I don't think there is any drawback concerning flexibility. In fact, I would go as far as if someone finds a case of a colormap which cannot be created via .from_list instead of from a dictionary, please notify me and I will prove that not to be true.Pforzheim
How about a colormap that goes from dark to light red from greater to lesser negatives, white for exactly equal to zero, increasing to yellow at 0.5, then light blue starting from 0.5 getting deep blue for greater positive numbers? How can that be done with the .from_list method?Plemmons
@AaronBramson Yes sure, I don't see the issue with that. If it doesn't work for you you may ask a question about it where you show your failed attempts.Pforzheim
Well, half of my point (setting where the colors change) is answered here, but it seems you have to specify them on a normalized [0,1] range. The OP wants to specify the color changes on specific values in the range [-2,2] (or whatever). How is that rescaling part done with .from_list? Does it have to be done manually, or is there an option to use non-normalized values.Plemmons
Good point, I did not answer the [-2,2] part here; so I updated the answer. Apart, every colormap in matplotlib ranges between 0 and 1. The normalization is not part of the colormap, but the Normalize or *Norm instance in use.Pforzheim
@Pforzheim it's not at all clear to me how one would create a colourmap that does is not divided equally using your method. For example, 0.0-0.1 being red-violet, 0.1-0.5 being violet-blue, and 0.5-1.0 being blue-green.Box
@Box LinearSegmentedColormap.from_list("", [(0,"red"), (.1,"violet"), (.5, "blue"), (1.0, "green")]). I updated the answer to hopefully make this clearer.Pforzheim
@ImportanceOfBeingErnest: Can you add alpha value for transparency with "from_list"?Polyzoic
@Polyzoic Yes, but you will need to supply the colors as RGBA tuples, like (1.0, 0, 0, 0.5) for half transparent red.Pforzheim
Is there a way to specify the ranges of the individual colors in your colormap more directly than passing in the colorvalues. For example, if I used: cvals = [-12., 0, 12] colors = ["red", "white", "blue"] This produces an evenly spaced distribution of the 3 colors going from red, white to blue. Is there a way to decrease the amount of the distribution that the white takes up, or increase red or blue specifically?Lissalissak
@ShaunLowis In this specific case you can just interpolate the RGB values yourself. More generally, you may create a colormap first and take the colors of the map, at non-equidistant steps. E.g. if cmap is the original colormap, then cvals=[-12, -2,0,2,12]; colors = cmap(plt.Normalize(-12,12)(np.array(cvals))) will give you the new colors for the squeezed map.Pforzheim
S
95

There is an illustrative example of how to create custom colormaps here. The docstring is essential for understanding the meaning of cdict. Once you get that under your belt, you might use a cdict like this:

cdict = {'red':   ((0.0, 1.0, 1.0), 
                   (0.1, 1.0, 1.0),  # red 
                   (0.4, 1.0, 1.0),  # violet
                   (1.0, 0.0, 0.0)), # blue

         'green': ((0.0, 0.0, 0.0),
                   (1.0, 0.0, 0.0)),

         'blue':  ((0.0, 0.0, 0.0),
                   (0.1, 0.0, 0.0),  # red
                   (0.4, 1.0, 1.0),  # violet
                   (1.0, 1.0, 0.0))  # blue
          }

Although the cdict format gives you a lot of flexibility, I find for simple gradients its format is rather unintuitive. Here is a utility function to help generate simple LinearSegmentedColormaps:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors


def make_colormap(seq):
    """Return a LinearSegmentedColormap
    seq: a sequence of floats and RGB-tuples. The floats should be increasing
    and in the interval (0,1).
    """
    seq = [(None,) * 3, 0.0] + list(seq) + [1.0, (None,) * 3]
    cdict = {'red': [], 'green': [], 'blue': []}
    for i, item in enumerate(seq):
        if isinstance(item, float):
            r1, g1, b1 = seq[i - 1]
            r2, g2, b2 = seq[i + 1]
            cdict['red'].append([item, r1, r2])
            cdict['green'].append([item, g1, g2])
            cdict['blue'].append([item, b1, b2])
    return mcolors.LinearSegmentedColormap('CustomMap', cdict)


c = mcolors.ColorConverter().to_rgb
rvb = make_colormap(
    [c('red'), c('violet'), 0.33, c('violet'), c('blue'), 0.66, c('blue')])
N = 1000
array_dg = np.random.uniform(0, 10, size=(N, 2))
colors = np.random.uniform(-2, 2, size=(N,))
plt.scatter(array_dg[:, 0], array_dg[:, 1], c=colors, cmap=rvb)
plt.colorbar()
plt.show()

enter image description here


By the way, the for-loop

for i in range(0, len(array_dg)):
  plt.plot(array_dg[i], markers.next(),alpha=alpha[i], c=colors.next())

plots one point for every call to plt.plot. This will work for a small number of points, but will become extremely slow for many points. plt.plot can only draw in one color, but plt.scatter can assign a different color to each dot. Thus, plt.scatter is the way to go.

Strachey answered 30/5, 2013 at 12:27 Comment(7)
Now I got a problem. I also would like to get a different marker symbol according to the color (I have 13 different colors). But the scatter plot allows only one marker per plot, or do I miss something?Chemotherapy
In that case you will need to call plt.scatter (or plt.plot) once for each color/marker combination.Strachey
Why can't I use a color map created with this awesome function in plt.set_cmap()? The error is very long, the last line is ValueError: Colormap CustomMap is not recognized.Prurigo
@Ilya: First register the colormap: plt.register_cmap(name=rvb.name, cmap=rvb) and then call plt.set_cmap(rvb).Strachey
any suggestions on how to use this with plt.cm? I was hoping to color my scatterplot by the ratio of x to y. I was previously using color = plt.cm.cool(x/y) and ax.scatter(x,y,c=colors)Briefing
@As3adTintin: rvb above is a full-fledge Colormap, just like plt.cm.cool. So they are fungible: color = rvb(x/y).Strachey
@Strachey oh that was right there... anywho, thanks so much for answering!Briefing
A
14

If you want to automate the creating of a custom divergent colormap commonly used for surface plots, this module combined with @unutbu method worked well for me.

def diverge_map(high=(0.565, 0.392, 0.173), low=(0.094, 0.310, 0.635)):
    '''
    low and high are colors that will be used for the two
    ends of the spectrum. they can be either color strings
    or rgb color tuples
    '''
    c = mcolors.ColorConverter().to_rgb
    if isinstance(low, basestring): low = c(low)
    if isinstance(high, basestring): high = c(high)
    return make_colormap([low, c('white'), 0.5, c('white'), high])

The high and low values can be either string color names or rgb tuples. This is the result using the surface plot demo: enter image description here

Apt answered 7/6, 2015 at 1:43 Comment(0)
C
4

This seems to work for me.

def make_Ramp( ramp_colors ): 
    from colour import Color
    from matplotlib.colors import LinearSegmentedColormap

    color_ramp = LinearSegmentedColormap.from_list( 'my_list', [ Color( c1 ).rgb for c1 in ramp_colors ] )
    plt.figure( figsize = (15,3))
    plt.imshow( [list(np.arange(0, len( ramp_colors ) , 0.1)) ] , interpolation='nearest', origin='lower', cmap= color_ramp )
    plt.xticks([])
    plt.yticks([])
    return color_ramp

custom_ramp = make_Ramp( ['#754a28','#893584','#68ad45','#0080a5' ] ) 

custom color ramp

Confer answered 2/6, 2020 at 22:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.