My cmap won't separate around 0, what exactly is wrong? - python-3.x

I'm trying to plot out a contour plot with a colour scheme that has one colour for positive values and one colour for negative values. I tried using the answer in Colorplot that distinguishes between positive and negative values but to no avail. I've attached a snippet of code below, with this code the cmap doesn't separate Red and Blue at 0 but at -40. What exactly is wrong?
import numpy as np
from matplotlib.colors import BoundaryNorm
from matplotlib.ticker import MaxNLocator
Vr=1438.7228 #some constants
Va=626.8932
dr=3.11
da=1.55
def func(x,y):
repulsive = Vr*np.log( ((y+x)**2 + dr**2)/((y-x)**2 + dr**2) )
attractive = Va*np.log( ((y+x)**2 + da**2)/((y-x)**2 + da**2) )
return (1.0/(4.0*x*y))*(repulsive-attractive)
x = np.linspace(1e-4,10,100)
y = np.linspace(1e-4,10,100)
X, Y = np.meshgrid(x, y)
Z = func(X,Y)
levels = MaxNLocator(nbins=15).tick_values(Z.min(), Z.max())
cmap = plt.get_cmap('RdBu')
norm = BoundaryNorm(levels, ncolors=cmap.N, clip=True)
sc = plt.contourf(X, Y, Z, norm=norm, cmap=cmap)
plt.colorbar(sc)
plt.show()

Related

Plotting colorbar in Python 3

I am trying to color the errorbar points based on the color from an array. But getting an error. My code is shown below:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.cm import ScalarMappable, coolwarm as cmap
from matplotlib.colors import Normalize
fig = plt.figure(1)
sp = fig.add_subplot(1, 1, 1)
sp.set_xlabel(r'$x$')
sp.set_ylabel(r'$y$')
x = np.random.rand(10)
y = np.random.rand(10)
M = np.logspace(9, 10, 10)
norm = Normalize(vmin=8, vmax=11,clip=False) # controls the min and max of the colorbar
smap = ScalarMappable(cmap=cmap, norm=norm)
for xi, yi, Mi in zip(x, y, M):
c = cmap(norm(np.log10(Mi))) # make sure to color by log of mass, not mass
sp.errorbar(
xi,
yi,
yerr=[[.1], [.1]],
xerr=[[.1], [.1]],
ecolor=c,
marker='o',
mec=c,
mfc=c
)
cb = plt.colorbar(smap)
cb.set_label(r'$\log_{10}M$')
I am getting the following error:
TypeError: You must first set_array for mappable
For matplotlib < 3.1, you need to set an array - which can be empty
sm = ScalarMappable(cmap=cmap, norm=norm)
sm.set_array([])
fig.colorbar(sm)
For matplotlib >= 3.1, this is not necessary any more.
sm = ScalarMappable(cmap=cmap, norm=norm)
fig.colorbar(sm)

Draw curves with triple colors and width by using matplotlib and LineCollection [duplicate]

