2025, Dec 22 15:02

Подгонка отрицательного биномиального распределения в SciPy с вещественным n

Как подогнать отрицательное биномиальное распределение в SciPy с нецелым n: почему падает stats.fit с ValueError и как обойти его патчем shape info и DE

Подгонка отрицательного биномиального распределения к дискретным данным в SciPy на первый взгляд проста — пока вы не попробуете оценить вещественное n. Функция pmf без проблем работает с нецелыми значениями n, но высокоуровневый API подбора параметров отвергает их с ошибкой области определения. Если вы столкнулись с ValueError о целочисленных параметрах и пустых границах, значит, вы уперлись в несоответствие между метаданными параметров распределения и тем, что на самом деле требуется оптимизировать.

Воспроизводим ошибку

Ниже приведён минимальный пример: он пытается подогнать stats.nbinom с нецелым n, используя scipy.stats.fit и оптимизатор дифференциальной эволюции. С самим оптимизатором всё в порядке; претензия возникает из-за того, как распределение сконфигурировано для подгонки.

from scipy import stats
from scipy.optimize import differential_evolution
import numpy as np

prng = np.random.default_rng()

def de_wrapper(objfun, box, *, integrality):
    return differential_evolution(
        objfun,
        box,
        strategy='best2bin',
        rng=prng,
        integrality=[False, False, False]
    )

observed = [0, 0, 0, 1, 2, 4, 11]
param_bounds = [(1.5, 1.55), (0, 1)]

fit_out = stats.fit(stats.nbinom, observed, param_bounds, optimizer=de_wrapper)
print(fit_out.params)

Запуск приводит к:

ValueError: There are no integer values for `n` on the interval defined by the user-provided bounds and the domain of the distribution.

Что на самом деле не так

Ошибка возникает не из-за оптимизатора; она срабатывает ещё до оптимизации, потому что stats.fit обращается к метаданным о форме параметров распределения. В отрицательном биномиальном распределении SciPy параметр n считается целочисленным. При границах (1.5, 1.55) внутри интервала нет ни одного целого значения, поэтому подгонка сразу отклоняется. Отсюда и сообщение о том, что «для n нет целых значений» в заданных границах.

При этом pmf из stats.nbinom принимает нецелые n и корректно вычисляется для таких входов. Из‑за этой несогласованности распределение можно использовать с вещественным n на уровне pmf, но стандартный конвейер подгонки принудительно требует целочисленности n.

Делаем n непрерывным, пропатчив распределение

Чтобы разрешить вещественные значения n при подгонке, можно слегка «пропатчить» определение распределения так, чтобы в его описании формы параметров n считался непрерывным. После снятия этого ограничения stats.fit будет работать как обычно с выбранным вами оптимизатором.

from scipy import stats
from scipy.optimize import differential_evolution
import numpy as np
from scipy.stats._discrete_distns import nbinom_gen, _ShapeInfo


class NBinomReal(nbinom_gen):
    def _shape_info(self):
        # объявляем 'n' нецелым, сохраняем его область определения и границы для 'p'
        return [
            _ShapeInfo("n", False, (0, np.inf), (True, False)),
            _ShapeInfo("p", False, (0, 1), (True, True))
        ]


def evo_driver(objfun, box, *, integrality):
    return differential_evolution(
        objfun,
        box,
        strategy='best2bin',
        integrality=[False, False, False]
    )

samples = [0, 0, 0, 1, 2, 4, 11]
search_space = [(0.1, 1.55), (0, 1)]
patched_dist = NBinomReal(name='nbinom')

fitted = stats.fit(patched_dist, samples, search_space, optimizer=evo_driver)
print(fitted)

Так сохраняется весь рабочий процесс подгонки, а n может свободно меняться на положительных действительных числах. Область определения для p по‑прежнему от 0 до 1. Параметр loc также доступен при подгонке и остаётся нескованным, если вы не зададите ограничения явно.

Почему это важно

В некоторых задачах моделирования, включая эпидемиологические, параметр n имеет прямую интерпретацию и необязательно должен быть целым. Например, вас может интересовать дисперсия, при этом n удобно варьировать непрерывно. Стандартное ограничение API на целочисленность блокирует такой сценарий; сняв его, вы синхронизируете подгонку с поведением pmf и получаете практическую возможность оценивать максимум правдоподобия для вещественного n.

Итог

Если при подгонке отрицательного биномиального распределения через scipy.stats.fit вы видите ValueError о целочисленном n, причина в конфигурации формы параметров распределения, а не в оптимизаторе. Когда требуется вещественное n, пропатчьте распределение так, чтобы в описании формы n считался непрерывным, затем запустите подгонку заново с выбранным оптимизатором и подходящими границами. Вы сохраните удобство интерфейса stats.fit и при этом получите нужную вам параметризацию.