Est-ce que quelqu'un connaît un module scipy/numpy qui permettra d'adapter la décroissance exponentielle aux données?
La recherche Google a renvoyé quelques articles, par exemple - http://exnumerus.blogspot.com/2010/04/how-to-fit-exponential-decay-example-in.html , mais cette solution exige que le décalage y soit prédéfini, ce qui n'est pas toujours possible
MODIFIER:
curve_fit fonctionne, mais il peut très mal échouer sans deviner les paramètres, ce qui est parfois nécessaire. Le code avec lequel je travaille est
#!/usr/bin/env python
import numpy as np
import scipy as sp
import pylab as pl
from scipy.optimize.minpack import curve_fit
x = np.array([ 50., 110., 170., 230., 290., 350., 410., 470.,
530., 590.])
y = np.array([ 3173., 2391., 1726., 1388., 1057., 786., 598.,
443., 339., 263.])
smoothx = np.linspace(x[0], x[-1], 20)
guess_a, guess_b, guess_c = 4000, -0.005, 100
guess = [guess_a, guess_b, guess_c]
exp_decay = lambda x, A, t, y0: A * np.exp(x * t) + y0
params, cov = curve_fit(exp_decay, x, y, p0=guess)
A, t, y0 = params
print "A = %s\nt = %s\ny0 = %s\n" % (A, t, y0)
pl.clf()
best_fit = lambda x: A * np.exp(t * x) + y0
pl.plot(x, y, 'b.')
pl.plot(smoothx, best_fit(smoothx), 'r-')
pl.show()
ce qui fonctionne, mais si on enlève "p0 = devine", ça échoue lamentablement.
Vous avez deux options:
scipy.optimize.curve_fit
La première option est de loin la plus rapide et la plus robuste. Cependant, vous devez connaître le décalage y a priori, sinon il est impossible de linéariser l'équation. (i.e. y = A * exp(K * t)
peut être linéarisé en ajustant y = log(A * exp(K * t)) = K * t + log(A)
, mais y = A*exp(K*t) + C
ne peut être linéarisé qu’en ajustant y - C = K*t + log(A)
, et comme y
est votre variable indépendante, C
doit être connu auparavant pour que ce soit un système linéaire.
Si vous utilisez une méthode non linéaire, il est a) non garanti de converger et d'obtenir une solution, b) sera beaucoup plus lent, c) donne une estimation beaucoup plus pauvre de l'incertitude de vos paramètres, et d) est souvent beaucoup moins précise . Cependant, une méthode non linéaire présente un énorme avantage par rapport à une inversion linéaire: elle peut résoudre un système d'équations non linéaire. Dans votre cas, cela signifie que vous n'avez pas besoin de connaître C
à l'avance.
Juste pour donner un exemple, résolvons pour y = A * exp (K * t) avec des données bruitées utilisant des méthodes linéaires et non linéaires:
import numpy as np
import matplotlib.pyplot as plt
import scipy as sp
import scipy.optimize
def main():
# Actual parameters
A0, K0, C0 = 2.5, -4.0, 2.0
# Generate some data based on these
tmin, tmax = 0, 0.5
num = 20
t = np.linspace(tmin, tmax, num)
y = model_func(t, A0, K0, C0)
# Add noise
noisy_y = y + 0.5 * (np.random.random(num) - 0.5)
fig = plt.figure()
ax1 = fig.add_subplot(2,1,1)
ax2 = fig.add_subplot(2,1,2)
# Non-linear Fit
A, K, C = fit_exp_nonlinear(t, noisy_y)
fit_y = model_func(t, A, K, C)
plot(ax1, t, y, noisy_y, fit_y, (A0, K0, C0), (A, K, C0))
ax1.set_title('Non-linear Fit')
# Linear Fit (Note that we have to provide the y-offset ("C") value!!
A, K = fit_exp_linear(t, y, C0)
fit_y = model_func(t, A, K, C0)
plot(ax2, t, y, noisy_y, fit_y, (A0, K0, C0), (A, K, 0))
ax2.set_title('Linear Fit')
plt.show()
def model_func(t, A, K, C):
return A * np.exp(K * t) + C
def fit_exp_linear(t, y, C=0):
y = y - C
y = np.log(y)
K, A_log = np.polyfit(t, y, 1)
A = np.exp(A_log)
return A, K
def fit_exp_nonlinear(t, y):
opt_parms, parm_cov = sp.optimize.curve_fit(model_func, t, y, maxfev=1000)
A, K, C = opt_parms
return A, K, C
def plot(ax, t, y, noisy_y, fit_y, orig_parms, fit_parms):
A0, K0, C0 = orig_parms
A, K, C = fit_parms
ax.plot(t, y, 'k--',
label='Actual Function:\n $y = %0.2f e^{%0.2f t} + %0.2f$' % (A0, K0, C0))
ax.plot(t, fit_y, 'b-',
label='Fitted Function:\n $y = %0.2f e^{%0.2f t} + %0.2f$' % (A, K, C))
ax.plot(t, noisy_y, 'ro')
ax.legend(bbox_to_anchor=(1.05, 1.1), fancybox=True, shadow=True)
if __== '__main__':
main()
Notez que la solution linéaire fournit un résultat beaucoup plus proche des valeurs réelles. Cependant, nous devons fournir la valeur y-offset pour utiliser une solution linéaire. La solution non linéaire ne nécessite pas cette connaissance a priori.
Je voudrais utiliser la fonction scipy.optimize.curve_fit
. La doc string for it a même un exemple d'adaptation d'une décroissance exponentielle que je vais copier ici:
>>> import numpy as np
>>> from scipy.optimize import curve_fit
>>> def func(x, a, b, c):
... return a*np.exp(-b*x) + c
>>> x = np.linspace(0,4,50)
>>> y = func(x, 2.5, 1.3, 0.5)
>>> yn = y + 0.2*np.random.normal(size=len(x))
>>> popt, pcov = curve_fit(func, x, yn)
Les paramètres ajustés varieront en raison du bruit aléatoire ajouté, mais j'ai obtenu 2,47990495, 1,40709306, 0,53753635 comme a, b et c, donc ce n'est pas si mal avec le bruit. Si je correspond à y au lieu de yn, j'obtiens les valeurs exactes a, b et c.
Procédure à mettre en place de manière exponentielle sans deviner, pas de processus itératif:
Cela provient de l'article (pp.16-17): https://fr.scribd.com/doc/14674814/Regressions-et-equations-integrales
Si nécessaire, cela peut être utilisé pour initialiser un calcul de régression non linéaire afin de choisir un critère d'optimisation spécifique.
EXEMPLE :
L'exemple donné par Joe Kington est intéressant. Malheureusement, les données ne sont pas affichées, mais uniquement le graphique. Ainsi, les données (x, y) ci-dessous proviennent d’un balayage graphique du graphique. Par conséquent, les valeurs numériques ne sont probablement pas exactement celles utilisées par Joe Kington. Néanmoins, les équations respectives des courbes "ajustées" sont très proches les unes des autres, compte tenu de la large dispersion des points.
La figure du haut est la copie du graphique de Kington.
La figure inférieure montre les résultats obtenus avec la procédure présentée ci-dessus.
La bonne façon de le faire est d’effectuer une estimation de Prony et d’utiliser le résultat comme estimation initiale pour l’ajustement des moindres carrés (ou toute autre routine d’ajustement plus robuste). L'estimation de Prony n'a pas besoin de deviner, mais elle nécessite beaucoup de points pour obtenir une bonne estimation.
Voici un aperçu
http://www.statsci.org/other/prony.html
Dans Octave, ceci est implémenté sous la forme expfit
. Vous pouvez donc écrire votre propre routine basée sur la fonction de bibliothèque Octave.
L’estimation Prony a besoin du décalage pour être connue, mais si vous allez «assez loin» dans votre désintégration, vous avez une estimation raisonnable du décalage, vous pouvez donc simplement décaler les données pour placer le décalage à 0. En tout cas, Prony L'estimation est juste un moyen d'obtenir une estimation initiale raisonnable pour d'autres routines d'ajustement.
Je n'ai jamais eu Courbe_fit pour fonctionner correctement, comme vous dites que je ne veux rien deviner. J'essayais de simplifier l'exemple de Joe Kington et c'est ce que j'ai réussi à faire. L'idée est de traduire les données "bruyantes" en log, puis de les retralger et d'utiliser polyfit et polyval pour comprendre les paramètres:
model = np.polyfit(xVals, np.log(yVals) , 1);
splineYs = np.exp(np.polyval(model,xVals[0]));
pyplot.plot(xVals,yVals,','); #show scatter plot of original data
pyplot.plot(xVals,splineYs('b-'); #show fitted line
pyplot.show()
où xVals et yVals ne sont que des listes.
Je ne connais pas python, mais je connais un moyen simple d'estimer de manière non itérative les coefficients de décroissance exponentielle avec un décalage, à partir de trois points de données avec une différence fixe dans leurs coordonnées indépendantes. Vos points de données ont une différence fixe dans leur coordonnée indépendante (vos valeurs x sont espacées d'un intervalle de 60), donc ma méthode peut leur être appliquée. Vous pouvez sûrement traduire les maths en python.
Assumer
y = A + B*exp(-c*x) = A + B*C^x
où C = exp(-c)
Étant donné y_0, y_1, y_2, pour x = 0, 1, 2, nous résolvons
y_0 = A + B
y_1 = A + B*C
y_2 = A + B*C^2
pour trouver A, B, C comme suit:
A = (y_0*y_2 - y_1^2)/(y_0 + y_2 - 2*y_1)
B = (y_1 - y_0)^2/(y_0 + y_2 - 2*y_1)
C = (y_2 - y_1)/(y_1 - y_0)
L'exponentielle correspondante passe exactement par les trois points (0, y_0), (1, y_1) et (2, y_2). Si vos points de données ne sont pas aux coordonnées x 0, 1, 2 mais plutôt à k, k + s et k + 2 * s, alors
y = A′ + B′*C′^(k + s*x) = A′ + B′*C′^k*(C′^s)^x = A + B*C^x
afin que vous puissiez utiliser les formules ci-dessus pour trouver A, B, C, puis calculer
A′ = A
C′ = C^(1/s)
B′ = B/(C′^k)
Les coefficients résultants sont très sensibles aux erreurs dans les coordonnées y, ce qui peut entraîner de grandes erreurs si vous extrapolez au-delà de la plage définie par les trois points de données utilisés. Il est donc préférable de calculer A, B, C à partir de trois points de données qui sont: aussi éloignés que possible (tout en maintenant une distance fixe entre eux).
Votre ensemble de données a 10 points de données équidistants. Choisissons les trois points de données (110, 2391), (350, 786), (590, 263) à utiliser - ils ont la plus grande distance fixe possible (240) dans la coordonnée indépendante. Donc, y_0 = 2391, y_1 = 786, y_2 = 263, k = 110, s = 240. Alors A = 10.20055, B = 2380,799, C = 0,3258567, A '= 10.20055, B' = 3980,329, C '= 0,9953388. L'exponentielle est
y = 10.20055 + 3980.329*0.9953388^x = 10.20055 + 3980.329*exp(-0.004672073*x)
Vous pouvez utiliser cette exponentielle comme estimation initiale dans un algorithme d'ajustement non linéaire.
La formule pour calculer A est la même que celle utilisée par la transformation de Shanks ( http://en.wikipedia.org/wiki/Shanks_transformation ).
Implémentation en python de la solution @ JJacquelin. J'avais besoin d'une solution approximative non résolue, sans suppositions initiales, donc la réponse de @JJacquelin a été vraiment utile. La question initiale était une requête numypy/scipy en python. J'ai pris le code Nice clean R de @ johanvdw et je l'ai remanié en python/numpy. J'espère utile à quelqu'un: https://Gist.github.com/friendtogeoff/00b89fa8d9acc1b2bdf3bdb675178a29
import numpy as np
"""
compute an exponential decay fit to two vectors of x and y data
result is in form y = a + b * exp(c*x).
ref. https://Gist.github.com/johanvdw/443a820a7f4ffa7e9f8997481d7ca8b3
"""
def exp_est(x,y):
n = np.size(x)
# sort the data into ascending x order
y = y[np.argsort(x)]
x = x[np.argsort(x)]
Sk = np.zeros(n)
for n in range(1,n):
Sk[n] = Sk[n-1] + (y[n] + y[n-1])*(x[n]-x[n-1])/2
dx = x - x[0]
dy = y - y[0]
m1 = np.matrix([[np.sum(dx**2), np.sum(dx*Sk)],
[np.sum(dx*Sk), np.sum(Sk**2)]])
m2 = np.matrix([np.sum(dx*dy), np.sum(dy*Sk)])
[d, c] = (m1.I * m2.T).flat
m3 = np.matrix([[n, np.sum(np.exp( c*x))],
[np.sum(np.exp(c*x)),np.sum(np.exp(2*c*x))]])
m4 = np.matrix([np.sum(y), np.sum(y*np.exp(c*x).T)])
[a, b] = (m3.I * m4.T).flat
return [a,b,c]
Si votre carie ne commence pas à 0, utilisez:
popt, pcov = curve_fit(self.func, x-x0, y)
où x0 le début de la décroissance (où vous voulez commencer l'ajustement). Et utilisez à nouveau x0 pour tracer:
plt.plot(x, self.func(x-x0, *popt),'--r', label='Fit')
où la fonction est:
def func(self, x, a, tau, c):
return a * np.exp(-x/tau) + c