Defining a discrete colormap for imshow in matplotlib
You can use a ListedColormap
to specify the white and red as the only colors in the color map, and the bounds determine where the transition is from one color to the next:
import matplotlib.pyplot as plt
from matplotlib import colors
import numpy as np
np.random.seed(101)
zvals = np.random.rand(100, 100) * 10
# make a color map of fixed colors
cmap = colors.ListedColormap(['white', 'red'])
bounds=[0,5,10]
norm = colors.BoundaryNorm(bounds, cmap.N)
# tell imshow about color map so that only set colors are used
img = plt.imshow(zvals, interpolation='nearest', origin='lower',
cmap=cmap, norm=norm)
# make a color bar
plt.colorbar(img, cmap=cmap, norm=norm, boundaries=bounds, ticks=[0, 5, 10])
plt.savefig('redwhite.png')
plt.show()
The resulting figure has only two colors:
I proposed essentially the same thing for a somewhat different question: 2D grid data visualization in Python
The solution is inspired by a matplotlib example. The example explains that the bounds
must be one more than the number of colors used.
The BoundaryNorm
is a normalization that maps a series of values to integers, which are then used to assign the corresponding colors. cmap.N
, in the example above, just defines the number of colors.
Matplotlib discrete colorbar
You can create a custom discrete colorbar quite easily by using a BoundaryNorm as normalizer for your scatter. The quirky bit (in my method) is making 0 showup as grey.
For images i often use the cmap.set_bad() and convert my data to a numpy masked array. That would be much easier to make 0 grey, but i couldnt get this to work with the scatter or the custom cmap.
As an alternative you can make your own cmap from scratch, or read-out an existing one and override just some specific entries.
import numpy as np
import matplotlib as mpl
import matplotlib.pylab as plt
fig, ax = plt.subplots(1, 1, figsize=(6, 6)) # setup the plot
x = np.random.rand(20) # define the data
y = np.random.rand(20) # define the data
tag = np.random.randint(0, 20, 20)
tag[10:12] = 0 # make sure there are some 0 values to show up as grey
cmap = plt.cm.jet # define the colormap
# extract all colors from the .jet map
cmaplist = [cmap(i) for i in range(cmap.N)]
# force the first color entry to be grey
cmaplist[0] = (.5, .5, .5, 1.0)
# create the new map
cmap = mpl.colors.LinearSegmentedColormap.from_list(
'Custom cmap', cmaplist, cmap.N)
# define the bins and normalize
bounds = np.linspace(0, 20, 21)
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
# make the scatter
scat = ax.scatter(x, y, c=tag, s=np.random.randint(100, 500, 20),
cmap=cmap, norm=norm)
# create a second axes for the colorbar
ax2 = fig.add_axes([0.95, 0.1, 0.03, 0.8])
cb = plt.colorbar.ColorbarBase(ax2, cmap=cmap, norm=norm,
spacing='proportional', ticks=bounds, boundaries=bounds, format='%1i')
ax.set_title('Well defined discrete colors')
ax2.set_ylabel('Very custom cbar [-]', size=12)
I personally think that with 20 different colors its a bit hard to read the specific value, but thats up to you of course.
Setting discrete colormap corresponding to specific data range in Matplotlib
There are various answers to other questions using ListedColormap and BoundaryNorm, but here's an alternative. I've ignored the placement of your colorbar, as that's not relevant to your question.
You can replace your binlabel
calculation with a call to np.digitize()
and replace your discrete_cmap()
function by using the lut
argument to get_cmap()
. Also, I find it easier to place the color bounds at .5 midpoints between the indexes rather than scale to awkward fractions of odd numbers:
import matplotlib.colors as mcol
import matplotlib.cm as cm
import matplotlib.pyplot as plt
import numpy as np
ratio = np.random.random((50,50)) * 50.0 - 20.0
fig2, ax2 = plt.subplots(figsize=(5,5))
# Turn the data into an array of N bin indexes (i.e., 0, 1 and 2).
bounds = [0,20]
iratio = np.digitize(ratio.flat,bounds).reshape(ratio.shape)
# Create a colormap containing N colors and a Normalizer that defines where
# the boundaries of the colors should be relative to the indexes (i.e., -0.5,
# 0.5, 1.5, 2.5).
cmap = cm.get_cmap("jet",lut=len(bounds)+1)
cmap_bounds = np.arange(len(bounds)+2) - 0.5
norm = mcol.BoundaryNorm(cmap_bounds,cmap.N)
# Plot using the colormap and the Normalizer.
ratio_plot = plt.pcolormesh(iratio,cmap=cmap,norm=norm)
cbar = plt.colorbar(ratio_plot,ticks=[0,1,2],orientation="horizontal")
cbar.set_ticklabels(["< 0","0~20",">20"])
Create a discrete colorbar in matplotlib
Indeed, the fist argument to colorbar
should be a ScalarMappable
, which would be the scatter plot PathCollection
itself.
Setup
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame({"x" : np.linspace(0,1,20),
"y" : np.linspace(0,1,20),
"cluster" : np.tile(np.arange(4),5)})
cmap = mpl.colors.ListedColormap(["navy", "crimson", "limegreen", "gold"])
norm = mpl.colors.BoundaryNorm(np.arange(-0.5,4), cmap.N)
Pandas plotting
The problem is that pandas does not provide you access to this ScalarMappable
directly. So one can catch it from the list of collections in the axes, which is easy if there is only one single collection present: ax.collections[0]
.
fig, ax = plt.subplots()
df.plot.scatter(x='x', y='y', c='cluster', marker='+', ax=ax,
cmap=cmap, norm=norm, s=100, edgecolor ='none', alpha=0.70, colorbar=False)
fig.colorbar(ax.collections[0], ticks=np.linspace(0,3,4))
plt.show()
Matplotlib plotting
One could consider using matplotlib directly to plot the scatter in which case you would directly use the return of the scatter
function as argument to colorbar
.
fig, ax = plt.subplots()
scatter = ax.scatter(x='x', y='y', c='cluster', marker='+', data=df,
cmap=cmap, norm=norm, s=100, edgecolor ='none', alpha=0.70)
fig.colorbar(scatter, ticks=np.linspace(0,3,4))
plt.show()
Output in both cases is identical.
how to define colormap with absolute values with matplotlib
The key in this case is the norm
, not the colormap.
The colormap defines colors for already scaled data. The norm
scales the data to a 0-1 range.
By default, a Normalize
instance will be created that scales between the min and max of the data or the vmin
and vmax
kwargs, if they are supplied.
However, there are a few different helper functions that may be useful in your case.
If you want a discrete color bar, there's a helper function to generate both a norm
and a cmap
for you: matplotlib.colors.from_levels_and_colors
It takes a list of values and a list of colors and returns a BoundaryNorm
instance and a LinearSegmentedColormap
instance:
For example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors
data1 = 3 * np.random.random((10, 10))
data2 = 5 * np.random.random((10, 10))
levels = [0, 1, 2, 3, 4, 5]
colors = ['red', 'brown', 'yellow', 'green', 'blue']
cmap, norm = matplotlib.colors.from_levels_and_colors(levels, colors)
fig, axes = plt.subplots(ncols=2)
for ax, dat in zip(axes, [data1, data2]):
im = ax.imshow(dat, cmap=cmap, norm=norm, interpolation='none')
fig.colorbar(im, ax=ax, orientation='horizontal')
plt.show()
Note that this creates a discrete colormap.
If we wanted to use a continuous colormap instead, we can either specify the same vmin
and vmax
arguments or create our own Normalize
instance and pass it in as the norm
argument for all images.
Also, there's a similar function to create a continuous colormap from a list of colors:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
data1 = 3 * np.random.random((10, 10))
data2 = 5 * np.random.random((10, 10))
colors = ['red', 'brown', 'yellow', 'green', 'blue']
cmap = LinearSegmentedColormap.from_list('name', colors)
norm = plt.Normalize(0, 5)
fig, axes = plt.subplots(ncols=2)
for ax, dat in zip(axes, [data1, data2]):
im = ax.imshow(dat, cmap=cmap, norm=norm, interpolation='none')
fig.colorbar(im, ax=ax, orientation='horizontal')
plt.show()
matplotlib imshow distorting colors
This worked for me:
plt.imshow(lena[:,:,::-1]) # RGB-> BGR
Same idea but nicer and more robust approach is to use "ellipsis" proposed by @rayryeng:
plt.imshow(lena[...,::-1])
Related Topics
Naturally Sorting Pandas Dataframe
How to Make Image/Images Disappear in Pygame
Python: Pandas Series - Why Use Loc
Python Os.Path.Join on Windows
Find the Recaptcha Element and Click on It -- Python + Selenium
Python Dictionary from an Object's Fields
How to Create Key or Append an Element to Key
How to Find the Groups of Consecutive Elements in a Numpy Array
Scraping Google Finance (Beautifulsoup)
Pyinstaller and --Onefile: How to Include an Image in the Exe File
How to Create Animated Sprites Using Sprite Sheets in Pygame
Writing a Dictionary to a CSV File with One Line for Every 'Key: Value'
Format Floats with Standard JSON Module
How to Calculate Mean Values Grouped on Another Column in Pandas
Best Way to Preserve Numpy Arrays on Disk
When Would the -E, --Editable Option Be Useful with Pip Install