Pulling x-values of spline integer y-values? - python

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.])

Related

plotting the stair step plot and extract continuous values

I have discrete x and y axis data as
y=[14.0,11.0,14.0,31.0]
x=[3.45,3.88,3.99,4.33]
I need to plot the figure as depicted below.
Finally i want to extract continuous red line values.
I tried using the code below but it doesnot give the expected result.Hope experts may help me.
import numpy as np
import matplotlib.pyplot as plt
y=[14.0,11.0,14.0,0.0]
x=[3.45,3.88,3.99,4.33]
plt.step(x,y)
plt.show()
To get continuous values, use interp1d from scipy:
import numpy as np
from scipy.interpolate import interp1d
import matplotlib.pyplot as plt
x = np.array([3.45, 3.88, 3.99, 4.33])
y = np.array([14.0, 11.0, 14.0, 31.0])
y = np.cumsum(y) # ???
y0 = np.array([0])
x0 = np.interp([0], y, x)
x = np.concatenate([x0, x])
y = np.concatenate([y0, y])
# Continuous data
NUM = 1000 # modify the number of points you want
funct = interp1d(x, y, kind='next')
x_cont = np.linspace(x[0], x[-1], NUM)
y_cont = funct(x_cont)
# Plot
fig, ax = plt.subplots()
ax.step(x_cont, y_cont, color='r', label='test.mod', where='post')
ax.set_xlim(2, 5)
ax.set_ylim(0, y.max())
ax.invert_yaxis()
plt.show()
Output:
>>> x_cont
array([3.45 , 3.54777778, 3.64555556, 3.74333333, 3.84111111,
3.93888889, 4.03666667, 4.13444444, 4.23222222, 4.33 ])
>>> y_cont
array([ 0., 25., 25., 25., 25., 39., 70., 70., 70., 70.])
The first thing to notice is the x values of the step plot. Matplotlib threats these as absolute values. You also need to add the x-value 3.45 twice for the first vertical segment.
For the vertical segments there is a possibility to set it to 'pre' to draw the vertical line before the point or 'post' for after the point. I chose to set it to 'post'.
import numpy as np
import matplotlib.pyplot as plt
x = [3.45, 3.45, 3.88, 3.99, 4.33]
y = [0.00, 14.0, 25.0, 39.0, 70.0]
plt.step(x, y, 'r', label='test.mod', where='post')
# set range of x-axis
plt.xlim([2.0, 5.0])
# invert y-axis by setting the lower limit to a higher value than the upper limit
plt.ylim([60, 0])
plt.legend()
plt.show()

How to find out the value y for a specific x after fitting a polynomial line using polyfit?

I have fitted a polynomial line on a graph using poly1D. How can I determine the value of y of this polynomial line for a specific value of x?
draw_polynomial = np.poly1d(np.polyfit(x, y, 8))
polyline = np.linspace(min_x, max_x, 300)
plt.plot(polyline, draw_polynomial(polyline), color='purple')
plt.show()
Here, I want to find out the y if x = 6.
You can directly call the fitted result p (polyline in your case) to get the y value. For example, x_val = 3.5, y_val_interp = round(p(x_val), 2) will give a y value of -0.36 in the code example below. I also added some annotations to visualize the result better.
import numpy as np
import numpy.polynomial.polynomial as npp
import matplotlib.pyplot as plt
# Since numpy version 1.4, the new polynomial API
# defined in numpy.polynomial is preferred.
x = np.array([0.0, 1.0, 2.0, 3.0, 4.0, 5.0])
y = np.array([0.0, 0.8, 0.9, 0.1, -0.8, -1.0])
z = npp.polyfit(x, y, 4)
p = np.poly1d(np.flip(z))
xp = np.linspace(-2, 6, 100)
plt.plot(x, y, '.', markersize=12, zorder=2.01)
plt.plot(xp, p(xp), '-')
plt.xlim(-1, 6)
plt.ylim(-1.5, 1)
# interrupting y value based on x value
x_val = 3.5
y_val_interp = round(p(x_val), 2)
# add dashed lines
plt.plot([x_val, xp[0]], [y_val_interp, y_val_interp], '--', color='k')
plt.plot([x_val, x_val], [p(xp[0]), y_val_interp], '--', color='k')
# add annotation and marker
plt.annotate(f'(x={x_val}, y={y_val_interp})', (x_val, y_val_interp), size=12, xytext=(x_val * 1.05, y_val_interp))
plt.plot(x_val, y_val_interp, 'o', color='r', zorder=2.01)
print(f'x = {x_val}, y = {y_val_interp}')
plt.tight_layout()
plt.show()
References:
https://numpy.org/doc/stable/reference/generated/numpy.polyfit.html
https://numpy.org/doc/stable/reference/generated/numpy.polynomial.polynomial.Polynomial.fit.html#numpy.polynomial.polynomial.Polynomial.fit
https://numpy.org/doc/stable/reference/generated/numpy.poly1d.html

