Подбор распределения по гистограмме с использованием scipy

Я хотел бы подогнать дистрибутив с помощью scipy (в моем случае с помощью weibull_min) к моим данным. Можно ли это сделать, учитывая гистограмму, а не точки данных? В моем случае, поскольку гистограмма имеет целочисленные интервалы размера 1, я знаю, что могу экстраполировать свои данные следующим образом:

import numpy as np
orig_hist = np.array([10, 5, 3, 2, 1])

ext_data = reduce(lambda x,y: x+y, [[i]*x for i, x in enumerate(orig_hist)])

В этом случае ext_data будет содержать следующее:

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 4]

И построить гистограмму, используя:

np.histogram(ext_data, bins=5)

будет эквивалентно orig_hist

Тем не менее, учитывая, что у меня уже есть построенная гистограмма, я хотел бы избежать экстраполяции данных и использовать orig_hist для подбора распределения, но я не знаю, можно ли использовать его непосредственно в процедуре подбора. Кроме того, есть ли функция numpy, которую можно использовать для выполнения чего-то подобного экстраполяции, которую я показал?


person Alberto A    schedule 17.11.2015    source источник
comment
Я добавил ответ на основе scipy.optimize.curve_fit, но потом понял, что вы хотели использовать stats.weibull_min.fit. Если я правильно понимаю, вам нужно ext_data для последнего. Как оказалось, для первого достаточно гистограммы. Может ли мой ответ работать для вас?   -  person Andras Deak    schedule 18.11.2015
comment
Я так не думаю, если честно. Я выполнил шаги, описанные в вашем ответе, но вместо этого использовал свои данные, и в результате соответствие было неудовлетворительным. Кажется, есть проблема с optimize.cuver_fit, потому что независимо от того, какие данные я использую в качестве входных данных, возвращаемое значение popt равно 1.00000001.   -  person Alberto A    schedule 18.11.2015


Ответы (1)


Я могу что-то неправильно понять, но я считаю, что подгонка к гистограмме — это именно то, что вам следует делать: вы пытаетесь аппроксимировать плотность вероятности. И гистограмма максимально приближена к базовой плотности вероятности. Вам просто нужно нормализовать его, чтобы интеграл был равен 1, или разрешить вашей подобранной модели содержать произвольный префактор.

import numpy as np
import scipy.stats as stats
import scipy.optimize as opt
import matplotlib.pyplot as plt

orig_hist = np.array([10, 5, 3, 2, 1])
norm_hist = orig_hist/float(sum(orig_hist))

popt,pcov = opt.curve_fit(lambda x,c: stats.weibull_min.pdf(x,c), np.arange(len(norm_hist)),norm_hist)

plt.figure()
plt.plot(norm_hist,'o-',label='norm_hist')
plt.plot(stats.weibull_min.pdf(np.arange(len(norm_hist)),popt),'s-',label='Weibull_min fit')
plt.legend()

Конечно, для вашего ввода подгонка Вейбулла будет далеко не удовлетворительной:

соответствовать данным

Обновлять

Как я упоминал выше, Weibull_min плохо подходит для вашего примера входных данных. Большая проблема заключается в том, что это также плохо соответствует вашим фактическим данным:

orig_hist = np.array([ 23., 14., 13., 12., 12., 12., 11., 11., 11., 11., 10., 10., 10., 9., 9., 8., 8., 8., 8., 8., 8., 8., 8., 8., 8., 8., 7., 7., 7., 7., 7., 7., 7., 7., 7., 7., 7., 7., 7., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.], dtype=np.float32)

новые данные гистограммы

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

Я вручную определил масштабированную функцию подбора Вейбулла в соответствии с формулой из Википедии:

my_weibull = lambda x,l,c,A: A*float(c)/l*(x/float(l))**(c-1)*np.exp(-(x/float(l))**c)

В этой функции x — независимая переменная, llambda (параметр масштаба), ck (параметр формы), а A — префактор масштабирования. Слабым преимуществом введения A является то, что вам не нужно нормализовать гистограмму.

Теперь, когда я поместил эту функцию в scipy.optimize.curve_fit, я обнаружил то же, что и вы: она на самом деле не выполняет подгонку, а придерживается исходных параметров подгонки, какие бы вы ни установили (используя параметр p0; все догадки по умолчанию равны 1 для каждого параметр). И curve_fit, похоже, считает, что примерка сходится.

После более чем часового биения головой о стену я понял, что проблема в том, что сингулярное поведение на x=0 отбрасывает нелинейный алгоритм наименьших квадратов. Исключив самую первую точку данных, вы получите фактическое соответствие вашим данным. Я подозреваю, что если мы установим c=1 и не позволим этому подходить, то эта проблема может исчезнуть, но, вероятно, будет более информативно разрешить это подгонку (поэтому я не проверял).

Вот соответствующий код:

import numpy as np
import scipy.optimize as opt
import matplotlib.pyplot as plt

