Skip to content

Commit 6f3193d

Browse files
authored
Add alternative parameterization to negative binomial distribution #4126 (#4134)
* Add alternative parameters p and n * Update Release Notes * Add test * Add test for invalid initializations * Refactor tests with pytest.mark.parametrize * Minor change
1 parent 0bd2d65 commit 6f3193d

File tree

3 files changed

+68
-1
lines changed

3 files changed

+68
-1
lines changed

RELEASE-NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- `sample_posterior_predictive_w` can now feed on `xarray.Dataset` - e.g. from `InferenceData.posterior`. (see [#4042](https://github.com/pymc-devs/pymc3/pull/4042))
1616
- Add MLDA, a new stepper for multilevel sampling. MLDA can be used when a hierarchy of approximate posteriors of varying accuracy is available, offering improved sampling efficiency especially in high-dimensional problems and/or where gradients are not available (see [#3926](https://github.com/pymc-devs/pymc3/pull/3926))
1717
- Change SMC metropolis kernel to independent metropolis kernel [#4115](https://github.com/pymc-devs/pymc3/pull/3926))
18+
- Add alternative parametrization to NegativeBinomial distribution in terms of n and p (see [#4126](https://github.com/pymc-devs/pymc3/issues/4126))
1819

1920

2021
## PyMC3 3.9.3 (11 August 2020)

pymc3/distributions/discrete.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,20 +612,60 @@ def NegBinom(a, m, x):
612612
Mean :math:`\mu`
613613
======== ==========================
614614
615+
The negative binomial distribution can be parametrized either in terms of mu or p,
616+
and either in terms of alpha or n. The link between the parametrizations is given by
617+
618+
.. math::
619+
620+
\mu &= \frac{n(1-p)}{p} \\
621+
\alpha &= n
622+
615623
Parameters
616624
----------
617625
mu: float
618626
Poission distribution parameter (mu > 0).
619627
alpha: float
620628
Gamma distribution parameter (alpha > 0).
629+
p: float
630+
Alternative probability of success in each trial (0 < p < 1).
631+
n: float
632+
Alternative number of target success trials (n > 0)
621633
"""
622634

623-
def __init__(self, mu, alpha, *args, **kwargs):
635+
def __init__(self, mu=None, alpha=None, p=None, n=None, *args, **kwargs):
624636
super().__init__(*args, **kwargs)
637+
mu, alpha = self.get_mu_alpha(mu, alpha, p, n)
625638
self.mu = mu = tt.as_tensor_variable(floatX(mu))
626639
self.alpha = alpha = tt.as_tensor_variable(floatX(alpha))
627640
self.mode = intX(tt.floor(mu))
628641

642+
def get_mu_alpha(self, mu=None, alpha=None, p=None, n=None):
643+
if alpha is None:
644+
if n is not None:
645+
alpha = n
646+
else:
647+
raise ValueError(
648+
"Incompatible parametrization. Must specify either alpha or n."
649+
)
650+
elif n is not None:
651+
raise ValueError(
652+
"Incompatible parametrization. Can't specify both alpha and n."
653+
)
654+
655+
if mu is None:
656+
if p is not None:
657+
mu = alpha * (1 - p) / p
658+
else:
659+
raise ValueError(
660+
"Incompatible parametrization. Must specify either mu or p."
661+
)
662+
elif p is not None:
663+
raise ValueError(
664+
"Incompatible parametrization. Can't specify both mu and p."
665+
)
666+
667+
return mu, alpha
668+
629669
def random(self, point=None, size=None):
630670
r"""
631671
Draw random values from NegativeBinomial distribution.

pymc3/tests/test_distributions.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,32 @@ def test_fun(value, mu, alpha):
795795
return sp.nbinom.logpmf(value, alpha, 1 - mu / (mu + alpha))
796796

797797
self.pymc3_matches_scipy(NegativeBinomial, Nat, {"mu": Rplus, "alpha": Rplus}, test_fun)
798+
self.pymc3_matches_scipy(
799+
NegativeBinomial,
800+
Nat,
801+
{"p": Unit, "n": Rplus},
802+
lambda value, p, n: sp.nbinom.logpmf(value, n, p),
803+
)
804+
805+
@pytest.mark.parametrize(
806+
"mu, p, alpha, n, expected",
807+
[
808+
(5, None, None, None, "Incompatible parametrization. Must specify either alpha or n."),
809+
(None, .5, None, None, "Incompatible parametrization. Must specify either alpha or n."),
810+
(None, None, None, None, "Incompatible parametrization. Must specify either alpha or n."),
811+
(5, None, 2, 2, "Incompatible parametrization. Can't specify both alpha and n."),
812+
(None, .5, 2, 2, "Incompatible parametrization. Can't specify both alpha and n."),
813+
(None, None, 2, 2, "Incompatible parametrization. Can't specify both alpha and n."),
814+
(None, None, 2, None, "Incompatible parametrization. Must specify either mu or p."),
815+
(None, None, None, 2, "Incompatible parametrization. Must specify either mu or p."),
816+
(5, .5, 2, None, "Incompatible parametrization. Can't specify both mu and p."),
817+
(5, .5, None, 2, "Incompatible parametrization. Can't specify both mu and p."),
818+
]
819+
)
820+
def test_negative_binomial_init_fail(self, mu, p, alpha, n, expected):
821+
with Model():
822+
with pytest.raises(ValueError, match=expected):
823+
NegativeBinomial("x", mu=mu, p=p, alpha=alpha, n=n)
798824

799825
def test_laplace(self):
800826
self.pymc3_matches_scipy(

0 commit comments

Comments
 (0)