Curve fitting with boundary conditions

I'm trying to fit a curve using LSQUnivariateSpline.
from scipy.interpolate import LSQUnivariateSpline, UnivariateSpline
import matplotlib.pyplot as plt
x = np.linspace(-3, 3, 50)
y = np.exp(-x**2) + 0.1 * np.random.randn(50)
t = [-2, -1, 0, 1, 2]
spl = LSQUnivariateSpline(x, y, t, k=4 )
xs = np.linspace(-3, 3, 1000)
plt.plot(x, y, 'ro', ms=5)
plt.plot(xs, spl(xs), 'g-', lw=3)
plt.show()
This code produce below graph.
I want to declare boundary conditions like dy/dx = 0 or d2x/dy = 0. Is there a way or another function to fit a curve with boundary conditions?

Plot Piecewise Function in 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-')

Render non-uniform grid and mark maximum value's location in contour plot

I have two problems with the contour plot in matplotlib:
How can I render an arbitrary meshgrid as a regular one?
I would like the position of the ticks on both axes to be evenly distributed while still reflecting the position of my nodes.
How can I highlight the position of my data's highest value with a colored marker?
Here is my code:
import numpy as np
import pylab as pl
def plot_s(data, xlist, ylist):
pl.subplot(111)
x = np.array(xlist)
y = np.array(ylist)
X, Y = np.meshgrid(x, y)
CS = pl.contour(X, Y, data, colors='k')
pl.clabel(CS, inline = 1, fontsize=10)
pl.xlabel('x list')
pl.ylabel('y list')
pl.xticks(xlist)
pl.yticks(ylist)
pl.title('Contour plot')
pl.show()
def main():
data = np.array([[ 0.56555019, 0.57933922, 0.58266252, 0.58067285, 0.57660236, 0.57185625, 0.56711252, 0.55557035, 0.55027705, 0.54480605],
[ 0.55486559, 0.57349717, 0.57940478, 0.57843897, 0.57463271, 0.56963449, 0.5643922 , 0.55095598, 0.54452534, 0.53762606],
[ 0.53529358, 0.56254991, 0.57328105, 0.57409218, 0.57066168, 0.5654082 , 0.55956853, 0.5432474 , 0.53501127, 0.52601203],
[ 0.50110483, 0.54004071, 0.55800178, 0.56173719, 0.55894846, 0.55328279, 0.54642887, 0.52598388, 0.51533094, 0.50354147]])
xlist = [10., 20., 30., 40., 50., 60., 70., 100., 120., 150.]
ylist = [50, 70, 90, 100]
plot_s(data, xlist, ylist)
if __name__ == '__main__':
main()
How can I render an arbitrary meshgrid as a regular one?
One suggestion is to create a regular meshgrid, by first creating arrays of evenly spaced values between your minimum and maximum x and y. Further you could use custom ticks to reflect the fact your data-points are not equidistant. See comments in the codes about how I implemented that.
How can I highlight the position of my data's highest value with a colored marker?
To retrieve the highest value, you could use np.max() and then find the position of this value in the data-array with np.where. Simply plot a marker on this location.
Alternatively, using plt.contour you could create a contour with a level sufficiently close to your maximum value's position, to create a ring around it, or even a point on it:
epsillon = 0.0001
levels = np.arange(max_value - epsillon, max_value + epsillon)
CS2 = plt.contour(X,Y,data, levels,
origin='lower',
linewidths=2,
extent=(-3,3,-2,2))
Note that with the first method, the dot will end up on the top of an existing grid node, while plt.contour interpolates your data, and depending on the interpolation algorithm used, it may result in a somewhat different location. Yet here it appear to concur.
The code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
def plot_s(data, x, y, xlist, ylist):
ax = plt.gca()
########### create your uniform meshgrid..... ############
X, Y = np.meshgrid(x, y)
CS = ax.contour(X, Y, data, colors='k')
###### ... and let ticks indicate that your new space is not linear
# assign tick positions according to the regular array
ax.set_yticks(y)
# Assign the label to reflect your original nodes position
ax.set_yticklabels(ylist)
# and same for x
ax.set_xticks(x)
ax.set_xticklabels(xlist)
#############################################################
########### GET MAXIMUM AND MARK IT WITH A POINT ########
# get maximum value in your data
max_value = np.max(data)
# get position index of this calue in your data array
local_max_index = np.where(data==max_value)
## retrieve position of your
max_x = X[local_max_index[0], local_max_index[1]]
max_y = Y[local_max_index[0], local_max_index[1]]
# plot one marker on this position
plt.plot(max_x, max_y, color="red", marker = "o", zorder = 10,
markersize=15, clip_on=False)
##############################################################
plt.title('Contour plot')
plt.show()
def main():
# Your data: 4 x 10 array
data = np.array([[ 0.56555019, 0.57933922, 0.58266252, 0.58067285, 0.57660236,
0.57185625, 0.56711252, 0.55557035, 0.55027705, 0.54480605],
[ 0.55486559, 0.57349717, 0.57940478, 0.57843897, 0.57463271,
0.56963449, 0.5643922 , 0.55095598, 0.54452534, 0.53762606],
[ 0.53529358, 0.56254991, 0.57328105, 0.57409218, 0.57066168,
0.5654082 , 0.55956853, 0.5432474 , 0.53501127, 0.52601203],
[ 0.50110483, 0.54004071, 0.55800178, 0.56173719, 0.55894846,
0.55328279, 0.54642887, 0.52598388, 0.51533094, 0.50354147]])
# create a list values with regular interval for the mesh grid
x = np.array([10 + i * (150.-10.)/9 for i in range(10)])
y = np.array([50 + i * (100.-50.)/4 for i in range(4)])
# create arrays with values to be displayed as ticks
xlist = np.array([10., 20., 30., 40., 50., 60., 70., 100., 120., 150.])
ylist = np.array([50, 70, 90, 100])
plot_s(data, x, y, xlist, ylist)
if __name__ == '__main__':
main()
voilĂ :
Here with the meshgrid in the background to show the deformation/mapping:
Below is essentially the same but a slightly more compact version of what has been proposed by snake_charmer. However, I am not sure if I understood your question correctly. If your points in xlist and ylist are not too irregularly spaced, a more elegant solution could be to keep the irregular grid but to highlight the location of data points using ax.grid(). This depends on what exactly you want to show in the figure, though.
import numpy as np
from matplotlib import pyplot as plt
def plot_s(data, xlist, ylist):
fig, ax = plt.subplots()
x = np.arange(len(xlist))
y = np.arange(len(ylist))
X, Y = np.meshgrid(x, y)
CS = ax.contour(X, Y, data, colors='k')
ax.clabel(CS, inline = 1, fontsize=10)
ax.set_xlabel('x list')
ax.set_ylabel('y list')
ax.set_xticks(x)
ax.set_yticks(y)
ax.set_xticklabels(xlist)
ax.set_yticklabels(ylist)
jmax, imax = np.unravel_index(np.argmax(data), data.shape)
ax.plot(imax, jmax, 'ro')
ax.set_title('Contour plot')
plt.show()
def main():
data = np.array([[ 0.56555019, 0.57933922, 0.58266252, 0.58067285,
0.57660236, 0.57185625, 0.56711252, 0.55557035,
0.55027705, 0.54480605],
[ 0.55486559, 0.57349717, 0.57940478, 0.57843897,
0.57463271, 0.56963449, 0.5643922 , 0.55095598,
0.54452534, 0.53762606],
[ 0.53529358, 0.56254991, 0.57328105, 0.57409218,
0.57066168, 0.5654082 , 0.55956853, 0.5432474 ,
0.53501127, 0.52601203],
[ 0.50110483, 0.54004071, 0.55800178, 0.56173719,
0.55894846, 0.55328279, 0.54642887, 0.52598388,
0.51533094, 0.50354147]])
xlist = [10., 20., 30., 40., 50., 60., 70., 100., 120., 150.]
ylist = [50, 70, 90, 100]
plot_s(data, xlist, ylist)
if __name__ == '__main__':
main()

Categories