How to create a discrete colormap that maps integers to colors, invariant to range of input data - matplotlib

Let's say I have a vector containing integers from the set [1,2,3]. I would like to create a colormap in which 1 always appears as blue, 2 always appears as red, and 3 always appears as purple, regardless of the range of the input data--e.g., even if the input vector only contains 1s and 2s, I would still like those to appear as blue and red, respectively (and purple is not used in this case).
I've tried the code below:
This works as expected (data contains 1, 2 and 3):
cmap = colors.ListedColormap(["blue", "red", "purple"])
bounds = [0.5,1.5,2.5,3.5]
norm = colors.BoundaryNorm(bounds, cmap.N)
data = np.array([1,2,1,2,3])
sns.heatmap(data.reshape(-1,1), cmap=cmap, norm=norm, annot=True)
Does not work as expected (data contains only 1 and 2):
cmap = colors.ListedColormap(["blue", "red", "purple"])
bounds = [0.5,1.5,2.5,3.5]
norm = colors.BoundaryNorm(bounds, cmap.N)
data = np.array([1,2,1,2,2])
sns.heatmap(data.reshape(-1,1), cmap=cmap, norm=norm, annot=True)
In the first example, 1 appears as blue, 2 appears as red and 3 appears as purple, as desired.
In the second example, 1 appears as blue and 2 appears as purple, while red is not used.

Not completely sure, but I think this minimal example solves your problem. Here, I've taken an actual colormap and edited it to produce a smaller version of it. Hope it helps!
#0. Import libraries
#==============================
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import colors
import seaborn as sns
import numpy as np
#==============================
#1. Create own colormap
#======================================
#1.1. Choose the colormap you want to
#pick up colors from
source_cmap=matplotlib.cm.get_cmap('Set2')
#1.2. Choose number of colors and set a step
cols=4;step=1/float(cols - 1)
#1.3. Declare a vector to store given colors
cmap_vec=[]
#1.4. Run from 0 to 1 (limits of colormap)
#stepwise and pick up equidistant colors
#---------------------------------------
for color in np.arange(0,1.1,step):
#store color in vector
cmap_vec.append( source_cmap(color) )
#---------------------------------------
#1.5. Create colormap with chosen colors
custom_cmap=\
colors.ListedColormap([ color for color in cmap_vec ])
#====================================
#2. Basic example to plot in
#======================================
A = np.matrix('0 3; 1 2')
B=np.asarray(A)
ax=sns.heatmap(B,annot=True,cmap=custom_cmap)
plt.show()
#======================================

Related

Multiple different kinds of plots on a single figure and save it to a video