The figure above is a great artwork showing the wind speed, wind direction and temperature simultaneously. detailedly:
The X axes represent the date
The Y axes shows the wind direction(Southern, western, etc)
The variant widths of the line were stand for the wind speed through timeseries
The variant colors of the line were stand for the atmospheric temperature
This simple figure visualized 3 different attribute without redundancy.
So, I really want to reproduce similar plot in matplotlib.
My attempt now
## Reference 1 http://stackoverflow.com/questions/19390895/matplotlib-plot-with-variable-line-width
## Reference 2 http://stackoverflow.com/questions/17240694/python-how-to-plot-one-line-in-different-colors
def plot_colourline(x,y,c):
c = plt.cm.jet((c-np.min(c))/(np.max(c)-np.min(c)))
lwidths=1+x[:-1]
ax = plt.gca()
for i in np.arange(len(x)-1):
ax.plot([x[i],x[i+1]], [y[i],y[i+1]], c=c[i],linewidth = lwidths[i])# = lwidths[i])
return
x=np.linspace(0,4*math.pi,100)
y=np.cos(x)
lwidths=1+x[:-1]
fig = plt.figure(1, figsize=(5,5))
ax = fig.add_subplot(111)
plot_colourline(x,y,prop)
ax.set_xlim(0,4*math.pi)
ax.set_ylim(-1.1,1.1)
Does someone has a more interested way to achieve this? Any advice would be appreciate!
Using as inspiration another question.
One option would be to use fill_between. But perhaps not in the way it was intended. Instead of using it to create your line, use it to mask everything that is not the line. Under it you can have a pcolormesh or contourf (for example) to map color any way you want.
Look, for instance, at this example:
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import interp1d
def windline(x,y,deviation,color):
y1 = y-deviation/2
y2 = y+deviation/2
tol = (y2.max()-y1.min())*0.05
X, Y = np.meshgrid(np.linspace(x.min(), x.max(), 100), np.linspace(y1.min()-tol, y2.max()+tol, 100))
Z = X.copy()
for i in range(Z.shape[0]):
Z[i,:] = c
#plt.pcolormesh(X, Y, Z)
plt.contourf(X, Y, Z, cmap='seismic')
plt.fill_between(x, y2, y2=np.ones(x.shape)*(y2.max()+tol), color='w')
plt.fill_between(x, np.ones(x.shape) * (y1.min() - tol), y2=y1, color='w')
plt.xlim(x.min(), x.max())
plt.ylim(y1.min()-tol, y2.max()+tol)
plt.show()
x = np.arange(100)
yo = np.random.randint(20, 60, 21)
y = interp1d(np.arange(0, 101, 5), yo, kind='cubic')(x)
dv = np.random.randint(2, 10, 21)
d = interp1d(np.arange(0, 101, 5), dv, kind='cubic')(x)
co = np.random.randint(20, 60, 21)
c = interp1d(np.arange(0, 101, 5), co, kind='cubic')(x)
windline(x, y, d, c)
, which results in this:
The function windline accepts as arguments numpy arrays with x, y , a deviation (like a thickness value per x value), and color array for color mapping. I think it can be greatly improved by messing around with other details but the principle, although not perfect, should be solid.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
x = np.linspace(0,4*np.pi,10000) # x data
y = np.cos(x) # y data
r = np.piecewise(x, [x < 2*np.pi, x >= 2*np.pi], [lambda x: 1-x/(2*np.pi), 0]) # red
g = np.piecewise(x, [x < 2*np.pi, x >= 2*np.pi], [lambda x: x/(2*np.pi), lambda x: -x/(2*np.pi)+2]) # green
b = np.piecewise(x, [x < 2*np.pi, x >= 2*np.pi], [0, lambda x: x/(2*np.pi)-1]) # blue
a = np.ones(10000) # alpha
w = x # width
fig, ax = plt.subplots(2)
ax[0].plot(x, r, color='r')
ax[0].plot(x, g, color='g')
ax[0].plot(x, b, color='b')
# mysterious parts
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
# mysterious parts
rgba = list(zip(r,g,b,a))
lc = LineCollection(segments, linewidths=w, colors=rgba)
ax[1].add_collection(lc)
ax[1].set_xlim(0,4*np.pi)
ax[1].set_ylim(-1.1,1.1)
fig.show()
I notice this is what I suffered.

Colormaps with a colorscale AND one color for unwanted values