orig_hist = np.array([ 23., 14., 13., 12., 12., 12., 11., 11., 11., 11., 10., 10., 10., 9., 9., 8., 8., 8., 8., 8., 8., 8., 8., 8., 8., 8., 7., 7., 7., 7., 7., 7., 7., 7., 7., 7., 7., 7., 7., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.], dtype=np.float32)

my_weibull = lambda x,l,c,A: A*float(c)/l*(x/float(l))**(c-1)*np.exp(-(x/float(l))**c)

popt,pcov = opt.curve_fit(my_weibull,np.arange(len(orig_hist))[1:],orig_hist[1:]) #throw away x=0!

plt.figure()
plt.plot(np.arange(len(orig_hist)),orig_hist,'o-',label='orig_hist')
plt.plot(np.arange(len(orig_hist)),my_weibull(np.arange(len(orig_hist)),*popt),'s-',label='Scaled Weibull fit')
plt.legend()

Результат:

новая посадка

In [631]: popt
Out[631]: array([  1.10511850e+02,   8.82327822e-01,   1.05206207e+03])

окончательные подобранные параметры находятся в порядке (l,c,A) с параметром формы около 0.88. Это соответствует расходящейся плотности вероятности, что объясняет, почему появляется несколько ошибок, говорящих

RuntimeWarning: в мощности обнаружено недопустимое значение

и почему нет точки данных из фитинга для x=0. Но, судя по визуальному совпадению данных и подгонки, можно оценить, приемлем результат или нет.

Если вы хотите переборщить, вы, вероятно, можете попробовать сгенерировать точки, используя np.random.weibull с этими параметрами, а затем сравнить полученные гистограммы со своими.

person Andras Deak    schedule 17.11.2015
comment
Спасибо, это кажется близким к тому, что я пытаюсь сделать. Моя проблема заключалась в том, что я пытался использовать stats.weibull_min.fit. Но этот конкретный метод принимает в качестве входных данных данные, которые необходимо установить. Если я правильно понял, в вашем случае вы использовали функциюOptimize.curve_fit для подбора данных, передав ей функцию, которую вы хотите подогнать (weibull_min.pdf), и значения X и Y. - person Alberto A; 18.11.2015
comment
@AlbertoA Я использовал curve_fit, чтобы подогнать плотность вероятности к гистограмме, которая является аппроксимацией плотности вероятности на основе ваших необработанных данных. Вы пробовали использовать curve_fit на реальной гистограмме? Это также вернуло popt=1.0001? - person Andras Deak; 18.11.2015
comment
Да, он вернул то же значение. Я нормализовал гистограмму, разделив ее на сумму, затем попробовал curve_fit, и результат был тот же 1.00000001, что и при использовании фиктивной гистограммы примера в моем вопросе. - person Alberto A; 18.11.2015
comment
У вас есть представление о том, какой должна быть c в посадке? Хотя бы приблизительную цифру? Вы можете установить это как отправную точку для curve_fit, что, вероятно, должно помочь. Ваши фактические данные более распределены по Вейбуллу (т. е. ваша гистограмма стремится к 0 при x->0, по крайней мере, если так выглядит weibull_min)? - person Andras Deak; 18.11.2015
comment
Глядя на некоторые графики Вейбулла, я бы сказал, что параметр формы c будет либо ‹ 1, либо равен 1. Следующий массив является примером моих данных (гистограммы): array([ 23., 14., 13., 12., 12., 12., 11., 11., 11., 11., 10., 10., 10., 9., 9., 8., 8., 8., 8., 8., 8., 8., 8., 8., 8., 8., 7., 7., 7., 7., 7., 7., 7., 7., 7., 7., 7., 7., 7., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6., 6.], dtype=float32) Теперь, когда вы сказали, я также не уверен, что weibull_min лучше всего подходит для моих данных (хотя это должен быть какой-то вейбулл). - person Alberto A; 18.11.2015
comment
@AlbertoA Я считаю, что решил проблему, основная проблема заключалась в том, что x=0 нарушил алгоритм подгонки. Пожалуйста, смотрите мой обновленный ответ. - person Andras Deak; 19.11.2015
comment
Андрас, спасибо за очень полный ответ. Это интересно, потому что я играл с подгонкой Вейбулла в Matlab (которая может работать, передавая значения X и их частоты, как я хотел), и функция жаловалась, когда пыталась использовать значение x = 0, но я не Не знаю, связано ли это с проблемой curve_fit. Кроме того, как вы подозревали, гистограмма — это только часть моего распределения. Мне все еще нужно немного поэкспериментировать с тем, как работать с моими данными, чтобы я мог подогнать к ним распределение Вейбулла. Мои примеры с Matlab пока кажутся хорошими, но я надеюсь воспроизвести их на python! - person Alberto A; 19.11.2015
comment
@AlbertoA, к сожалению, из того, что я видел, работая над этой проблемой, набор инструментов Matlab Curve Fitting Toolbox гораздо более гибкий и умный, чем scipy.optimize. Например, я обычно использую нижнюю и верхнюю границы для параметров при подгонке в Matlab, но я не понимаю, как это можно сделать в python (даже после просмотра scipy.optimize.leastsq, который curve_fit использует под капотом. - person Andras Deak; 19.11.2015