Plot Piecewise Function in Python - python

I would like to plot the following piecewise function in Python using Matplotlib, from 0 to 5.
f(x) = 1, x != 2; f(x) = 0, x = 2
In Python...
def f(x):
if(x == 2): return 0
else: return 1
Using NumPy I create an array
x = np.arange(0., 5., 0.2)
array([ 0. , 0.2, 0.4, 0.6, 0.8, 1. , 1.2, 1.4, 1.6, 1.8, 2. ,
2.2, 2.4, 2.6, 2.8, 3. , 3.2, 3.4, 3.6, 3.8, 4. , 4.2,
4.4, 4.6, 4.8])
I have tried things like...
import matplotlib.pyplot as plt
plt.plot(x,f(x))
Or...
vecfunc = np.vectorize(f)
result = vecfunc(t)
Or...
def piecewise(x):
if x == 2: return 0
else: return 1
import matplotlib.pyplot as plt
x = np.arange(0., 5., 0.2)
plt.plot(x, map(piecewise, x))
ValueError: x and y must have same first dimension
But I am not using these functions correctly, and am now just randomly guessing how to do this.
Some answers are starting to get there... But the points are being connected into a line on the plot. How do we just plot the points?

Some answers are starting to get there... But the points are being
connected into a line on the plot. How do we just plot the points?
import matplotlib.pyplot as plt
import numpy as np
def f(x):
if(x == 2): return 0
else: return 1
x = np.arange(0., 5., 0.2)
y = []
for i in range(len(x)):
y.append(f(x[i]))
print x
print y
plt.plot(x,y,c='red', ls='', ms=5, marker='.')
ax = plt.gca()
ax.set_ylim([-1, 2])
plt.show()

The problem is that the function f does not take an array as input but a single numer. You can:
plt.plot(x, map(f, x))
The map function takes a function f, an array x and returns another array where the function f is applied to each element of the array.

You can use np.piecewise on the array:
x = np.arange(0., 5., 0.2)
import matplotlib.pyplot as plt
plt.plot(x, np.piecewise(x, [x == 2, x != 2], [0, 1]))

Your function is continuous except for an interval of zero measure. In my opinion the correct way to plot it is
In [8]: import matplotlib.pyplot as plt
...: plt.plot((0, 5), (1, 1), color='blue', label='Discontinuos function')
...: plt.scatter(2, 0, color='blue')
...: plt.grid()
...: plt.legend()
...: plt.show()
In [9]:

the append works but requires a little extra processing. np's piecewise works fine. could just do this for any function:
`
import math
import matplotlib as plt
xs=[]
xs=[x/10 for x in range(-50,50)] #counts in tenths from -5 to 5
plt.plot(xs,[f(x) for x in xs])
`

if you are using python 2.x, map() returns a list.
so you can write code as this:
import matplotlib.pyplot as plt
import numpy as np
def f(t):
if t < 10:
return 0;
else:
return t * t - 100;
t = np.arange(0, 50, 1)
plt.plot(t, map(f, t), 'b-')
plt.show()
if you are using python 3.x, map() returns a iterator.
so convert the map to a list.
plt.plot(t, list(map(f, t)), 'b-')

Related

How to smooth this Figure in Python with from scipy.interpolate import make_interp_spline