I would like to plot a grid of 3 variables (same min, same max, same spacing) in 3D and I would like each point on the grid to have a specific color according to a function f which is a function of these 3 variables except for when the values of the function are superior to a specific threshold for which I assign another color.
The code below as what I have tried so far:
from mpl_toolkits.mplot3d import axes3d
import matplotlib.colors
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
import math
from matplotlib.colors import ListedColormap, LinearSegmentedColormap
%matplotlib notebook
x = np.arange(0.001, 0.175, 0.01)
y = np.arange(0.001, 0.175, 0.01)
z = np.arange(0.001, 0.175, 0.01)
X, Y, Z = np.meshgrid(x, y, z)
def function(X,Y,Z):
'''function of (X,Y,Z) going from 0 to high values'''
return(f)
f=function(X,Y,Z)
#flatten the f array (I think there is a function to flatten an array but I have seen it to late)
fflat=[]
for l in f:
for p in l:
for t in p:
fflat.append(t)
#masking high values with the highest interesting value: maxV
mfflat = ma.masked_greater(fflat, maxV)
mfflat = mfflat.filled(maxV)
#normalizing values and mapping to veridis cmap:
cmap = matplotlib.cm.get_cmap('viridis')
norm = matplotlib.colors.Normalize(vmin=min(mfflat), vmax=maxV) #(vmax=maxV=max(mfflat))
colors = [cmap(norm(value)) for value in mfflat]
#plot
ax.scatter(X, Y, Z, color=colors, s=10, alpha=1)
cax, _ = matplotlib.colorbar.make_axes(ax)
cbar = matplotlib.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm)
The problem is that now all the "unwanted high values", i.e. values > maxV have the same colors as my "maximal wanted values", i.e. maxV ...
I would like all my "unwanted values" outside of my veridis colorscale and giving them another unique color.
Thanks for your help !
Cheers
Thanks to ImportanceOfBeingErnest for the answer, I just had to use: cmap.set_over, here is the corrected code:
x = np.arange(0.001, 0.175, 0.01)
y = np.arange(0.001, 0.175, 0.01)
z = np.arange(0.001, 0.175, 0.01)
X, Y, Z = np.meshgrid(x, y, z)
def function(X,Y,Z):
'''function of (X,Y,Z) going from 0 to high values'''
return(f)
f=function(X,Y,Z)
#flatten the f array (I think there is a function to flatten an array but I have seen it to late)
fflat=[]
for l in f:
for p in l:
for t in p:
fflat.append(t)
cmap = plt.cm.get_cmap('viridis')
cmap.set_over(color=(0,0,0), alpha=0.5)
norm = matplotlib.colors.Normalize(vmin=minV, vmax=maxV)
colors = [cmap(norm(value)) for value in fflat]
#plot
ax.scatter(X, Y, Z, color=colors, s=10, alpha=1)
cax, _ = matplotlib.colorbar.make_axes(ax)
cbar = matplotlib.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm)
In the meantime I have found a workaround by creating the "colors" list selectively which is of course not as clean as using cmap.set_over:
colors=[]
for value in fflat:
if minV <= value <= maxV:
colors.append(cmap(norm(value)))
else:
colors.append((255/258,255/258,255/258,0))

Axis label missing

I am trying to create a 3D plot but I am having trouble with the z-axis label. It simply doesn't appear in the graph. How do I amend this? The code is as follows
# Gamma vs Current step 2
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
h = np.arange(0.1,5.1,0.1)
gamma = np.arange(0.1,5.1,0.1)
sigmaz_hgam = np.array([.009998,.03988,.08878,.15403
,.230769,.312854,.394358,.4708311,.539697879,.6,.6518698
,.696033486,.73345752165,.7651390123,.792,.814845635
,.8343567,.851098499,.865535727,.8780487,.8889486,.89848986
,.906881,.914295,.9208731,.9267338,.93197569,.93668129
,.9409202379,.94475138,.951383,.9542629,.956895,.959309
,.961526,.9635675,.96545144,.9671934,.968807,.97030539
,.9716983,.972995,.974206,.975337,.97639567,.977387,.978318
,.97919266,.98,.9807902])
mu = 1
sigmaz_hgam = mu*sigmaz_hgam
# creates an empty list for current values to be stored in
J1 = []
for i in range(sigmaz_hgam.size):
expec_sz = sigmaz_hgam[i]
J = 4*gamma[i]*(mu-expec_sz)
J1.append(J.real)
#print(J)
This part of the code is what is used to graph out which is where the problem lies
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure()
ax = fig.gca(projection='3d')
x = h
y = gamma
z = J1
ax.plot(x, y, z, label='Dephasing Model')
ax.legend()
ax.set_xlabel('h', fontsize=10)
ax.set_ylabel('$\gamma$')
ax.yaxis._axinfo['label']['space_factor'] = 3.0
for t in ax.zaxis.get_major_ticks(): t.label.set_fontsize(10)
# disable auto rotation
ax.zaxis.set_rotate_label(False)
ax.set_zlabel('J', fontsize=10, rotation = 0)
plt.show()
On my version of Matplotlib (2.0.2), on a Mac, I see the label (which is there – most of it is just being cropped out of the image in your case).
You could try to reduce the padding between the ticks and the label:
ax.zaxis.labelpad = 0

