pythonscipycurve-fittingexponentialmoving-average

Wrong graph with scipy.optimize.curve_fit (similar to moving average)


I am trying to fit an exponential law into my data. My (x,y) sample is rather complicated to explain, so for general understanding and reproducibility I will say that: both variables are float and continuous, 0<=x<=100, and 0<=y<=1.

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

#ydata=[...] is my list with y values, which contains 0 values
#xdata=[...] is my list with x values

transf_y=[]
for i in range(len(ydata)):
    transf_y.append(ydata[i]+0.00001) #Adding something to avoid zero values

x=numpy.array(xdata,dtype=float)
y=numpy.array(transf_y,dtype=float)

def func(x, a, c, d):
    return a * numpy.exp(-c*x)+d

popt, pcov = curve_fit(func, x, y,p0 = (1, 1e-6, 1))

print ("a = %s , c = %s, d = %s" % (popt[0], popt[1], popt[2]))

xx = numpy.linspace(300, 6000, 1000)
yy = func(xx, *popt)

plt.plot(x,y,label='Original Data')
plt.plot(xx, yy, label="Fitted Curve")

plt.legend(loc='upper left')
plt.show()

Now my fitted curve doesn't look anything like a fitted exponential curve. Rather, it looks like a moving average curve as if such curve was added as a trendline on Excel. What could be the problem? If necessary I'll find a way to make the datasets available to make the example reproducible.

This is what I get out of my code (I don't even know why I am getting three elements in the legend while only two are plotted, at least apparently): enter image description here


Solution

  • enter image description here

    1. your plot depicts a original data twice and no discernible fitted data
    2. your data does not seem to be ordered, I assume that is why you get zickzack lines
    3. in your example, your predicted plot will be in the range between 300 and 6000 whereas your raw data 0<=x<=100

    That aside, your code is correct.

    from scipy.optimize import curve_fit
    import numpy
    import matplotlib.pyplot as plt
    
    xdata=[100.0, 0.0, 90.0, 20.0, 80.0] #is my list with y values, which contains 0 values - edit, you need some raw data which you fit, I inserted some
    ydata=[0.001, 1.0, 0.02, 0.56, 0.03] #is my list with x values
    
    transf_y=[]
    for i in range(len(ydata)):
        transf_y.append(ydata[i]+0.00001) #Adding something to avoid zero values
    
    x1=numpy.array(xdata,dtype=float)
    y1=numpy.array(transf_y,dtype=float)
    
    def func(x, a, c, d):
        return a * numpy.exp(-c*x)+d
    
    popt, pcov = curve_fit(func, x1, y1,p0 = (1, 1e-6, 1))
    
    print ("a = %s , c = %s, d = %s" % (popt[0], popt[1], popt[2]))
    
    #ok, sorting your data
    pairs = []
    for i, j in zip(x1, y1):
        pairs.append([i,j])
    
    sortedList = sorted(pairs, key = lambda x:x[0])
    sorted_x = numpy.array(sortedList)[:,0]
    sorted_y = numpy.array(sortedList)[:,1]
    
    
    #adjusting interval to the limits of your raw data
    xx = numpy.linspace(0, 100.0, 1000)
    yy = func(xx, *popt)
    
    
    #and  everything looks fine
    plt.plot(sorted_x,sorted_y, 'o',label='Original Data')
    plt.plot(xx,yy,label='Fitted Data')
    
    plt.legend(loc='upper left')
    plt.show()