How to plot the derivative of a Cubic spline in Python 3? - python-3.x

I am using Python 3 for a task related to numerical analysis.
I have to plot some points originated from a sine function. Moreover, I need to do a cubic interpolation of these points (cubic spline).
So, these tasks are done. The output picture is great and the code works.
However, I need to check if the the derivative of the cubic spline looks like a cosine function.
Take a look at this image:
In orange, you see the cosine function. In blue, you see the sine function. In red, the 5 dots that I sampled. In purple, you can see a linear interpolation. And, in dashes, you see the cubic interpolation.
I need to plot the derivative of the dashed curve and compare it to the orange one.
Intuitively, I know they are going to be pretty similar. However, I was not able to prove that with a graph.
That's the code:
import math
import random
from numpy import array
import numpy as np
import matplotlib.pyplot as plot
from scipy.interpolate import interp1d
from scipy import interpolate
from scipy.misc import derivative as deriv
def random_sine():
lista_f_x = []
lista_x = []
for i in range(1,6):
aleatorio = random.uniform(0,360)
aleatorio = math.radians(aleatorio)
lista_x.append(aleatorio)
sine_random = math.sin(aleatorio)
lista_f_x.append(sine_random)
lista_x = array(lista_x)
lista_f_x = array(lista_f_x)
return ("x",lista_x,"f(x)", lista_f_x)
# para ter um grupo controle melhor, deixe esses valores aleatórios, gerados uma vez, como fixos
fixed_x = array([5.80990031, 1.7836885, 4.62073799, 0.89337425, 5.62219906])
fixed_y = array([-0.45581264, 0.97742392, -0.99580299, 0.77919112, -0.61389568])
x = fixed_x
y = fixed_y
"""
caso deseje usar os valores fixos
basta inserir o comentário "#" nas linhas
39, 40 e 41 abaixo
"""
#teste_dinamico = random_sine()
#x = teste_dinamico[1]
#y = teste_dinamico[3]
time = np.arange(0,10,0.1)
amplitude = np.sin(time)
amplitude_cosine = np.cos(time)
plot.plot(time, amplitude, time, amplitude_cosine)
plot.title('Função Seno')
plot.xlabel('Coordenadas de X')
plot.ylabel('Seno(x)')
plot.grid(True, which='both')
plot.axhline(y=0, color='k')
pares_x_y = list(zip(x,y))
sort_pares_x_y = sorted(pares_x_y)
x_ordenado = []
y_ordenado_simetric = []
for i in sort_pares_x_y:
x_ordenado.append(i[0])
y_ordenado_simetric.append(i[1])
x_ordenado = array(x_ordenado)
y_ordenado_simetric = array(y_ordenado_simetric)
f = interp1d(x_ordenado, y_ordenado_simetric)
f2 = interp1d(x_ordenado, y_ordenado_simetric, kind="cubic")
plot.plot(x_ordenado, f2(x_ordenado))
minimo = min(x_ordenado)
maximo = max(x_ordenado)
xnew = np.linspace(minimo, maximo, num=400, endpoint=True)
plot.plot(x_ordenado, y_ordenado_simetric, 'o', xnew, f(xnew), '-', xnew, f2(xnew), '--')
plot.scatter(x,y)
plot.show()
I tried to follow this post. However, the post suggests a different package. I am using interp1d so far and the post suggests using interpolate. I tried converting what I did so far but it didn't work.
What can I do?
If I have a curve being plot with matplotlib, is there an easier way to plot the curve's derivative?
What's the best strategy to solve this?
1 - Should I try again and change everything to the other package suggested on SO?
2 - Should I use some numerical method for differentiation?
Thanks in advance.