I am trying to plot multiple different plots on a single matplotlib figure with in a for loop. At the moment it is all good in matlab as shown in the picture below and then am able to save the figure as a video frame. Here is a link of a sample video generated in matlab for 10 frames
In python, tried it as below
import matplotlib.pyplot as plt
for frame in range(FrameStart,FrameEnd):#loop1
# data generation code within a for loop for n frames from source video
array1 = np.zeros((200, 3800))
array2 = np.zeros((19,2))
array3 = np.zeros((60,60))
for i in range(len(array2)):#loop2
#generate data for arrays 1 to 3 from the frame data
#end loop2
plt.subplot(6,1,1)
plt.imshow(DataArray,cmap='gray')
plt.subplot(6, 1, 2)
plt.bar(data2D[:,0], data2D[:,1])
plt.subplot(2, 2, 3)
plt.contourf(mapData)
# for fourth plot, use array2[3] and array2[5], plot it as shown and keep the\is #plot without erasing for next frame
not sure how to do the 4th axes with line plots. This needs to be there (done using hold on for this axis in matlab) for the entire sequence of frames processing in the for loop while the other 3 axes needs to be erased and updated with new data for each frame in the movie. The contour plot needs to be square all the time with color bar on the side. At the end of each frame processing, once all the axes are updated, it needs to be saved as a frame of a movie. Again this is easily done in matlab, but not sure in python.
Any suggestions
thanks
I guess you need something like this format.
I have used comments # in code to answer your queries. Please check the snippet
import matplotlib.pyplot as plt
fig=plt.figure(figsize=(6,6))
ax1=fig.add_subplot(311) #3rows 1 column 1st plot
ax2=fig.add_subplot(312) #3rows 1 column 2nd plot
ax3=fig.add_subplot(325) #3rows 2 column 5th plot
ax4=fig.add_subplot(326) #3rows 2 column 6th plot
plt.show()
To turn off ticks you can use plt.axis('off'). I dont know how to interpolate your format so left it blank . You can adjust your figsize based on your requirements.
import numpy as np
from numpy import random
import matplotlib.pyplot as plt
fig=plt.figure(figsize=(6,6)) #First is width Second is height
ax1=fig.add_subplot(311)
ax2=fig.add_subplot(312)
ax3=fig.add_subplot(325)
ax4=fig.add_subplot(326)
#Bar Plot
langs = ['C', 'C++', 'Java', 'Python', 'PHP']
students = [23,17,35,29,12]
ax2.bar(langs,students)
#Contour Plot
xlist = np.linspace(-3.0, 3.0, 100)
ylist = np.linspace(-3.0, 3.0, 100)
X, Y = np.meshgrid(xlist, ylist)
Z = np.sqrt(X**2 + Y**2)
cp = ax3.contourf(X, Y, Z)
fig.colorbar(cp,ax=ax3) #Add a colorbar to a plot
#Multiple line plot
x = np.linspace(-1, 1, 50)
y1 = 2*x + 1
y2 = 2**x + 1
ax4.plot(x, y2)
ax4.plot(x, y1, color='red',linewidth=1.0)
plt.tight_layout() #Make sures plots dont overlap
plt.show()

mayavi color all the shape by one color except "some" vertices

To point out shape correspondences I've created, I would like to color a 3d mesh in gray, except some points (e.g. 1 point) in red.
Here's my current code, which Unfortunately colors the whole figure in blue, and the last point in red.
And my code
mlab.figure()
part_color = np.full((self.f.shape[0]),0.98)
part_color[point_idx] = 1
part_plot = mlab.triangular_mesh(self.part.vx, self.part.vy, self.part.vz, self.part.triv,
scalars=part_color[:, np.newaxis])
Here is optimally what I'm aiming for (ignore the rest of the figures, I just want a red ball around some points)
you have to modify the Look Up Table (LUT) for that.
I took the "LUT modification" example and adapted it to your needs (highest value is red, everything else is gray):
# Create some data
import numpy as np
x, y = np.mgrid[-10:10:200j, -10:10:200j]
z = 100 * np.sin(x * y) / (x * y)
# Visualize it with mlab.surf
from mayavi import mlab
mlab.figure(bgcolor=(1, 1, 1))
surf = mlab.surf(z, colormap='cool')
# Retrieve the LUT of the surf object.
lut = surf.module_manager.scalar_lut_manager.lut.table.to_array()
# The lut is a 255x4 array, with the columns representing RGBA
# (red, green, blue, alpha) coded with integers going from 0 to 255.
# We modify the alpha channel to add a transparency gradient
lut[:] = 255/2 # all grey
# red
lut[-1,0] = 255
lut[-1,1] = 0
lut[-1,2] = 0
lut[:, -1] = 255 # 100% translucency
# and finally we put this LUT back in the surface object. We could have
# added any 255*4 array rather than modifying an existing LUT.
surf.module_manager.scalar_lut_manager.lut.table = lut
# We need to force update of the figure now that we have changed the LUT.
mlab.draw()
mlab.view(40, 85)

Annotation box does not appear in matplotlib

