Skip to content

Commit f361844

Browse files
paulkernfeldJunpeng Lao
authored and
Junpeng Lao
committed
Add Kumaraswamy distribution (#2994) (#2998)
* Add Kumaraswamy * I forgot that "as" is a keyword * Test Kumaraswamy PDF against scipy * Fix Kumaraswamy LaTeX * Add Kumaraswamy to release notes * Only test Kumaraswamy with nonnegative parameters * Revert "Only test Kumaraswamy with nonnegative parameters" This reverts commit 7e8a121. * Calculate Kumaraswamy moments in log space
1 parent 452be39 commit f361844

File tree

6 files changed

+104
-2
lines changed

6 files changed

+104
-2
lines changed

RELEASE-NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
- Improve error message `Mass matrix contains zeros on the diagonal. Some derivatives might always be zero` during tuning of `pm.sample`
1313
- Improve error message `NaN occurred in optimization.` during ADVI
1414
- Save and load traces without `pickle` using `pm.save_trace` and `pm.load_trace`
15+
- Add `Kumaraswamy` distribution
1516

1617
### Fixes
1718

docs/source/api/distributions/continuous.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Continuous
1212
HalfNormal
1313
SkewNormal
1414
Beta
15+
Kumaraswamy
1516
Exponential
1617
Laplace
1718
StudentT

pymc3/distributions/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .continuous import HalfFlat
77
from .continuous import Normal
88
from .continuous import Beta
9+
from .continuous import Kumaraswamy
910
from .continuous import Exponential
1011
from .continuous import Laplace
1112
from .continuous import StudentT
@@ -89,6 +90,7 @@
8990
'HalfFlat',
9091
'Normal',
9192
'Beta',
93+
'Kumaraswamy',
9294
'Exponential',
9395
'Laplace',
9496
'StudentT',

pymc3/distributions/continuous.py

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from .dist_math import bound, logpow, gammaln, betaln, std_cdf, alltrue_elemwise, SplineWrapper, i0e
2525
from .distribution import Continuous, draw_values, generate_samples
2626

27-
__all__ = ['Uniform', 'Flat', 'HalfFlat', 'Normal', 'Beta', 'Exponential',
27+
__all__ = ['Uniform', 'Flat', 'HalfFlat', 'Normal', 'Beta', 'Kumaraswamy', 'Exponential',
2828
'Laplace', 'StudentT', 'Cauchy', 'HalfCauchy', 'Gamma', 'Weibull',
2929
'HalfStudentT', 'Lognormal', 'ChiSquared', 'HalfNormal', 'Wald',
3030
'Pareto', 'InverseGamma', 'ExGaussian', 'VonMises', 'SkewNormal',
@@ -701,6 +701,92 @@ def _repr_latex_(self, name=None, dist=None):
701701
get_variable_name(alpha),
702702
get_variable_name(beta))
703703

704+
class Kumaraswamy(UnitContinuous):
705+
R"""
706+
Kumaraswamy log-likelihood.
707+
708+
The pdf of this distribution is
709+
710+
.. math::
711+
712+
f(x \mid a, b) =
713+
abx^{a-1}(1-x^a)^{b-1}
714+
715+
.. plot::
716+
717+
import matplotlib.pyplot as plt
718+
import numpy as np
719+
plt.style.use('seaborn-darkgrid')
720+
x = np.linspace(0, 1, 200)
721+
a_s = [.5, 5., 1., 2., 2.]
722+
b_s = [.5, 1., 3., 2., 5.]
723+
for a, b in zip(a_s, b_s):
724+
pdf = a * b * x ** (a - 1) * (1 - x ** a) ** (b - 1)
725+
plt.plot(x, pdf, label=r'$a$ = {}, $b$ = {}'.format(a, b))
726+
plt.xlabel('x', fontsize=12)
727+
plt.ylabel('f(x)', fontsize=12)
728+
plt.ylim(0, 3.)
729+
plt.legend(loc=9)
730+
plt.show()
731+
732+
======== ==============================================================
733+
Support :math:`x \in (0, 1)`
734+
Mean :math:`b B(1 + \tfrac{1}{a}, b)`
735+
Variance :math:`b B(1 + \tfrac{2}{a}, b) - (b B(1 + \tfrac{1}{a}, b))^2`
736+
======== ==============================================================
737+
738+
Parameters
739+
----------
740+
a : float
741+
a > 0.
742+
b : float
743+
b > 0.
744+
"""
745+
746+
def __init__(self, a, b, *args, **kwargs):
747+
super(Kumaraswamy, self).__init__(*args, **kwargs)
748+
749+
self.a = a = tt.as_tensor_variable(a)
750+
self.b = b = tt.as_tensor_variable(b)
751+
752+
ln_mean = tt.log(b) + tt.gammaln(1 + 1 / a) + tt.gammaln(b) - tt.gammaln(1 + 1 / a + b)
753+
self.mean = tt.exp(ln_mean)
754+
ln_2nd_raw_moment = tt.log(b) + tt.gammaln(1 + 2 / a) + tt.gammaln(b) - tt.gammaln(1 + 2 / a + b)
755+
self.variance = tt.exp(ln_2nd_raw_moment) - self.mean ** 2
756+
757+
assert_negative_support(a, 'a', 'Kumaraswamy')
758+
assert_negative_support(b, 'b', 'Kumaraswamy')
759+
760+
def _random(self, a, b, size=None):
761+
u = np.random.uniform(size=size)
762+
return (1 - (1 - u) ** (1 / b)) ** (1 / a)
763+
764+
def random(self, point=None, size=None):
765+
a, b = draw_values([self.a, self.b],
766+
point=point, size=size)
767+
return generate_samples(self._random, a, b,
768+
dist_shape=self.shape,
769+
size=size)
770+
771+
def logp(self, value):
772+
a = self.a
773+
b = self.b
774+
775+
logp = tt.log(a) + tt.log(b) + (a - 1) * tt.log(value) + (b - 1) * tt.log(1 - value ** a)
776+
777+
return bound(logp,
778+
value >= 0, value <= 1,
779+
a > 0, b > 0)
780+
781+
def _repr_latex_(self, name=None, dist=None):
782+
if dist is None:
783+
dist = self
784+
a = dist.a
785+
b = dist.b
786+
name = r'\text{%s}' % name
787+
return r'${} \sim \text{{Kumaraswamy}}(\mathit{{a}}={},~\mathit{{b}}={})$'.format(name,
788+
get_variable_name(a),
789+
get_variable_name(b))
704790

705791
class Exponential(PositiveContinuous):
706792
R"""

pymc3/tests/test_distributions.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
Flat, LKJCorr, Wald, ChiSquared, HalfNormal, DiscreteUniform,
1717
Bound, Uniform, Triangular, Binomial, SkewNormal, DiscreteWeibull,
1818
Gumbel, Logistic, OrderedLogistic, LogitNormal, Interpolated,
19-
ZeroInflatedBinomial, HalfFlat, AR1, KroneckerNormal, Rice)
19+
ZeroInflatedBinomial, HalfFlat, AR1, KroneckerNormal, Rice,
20+
Kumaraswamy)
2021

2122
from ..distributions import continuous
2223
from pymc3.theanof import floatX
@@ -580,6 +581,12 @@ def test_beta(self):
580581
lambda value, alpha, beta: sp.beta.logpdf(value, alpha, beta))
581582
self.pymc3_matches_scipy(Beta, Unit, {'mu': Unit, 'sd': Rplus}, beta_mu_sd)
582583

584+
def test_kumaraswamy(self):
585+
# Scipy does not have a built-in Kumaraswamy pdf
586+
def scipy_log_pdf(value, a, b):
587+
return np.log(a) + np.log(b) + (a - 1) * np.log(value) + (b - 1) * np.log(1 - value ** a)
588+
self.pymc3_matches_scipy(Kumaraswamy, Unit, {'a': Rplus, 'b': Rplus}, scipy_log_pdf)
589+
583590
def test_exponential(self):
584591
self.pymc3_matches_scipy(Exponential, Rplus, {'lam': Rplus},
585592
lambda value, lam: sp.expon.logpdf(value, 0, 1 / lam))

pymc3/tests/test_distributions_random.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,11 @@ class TestBeta(BaseTestCases.BaseTestCase):
230230
params = {'alpha': 1., 'beta': 1.}
231231

232232

233+
class TestKumaraswamy(BaseTestCases.BaseTestCase):
234+
distribution = pm.Kumaraswamy
235+
params = {'a': 1., 'b': 1.}
236+
237+
233238
class TestExponential(BaseTestCases.BaseTestCase):
234239
distribution = pm.Exponential
235240
params = {'lam': 1.}

0 commit comments

Comments
 (0)