There is a plot which I want to make smooth for better representation. I tried scipy.interpolate, however it produced this error:
raise ValueError("Expect x to be a 1-D sorted array_like.")
ValueError: Expect x to be a 1-D sorted array_like.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import interpolate
from scipy.interpolate import make_interp_spline
startnm = 550
endnm = 700
y = np.empty((10,))
for aci in range(0, 91, 10):
data = pd.read_csv(f".\\30mg-ml-PSQD-withZNO-12-nov-21\\{aci}.txt",
delimiter="\t") .to_numpy()[:, [0, 1]]
print(type(data[0, 0]))
starti, endi = 0, 0
for i in range(len(data[:, 0])):
if startnm < float(data[i, 0]) and starti == 0:
starti = i
elif endnm < float(data[i, 0]) and endi == 0:
endi = i
break
y[aci//10] = np.sum(data[starti:endi, 1])
theta = np.linspace(0, np.pi, 19)
output = []
x = []
for i in range(10):
temp0 = y[i]
output.append(temp0*np.cos(theta[i])/y.max())
x.append(temp0*np.sin(theta[i])/y.max())
pass
print(output)
print(x)
plt.title("title")
plt.xlabel("x")
plt.ylabel("y")
plt.plot(x, output,"--")
plt.plot(-np.array(x), output, "--")
x = np.sin(theta)*np.cos(theta)
y = np.cos(theta)*np.cos(theta)
plt.plot(x, y, "r")
plt.grid(color = 'green', linestyle = '--', linewidth = 0.5)
plt.show()
I want to smooth this graph as much as possible. How can I do it?
The error just tells you that the x array needs to be sorted.
Note also that make_interp_spline does not do any smoothing. For that, use splrep .
My friend's solution to this problem:
from scipy.interpolate import interp1d
f1 = interp1d(list(range(10)), x, kind="quadratic")
f2 = interp1d(list(range(10)), output, kind="quadratic")
xnew = f1(np.linspace(0, 8.9, 100))
outnew = f2(np.linspace(0, 8.9, 100))
plt.plot(xnew, outnew)
plt.plot(-xnew, outnew, "b")

Pulling x-values of spline integer y-values?

I have the given sample data and interpolated spline:
import matplotlib.pyplot as plt
import numpy as np
from scipy import interpolate
x = [0.1, 1.5, 2.3, 5.5, 6.7, 7]
y = [5, 4, 5, 2, 2, 3]
s = interpolate.UnivariateSpline(x, y, s=0)
xs = np.linspace(min(x), max(x), 10000) #values for x axis
ys = s(xs)
plt.figure()
plt.plot(xs, ys, label='spline')
plt.plot(x, y, 'x', label='collected data')
plt.legend()
I would like to pull the x values that correspond to the integer y values of the spline, but am not sure how to do this. I assume I will be using np.where() and have tried (to no avail):
root_index = np.where(ys == ys.round())
You could use the find_roots function from this post to find the exact interpolated x-values:
import matplotlib.pyplot as plt
import numpy as np
from scipy import interpolate
def find_roots(x, y):
s = np.abs(np.diff(np.sign(y))).astype(bool)
return x[:-1][s] + np.diff(x)[s] / (np.abs(y[1:][s] / y[:-1][s]) + 1)
x = [0.1, 1.5, 2.3, 5.5, 6.7, 7]
y = [5, 4, 5, 2, 2, 3]
s = interpolate.UnivariateSpline(x, y, s=0)
xs = np.linspace(min(x), max(x), 500) # values for x axis
ys = s(xs)
plt.figure()
plt.plot(xs, ys, label='spline')
for y0 in range(0, 7):
r = find_roots(xs, ys - y0)
if len(r) > 0:
plt.scatter(r, np.repeat(y0, len(r)), label=f'y0 = {y0}')
for ri in r:
plt.text(ri, y0, f' {ri:.3f}', ha='left', va='center')
plt.legend()
plt.xlim(min(x) - 0.2, max(x) + 1)
plt.show()
PS: with much less points for the x's, e.g. xs = np.linspace(min(x), max(x), 50), the curve would look a bit bumpy, and the interpolation would be slightly different:
It's not a good idea comparing two floats with equal. Use:
# play with `thresh`
thresh = 1e-4
root_index = np.where(np.abs(ys - ys.round())<1e-4)
printt(xs[root_index])
print(ys[root_index])
Output:
array([0.1 , 0.44779478, 2.29993999, 4.83732373, 7. ])
array([5. , 4.00009501, 4.99994831, 3.00006626, 3. ])
Numpy also has np.isclose. So something like:
root_index = np.isclose(ys, ys.round(), rtol=1e-5, atol=1e-4)
ys[root_index]
And you have the same output.
The object returned by scipy.interpolate.UnivariateSpline is a wrapper about fitpack interpolation routines. You can get an identical interpolation using the CubicSpline class: replace s = interpolation.UnivariateSpline(x, y, s=0) with s = interpolation.CubicSpline(x, y). The results are identical, as you can see in the figure at the end.
The advantage to using CubicSpline is that it returns a PPoly object which has working roots and solve methods. You can use this to compute the roots with integer offsets.
To compute the range of possible integers, you can use the derivative method coupled with roots:
x_extrema = np.concatenate((x[:1], s.derivative().roots(), x[-1:]))
y_extrema = s(x_extrema)
i_min = np.ceil(y_extrema.min())
i_max = np.floor(y_extrema.max())
Now you can compute the roots of the offset spline:
x_ints = [s.solve(i) for i in np.arange(i_min, i_max + 1)]
x_ints = np.concatenate(x_ints)
x_ints.sort()
Here are some additional plotting commands and their output:
plt.figure()
plt.plot(xs, interpolate.UnivariateSpline(x, y, s=0)(xs), label='Original Spline')
plt.plot(xs, ys, 'r:', label='CubicSpline')
plt.plot(x, y, 'x', label = 'Collected Data')
plt.plot(x_extrema, y_extrema, 's', label='Extrema')
plt.hlines([i_min, i_max], xs[0], xs[-1], label='Integer Limits')
plt.plot(x_ints, s(x_ints), '^', label='Integer Y')
plt.legend()
You can verify the numbers:
>>> s(x_ints)
array([5., 4., 4., 5., 5., 4., 3., 2., 2., 3., 4., 5.])

numpy.linspace - bad labels on the X axis

I have this small plotting program.
But when I run it I notice the labels on the X axis are incorrect.
They go from 0 to 5000 while actually I have the interval [-1.5, 1.5]
1... How can I fix that?
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-1.5, 1.5, 5000)
y1 = np.tan(x) * np.arctan(x)
y2 = x * x
plt.plot(y1)
plt.plot(y2)
plt.show()
2... Also, if I change the linspace to call
x = np.linspace(-mt.pi/2.0 + 1/(10**6), mt.pi/2.0 - 1/(10**6), 5000)
I get an even stranger and really incorrect plot.
Something gets messed up completely.
Why? I want to plot these 2 functions in the range (-pi/2, pi/2)
How do I do this?
Try:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-1.5, 1.5, 5000)
y1 = np.tan(x) * np.arctan(x)
y2 = x * x
plt.plot(x,y1)
plt.plot(x,y2)
plt.show()
Now x axis values are is between -1.5 and 1.5.
Regarding strange plot in second case just notice that:
np.tan(-1.5)
-14.101419947171719
and:
np.tan(-mt.pi/2.0)
-1.633123935319537e+16
which is much much bigger.

