Создание новых распределений в scipy

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

from scipy import stats
import numpy

def getDistribution(data):
    kernel = stats.gaussian_kde(data)
    class rv(stats.rv_continuous):
        def _cdf(self, x):
            return kernel.integrate_box_1d(-numpy.Inf, x)
    return rv()

if __name__ == "__main__":
    # pretend this is real data
    data = numpy.concatenate((numpy.random.normal(2,5,100), numpy.random.normal(25,5,100)))
    d = getDistribution(data)

    print d.rvs(size=100) # this usually fails

Я думаю, что это делает то, что я хочу, но часто получаю ошибку (см. ниже), когда я пытаюсь сделать d.rvs(), а d.rvs(100) никогда не работает. Я делаю что-то неправильно? Есть ли более простой или лучший способ сделать это? Если это ошибка в scipy, есть ли способ обойти это?

Наконец, есть ли еще какая-то документация по созданию пользовательских дистрибутивов? Лучшее, что я нашел, это документация scipy.stats.rv_continuous, которая довольно спартанская и не содержит полезных примеров.

След:

Traceback (последний последний вызов): Файл "testDistributions.py", строка 19, в     print d.rvs(size = 100) Файл "/usr/local/lib/python2.6/dist-packages/scipy-0.10.0-py2.6-linux-x86_64.egg/scipy/stats/distributions.py" , строка 696, в rvs     vals = self._rvs (* args) Файл "/usr/local/lib/python2.6/dist-packages/scipy-0.10.0-py2.6-linux-x86_64.egg/scipy/stats/distributions.py" , строка 1193, в _rvs     Y = self._ppf (U, * args) Файл "/usr/local/lib/python2.6/dist-packages/scipy-0.10.0-py2.6-linux-x86_64.egg/scipy/stats/distributions. ру", строка 1212, в _ppf     return self.vecfunc(q, * args) Файл "/usr/local/lib/python2.6/dist-packages/numpy-1.6.1-py2.6-linux-x86_64.egg/numpy/lib/function_base.py", строка 1862, в вызов    theout = self.thefunc(* newargs) Файл "/usr/local/lib/python2.6/dist-packages/scipy-0.10.0-py2.6-linux-x86_64.egg/scipy/stats/distributions.py" , строка 1158, в _ppf_single_call     return optimize.brentq(self._ppf_to_solve, self.xa, self.xb, args = (q,) + args, xtol = self.xtol) Файл "/usr/local/lib/python2.6/dist-packages/scipy-0.10.0-py2.6-linux-x86_64.egg/scipy/optimize/zeros.py", строка 366, в brentq     r = _zeros._brentq (f, a, b, xtol, maxiter, args, full_output, disp) ValueError: f (a) и f (b) должны иметь разные знаки

Edit

Для любопытных, следуя советам ниже, здесь код, который работает:

from scipy import stats
import numpy

def getDistribution(data):
    kernel = stats.gaussian_kde(data)
    class rv(stats.rv_continuous):
        def _rvs(self, *x, **y):
            # don't ask me why it using self._size 
            # nor why I have to cast to int
            return kernel.resample(int(self._size)) 
        def _cdf(self, x):
            return kernel.integrate_box_1d(-numpy.Inf, x)
        def _pdf(self, x):
            return kernel.evaluate(x)
    return rv(name='kdedist', xa=-200, xb=200)

Ответ 1

В частности, ваша трассировка:

rvs использует обратный cdf, ppf для создания случайных чисел. Поскольку вы не указываете ppf, он вычисляется с помощью алгоритма поиска корней, brentq. brentq использует нижнюю и верхнюю границы на том, где он должен искать значение at, при этом функция равна нулю (найдите x, так что cdf (x) = q, q является квантильным).

По умолчанию для пределов, xa и xb, слишком малы в вашем примере. Следующие работы для меня с scipy 0.9.0, xa, xb могут быть установлены при создании экземпляра функции

def getDistribution(data):
    kernel = stats.gaussian_kde(data)
    class rv(stats.rv_continuous):
        def _cdf(self, x):
            return kernel.integrate_box_1d(-numpy.Inf, x)
    return rv(name='kdedist', xa=-200, xb=200)

В настоящее время существует запрос на pull для scipy, чтобы улучшить это, поэтому в следующей версии xa и xb будут автоматически расширены, чтобы избежать исключения f(a) and f(b) must have different signs.

В этом документе мало документации, проще всего следовать некоторым примерам (и спросить в списке рассылки).

edit: дополнение

pdf. Поскольку у вас есть функция плотности, также заданная gaussian_kde, я бы добавил метод _pdf, который сделает некоторые вычисления более эффективными.

edit2: дополнение

rvs: если вы заинтересованы в генерации случайных чисел, gaussian_kde имеет метод resample. Случайные образцы могут быть сгенерированы путем выборки из данных и добавления гауссовского шума. Таким образом, это будет быстрее, чем общие rvs, используя метод ppf. Я бы написал метод._rvs, который просто вызывает метод resample gaussian_kde.

precomputing ppf: я не знаю какого-либо общего способа прекомполяции ppf. Тем не менее, как я думал о том, чтобы делать это (но до сих пор не пробовал), нужно прекомпретировать ppf во многих точках, а затем использовать линейную интерполяцию для аппроксимации функции ppf.

edit3: about _rvs, чтобы ответить на вопрос Шривацана в комментарии

_rvs - это специальный метод распределения, который вызывается публичным методом rvs. rvs - это общий метод, который выполняет некоторую проверку аргументов, добавляет местоположение и масштаб и устанавливает атрибут self._size, который является размером запрашиваемого массива случайных величин, а затем вызывает метод, специфичный для распределения ._rvs, или его общий коллега. Дополнительные аргументы в ._rvs являются параметрами формы, но поскольку в этом случае их нет, *x и **y являются избыточными и неиспользуемыми.

Я не знаю, насколько хорошо size или форма метода .rvs работает в многомерном случае. Эти дистрибутивы предназначены для одномерных распределений и могут не полностью работать для многомерного случая или могут потребоваться некоторые изменения.