I would prefer to use interpolate.splev instead of interp1d because apart from providing interpolations, the former also allows to compute derivatives easily with just a simple argument der=n where n is the order of derivative. I had to do only minor changes to your code to make things work.
Below I am only showing the relevant lines of code which I added/modified (highlighted by a comment) and the resulting plot is attached. The red dashed line is the required derivative which is comparable to the cosine function.
f2 = interpolate.splrep(x_ordenado, y_ordenado_simetric) # Added
minimo = min(x_ordenado)
maximo = max(x_ordenado)
xnew = np.linspace(minimo, maximo, num=400, endpoint=True)
ynew = interpolate.splev(xnew, f2) # Added
ynew_der = interpolate.splev(xnew, f2, der=1) # Added to compute first derivative
plot.plot(x_ordenado, y_ordenado_simetric, 'o', xnew, f(xnew), '-', xnew, ynew, '--') # Modified
plot.plot(xnew, ynew_der, '--r') # Derivative Added
Output

Other packages or functions might of course be more handy, but for understanding, you may simply calculate the derivative (dy/dx) yourself and plot it.
plot.plot(xnew[:-1], np.diff(f2(xnew))/np.diff(xnew), color="red")
Adding this line will result in
where the red line is the derivative of f2.

Related

Find out if point is part of curve (spline, splipy)

I have some coordinates of a 3D point curve through which I lay a spline like so:
from splipy import curve_factory
pts = [...] #3D coordinate points
curve = curve_factory.curve(pts)
I know that I can get a point in 3D along the curve by evaluating it after a certain length:
point_on_curve = curve.evaluate(t)
print(point_on_curve) #outputs coordinates: (x y z)
Is it however somehow possible to do it the other way round? Is there a function/method that can tell me if a certain point is part of the curve? Or if its almost part of the curve? Something like:
curve.func(point) #output: True
or
curve.func(point) #output: distance to curve 0.0001 --> also part of curve
Thanks!
I've found this script by ventusff that performs an optimization to find the value of the parameter that you call t (in the script is u) which gives the point on the spline closest to the external point.
I report below the code with some changes to make it clearer for you. I've defined a tolerance equal to 0.001.
The selection of the optimization solver and of its parameter values requires a little bit of study. I do not have enough time now for doing that, but you can try to experiment a little bit.
In this case SciPy is used for spline generation and evaluation, but you can easily replace it with splipy. The optimization is the interesting part performed using SciPy.
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import splprep, splev
from scipy.spatial.distance import euclidean
from scipy.optimize import fmin_bfgs
points_count = 40
phi = np.linspace(0, 2. * np.pi, points_count)
k = np.linspace(0, 2, points_count)
r = 0.5 + np.cos(phi)
x, y, z = r * np.cos(phi), r * np.sin(phi), k
tck, u = splprep([x, y, z], s=1)
points = splev(u, tck)
idx = np.random.randint(low=0, high=40)
noise = np.random.normal(scale=0.01)
external_point = np.array([points[0][idx], points[1][idx], points[2][idx]]) + noise
def distance_to_point(u_):
s = splev(u_, tck)
return euclidean(external_point, [s[0][0], s[1][0], s[2][0]])
closest_u = fmin_bfgs(distance_to_point, x0=np.array([0.0]), gtol=1e-8)
closest_point = splev(closest_u, tck)
tol = 1e-3
if euclidean(external_point, [closest_point[0][0], closest_point[1][0], closest_point[2][0]]) < tol:
print("The point is very close to the spline.")
ax = plt.figure().add_subplot(projection='3d')
ax.plot(points[0], points[1], points[2], "r-", label="Spline")
ax.plot(external_point[0], external_point[1], external_point[2], "bo", label="External Point")
ax.plot(closest_point[0], closest_point[1], closest_point[2], "go", label="Closest Point")
plt.legend()
plt.show()
The script draws the plot below:
and prints the following output:
Current function value: 0.000941
Iterations: 5
Function evaluations: 75
Gradient evaluations: 32
The point is very close to the spline.

Python, Extract spline coefficient