The planned annotation box does not appear on my plot, however, I've tried a wide range of values for its coordinates.
What's wrong with that?!
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
def f(s,t):
a = 0.7
b = 0.8
Iext= 0.5
tau = 12.5
v = s[0]
w = s[1]
dndt = v - np.power(v,3)/3 - w + Iext
dwdt = (v + a - b * w)/tau
return [dndt, dwdt]
t = np.linspace(0,200)
s0=[1,1]
s = odeint(f,s0,t)
plt.plot(t,s[:,0],'b-', linewidth=1.0)
plt.xlabel(r"$t(sec.)$")
plt.ylabel(r"$V (volt)$")
plt.legend([r"$V$"])
annotation_string = r"$I_{ext}=0.5$"
plt.text(15, 60, annotation_string, bbox=dict(facecolor='red', alpha=0.5))
plt.show()
The coordinates to plt.text are data coordinates by default. This means in order to be present in the plot they should not exceed the data limits of your plot (here, ~0..200 in x direction, ~-2..2 in y direction).
Something like plt.text(10,1.8) should work.
The problem with that is that once the data limits change (because you plot something different or add another plot) the text item will be at a different position inside the canvas.
If this is undesired, you can specify the text in axes coordinates (ranging from 0 to 1 in both directions). In order to place the text always in the top left corner of the axes, independent on what you plot there, you can use e.g.
plt.text(0.03,0.97, annotation_string, bbox=dict(facecolor='red', alpha=0.5),
transform=plt.gca().transAxes, va = "top", ha="left")
Here the transform keyword tells the text to use Axes coordinates, and va = "top", ha="left" means, that the top left corner of the text should be the anchor point.
The annotation is appearing far above your plot because you have given a 'y' coordinate of 60, whereas your plot ends at '2' (upwards).
Change the second argument here:
plt.text(15, 60, annotation_string, bbox=dict(facecolor='red', alpha=0.5))
It needs to be <=2 to show up on the plot itself. You may also want to change the x coorinate (from 15 to something less), so that it doesn't obscure your lines.
e.g.
plt.text(5, 1.5, annotation_string, bbox=dict(facecolor='red', alpha=0.5))
Don't be alarmed by my (5,1.5) suggestion, I would then add the following line to the top of your script (beneath your imports):
rcParams['legend.loc'] = 'best'
This will choose a 'best fit' for your legend; in this case, top left (just above your annotation). Both look quite neat then, your choice though :)

How to change colorbar's color (in some particular value interval)?

In matplotlib, I would like to change colorbar's color in some particular value interval. For example, I would like to change the seismic colorbar, to let the values between -0.5 and 0.5 turn white, how can I do this?
thank you very much
You basically need to create your own colormap that has the particular features you want. Of course it is possible to make use of existing colormaps when doing so.
Colormaps are always ranged between 0 and 1. This range will then be mapped to the data interval. So in order to create whites between -0.5 and 0.5 we need to know the range of data - let's say data goes from -1 to 1. We can then decide to have the lower (blues) part of the seismic map go from -1 to -0.5, then have white between -0.5 and +0.5 and finally the upper part of the seismic map (reds) from 0.5 to 1. In the language of a colormap this corresponds to the ranges [0,0.25], [0.25, 0.75] and [0.75,1]. We can then create a list, with the first and last 25% percent being the colors of the seismic map and the middle 50% white.
This list can be used to create a colormap, using matplotlib.colors.LinearSegmentedColormap.from_list("colormapname", listofcolors).
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors
n=50
x = 0.5
lower = plt.cm.seismic(np.linspace(0, x, n))
white = plt.cm.seismic(np.ones(100)*0.5)
upper = plt.cm.seismic(np.linspace(1-x, 1, n))
colors = np.vstack((lower, white, upper))
tmap = matplotlib.colors.LinearSegmentedColormap.from_list('terrain_map_white', colors)
x = np.linspace(0,10)
X,Y = np.meshgrid(x,x)
z = np.sin(X) * np.cos(Y*0.4)
fig, ax = plt.subplots()
im = ax.imshow(z, cmap=tmap)
plt.colorbar(im)
plt.show()
For more general cases, you may need a color normalization (using matplotlib.colors.Normalize). See e.g. this example, where a certain color in the colormap is always fixed at a data value of 0, independent of the data range.

Show unique color for unique value

I have a 2d numpy array obtained by reading from an image. The unique values of the array are 0, 1, and 2. I want to plot the image showing unique colors red, green, and blue for the values 0,1, and 2 respectively.
plt.imshow(data, cmap=colors.ListedColormap(['red'])
How would you do it?
from matplotlib.colors import from_levels_and_colors
cmap, norm = from_levels_and_colors([0,1,2,3],['red','green','blue'])
plt.imshow(data, cmap=cmap, norm=norm)