Подгонка гауссианы для данных, которые везде равны нулю, за исключением резкого пика в центральной точке - PullRequest
0 голосов
/ 27 ноября 2018

Тестовый код для данных этого типа:

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

x = np.linspace(0,1,20)
y = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0])

n = np.size(x)
mean = sum(x*y)/n
sigma = np.sqrt(sum(y*(x-mean)**2)/n)

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

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

plt.plot(x,y,'b+:',label='data')
plt.plot(x,gaus(x,*popt),'ro:',label='fit')
plt.legend()

Мне нужно подогнать множество данных, которые аналогичны массиву y, указанному выше, для распределения Гаусса.

Использованиестандартная процедура подгонки по Гауссу с использованием scipy.optimize дает такой вид подгонки:

enter image description here

Я пробовал много разных начальных значений, но не могу получить какую-либо подгонку.

У кого-нибудь есть идеи, как я мог бы приспособить эти данные к гауссову?

Спасибо

Ответы [ 2 ]

0 голосов
/ 27 ноября 2018

Проблема

Ваша основная проблема заключается в том, что у вас есть строго неопределенная проблема с подгонкой.Подумайте об этом так: у вас есть три неизвестных, но только одна точка данных.Это похоже на решение для x, y, z, когда у вас есть только одно уравнение.Поскольку высота вашего гауссиана может изменяться независимо от его ширины, существует бесконечно много распределений, все с различной шириной, которые будут удовлетворять ограничениям вашей подгонки.

Более конкретно, ваши параметры a и sigmaоба могут изменить максимальную высоту распределения, что является в значительной степени единственной вещью, которая имеет значение с точки зрения достижения хорошего соответствия (по крайней мере, когда распределение является центрированным и довольно узким).Таким образом, подпрограммы подбора в Scipy не могут определить, какие из них нужно изменить на любом данном шаге.

Исправление

Самый простой способ решить проблему - заблокировать один из ваших параметров.Вам не нужно менять уравнение, но вам нужно сделать хотя бы одну из a, x0 или sigma константой.Наилучший выбор параметра для исправления - это, вероятно, x0, поскольку определить среднее значение / медиану / режим ваших данных тривиально, просто получив координату x одного элемента данных, отличного от нуля в y.Вам также нужно будет немного более умным о том, как вы устанавливаете свои первоначальные догадки.Вот как это выглядит:

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

x = np.linspace(0,1,20)
xdiff = x[1] - x[0]
y = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0])

# the mean/median/mode all occur at the x coordinate of the one datapoint that is non-zero in y
mean = x[np.argmax(y)]
# sigma should be tiny, since we want a narrow distribution
sigma = xdiff
# the scaling factor should be roughly equal to the "height" of the one datapoint
a = y.max()

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

bounds = ((1, .015), (20, 1))
popt,pcov = curve_fit(gaus, x, y, p0=[a, sigma], maxfev=20000, bounds=bounds)
residual = ((gaus(x,*popt) - y)**2).sum()

plt.figure(figsize=(8,6))

plt.plot(x,y,'b+:',label='data')

xdist = np.linspace(x.min(), x.max(), 1000)
plt.plot(xdist,gaus(xdist,*popt),'C0', label='fit distribution')

plt.plot(x,gaus(x,*popt),'ro:',label='fit')
plt.text(.1,6,"residual: %.6e" % residual)

plt.legend()
plt.show()

Вывод:

enter image description here

Лучшее исправление

Вы неТебе не нужно подходить, чтобы получить того гауссиана, которого ты хочешь.Вместо этого вы можете использовать простое выражение закрытой формы для вычисления необходимых параметров, как в функции fitonegauss в приведенном ниже коде:

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

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

def fitonegauss(x, y, fwhm=None):
    if fwhm is None:
        # determine full width at half maximum from the spacing between the x points
        fwhm = (x[1] - x[0])

    # the mean/median/mode all occur at the x coordinate of the one datapoint that is non-zero in y
    mean = x[np.argmax(y)]

    # solve for sigma in terms of the desired full width at half maximum
    sigma = fwhm/(2*np.sqrt(2*np.log(2)))

    # max(pdf) == 1/(np.sqrt(2*np.pi)*sigma). Use that to determine a
    a = y.max() #(np.sqrt(2*np.pi)*sigma)

    return a, mean, sigma

N = 20
x = np.linspace(0,1,N)
y = np.zeros(N)
y[N//2] = 10

popt = fitonegauss(x, y)

plt.figure(figsize=(8,6))
plt.plot(x,y,'b+:',label='data')

xdist = np.linspace(x.min(), x.max(), 1000)
plt.plot(xdist,gauss(xdist,*popt),'C0', label='fit distribution')

residual = ((gauss(x,*popt) - y)**2).sum()
plt.plot(x, gauss(x,*popt),'ro:',label='fit')
plt.text(.1,6,"residual: %.6e" % residual)

plt.legend()
plt.show()

Вывод:

enter image description here

Преимуществ этого подхода много.Он намного эффективнее в вычислительном отношении, чем любая подгонка, он (по большей части) никогда не потерпит неудачу, и он дает вам гораздо больший контроль над фактической шириной дистрибутива, с которой вы в конечном итоге.

The *Функция 1041 * настроена так, что вы можете напрямую установить полную ширину на половину максимальной установленного распределения.Если вы оставите его неустановленным, код будет автоматически угадывать его по расстоянию между данными x.Похоже, это дает разумные результаты для вашего приложения.

0 голосов
/ 27 ноября 2018

Не используйте общий параметр «a», вместо этого используйте правильное нормальное уравнение распределения :

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

x = np.linspace(0,1,20)
y = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0])

n = np.size(x)
mean = sum(x*y)/n
sigma = np.sqrt(sum(y*(x-mean)**2)/n)

def gaus(x, x0, sigma):
    return 1/np.sqrt(2 * np.pi * sigma**2)*np.exp(-(x-x0)**2/(2*sigma**2))

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

plt.plot(x,y,'b+:',label='data')
plt.plot(x,gaus(x,*popt),'ro:',label='fit')
plt.legend()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...