When attempting to plot an exponential curve to a set of data:
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import style
from matplotlib import pylab
import numpy as np
from scipy.optimize import curve_fit
x = np.array([30,40,50,60])
y = np.array([0.027679854,0.055639098,0.114814815,0.240740741])
def exponenial_func(x, a, b, c):
return a*np.exp(-b*x)+c
popt, pcov = curve_fit(exponenial_func, x, y, p0=(1, 1e-6, 1))
xx = np.linspace(10,60,1000)
yy = exponenial_func(xx, *popt)
plt.plot(x,y,'o', xx, yy)
pylab.title('Exponential Fit')
ax = plt.gca()
fig = plt.gcf()
plt.xlabel(r'Temperature, C')
plt.ylabel(r'1/Time, $s^-$$^1$')
plt.show()
Graph for the above code:
However when I add the data point 20 (x) and 0.015162344 (y):
import matplotlib
import matplotlib.pyplot as plt
from matplotlib import style
from matplotlib import pylab
import numpy as np
from scipy.optimize import curve_fit
x = np.array([20,30,40,50,60])
y = np.array([0.015162344,0.027679854,0.055639098,0.114814815,0.240740741])
def exponenial_func(x, a, b, c):
return a*np.exp(-b*x)+c
popt, pcov = curve_fit(exponenial_func, x, y, p0=(1, 1e-6, 1))
xx = np.linspace(20,60,1000)
yy = exponenial_func(xx, *popt)
plt.plot(x,y,'o', xx, yy)
pylab.title('Exponential Fit')
ax = plt.gca()
fig = plt.gcf()
plt.xlabel(r'Temperature, C')
plt.ylabel(r'1/Time, $s^-$$^1$')
plt.show()
The above code generates the error
'RuntimeError: Optimal parameters not found: Number of calls to
function has reached maxfev = 800.'
If maxfev is set to maxfev = 1300
popt, pcov = curve_fit(exponenial_func, x, y, p0=(1, 1e-6, 1),maxfev=1300)
The graph is plotted but does not fit the curve correctly. Graph from above code change, maxfev = 1300:
I think this is because points 20 and 30 a too close to each other? For comparison, excel plots the data like this:
How can I plot this curve correctly?
From your data it is obvious that you need a positive exponent, therefore, b needs to be negative as you use a*np.exp(-b*x) + c as the underlying model. However, you start with a positive initial value for b which most likely causes the issues.
If you change
popt, pcov = curve_fit(exponenial_func, x, y, p0=(1, 1e-6, 1))
to
popt, pcov = curve_fit(exponenial_func, x, y, p0=(1, -1e-6, 1))
it works fine and gives the expected outcome.
Alternatively, you could also change your equation to
return a*np.exp(b*x) + c
and start with the same initial values as you had.
Here is the entire code:
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import curve_fit
def exponenial_func(x, a, b, c):
return a*np.exp(b*x)+c
x = np.array([20, 30, 40, 50, 60])
y = np.array([0.015162344, 0.027679854, 0.055639098, 0.114814815, 0.240740741])
popt, pcov = curve_fit(exponenial_func, x, y, p0=(1, 1e-6, 1))
xx = np.linspace(20, 60, 1000)
yy = exponenial_func(xx, *popt)
# please check whether that is correct
r2 = 1. - sum((exponenial_func(x, *popt) - y) ** 2) / sum((y - np.mean(y)) ** 2)
plt.plot(x, y, 'o', xx, yy)
plt.title('Exponential Fit')
plt.xlabel(r'Temperature, C')
plt.ylabel(r'1/Time, $s^-$$^1$')
plt.text(30, 0.15, "equation:\n{:.4f} exp({:.4f} x) + {:.4f}".format(*popt))
plt.text(30, 0.1, "R^2:\n {}".format(r2))
plt.show()
Related
I am trying to fit two curve into one equation. y = (a * exp(b * (T^-1)))cexp(d100)(x^0.5)
for y1, T =10,
for y2, T =25.
how do a get a,b,c,d
I have a code that simplified to fit one data. I don't know how to do both.
I find a similar question with solution but I can't follow.. fit multiple parametric curves with scipy
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
from scipy.optimize import leastsq
import numpy as np
import pandas as pd
from math import exp
def func(params,x,y):
a, b, c, d = params[0], params[1], params[2],params[3]
return y-(a*exp(b*(10**-1)))*c*exp(d*100)*(x**0.5)
x = [0,33,65,98,135,261,374]
y = [0.000,0.006,0.010,0.018,0.023,0.033,0.035]
y2 = [0.000,0.013,0.032,0.036,0.042,0.046,0.051]
plt.scatter(x,y, label='y1')
plt.scatter(x,y2, label='y1')
params=[0, 0, 0, 0]
result = leastsq(func, params, (x, y))
a, b, c, d = result[0][0], result[0][1], result[0][2], result[0][3]
yfit1 = (a*exp(b*(25**-1)))*c*exp(d*100)*(x**0.5)
plt.plot(x, yfit1, color="red")
print (b,c,d)
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.grid()
plt.show()
You need to include T in the independent variable being passed to curve_fit. Also, you should use numpy mathematical functions in func.
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
import numpy as np
def func(xT, a, b, c, d,):
x = xT[0,:]
T = xT[1,:]
return a * np.exp(b * np.power(T, -1)) * c * np.exp(d * 100.0) * np.power(x, 0.5)
x0 = np.array([0,33,65,98,135,261,374])
y1 = np.array([0.000,0.006,0.010,0.018,0.023,0.033,0.035])
T1 = 10.0 * np.ones(len(x0))
y2 = np.array([0.000,0.013,0.032,0.036,0.042,0.046,0.051])
T2 = 25.0 * np.ones(len(x0))
x = np.concatenate((x0, x0))
y = np.concatenate((y1, y2))
T = np.concatenate((T1, T2))
popt, _ = curve_fit(func, np.vstack((x, T)), y)
N = 101 # number of points for parametric curves
x_ = np.linspace(np.min(x0), np.max(x0), N)
y1_ = func(np.vstack((x_, 10.0 * np.ones(N))), *popt)
plt.plot(x0, y1, 'k.')
plt.plot(x_, y1_, 'k-')
y2_ = func(np.vstack((x_, 25.0 * np.ones(N))), *popt)
plt.plot(x0, y2, 'b.')
plt.plot(x_, y2_, 'b-')
I am trying to surface fit 3d data (z is a function of x and y). I have assymetrical error bars for each point. I would like the fit to take this uncertainty into account.
I am using scipy.linalg.lstsq(). It does not have any option for uncertainties in its arguments.
I am trying to adapt some code found on this page.
import numpy as np
import scipy.linalg
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# Create data with x and y random over [-2, 2], and z a Gaussian function of x and y.
np.random.seed(12345)
x = 2 * (np.random.random(500) - 0.5)
y = 2 * (np.random.random(500) - 0.5)
def f(x, y):
return np.exp(-(x + y ** 2))
z = f(x, y)
data = np.c_[x,y,z]
# regular grid covering the domain of the data
mn = np.min(data, axis=0)
mx = np.max(data, axis=0)
X,Y = np.meshgrid(np.linspace(mn[0], mx[0], 20), np.linspace(mn[1], mx[1], 20))
XX = X.flatten()
YY = Y.flatten()
# best-fit quadratic curve (2nd-order)
A = np.c_[np.ones(data.shape[0]), data[:,:2], np.prod(data[:,:2], axis=1), data[:,:2]**2]
C,_,_,_ = scipy.linalg.lstsq(A, data[:,2])
# evaluate it on a grid
Z = np.dot(np.c_[np.ones(XX.shape), XX, YY, XX*YY, XX**2, YY**2], C).reshape(X.shape)
# plot points and fitted surface using Matplotlib
fig = plt.figure(figsize=(10, 10))
ax = fig.gca(projection='3d')
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, alpha=0.2)
ax.scatter(data[:,0], data[:,1], data[:,2], c='r', s=50)
plt.xlabel('X')
plt.ylabel('Y')
ax.set_zlabel('Z')
ax.axis('equal')
ax.axis('tight')
My specific issue is that I cannot seem to get my data to converted to floating points. I have data and simply want to fit a robust curve using my model equation:
y = a * e^(-b*z)
This cookbook is my reference: click
Below is my attempt. I am getting this:
TypeError: 'data type not understood'
which I believe is because my columns are strings, so I tried pd.Series.astype().
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import least_squares
for i in range(1):
def model(z, a, b):
y = a * np.exp(-b * z)
return y
data = pd.read_excel('{}.xlsx'.format(600+i), names = ['EdGnd','380','395','412','443','465','490','510','520','532','555','560','565','589','625','665','670','683','694','710','Temp','z','EdZTemp','Tilt','Roll','EdZVin'])
data.dropna(axis = 0, how = 'any')
data.astype('float')
np.dtype(data)
data.plot.scatter('z','380')
def fun(x, z, y):
return x[0] * np.exp(-x[1] * z) - y
x0 = np.ones(3)
rbst1 = least_squares(fun, x0, loss='soft_l1', f_scale=0.1, args=('z', 'ed380'))
y_robust = model('z', *rbst1.x)
plt.plot('z', y_robust, label='robust lsq')
plt.xlabel('$z$')
plt.ylabel('$Ed$')
plt.legend();
I think the problem is that you pass 'z' in args which is a string and can therefore not be used in the multiplication.
Below is some code using curve_fit which uses least_squares but might be slightly easier to use:
import matplotlib.pyplot as plt
import numpy as np
from scipy.optimize import curve_fit
# your model definition
def model(z, a, b):
return a * np.exp(-b * z)
# your input data
x = np.array([20, 30, 40, 50, 60])
y = np.array([5.4, 4.0, 3.0, 2.2, 1.6])
# do the fit with some initial values
popt, pcov = curve_fit(model, x, y, p0=(5, 0.1))
# prepare some data for a plot
xx = np.linspace(20, 60, 1000)
yy = model(xx, *popt)
plt.plot(x, y, 'o', xx, yy)
plt.title('Exponential Fit')
plt.show()
This will plot
You could try to adapt this code for your needs.
If you want to use f_scale you can use:
popt, pcov = curve_fit(model, x, y, p0=(5, 0.1), method='trf', f_scale=0.1)
See the documentation:
kwargs
Keyword arguments passed to leastsq for method='lm' or least_squares otherwise.
If you have an unbound problem, by default method='lm' is used which uses leastsq which does not accept f_scale as a keyword. Therefore, we can use method='trf' which then uses least_squares which accepts f_scale.
I want to solve the following function so that after fitting, I want to get the value of x when y=0.5.
The function:
import numpy as np
from scipy.optimize import curve_fit
def sigmoid(x, b, c):
y = 1 / (1 + c*np.exp(-b*x))
return y
x_data = [4, 6, 8, 10]
y_data = [0.86, 0.73, 0.53, 0.3]
popt, pcov = curve_fit(sigmoid, x_data, y_data,(28.14,-0.25))
please explain how would you carry out this using python!
Thanks!
When I run your code I get a warning, and popt is the same as your initial guess, (28.14, -0.25). If you try plotting this you'll see that it's essentially a straight line at y == 1 that doesn't fit your data well at all:
from matplotlib import pyplot as plt
x = np.linspace(4, 10, 1000)
y = sigmoid(x, *popt)
fig, ax = plt.subplots(1, 1)
ax.hold(True)
ax.scatter(x_data, y_data, s=50, zorder=20)
ax.plot(x, y, '-k', lw=2)
The problem is that you're initializing with a negative value for the b parameter. Remember that b gets negated, so you're actually exponentiating x times a positive number, which blows up your denominator. Instead you want to initialize with a positive value for b, but perhaps a negative value for c (to give you your negative slope):
popt2, pcov2 = curve_fit(sigmoid, x_data, y_data, (-0.5, 0.1))
y2 = sigmoid(x, *popt2)
ax.plot(x, y2, '-r', lw=2)
To get the value of x at y == 0.5 using nonlinear optimization you need to define an objective function, which could be the square of the difference between 0.5 and sigmoid(x, b, c):
def objective(x, b, c):
return (0.5 - sigmoid(x, b, c)) ** 2
You can then use scipy.optimize.minimize or scipy.optimize.minimize_scalar to find the value of x that minimizes the objective function:
from scipy.optimize import minimize_scalar
res = minimize_scalar(objective, bracket=(4, 10), args=tuple(popt2))
ax.annotate("$y = 0.5$", (res.x, 0.5), (30, 30), textcoords='offset points',
arrowprops=dict(facecolor='black', shrink=0.05), fontsize='x-large')
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: http://i.imgur.com/Nx44wsS.jpg
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()
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:
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).