Plot the element-wise product of two numpy arrays

I'm very new to python, I wanted to write a program that multiplies elements present inside two arrays and plots a graph
How should I correct the code?
import matplotlib.pyplot as plt
import numpy as np
x=(np.double[0.1,0.001,0.0001,0.0001,0.00001])
y=(np.double[0.1,0.001,0.0001,0.0001,0.00001])
m=len(x)
n=len(y)
for m in range(0,m):
for n in range(0,n):
plt.plot[x(m),y(n)]
plt.show()
x = np.array([0.1, 0.001, 0.0001, 0.0001, 0.00001] )
plt.plot(x ** 2)
plt.show()
If x and y are different, use:
x = np.array([...]) # replace [...] with your list
y = np.array([...])
plt.plot(x * y)
plt.show()

Show z-value at mouse pointer position in status line with matplotlib's pcolormesh()

When using imshow() the z-value of the mouse pointer is shown in the status line as shown in the screen shot (on the right):
How do I achieve the same behavior with pcolormesh()?
The image was generated by the following code:
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(-1, 1, 101)
X, Y = np.meshgrid(t, 2*t)
Z = np.sin(2*np.pi*(X**2+Y**2))
fig, axx = plt.subplots(1, 2)
axx[0].set_title("imshow()")
axx[0].imshow(Z, origin='lower', aspect='auto', extent=[-1, 1, -2, 2])
axx[1].set_title("pcolormesh()")
axx[1].pcolormesh(X, Y, Z)
fig.tight_layout()
plt.show()
One idea is to monkey patch the ax.format_coord function to include the desired value. This is also shown in a matplotlib example.
Specific solution
Now if you want both plots share the same function, a little bit of work needs to be spent on getting the axes limits correct.
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(-1, 1, 101)
X, Y = np.meshgrid(t, 2*t)
Z = np.sin(np.pi*(X**2+Y**2))
fig, axx = plt.subplots(1, 2)
axx[0].set_title("imshow()")
extent = [-1-(t[1]-t[0])/2., 1+(t[1]-t[0])/2., -2-(t[1]-t[0]), 2+(t[1]-t[0])]
axx[0].imshow(Z, origin='lower', aspect='auto', extent=extent)
axx[1].set_title("pcolormesh()")
axx[1].pcolormesh(X-(t[1]-t[0])/2., Y-(t[1]-t[0]), Z)
axx[1].set_xlim(-1-(t[1]-t[0])/2., 1+(t[1]-t[0])/2.)
axx[1].set_ylim( -2-(t[1]-t[0]), 2+(t[1]-t[0]) )
def format_coord(x, y):
x0, x1 = axx[1].get_xlim()
y0, y1 = axx[1].get_ylim()
col = int(np.floor((x-x0)/float(x1-x0)*X.shape[1]))
row = int(np.floor((y-y0)/float(y1-y0)*Y.shape[0]))
if col >= 0 and col < Z.shape[1] and row >= 0 and row < Z.shape[0]:
z = Z[row, col]
return 'x=%1.4f, y=%1.4f, z=%1.4f' % (x, y, z)
else:
return 'x=%1.4f, y=%1.4f' % (x, y)
axx[1].format_coord = format_coord
fig.tight_layout()
plt.show()
General solution
The above is specific to the data in the question and has the drawback of not allowing to zoom and pan in the plot. A completely general solution would need to take into account the possibility that the image does not fill the complete axes and also the fact that pcolormesh pixels may be of unequal sizes.
This could look as follows, where in addition, the pixel numbers are shown:
import numpy as np; np.random.seed(42)
import matplotlib.pyplot as plt
x = [-3, -2, 0, 1.5, 2.2, 3.2, 3.9, 5, 6.75, 9]
y = [7, 7.1, 7.5, 7.7, 8, 8.2, 8.4, 8.8, 9]
X,Y = np.meshgrid(x,y)
Z = np.random.randint(0, 100, size=np.array(X.shape)-1)
fig, ax = plt.subplots()
pc = ax.pcolormesh(X,Y,Z)
fig.colorbar(pc)
def format_coord(x, y):
xarr = X[0,:]
yarr = Y[:,0]
if ((x > xarr.min()) & (x <= xarr.max()) &
(y > yarr.min()) & (y <= yarr.max())):
col = np.searchsorted(xarr, x)-1
row = np.searchsorted(yarr, y)-1
z = Z[row, col]
return f'x={x:1.4f}, y={y:1.4f}, z={z:1.4f} [{row},{col}]'
else:
return f'x={x:1.4f}, y={y:1.4f}'
ax.format_coord = format_coord
plt.show()

Categories