Parabolic fit with fixed peak

Viewed 432

I have a set of data and want to put a parabolic fit over it. This already works with the polyfit function from numpy like this:

fit = np.polyfit(X, y, 2)
formula = np.poly1d(fit)

Now I want the parabula to have its peak value at a fixed x value and that the fit is still carried out as best as possible with this fixed peak. Is there a way to accomplish that?

From my data I know that the parabola will always be open downwards.

3 Answers

I think this is quite a difficult problem since the x coordinate of the peak of a second-order polynomial (ax^2 + bx + c) always lies in x = -b/2a.

A thing you could do is to drop the b term and offset it by the desired peak x value in fitting the polynomial like the code below. Note that I used scipy.optimize.curve_fit to fit for the custom function func.

import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit

# generating a parabola with noise
np.random.seed(42)
x = np.linspace(-10, 10, 100)
y = 10 -(x-2)**2 + np.random.normal(0, 5, x.shape)

# function to fit
def func(x, a, c):
    return a*x**2 + c

# desired x peak value
x_peak = 2

popt, pcov = curve_fit(func, x - x_peak, y)

y_fit = func(x - x_peak, *popt)

# plotting
plt.plot(x, y, 'k.')
plt.plot(x, y_fit)
plt.axvline(x_peak)
plt.show()

Outputs the image:

enter image description here

Fixing a point on your parabola simplifies the problem, since you can rewrite your equation slightly in terms of a constant now:

y = A(x - B)**2 + C

Given the coefficients a, b, c in your original unconstrained fit, you have the relationships

a = A
b = -2AB
c = AB**2 + C

The only difference is that since B is a constant and you don't have an x - B term in the equation, you need to set up the least-squares problem yourself. Given arrays x, y and constant B, the problem looks like this:

m = np.stack((x - B, np.ones_like(x)), axis=-1)
(A, C), *_ = np.linalg.lstsq(m, y, rcond=None)

You can then extract the normal coefficient from the formulas for a, b, c above.

Here is a complete example, just like the one in the other answer:

B = 2

np.random.seed(42)
x = np.linspace(-10, 10, 100)
y = 10 -(x - B)**2 + np.random.normal(0, 5, x.shape)

m = np.stack(((x - B)**2, np.ones_like(x)), axis=-1)
(A, C), *_ = np.linalg.lstsq(m, y, rcond=None)

a = A
b = -2 * A * B
c = A * B**2 + C

y_fit = a * x**2 + b * x + c

You can drop a, b, c entirely and do

y_fit = A * (x - B)**2 + C

The result will be identical.

plt.plot(x, y, 'k.')
plt.plot(x, y_fit)

enter image description here

Without the condition of location of the peak the function to be fitted would be :

y = a x^2 + b x + c

With condition of location of the peak at x=p , given p :

-b/(2a)=p

b=-2 a p

y = a x^2 -2 a p x + c

y = a (x^2 - 2 p x) +c

Knowing p , one change of variable :

X = x^2 -2 p x

So, from the data (x,y) one first compute the new data (X,y)

Then a and c are computed thanks to linear regression

y = a X + c

Related