I have data that is in the range -70,0 that I display using imshow() and would like to use a non-linear colorbar to represent the data as I have paterns both in the -70,-60 range and -70,0 range.
I would like the easiest way to rescale/renormalize using an arbitrary function (see example) the colorbar so that all paterns appear nicely.
Here is an example of data and function:
sample_data=(np.ones((20,20))*np.linspace(0,1,20)**3)*70-70
def renorm(value):
"""
Example of the way I would like to adjust the colorbar but it might as well be an arbitrary function
Returns a number between 0 and 1 that would correspond to the color wanted on the original colorbar
For the cmap 'inferno' 0 would be the dark purple, 0.5 the purplish orange and 1 the light yellow
"""
return np.log(value+70+1)/np.log(70+1)
This is what I managed to do:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import PowerNorm
sample_data=(np.ones((20,20))*np.linspace(0,1,20)**3)*70-70
plt.figure()
im = plt.imshow(sample_data+70, norm=PowerNorm(gamma=0.5))
cbar = plt.colorbar(orientation='horizontal')
cbar.ax.set_xticklabels(np.arange(-70, 0, 8))
plt.show()
You can change the gamma.
However, this kind of visualization is not recommended, see: http://matplotlib.org/users/colormapnorms.html
under "Power-law" -> "Note"
Related
I am quite new to python programming. I have a script with me that plots out a heat map using matplotlib. Range of X-axis value = (-180 to +180) and Y-axis value =(0 to 180). The 2D heatmap colours areas in Rainbow according to the number of points occuring in a specified area in the x-y graph (defined by the 'bin' (see below)).
In this case, x = values_Rot and y = values_Tilt (see below for code).
As of now, this script colours the 2D-heatmap in the linear scale. How do I change this script such that it colours the heatmap in the log scale? Please note that I only want to change the heatmap colouring scheme to log-scale, i.e. only the number of points in a specified area. The x and y-axis stay the same in linear scale (not in logscale).
A portion of the code is here.
rot_number = get_header_number(headers, AngleRot)
tilt_number = get_header_number(headers, AngleTilt)
psi_number = get_header_number(headers, AnglePsi)
values_Rot = []
values_Tilt = []
values_Psi = []
for line in data:
try:
values_Rot.append(float(line.split()[rot_number]))
values_Tilt.append(float(line.split()[tilt_number]))
values_Psi.append(float(line.split()[psi_number]))
except:
print ('This line didnt work, it may just be a blank space. The line is:' + line)
# Change the values here if you want to plot something else, such as psi.
# You can also change how the data is binned here.
plt.hist2d(values_Rot, values_Tilt, bins=25,)
plt.colorbar()
plt.show()
plt.savefig('name_of_output.png')
You can use a LogNorm for the colors, using plt.hist2d(...., norm=LogNorm()). Here is a comparison.
To have the ticks in base 2, the developers suggest adding the base to the LogLocator and the LogFormatter. As in this case the LogFormatter seems to write the numbers with one decimal (.0), a StrMethodFormatter can be used to show the number without decimals. Depending on the range of numbers, sometimes the minor ticks (shorter marker lines) also get a string, which can be suppressed assigning a NullFormatter for the minor colorbar ticks.
Note that base 2 and base 10 define exactly the same color transformation. The position and the labels of the ticks are different. The example below creates two colorbars to demonstrate the different look.
import matplotlib.pyplot as plt
from matplotlib.ticker import NullFormatter, StrMethodFormatter, LogLocator
from matplotlib.colors import LogNorm
import numpy as np
from copy import copy
# create some toy data for a standalone example
values_Rot = np.random.randn(100, 10).cumsum(axis=1).ravel()
values_Tilt = np.random.randn(100, 10).cumsum(axis=1).ravel()
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(15, 4))
cmap = copy(plt.get_cmap('hot'))
cmap.set_bad(cmap(0))
_, _, _, img1 = ax1.hist2d(values_Rot, values_Tilt, bins=40, cmap='hot')
ax1.set_title('Linear norm for the colors')
fig.colorbar(img1, ax=ax1)
_, _, _, img2 = ax2.hist2d(values_Rot, values_Tilt, bins=40, cmap=cmap, norm=LogNorm())
ax2.set_title('Logarithmic norm for the colors')
fig.colorbar(img2, ax=ax2) # default log 10 colorbar
cbar2 = fig.colorbar(img2, ax=ax2) # log 2 colorbar
cbar2.ax.yaxis.set_major_locator(LogLocator(base=2))
cbar2.ax.yaxis.set_major_formatter(StrMethodFormatter('{x:.0f}'))
cbar2.ax.yaxis.set_minor_formatter(NullFormatter())
plt.show()
Note that log(0) is minus infinity. Therefore, the zero values in the left plot (darkest color) are left empty (white background) on the plot with the logarithmic color values. If you just want to use the lowest color for these zeros, you need to set a 'bad' color. In order not the change a standard colormap, the latest matplotlib versions wants you to first make a copy of the colormap.
PS: When calling plt.savefig() it is important to call it before plt.show() because plt.show() clears the plot.
Also, try to avoid the 'jet' colormap, as it has a bright yellow region which is not at the extreme. It may look nice, but can be very misleading. This blog article contains a thorough explanation. The matplotlib documentation contains an overview of available colormaps.
Note that to compare two plots, plt.subplots() needs to be used, and instead of plt.hist2d, ax.hist2d is needed (see this post). Also, with two colorbars, the elements on which the colorbars are based need to be given as parameter. A minimal change to your code would look like:
from matplotlib.ticker import NullFormatter, StrMethodFormatter, LogLocator
from matplotlib.colors import LogNorm
from matplotlib import pyplot as plt
from copy import copy
# ...
# reading the data as before
cmap = copy(plt.get_cmap('magma'))
cmap.set_bad(cmap(0))
plt.hist2d(values_Rot, values_Tilt, bins=25, cmap=cmap, norm=LogNorm())
cbar = plt.colorbar()
cbar.ax.yaxis.set_major_locator(LogLocator(base=2))
cbar.ax.yaxis.set_major_formatter(StrMethodFormatter('{x:.0f}'))
cbar.ax.yaxis.set_minor_formatter(NullFormatter())
plt.savefig('name_of_output.png') # needs to be called prior to plt.show()
plt.show()
How do I change a colormap color scheme to show the same color beyond a point.
E.g. here's my colormap:
import palettable
cmap = palettable.colorbrewer.sequential.YlGn_9.mpl_colormap
If I use this colormap to plot a range from 0 to 100, how can I modify the color map such that beyond 50, it changes to the color red?
You could create the colormap for the given range (0 →100) by stacking two different colormaps on top of each other as shown:
Illustration:
import numpy as np
import matplotlib.pyplot as plt
import palettable
import matplotlib.colors as mcolors
# Set random seed
np.random.seed(42)
# Create random values of shape 10x10
data = np.random.rand(10,10) * 100
# Given colormap which takes values from 0→50
colors1 = palettable.colorbrewer.sequential.YlGn_9.mpl_colormap(np.linspace(0, 1, 256))
# Red colormap which takes values from 50→100
colors2 = plt.cm.Reds(np.linspace(0, 1, 256))
# stacking the 2 arrays row-wise
colors = np.vstack((colors1, colors2))
# generating a smoothly-varying LinearSegmentedColormap
cmap = mcolors.LinearSegmentedColormap.from_list('colormap', colors)
plt.pcolor(data, cmap=cmap)
plt.colorbar()
# setting the lower and upper limits of the colorbar
plt.clim(0, 100)
plt.show()
Incase you want the upper portion to be of the same color and not spread over the length of the colormap, you could make the following modification:
colors2 = plt.cm.Reds(np.linspace(1, 1, 256))
cmap.set_over("red")
And you may wanna use one of the norm functions to set your specific bounds. If using imshow, you can also set the parameter vmin=50 to make that your top value.
You can create a new colormap from an existing colormap using:
newcmap = cmap.from_list('newcmap',list(map(cmap,range(50))), N=50)
This new map uses the last value from the colormap for colors over 50. To make the last color red, we can just append red to the last color in the list that defines the colormap.
newcmap = cmap.from_list('newcmap',list(map(cmap,range(50)))+[(1,0,0,1)], N=51)
import palettable
from matplotlib import pyplot as plt
cmap = palettable.colorbrewer.sequential.YlGn_9.mpl_colormap
newcmap = cmap.from_list('newcmap',list(map(cmap,range(50))), N=50)
for x in range(80):
plt.bar(x,1, width=1, edgecolor='none',facecolor=newcmap(x))
plt.show()
newcmap = cmap.from_list('newcmap',list(map(cmap,range(50)))+[(1,0,0,1)], N=51)
for x in range(80):
plt.bar(x,1, width=1, edgecolor='none',facecolor=newcmap(x))
plt.show()
You can access the colors with:
cmap_dict = cmap._segmentdata
which yields a dictionary. By indexing it with:
red = cmap_dict["red"]
green= cmap_dict["green"]
blue = cmap_dict["blue"]
alpha = cmap_dict["alpha"]
Now you can add a color from the list like this:
red .append(red [1])
recombine them into a dictionary with the 4 keys like:
cmap_dict_new["red"] = red
and create a new colormap with:
new_cmap = palettable.palette.ListedColormap(cmap_dict_new)
I don't think you should change the colormap, but rather the object using the colormap. I asked a similar question not so long ago: change color for first level of contourf, and I took the answer from here: Python matplotlib change default color for values exceeding colorbar range
If you use contours in your plot for example, you should do something like this:
cs = pyplot.contourf(x,y,z, cmap=your_cmap)
cs.cmap.set_over('r') # Change color to red
cs.set_clim(0, 50) # Set the limit beyond which everything is red
cb = pyplot.colorbar(cs) # Plot the colorbar (if needed)
I created a 2D contourf plot with viridis color scheme in bounds vmin=0,vmax=4. Here is the code:
ax.contourf(xi/d,yi/d,data/d,12,vmin=0,vmax=4,cmap=vs.viridis(),aspect='equal')
Now I have from another calculation a data point at a specific location with data=3.5 and want to superimpose this to the contour using a colored circle patch.
So how can I add the rgb color information to the circle within the range and used colorscheme of my contourf
Thanks for help
you can use a matplotlib.colors.Normalize instance to do this:
import matplotlib.pyplot as plt
import matplotlib.colors as colors
norm = colors.Normalize(vmin=0,vmax=4)
cmap = plt.cm.viridis
mycolor = cmap(norm(3.5))
print mycolor
# (0.67848900000000001, 0.86374200000000001, 0.189503, 1.0)
Note, if the colormap has been imported from a different file, then it will need to be registered with matplotlib first before this works. For example:
import myviridisfile as vs
plt.register_cmap(name='viridis', cmap=vs.viridis)
I'm trying to add a color bar in a graph, but I don't understand how it works. The problem is that I make my own colorcode by:
x = np.arange(11)
ys = [i+x+(i*x)**2 for i in range(11)]
colors = cm.rainbow(np.linspace(0, 1, len(ys)))
and colors[i] will give me a new color. Then I use (homemade) functions to select the relevant data and plot them accordingly. This would look something like this:
function(x,y,concentration,temperature,1,37,colors[0])
function(x,y,concentration,temperature,2,37,colors[1])
# etc
Now I want to add the colors in a color bar, with labels I can change. How do I do this?
I have seen several examples where you plot all the data as one array, with automated color bars, but here I plot the data one by one (by using functions to select the relevant data).
EDIT:
function(x,y,concentration,temperature,1,37,colors[0]) looks like this (simplified):
def function(x,y,c,T,condition1,condition2,colors):
import matplotlib.pyplot as plt
i=0
for element in c:
if element == condition1:
if T[i]==condition2:
plt.plot(x,y,color=colors,linewidth=2)
i=i+1
return
Drawing a colorbar aside a line plot
Please map my solution (I used simply 11 sines of different amplitudes) to your problem (as I told you, it is difficult to understand from what you wrote in your Q).
import matplotlib
import numpy as np
from matplotlib import pyplot as plt
# an array of parameters, each of our curves depend on a specific
# value of parameters
parameters = np.linspace(0,10,11)
# norm is a class which, when called, can normalize data into the
# [0.0, 1.0] interval.
norm = matplotlib.colors.Normalize(
vmin=np.min(parameters),
vmax=np.max(parameters))
# choose a colormap
c_m = matplotlib.cm.cool
# create a ScalarMappable and initialize a data structure
s_m = matplotlib.cm.ScalarMappable(cmap=c_m, norm=norm)
s_m.set_array([])
# plotting 11 sines of varying amplitudes, the colors are chosen
# calling the ScalarMappable that was initialised with c_m and norm
x = np.linspace(0,np.pi,31)
for parameter in parameters:
plt.plot(x,
parameter*np.sin(x),
color=s_m.to_rgba(parameter))
# having plotted the 11 curves we plot the colorbar, using again our
# ScalarMappable
plt.colorbar(s_m)
# That's all, folks
plt.show()
Example
Acknowledgements
A similar problem, about a scatter plot
Update — April 14, 2021
With recent versions of Matplotlib, the statement s_m.set_array([]) is not required any more. On the other hand, it does no harm.
When plotting, in place of color=s_m.to_rgba(parameter) one may want to use the (slightly) more obvious color=c_m(norm(parameter)).
I have a figure that consists of an image displayed by imshow(), a contour and a vector field set by quiver(). I have colored the vector field based on another scalar quantity. On the right of my figure, I have made a colorbar(). This colorbar() represents the values displayed by imshow() (which can be positive and negative in my case). I'd like to know how I could setup another colorbar which would be based on the values of the scalar quantity upon which the color of the vectors is based. Does anyone know how to do that?
Here is an example of the image I've been able to make. Notice that the colors of the vectors go from blue to red. According to the current colorbar, blue means negative. However I know that the quantity represented by the color of the vector is always positive.
Simply call colorbar twice, right after each plotting call. Pylab will create a new colorbar matching to the latest plot. Note that, as in your example, the quiver values range from 0,1 while the imshow takes negative values. For clarity (not shown in this example), I would use different colormaps to distinguish the two types of plots.
import numpy as np
import pylab as plt
# Create some sample data
dx = np.linspace(0,1,20)
X,Y = np.meshgrid(dx,dx)
Z = X**2 - Y
Z2 = X
plt.imshow(Z)
plt.colorbar()
plt.quiver(X,Y,Z2,width=.01,linewidth=1)
plt.colorbar()
plt.show()
Running quiver doesn't necessarily return the type of mappable object that colorbar() requires. I think it might be because I explicitly "have colored the vector field based on another scalar quantity" like Heimdall says they did. Therefore, Hooked's answer didn't work for me.
I had to create my own mappable for the color bar to read. I did this by using Normalize from matplotlib.colors on the data that I wanted to use to color my quiver vectors (which I'll call C, which is an array of the same shape as X, Y, U, and V.)
My quiver call looks like this:
import matplotlib.pyplot as pl
import matplotlib.cm as cm
import matplotlib.colors as mcolors
import matplotlib.colorbar as mcolorbar
pl.figure()
nz = mcolors.Normalize()
nz.autoscale(C)
pl.quiver(X, Y, U, V, color=cm.jet(nz(C)))
cax,_ = mcolorbar.make_axes(pl.gca())
cb = mcolorbar.ColorbarBase(cax, cmap=cm.jet, norm=nz)
cb.set_label('color data meaning')
Giving any other arguments to the colorbar function gave me a variety of errors.