Trapezoidal wave in Python

How do I generate a trapezoidal wave in Python?
I looked into the modules such as SciPy and NumPy, but in vain. Is there a module such as the scipy.signal.gaussian which returns an array of values representing the Gaussian function wave?
I generated this using the trapezoidal kernel of Astropy,
Trapezoid1DKernel(30,slope=1.0)
. I want to implement this in Python without using Astropy.
While the width and the slope are sufficient to define a triangular signal, you would need a third parameter for a trapezoidal signal: the amplitude.
Using those three parameters, you can easily adjust the scipy.signal.sawtooth function to give you a trapeziodal shape by truncating and offsetting the triangular shaped function.
from scipy import signal
import matplotlib.pyplot as plt
import numpy as np
def trapzoid_signal(t, width=2., slope=1., amp=1., offs=0):
a = slope*width*signal.sawtooth(2*np.pi*t/width, width=0.5)/4.
a[a>amp/2.] = amp/2.
a[a<-amp/2.] = -amp/2.
return a + amp/2. + offs
t = np.linspace(0, 6, 501)
plt.plot(t,trapzoid_signal(t, width=2, slope=2, amp=1.), label="width=2, slope=2, amp=1")
plt.plot(t,trapzoid_signal(t, width=4, slope=1, amp=0.6), label="width=4, slope=1, amp=0.6")
plt.legend( loc=(0.25,1.015))
plt.show()
Note that you may also like to define a phase, depeding on the use case.
In order to define a single pulse, you might want to modify the function a bit and supply an array which ranges over [0,width].
from scipy import signal
import matplotlib.pyplot as plt
import numpy as np
def trapzoid_signal(t, width=2., slope=1., amp=1., offs=0):
a = slope*width*signal.sawtooth(2*np.pi*t/width, width=0.5)/4.
a += slope*width/4.
a[a>amp] = amp
return a + offs
for w,s,a in zip([2,5], [2,1], [1,0.6]):
t = np.linspace(0, w, 501)
l = "width={}, slope={}, amp={}".format(w,s,a)
plt.plot(t,trapzoid_signal(t, width=w, slope=s, amp=a), label=l)
plt.legend( loc="upper right")
plt.show()
From the SciPy website it looks like this isn't included (they currently have sawtooth and square, but not trapezoid). As a generalised version of the C example the following will do what you want,
import numpy as np
import matplotlib.pyplot as plt
def trapezoidalWave(xin, width=1., slope=1.):
x = xin%(4*width)
if (x <= width):
# Ascending line
return x*slope;
elif (x <= 2.*width):
# Top horizontal line
return width*slope
elif (x <= 3.*width):
# Descending line
return 3.*width*slope - x*slope
elif (x <= 4*width):
# Bottom horizontal line
return 0.
x = np.linspace(0.,20,1000)
for i in x:
plt.plot(i, trapezoidalWave(i), 'k.')
plt.plot(i, trapezoidalWave(i, 1.5, 2.), 'r.')
plt.show()
which looks like,
This can be done more elegantly with Heaviside functions which allow you to use NumPy arrays,
import numpy as np
import matplotlib.pyplot as plt
def H(x):
return 0.5 * (np.sign(x) + 1)
def trapWave(xin, width=1., slope=1.):
x = xin%(4*width)
y = ((H(x)-H(x-width))*x*slope +
(H(x-width)-H(x-2.*width))*width*slope +
(H(x-2.*width)-H(x-3.*width))*(3.*width*slope - x*slope))
return y
x = np.linspace(0.,20,1000)
plt.plot(x, trapWave(x))
plt.plot(x, trapWave(x, 1.5, 2.))
plt.show()
For this example, the Heaviside version is about 20 times faster!
The below example shows how to do that to get points and show scope.
Equation based on reply: Equation for trapezoidal wave equation
import math
import numpy as np
import matplotlib.pyplot as plt
def get_wave_point(x, a, m, l, c):
# Equation from: https://stackoverflow.com/questions/11041498/equation-for-trapezoidal-wave-equation
# a/pi(arcsin(sin((pi/m)x+l))+arccos(cos((pi/m)x+l)))-a/2+c
# a is the amplitude
# m is the period
# l is the horizontal transition
# c is the vertical transition
point = a/math.pi*(math.asin(math.sin((math.pi/m)*x+l))+math.acos(math.cos((math.pi/m)*x+l)))-a/2+c
return point
print('Testing wave')
x = np.linspace(0., 10, 1000)
listofpoints = []
for i in x:
plt.plot(i, get_wave_point(i, 5, 2, 50, 20), 'k.')
listofpoints.append(get_wave_point(i, 5, 2, 50, 20))
print('List of points : {} '.format(listofpoints))
plt.show()
The whole credit goes to #ImportanceOfBeingErnest . I am just revising some edits to his code which just made my day.
from scipy import signal
import matplotlib.pyplot as plt
from matplotlib import style
import numpy as np
def trapzoid_signal(t, width=2., slope=1., amp=1., offs=0):
a = slope*width*signal.sawtooth(2*np.pi*t/width, width=0.5)/4.
a += slope*width/4.
a[a>amp] = amp
return a + offs
for w,s,a in zip([32],[1],[0.0322]):
t = np.linspace(0, w, 34)
plt.plot(t,trapzoid_signal(t, width=w, slope=s, amp=a))
plt.show()
The result:
I'll throw a very late hat into this ring, namely, a function using only numpy that produces a single (symmetric) trapezoid at a desired location, with all the usual parameters. Also posted here
import numpy as np
def trapezoid(x, center=0, slope=1, width=1, height=1, offset=0):
"""
For given array x, returns a (symmetric) trapezoid with plateau at y=h (or -h if
slope is negative), centered at center value of "x".
Note: Negative widths and heights just converted to 0
Parameters
----------
x : array_like
array of x values at which the trapezoid should be evaluated
center : float
x coordinate of the center of the (symmetric) trapezoid
slope : float
slope of the sides of the trapezoid
width : float
width of the plateau of the trapezoid
height : float
(positive) vertical distance between the base and plateau of the trapezoid
offset : array_like
vertical shift (either single value or the same shape as x) to add to y before returning
Returns
-------
y : array_like
y value(s) of trapezoid with above parameters, evaluated at x
"""
# ---------- input checking ----------
if width < 0: width = 0
if height < 0: height = 0
x = np.asarray(x)
slope_negative = slope < 0
slope = np.abs(slope) # Do all calculations with positive slope, invert at end if necessary
# ---------- Calculation ----------
y = np.zeros_like(x)
mask_left = x - center < -width/2.0
mask_right = x - center > width/2.0
y[mask_left] = slope*(x[mask_left] - center + width/2.0)
y[mask_right] = -slope*(x[mask_right] - center - width/2.0)
y += height # Shift plateau up to y=h
y[y < 0] = 0 # cut off below zero (so that trapezoid flattens off at "offset")
if slope_negative: y = -y # invert non-plateau
return y + offset
Which outputs something like
import matplotlib.pyplot as plt
plt.style.use("seaborn-colorblind")
x = np.linspace(-5,5,1000)
for i in range(1,4):
plt.plot(x,trapezoid(x, center=0, slope=1, width=i, height=i, offset = 0), label=f"width = height = {i}\nslope=1")
plt.plot(x,trapezoid(x, center=0, slope=-1, width=2.5, height=1, offset = 0), label=f"width = height = 1.5,\nslope=-1")
plt.ylim((-2.5,3.5))
plt.legend(frameon=False, loc='lower center', ncol=2)
Example output:

Resources