Search code examples
pythonexponential

Python exponential decay curve_fit gives me a linear fit


Hi I'm attempting to produce a fit for each of my three exponential decays. I am not successful with producing a satisfactory fit. This is what I get: https://i.sstatic.net/abRZa.png

Any help is greatly appreciated. My code is below.

import pylab as plb
import matplotlib.pyplot as plt
import matplotlib.axes as ax
import scipy as sp
from scipy.optimize import curve_fit
from matplotlib import rc
rc('font',**{'family':'sans-serif','sans-serif':['Helvetica']})
## for Palatino and other serif fonts use:
#rc('font',**{'family':'serif','serif':['Palatino']})
rc('text', usetex=True)

data = plb.loadtxt('data.csv',skiprows=2)
yp = data[:,4]
yr = data[:,5]
yl = data[:,6]
x = data[:,0]

def func(x,a,b,c):
    return a*np.exp(-b*x) + c

popt, pcov = curve_fit(func, x, yl,maxfev=20000)
a = popt[0]
b = popt[1]
c = popt[2]
print a
print b
print c
print func(x,a,b,c)

xf = np.linspace(0,70,100)
yf = a*np.exp(-b*x) + c

plt.clf()
plt.plot(x,yf,'r-', label="Fitted Curve")
plt.plot(x,func(x,*popt))

plt.plot(x,yp,'bo',label='Polished')
plt.plot(x,yr,'ro',label='Rough')
plt.plot(x,yl,'go',label='Lacquered')

plt.legend()
plt.ylabel("Temperature (K)")
plt.xlabel("Time (min)")
plt.show()

Solution

  • Nonlinear fits are difficult and the trick is that you have to provide a reasonable initial guess.

    Here is a version of your code which does two fits, one with an approximate initial guess and one with the default initial guess:

    import pylab as plb
    import matplotlib.pyplot as plt
    import matplotlib.axes as ax
    import scipy as sp
    from scipy.optimize import curve_fit
    from matplotlib import rc
    import numpy as np
    rc('font', **{'family':'sans-serif', 'sans-serif':['Helvetica']})
    rc('text', usetex=True)
    
    # Fake data
    x = np.arange(0, 70., 2.)
    yl = 300 + 63*np.exp(-x/35.)
    
    def func(x, a, b, c):
        return a*np.exp(-b*x) + c
    
    popt, pcov = curve_fit(func, x, yl, p0=(40, 0.012, 250), maxfev=20000)
    a, b, c = popt
    print 'a=', a, 'b=', b, 'c=', c
    print 'func=', func(x, a, b, c)
    
    popt2, pcov2 = curve_fit(func, x, yl, p0=None, maxfev=20000)
    a2, b2, c2 = popt2
    print 'a2=', a2, 'b2=', b2, 'c2=', c2
    print 'func=', func(x, a2, b2, c2)
    
    xf = np.linspace(0, 70, 100)
    yf = a*np.exp(-b*x) + c
    
    plt.clf()
    plt.plot(x, yf, 'r-', label="Fitted Curve")
    plt.plot(x, func(x, *popt))
    plt.plot(x, func(x, *popt2), 'b-', label='Fit w/o guess')
    
    plt.plot(x, yl, 'go', label='Lacquered')
    
    plt.legend()
    plt.ylabel("Temperature (K)")
    plt.xlabel("Time (min)")
    plt.show()
    

    And here are the resulting fits:

    enter image description here

    As you can see, the fit with a reasonable initial guess does very well (red line). If you don't provide an initial guess, scipy assumes 1 for all parameters and that works poorly (blue line).