I am using python3, Scipy
I have a 3d points (x,y,z]
From them I make s apline using scipy.interpolate.splprep
x_points = np.linspace(0, 2*np.pi, 10)
y_points = np.sin(x_points)
z_points = np.cos(x_points)
path = np.vstack([x_points, y_points, z_points])
tck, u = sc.splprep(path, k=3, s=0)
I wish to get the coefficients of the spline[i]:
For example the latest splins:
sp9 = a9 + b9(x-x4) + c9(x-x4)^2 + d9(x-x4)^3
I know that the tck is (t,c,k) a tuple containing the vector of knots, the B-spline coefficients, and the degree of the spline.
But I don't see how I can get this spline function and plot only it
I tried using this method:
import numpy as np
import scipy.interpolate as sc
x_points = np.linspace(0, 2*np.pi, 10)
y_points = np.sin(x_points)
z_points = np.cos(x_points)
path = np.vstack([x_points, y_points, z_points])
tck, u = sc.splprep(path, k=3, s=0)
p = sc.PPoly.from_spline(tck)
but I'm getting this error on the last line:
p = sc.PPoly.from_spline(tck) File
"C:\Users...\Python38\lib\site-packages\scipy\interpolate\interpolate.py",
line 1314, in from_spline cvals = np.empty((k + 1, len(t)-1),
dtype=c.dtype)
AttributeError: 'list' object has no attribute 'dtype'
The coefficients in the tck tuple are in the b-spline basis. If you want to convert them to the power basis, you can do PPoly.from_spline(tck) .
An obligatory note however: converting between bases incurs numerical errors.
EDIT. First, as it's splprep, you'll need to convert the list-of-arrays c into a proper numpy array and transpose (it's a known wart of splPrep). Then, as it turns out, PPoly.from_spline does not handle multidimensional c (this might be a nice pull request to the scipy repository), so you'll need to e.g. loop over the dimensions. Something along the lines of (continuing from your OP)
t, c, k = tck
cc = np.asarray(c) # cc.shape is (3, 10) now
spl0 = sc.PPoly.from_spline((t, cc.T[0], k))
print(spl0.c) # here are your coefficients for the component 0

Python matplotlib fails to draw the Acnode (isolated point) on the Elliptic Curve y^2+x^3+x^2=0

I'm using the below code to draw the ECC curve y^2+x^3+x^2 =0
import numpy as np
import matplotlib.pyplot as plt
import math
def main():
fig = plt.figure()
ax = fig.add_subplot(111)
y, x = np.ogrid[-2:2:1000j, -2:2:1000j]
ax.contour(x.ravel(), y.ravel(), pow(y, 2) + pow(x, 3) + pow(x, 2) , [0],colors='red')
ax.grid()
plt.show()
if __name__ == '__main__':
main()
The output is
The expected image, however, is this
As we can see, the isolated point at (0,0) is not drawn. Any suggestions to solve this issue?
As already mentioned in the comment, it seems that a single point is not displayed as a contour. The best solution would be if the application indicates such points in some way by itself. Perhaps the library allows this, but I have not found a way and therefore show two workarounds here:
Option 1:
The isolated point at (0,0) could be marked explicitly:
ax.plot(0, 0, color="red", marker = "o", markersize = 2.5, zorder = 10)
In the case of multiple points, a masked array is a good choice, here.
Option 2:
The plot can be slightly varied around z = 0, e.g. z = 0.0002:
z = pow(y,2) + pow(x, 2) + pow(x, 3)
ax.contour(x.ravel(), y.ravel(), z, [0.0002], colors='red', zorder=10)
This will move the whole plot. Alternatively, the area around the isolated point alone could be shifted (by adding a second contour call with a small x,y grid around the isolated point at (0,0)). This does not change the rest.

Rotate xtick labels (not ticks) in seaborn plot after adding lowess line

this is a bit detailed, but help appreciated. It's a slightly annoying feature of seaborn that regplot can't handle datetime axes. This is especially so when you want to use the lowess parameter, which estimates local means to give a curve (as opposed to line) that tracks changes in a plot. (This functionality is available in R, for instance.)
This is a problem for me because I have a linear plot that I'd like to smooth out but without using a rolling mean. This is my plot:
To solve this problem, I took the index of my dataframe as the x-axis, and used statsmodels to calculate the lowess line as follows:
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import statsmodels.api as sm
rated = pd.read_csv('filepath.csv')
rated['linear'] = rated.index
x = rated['linear']
y = rated['trust_num']
lowess = sm.nonparametric.lowess(y, x, frac=.2)
low_y = [i[1] for i in lowess]
low_x = [i[0] for i in lowess]
rated['low_y'] = low_y
rated['low_x'] = low_x
rated['low_y'] = low_y
rated['low_x'] = low_x
h = sns.lineplot(x = 'linear', y = 'trust_num', data = rated)
h = sns.lineplot(x = 'linear', y = 'low_y', data = rated, color = 'r')
This produces exactly what I'd like:
The final step comes with assigning dates to the x-axis, which I do as follows:
labels = [i for i in rated['date']]
h.set_xticklabels(labels)
The result is a clustered x-axis, as below:
Fair enough, this is a common plotting problem. So I try to rotate my xtick labels:
plt.xticks(rotation=45)
But it makes no difference. Can anyone advise how I might declutter the axis? Seems a pain to nearly get there and fall at a seemingly simple problem!

1-D interpolation using python 3.x

I have a data that looks like a sigmoidal plot but flipped relative to the vertical line.
But the plot is a result of plotting 1D data instead of some sort of function.
My goal is to find the x value when the y value is at 50%. As you can see, there is no data point when y is exactly at 50%.
Interpolate comes to my mind. But I'm not sure if interpolate enable me to find the x value when the y value is 50%. So my question is 1) can you use interpolate to find the x when the y is 50%? or 2)do you need to fit the data to some sort of a function?
Below is what I currently have in my code
import numpy as np
import matplotlib.pyplot as plt
my_x = [4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66]
my_y_raw=np.array([0.99470977497817203, 0.99434995886145172, 0.98974611323163653, 0.961630837657524, 0.99327633558441175, 0.99338952769251909, 0.99428263292577534, 0.98690514212711611, 0.99111667721533181, 0.99149418924880861, 0.99133773062680464, 0.99143506380003499, 0.99151080464011454, 0.99268261743308517, 0.99289757252812316, 0.99100207861144063, 0.99157171773324027, 0.99112571824824358, 0.99031608691035722, 0.98978104266076905, 0.989782674787969, 0.98897835092187614, 0.98517540405423909, 0.98308943666187076, 0.96081810781994603, 0.85563541881892147, 0.61570811548079107, 0.33076276040577052, 0.14655134838124245, 0.076853147122142126, 0.035831324928136087, 0.021344669212790181])
my_y=my_y_raw/np.max(my_y_raw)
plt.plot(my_x, my_y,color='k', markersize=40)
plt.scatter(my_x,my_y,marker='*',label="myplot", color='k', edgecolor='k', linewidth=1,facecolors='none',s=50)
plt.legend(loc="lower left")
plt.xlim([4,102])
plt.show()
Using SciPy
The most straightforward way to do the interpolation is to use the SciPy interpolate.interp1d function. SciPy is closely related to NumPy and you may already have it installed. The advantage to interp1d is that it can sort the data for you. This comes at the cost of somewhat funky syntax. In many interpolation functions it is assumed that you are trying to interpolate a y value from an x value. These functions generally need the "x" values to be monotonically increasing. In your case, we swap the normal sense of x and y. The y values have an outlier as #Abhishek Mishra has pointed out. In the case of your data, you are lucky and you can get away with the the leaving the outlier in.
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
my_x = [4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,
48,50,52,54,56,58,60,62,64,66]
my_y_raw=np.array([0.99470977497817203, 0.99434995886145172,
0.98974611323163653, 0.961630837657524, 0.99327633558441175,
0.99338952769251909, 0.99428263292577534, 0.98690514212711611,
0.99111667721533181, 0.99149418924880861, 0.99133773062680464,
0.99143506380003499, 0.99151080464011454, 0.99268261743308517,
0.99289757252812316, 0.99100207861144063, 0.99157171773324027,
0.99112571824824358, 0.99031608691035722, 0.98978104266076905,
0.989782674787969, 0.98897835092187614, 0.98517540405423909,
0.98308943666187076, 0.96081810781994603, 0.85563541881892147,
0.61570811548079107, 0.33076276040577052, 0.14655134838124245,
0.076853147122142126, 0.035831324928136087, 0.021344669212790181])
# set assume_sorted to have scipy automatically sort for you
f = interp1d(my_y_raw, my_x, assume_sorted = False)
xnew = f(0.5)
print('interpolated value is ', xnew)
plt.plot(my_x, my_y_raw,'x-', markersize=10)
plt.plot(xnew, 0.5, 'x', color = 'r', markersize=20)
plt.plot((0, xnew), (0.5,0.5), ':')
plt.grid(True)
plt.show()
which gives
interpolated value is 56.81214249272691
Using NumPy
Numpy also has an interp function, but it doesn't do the sort for you. And if you don't sort, you'll be sorry:
Does not check that the x-coordinate sequence xp is increasing. If xp
is not increasing, the results are nonsense.
The only way I could get np.interp to work was to shove the data in to a structured array.
import numpy as np
import matplotlib.pyplot as plt
my_x = np.array([4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,
48,50,52,54,56,58,60,62,64,66], dtype = np.float)
my_y_raw=np.array([0.99470977497817203, 0.99434995886145172,
0.98974611323163653, 0.961630837657524, 0.99327633558441175,
0.99338952769251909, 0.99428263292577534, 0.98690514212711611,
0.99111667721533181, 0.99149418924880861, 0.99133773062680464,
0.99143506380003499, 0.99151080464011454, 0.99268261743308517,
0.99289757252812316, 0.99100207861144063, 0.99157171773324027,
0.99112571824824358, 0.99031608691035722, 0.98978104266076905,
0.989782674787969, 0.98897835092187614, 0.98517540405423909,
0.98308943666187076, 0.96081810781994603, 0.85563541881892147,
0.61570811548079107, 0.33076276040577052, 0.14655134838124245,
0.076853147122142126, 0.035831324928136087, 0.021344669212790181],
dtype = np.float)
dt = np.dtype([('x', np.float), ('y', np.float)])
data = np.zeros( (len(my_x)), dtype = dt)
data['x'] = my_x
data['y'] = my_y_raw
data.sort(order = 'y') # sort data in place by y values
print('numpy interp gives ', np.interp(0.5, data['y'], data['x']))
which gives
numpy interp gives 56.81214249272691
As you said, your data looks like a flipped sigmoidal. Can we make the assumption that your function is a strictly decreasing function? If that is the case, we can try the following methods:
Remove all the points where the data is not strictly decreasing.For example, for your data that point will be near 0.
Use the binary search to find the location where y=0.5 should be put in.
Now you know two (x, y) pairs where your desired y=0.5 should lie.
You can use simple linear interpolation if (x, y) pairs are very close.
Otherwise, you can see what is the approximation of sigmoid near those pairs.
You might not need to fit any functions to your data. Simply find the following two elements:
The largest x for which y<50%
The smallest x for which y>50%
Then use interpolation and find the x*. Below is the code
my_x = np.array([4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66])
my_y=np.array([0.99470977497817203, 0.99434995886145172, 0.98974611323163653, 0.961630837657524, 0.99327633558441175, 0.99338952769251909, 0.99428263292577534, 0.98690514212711611, 0.99111667721533181, 0.99149418924880861, 0.99133773062680464, 0.99143506380003499, 0.99151080464011454, 0.99268261743308517, 0.99289757252812316, 0.99100207861144063, 0.99157171773324027, 0.99112571824824358, 0.99031608691035722, 0.98978104266076905, 0.989782674787969, 0.98897835092187614, 0.98517540405423909, 0.98308943666187076, 0.96081810781994603, 0.85563541881892147, 0.61570811548079107, 0.33076276040577052, 0.14655134838124245, 0.076853147122142126, 0.035831324928136087, 0.021344669212790181])
tempInd1 = my_y<.5 # This will only work if the values are monotonic
x1 = my_x[tempInd1][0]
y1 = my_y[tempInd1][0]
x2 = my_x[~tempInd1][-1]
y2 = my_y[~tempInd1][-1]
scipy.interp(0.5, [y1, y2], [x1, x2])

Resources