web-dev-qa-db-fra.com

Ajustement gaussien pour Python

J'essaie d'adapter un gaussien pour mes données (qui est déjà un gaussien approximatif). J'ai déjà suivi les conseils de ceux qui sont ici et j'ai essayé curve_fit et leastsq mais je pense qu'il me manque quelque chose de plus fondamental (en ce que je n'ai aucune idée de comment utiliser la commande). Voici un aperçu du script que j'ai jusqu'à présent

import pylab as plb
import matplotlib.pyplot as plt

# Read in data -- first 2 rows are header in this example. 
data = plb.loadtxt('part 2.csv', skiprows=2, delimiter=',')

x = data[:,2]
y = data[:,3]
mean = sum(x*y)
sigma = sum(y*(x - mean)**2)

def gauss_function(x, a, x0, sigma):
    return a*np.exp(-(x-x0)**2/(2*sigma**2))
popt, pcov = curve_fit(gauss_function, x, y, p0 = [1, mean, sigma])
plt.plot(x, gauss_function(x, *popt), label='fit')

# plot data

plt.plot(x, y,'b')

# Add some axis labels

plt.legend()
plt.title('Fig. 3 - Fit for Time Constant')
plt.xlabel('Time (s)')
plt.ylabel('Voltage (V)')
plt.show()

Ce que j'en retire est une forme gaussienne qui est mes données originales et une ligne horizontale droite.

enter image description here

Aussi, je voudrais tracer mon graphique en utilisant des points, au lieu de les connecter. Toute contribution est appréciée!

23
Richard Hsia

Voici le code corrigé:

import pylab as plb
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
from scipy import asarray as ar,exp

x = ar(range(10))
y = ar([0,1,2,3,4,5,4,3,2,1])

n = len(x)                          #the number of data
mean = sum(x*y)/n                   #note this correction
sigma = sum(y*(x-mean)**2)/n        #note this correction

def gaus(x,a,x0,sigma):
    return a*exp(-(x-x0)**2/(2*sigma**2))

popt,pcov = curve_fit(gaus,x,y,p0=[1,mean,sigma])

plt.plot(x,y,'b+:',label='data')
plt.plot(x,gaus(x,*popt),'ro:',label='fit')
plt.legend()
plt.title('Fig. 3 - Fit for Time Constant')
plt.xlabel('Time (s)')
plt.ylabel('Voltage (V)')
plt.show()

résultat:
enter image description here

22
Developer

Explication

Vous avez besoin de bonnes valeurs de départ telles que curve_fit la fonction converge vers de "bonnes" valeurs. Je ne peux pas vraiment dire pourquoi votre ajustement n'a pas convergé (même si la définition de votre moyenne est étrange - vérifiez ci-dessous) mais je vais vous donner une stratégie qui fonctionne pour des fonctions gaussiennes non normalisées comme la vôtre.

Exemple

Les paramètres estimés doivent être proches des valeurs finales (utiliser la moyenne arithmétique pondérée - diviser par la somme de toutes les valeurs):

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

x = np.arange(10)
y = np.array([0, 1, 2, 3, 4, 5, 4, 3, 2, 1])

# weighted arithmetic mean (corrected - check the section below)
mean = sum(x * y) / sum(y)
sigma = np.sqrt(sum(y * (x - mean)**2) / sum(y))

def Gauss(x, a, x0, sigma):
    return a * np.exp(-(x - x0)**2 / (2 * sigma**2))

popt,pcov = curve_fit(Gauss, x, y, p0=[max(y), mean, sigma])

plt.plot(x, y, 'b+:', label='data')
plt.plot(x, Gauss(x, *popt), 'r-', label='fit')
plt.legend()
plt.title('Fig. 3 - Fit for Time Constant')
plt.xlabel('Time (s)')
plt.ylabel('Voltage (V)')
plt.show()

Personnellement, je préfère utiliser numpy.

Commentaire sur la définition de la moyenne (y compris la réponse du développeur)

Étant donné que les examinateurs n'ont pas aimé mon éditer sur # code du développeur , je vais expliquer dans quel cas je suggérerais un code amélioré. La moyenne du développeur ne correspond pas à l'une des définitions normales de la moyenne.

Votre définition renvoie:

>>> sum(x * y)
125

La définition du développeur renvoie:

>>> sum(x * y) / len(x)
12.5 #for Python 3.x

La moyenne arithmétique pondérée:

>>> sum(x * y) / sum(y)
5.0

De même, vous pouvez comparer les définitions de l'écart type (sigma). Comparez avec la figure de l'ajustement résultant:

Resulting fit

Commentaire pour Python 2.x

Dans Python 2.x, vous devez en outre utiliser la nouvelle division pour ne pas générer de résultats étranges ou convertir explicitement les nombres avant la division:

from __future__ import division

ou par ex.

sum(x * y) * 1. / sum(y)
9
strpeter

Vous obtenez une ligne droite horizontale car elle n'a pas convergé.

Une meilleure convergence est atteinte si le premier paramètre de l'ajustement (p0) est mis comme max (y), 5 dans l'exemple, au lieu de 1.

6
user3406246

Après avoir perdu des heures à essayer de trouver mon erreur, le problème est votre formule:

sigma = sum (y * (x-mean) ** 2)/n c'est faux, la bonne formule est la racine carrée de cela !;

sqrt (somme (y * (moyenne x) ** 2)/n)

J'espère que cela t'aides

4
James

Il existe une autre façon d'effectuer l'ajustement, qui consiste à utiliser le package "lmfit". Il utilise essentiellement le cuve_fit mais est beaucoup mieux adapté et offre également un ajustement complexe. Des instructions détaillées étape par étape sont données dans le lien ci-dessous. http://cars9.uchicago.edu/software/python/lmfit/model.html#model.best_fit

1
sravani vaddi
sigma = sum(y*(x - mean)**2)

devrait être

sigma = np.sqrt(sum(y*(x - mean)**2))
1
gastro