Search code examples
pythonmatplotlibdata-fitting

Fitting data points while forcing the shape of the curve


I'm trying to fit 2D data points with a polynomial curve; see the picture below. The blue points are the data. The blue, dashed line is a 2nd order polynomial fit to these points. I want to force my fit to have exactly the same shape as the black line, and I want to compute the y offset of the new fit from the black curve. Any ideas on how this would be possible? Thanks in advance.

x = np.linspace(6.0,12.0,num=100)

a = -0.0864
b = 11.18
c = 9.04
fit_y = a*(x - b)**2 + c # black line

z = np.polyfit(data_x,data_y,2)

zfit=z[2]+z[1]*x+z[0]*x**2

fig, ax = plt.subplots()
ax.plot(data_x,data_y,'.',color='b')
ax.plot(x,fit_y,color='black') #curve of which we want the shape
ax.plot(x,zfit,color='blue',linestyle='dashed') #polynomial fit
ax.set_xlim([6.5,11.0])
ax.set_ylim([6.5,10.5])
plt.show()

data points and desired fit shape

Edit: This is the solution to my problem:

x = np.linspace(6.0,12.0,num=100)

# We want to keep a and b fixed to keep the same shape
# a = -0.0864     
# b = 11.18
c = 9.04

#Only c is a variable because we only want to shift the plot on the y axis
def f(x, c):
    return -0.0864*(x - 11.18)**2 + c

popt, pcov = curve_fit(f, data_x, data_y)  # popt are the fitted parameters

plt.plot(data_x, data_y,'.') #blue data points
plt.plot(x,f(x, c),'black') #black line, this is the shape we want our fit to have

plt.plot(x, f(x, *popt), 'red')  # new fitted line to the data (with same shape as black line)

plt.xlim([6.5,11.0])
plt.ylim([6.5,10.5])

plt.show()

print("y offset:", popt[0] - c)

y offset: 0.23492393887717355

solution


Solution

  • You want to use scipy.optimize.curve_fit. As you can see in the documentation, you can define your own function fit_y with the parameters to fit. Once the fit is done, you can compute the y offset (respect to the origin?) simply calculating the function in x=0. Below I show you an example code where I used a root function (this is what your black curve looks like):

    import numpy as np
    from scipy.optimize import curve_fit
    import matplotlib.pyplot as plt
    
    def f(x, a, b, c):
        return a * np.power(x, b) + c
    
    
    x_data = np.arange(100)
    noise = np.random.normal(size=100)
    y_data = np.power(x_data, 0.5) + noise
    y = f(x_data, 1, 2, 0.3)  # random values to initialize the fit
    popt, _ = curve_fit(f, x_data, y_data)  # popt are the fitted parameters
    
    plt.scatter(x_data, y_data)
    plt.plot(x_data, f(x_data, *popt), 'r')  # fitted line
    plt.show()
    
    print("y offset:", f(0, *popt))
    

    I don't have enough reputation to post the plot, but just run the code to see yourself.