• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pymc-devs / pymc3 / 9346

pending completion
9346

Pull #3611

travis-ci

web-flow
Add pandas_to_array tests for cast and no cast
Pull Request #3611: Do not cast the content of pm.Data to float inappropriately

45 of 45 new or added lines in 3 files covered. (100.0%)

17603 of 19757 relevant lines covered (89.1%)

3.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.35
/pymc3/distributions/continuous.py
1
# coding: utf-8
2
"""
8✔
3
A collection of common probability distributions for stochastic
4
nodes in PyMC.
5
"""
6
import numpy as np
8✔
7
import theano.tensor as tt
8✔
8
from scipy import stats
8✔
9
from scipy.special import expit
8✔
10
from scipy.interpolate import InterpolatedUnivariateSpline
8✔
11
import warnings
8✔
12

13
from pymc3.theanof import floatX
8✔
14
from . import transforms
8✔
15
from pymc3.util import get_variable_name
8✔
16
from .special import log_i0
8✔
17
from ..math import invlogit, logit, logdiffexp
8✔
18
from .dist_math import (
8✔
19
    alltrue_elemwise, betaln, bound, gammaln, i0e, incomplete_beta, logpow,
20
    normal_lccdf, normal_lcdf, SplineWrapper, std_cdf, zvalue,
21
)
22
from .distribution import (Continuous, draw_values, generate_samples)
8✔
23

24
__all__ = ['Uniform', 'Flat', 'HalfFlat', 'Normal', 'TruncatedNormal', 'Beta',
8✔
25
           'Kumaraswamy', 'Exponential', 'Laplace', 'StudentT', 'Cauchy',
26
           'HalfCauchy', 'Gamma', 'Weibull', 'HalfStudentT', 'Lognormal',
27
           'ChiSquared', 'HalfNormal', 'Wald', 'Pareto', 'InverseGamma',
28
           'ExGaussian', 'VonMises', 'SkewNormal', 'Triangular', 'Gumbel',
29
           'Logistic', 'LogitNormal', 'Interpolated', 'Rice']
30

31

32
class PositiveContinuous(Continuous):
8✔
33
    """Base class for positive continuous distributions"""
34

35
    def __init__(self, transform=transforms.log, *args, **kwargs):
8✔
36
        super().__init__(transform=transform, *args, **kwargs)
8✔
37

38

39
class UnitContinuous(Continuous):
8✔
40
    """Base class for continuous distributions on [0,1]"""
41

42
    def __init__(self, transform=transforms.logodds, *args, **kwargs):
8✔
43
        super().__init__(transform=transform, *args, **kwargs)
8✔
44

45

46
class BoundedContinuous(Continuous):
8✔
47
    """Base class for bounded continuous distributions"""
48

49
    def __init__(self, transform='auto', lower=None, upper=None,
8✔
50
                 *args, **kwargs):
51

52
        lower = tt.as_tensor_variable(lower) if lower is not None else None
6✔
53
        upper = tt.as_tensor_variable(upper) if upper is not None else None
6✔
54

55
        if transform == 'auto':
6✔
56
            if lower is None and upper is None:
6✔
57
                transform = None
×
58
            elif lower is not None and upper is None:
6✔
59
                transform = transforms.lowerbound(lower)
×
60
            elif lower is None and upper is not None:
6✔
61
                transform = transforms.upperbound(upper)
×
62
            else:
63
                transform = transforms.interval(lower, upper)
6✔
64

65
        super().__init__(transform=transform, *args, **kwargs)
6✔
66

67

68
def assert_negative_support(var, label, distname, value=-1e-6):
8✔
69
    # Checks for evidence of positive support for a variable
70
    if var is None:
8✔
71
        return
×
72
    try:
8✔
73
        # Transformed distribution
74
        support = np.isfinite(var.transformed.distribution.dist
8✔
75
                              .logp(value).tag.test_value)
76
    except AttributeError:
8✔
77
        try:
8✔
78
            # Untransformed distribution
79
            support = np.isfinite(var.distribution.logp(value).tag.test_value)
8✔
80
        except AttributeError:
8✔
81
            # Otherwise no direct evidence of non-positive support
82
            support = False
8✔
83

84
    if np.any(support):
8✔
85
        msg = "The variable specified for {0} has negative support for {1}, ".format(
×
86
            label, distname)
87
        msg += "likely making it unsuitable for this parameter."
×
88
        warnings.warn(msg)
×
89

90

91
def get_tau_sigma(tau=None, sigma=None):
8✔
92
    """
93
    Find precision and standard deviation. The link between the two
94
    parameterizations is given by the inverse relationship:
95

96
    .. math::
97
        \tau = \frac{1}{\sigma^2}
98

99
    Parameters
100
    ----------
101
    tau : array-like, optional
102
    sigma : array-like, optional
103

104
    Results
105
    -------
106
    Returns tuple (tau, sigma)
107

108
    Notes
109
    -----
110
    If neither tau nor sigma is provided, returns (1., 1.)
111
    """
112
    if tau is None:
8✔
113
        if sigma is None:
8✔
114
            sigma = 1.
6✔
115
            tau = 1.
6✔
116
        else:
117
            tau = sigma**-2.
8✔
118

119
    else:
120
        if sigma is not None:
8✔
121
            raise ValueError("Can't pass both tau and sigma")
×
122
        else:
123
            sigma = tau**-.5
8✔
124

125
    # cast tau and sigma to float in a way that works for both np.arrays
126
    # and pure python
127
    tau = 1. * tau
8✔
128
    sigma = 1. * sigma
8✔
129

130
    return floatX(tau), floatX(sigma)
8✔
131

132

133
class Uniform(BoundedContinuous):
8✔
134
    R"""
135
    Continuous uniform log-likelihood.
136

137
    The pdf of this distribution is
138

139
    .. math::
140

141
       f(x \mid lower, upper) = \frac{1}{upper-lower}
142

143
    .. plot::
144

145
        import matplotlib.pyplot as plt
146
        import numpy as np
147
        plt.style.use('seaborn-darkgrid')
148
        x = np.linspace(-3, 3, 500)
149
        ls = [0., -2]
150
        us = [2., 1]
151
        for l, u in zip(ls, us):
152
            y = np.zeros(500)
153
            y[(x<u) & (x>l)] = 1.0/(u-l)
154
            plt.plot(x, y, label='lower = {}, upper = {}'.format(l, u))
155
        plt.xlabel('x', fontsize=12)
156
        plt.ylabel('f(x)', fontsize=12)
157
        plt.ylim(0, 1)
158
        plt.legend(loc=1)
159
        plt.show()
160

161
    ========  =====================================
162
    Support   :math:`x \in [lower, upper]`
163
    Mean      :math:`\dfrac{lower + upper}{2}`
164
    Variance  :math:`\dfrac{(upper - lower)^2}{12}`
165
    ========  =====================================
166

167
    Parameters
168
    ----------
169
    lower : float
170
        Lower limit.
171
    upper : float
172
        Upper limit.
173
    """
174

175
    def __init__(self, lower=0, upper=1, *args, **kwargs):
8✔
176
        self.lower = lower = tt.as_tensor_variable(floatX(lower))
6✔
177
        self.upper = upper = tt.as_tensor_variable(floatX(upper))
6✔
178
        self.mean = (upper + lower) / 2.
6✔
179
        self.median = self.mean
6✔
180

181
        super().__init__(lower=lower, upper=upper, *args, **kwargs)
6✔
182

183
    def random(self, point=None, size=None):
8✔
184
        """
185
        Draw random values from Uniform distribution.
186

187
        Parameters
188
        ----------
189
        point : dict, optional
190
            Dict of variable values on which random values are to be
191
            conditioned (uses default point if not specified).
192
        size : int, optional
193
            Desired size of random sample (returns one sample if not
194
            specified).
195

196
        Returns
197
        -------
198
        array
199
        """
200

201
        lower, upper = draw_values([self.lower, self.upper],
3✔
202
                                   point=point, size=size)
203
        return generate_samples(stats.uniform.rvs, loc=lower,
3✔
204
                                scale=upper - lower,
205
                                dist_shape=self.shape,
206
                                size=size)
207

208
    def logp(self, value):
8✔
209
        """
210
        Calculate log-probability of Uniform distribution at specified value.
211

212
        Parameters
213
        ----------
214
        value : numeric
215
            Value for which log-probability is calculated.
216

217
        Returns
218
        -------
219
        TensorVariable
220
        """
221
        lower = self.lower
6✔
222
        upper = self.upper
6✔
223
        return bound(-tt.log(upper - lower),
6✔
224
                     value >= lower, value <= upper)
225

226
    def _repr_latex_(self, name=None, dist=None):
8✔
227
        if dist is None:
×
228
            dist = self
×
229
        lower = dist.lower
×
230
        upper = dist.upper
×
231
        name = r'\text{%s}' % name
×
232
        return r'${} \sim \text{{Uniform}}(\mathit{{lower}}={},~\mathit{{upper}}={})$'.format(
×
233
            name, get_variable_name(lower), get_variable_name(upper))
234

235
    def logcdf(self, value):
8✔
236
        """
237
        Compute the log of the cumulative distribution function for Uniform distribution
238
        at the specified value.
239

240
        Parameters
241
        ----------
242
        value: numeric
243
            Value(s) for which log CDF is calculated. If the log CDF for multiple
244
            values are desired the values must be provided in a numpy array or theano tensor.
245

246
        Returns
247
        -------
248
        TensorVariable
249
        """
250
        return tt.switch(
1✔
251
            tt.or_(tt.lt(value, self.lower), tt.gt(value, self.upper)),
252
            -np.inf,
253
            tt.switch(
254
                tt.eq(value, self.upper),
255
                0,
256
                tt.log((value - self.lower)) -
257
                tt.log((self.upper - self.lower))
258
            )
259
        )
260

261

262
class Flat(Continuous):
8✔
263
    """
264
    Uninformative log-likelihood that returns 0 regardless of
265
    the passed value.
266
    """
267

268
    def __init__(self, *args, **kwargs):
8✔
269
        self._default = 0
8✔
270
        super().__init__(defaults=('_default',), *args, **kwargs)
8✔
271

272
    def random(self, point=None, size=None):
8✔
273
        """Raises ValueError as it is not possible to sample from Flat distribution
274

275
        Parameters
276
        ----------
277
        point : dict, optional
278
        size : int, optional
279

280
        Raises
281
        -------
282
        ValueError
283
        """
284
        raise ValueError('Cannot sample from Flat distribution')
2✔
285

286
    def logp(self, value):
8✔
287
        """
288
        Calculate log-probability of Flat distribution at specified value.
289

290
        Parameters
291
        ----------
292
        value : numeric
293
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
294
            values are desired the values must be provided in a numpy array or theano tensor
295

296
        Returns
297
        -------
298
        TensorVariable
299
        """
300
        return tt.zeros_like(value)
5✔
301

302
    def _repr_latex_(self, name=None, dist=None):
8✔
303
        name = r'\text{%s}' % name
×
304
        return r'${} \sim \text{{Flat}}()$'.format(name)
×
305

306
    def logcdf(self, value):
8✔
307
        """
308
        Compute the log of the cumulative distribution function for Flat distribution
309
        at the specified value.
310

311
        Parameters
312
        ----------
313
        value: numeric
314
            Value(s) for which log CDF is calculated. If the log CDF for multiple
315
            values are desired the values must be provided in a numpy array or theano tensor.
316

317
        Returns
318
        -------
319
        TensorVariable
320
        """
321
        return tt.switch(
1✔
322
            tt.eq(value, -np.inf),
323
            -np.inf,
324
            tt.switch(
325
                tt.eq(value, np.inf),
326
                0,
327
                tt.log(0.5)
328
            )
329
        )
330

331

332
class HalfFlat(PositiveContinuous):
8✔
333
    """Improper flat prior over the positive reals."""
334

335
    def __init__(self, *args, **kwargs):
8✔
336
        self._default = 1
4✔
337
        super().__init__(defaults=('_default',), *args, **kwargs)
4✔
338

339
    def random(self, point=None, size=None):
8✔
340
        """Raises ValueError as it is not possible to sample from HalfFlat distribution
341

342
        Parameters
343
        ----------
344
        point : dict, optional
345
        size : int, optional
346

347
        Raises
348
        -------
349
        ValueError
350
        """
351
        raise ValueError('Cannot sample from HalfFlat distribution')
3✔
352

353
    def logp(self, value):
8✔
354
        """
355
        Calculate log-probability of HalfFlat distribution at specified value.
356

357
        Parameters
358
        ----------
359
        value : numeric
360
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
361
            values are desired the values must be provided in a numpy array or theano tensor
362

363
        Returns
364
        -------
365
        TensorVariable
366
        """
367
        return bound(tt.zeros_like(value), value > 0)
4✔
368

369
    def _repr_latex_(self, name=None, dist=None):
8✔
370
        name = r'\text{%s}' % name
×
371
        return r'${} \sim \text{{HalfFlat}}()$'.format(name)
×
372

373
    def logcdf(self, value):
8✔
374
        """
375
        Compute the log of the cumulative distribution function for HalfFlat distribution
376
        at the specified value.
377

378
        Parameters
379
        ----------
380
        value: numeric
381
            Value(s) for which log CDF is calculated. If the log CDF for multiple
382
            values are desired the values must be provided in a numpy array or theano tensor.
383

384
        Returns
385
        -------
386
        TensorVariable
387
        """
388
        return tt.switch(
1✔
389
            tt.lt(value, np.inf),
390
            -np.inf,
391
            tt.switch(
392
                tt.eq(value, np.inf),
393
                0,
394
                -np.inf
395
            )
396
        )
397

398

399
class Normal(Continuous):
8✔
400
    R"""
401
    Univariate normal log-likelihood.
402

403
    The pdf of this distribution is
404

405
    .. math::
406

407
       f(x \mid \mu, \tau) =
408
           \sqrt{\frac{\tau}{2\pi}}
409
           \exp\left\{ -\frac{\tau}{2} (x-\mu)^2 \right\}
410

411
    Normal distribution can be parameterized either in terms of precision
412
    or standard deviation. The link between the two parametrizations is
413
    given by
414

415
    .. math::
416

417
       \tau = \dfrac{1}{\sigma^2}
418

419
    .. plot::
420

421
        import matplotlib.pyplot as plt
422
        import numpy as np
423
        import scipy.stats as st
424
        plt.style.use('seaborn-darkgrid')
425
        x = np.linspace(-5, 5, 1000)
426
        mus = [0., 0., 0., -2.]
427
        sigmas = [0.4, 1., 2., 0.4]
428
        for mu, sigma in zip(mus, sigmas):
429
            pdf = st.norm.pdf(x, mu, sigma)
430
            plt.plot(x, pdf, label=r'$\mu$ = {}, $\sigma$ = {}'.format(mu, sigma))
431
        plt.xlabel('x', fontsize=12)
432
        plt.ylabel('f(x)', fontsize=12)
433
        plt.legend(loc=1)
434
        plt.show()
435

436
    ========  ==========================================
437
    Support   :math:`x \in \mathbb{R}`
438
    Mean      :math:`\mu`
439
    Variance  :math:`\dfrac{1}{\tau}` or :math:`\sigma^2`
440
    ========  ==========================================
441

442
    Parameters
443
    ----------
444
    mu : float
445
        Mean.
446
    sigma : float
447
        Standard deviation (sigma > 0) (only required if tau is not specified).
448
    tau : float
449
        Precision (tau > 0) (only required if sigma is not specified).
450

451
    Examples
452
    --------
453
    .. code-block:: python
454

455
        with pm.Model():
456
            x = pm.Normal('x', mu=0, sigma=10)
457

458
        with pm.Model():
459
            x = pm.Normal('x', mu=0, tau=1/23)
460
    """
461

462
    def __init__(self, mu=0, sigma=None, tau=None, sd=None, **kwargs):
8✔
463
        if sd is not None:
8✔
464
            sigma = sd
6✔
465
        tau, sigma = get_tau_sigma(tau=tau, sigma=sigma)
8✔
466
        self.sigma = self.sd = tt.as_tensor_variable(sigma)
8✔
467
        self.tau = tt.as_tensor_variable(tau)
8✔
468

469
        self.mean = self.median = self.mode = self.mu = mu = tt.as_tensor_variable(floatX(mu))
8✔
470
        self.variance = 1. / self.tau
8✔
471

472
        assert_negative_support(sigma, 'sigma', 'Normal')
8✔
473
        assert_negative_support(tau, 'tau', 'Normal')
8✔
474

475
        super().__init__(**kwargs)
8✔
476

477
    def random(self, point=None, size=None):
8✔
478
        """
479
        Draw random values from Normal distribution.
480

481
        Parameters
482
        ----------
483
        point : dict, optional
484
            Dict of variable values on which random values are to be
485
            conditioned (uses default point if not specified).
486
        size : int, optional
487
            Desired size of random sample (returns one sample if not
488
            specified).
489

490
        Returns
491
        -------
492
        array
493
        """
494
        mu, tau, _ = draw_values([self.mu, self.tau, self.sigma],
6✔
495
                                 point=point, size=size)
496
        return generate_samples(stats.norm.rvs, loc=mu, scale=tau**-0.5,
6✔
497
                                dist_shape=self.shape,
498
                                size=size)
499

500
    def logp(self, value):
8✔
501
        """
502
        Calculate log-probability of Normal distribution at specified value.
503

504
        Parameters
505
        ----------
506
        value : numeric
507
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
508
            values are desired the values must be provided in a numpy array or theano tensor
509

510
        Returns
511
        -------
512
        TensorVariable
513
        """
514
        sigma = self.sigma
8✔
515
        tau = self.tau
8✔
516
        mu = self.mu
8✔
517

518
        return bound((-tau * (value - mu)**2 + tt.log(tau / np.pi / 2.)) / 2.,
8✔
519
                     sigma > 0)
520

521
    def _repr_latex_(self, name=None, dist=None):
8✔
522
        if dist is None:
1✔
523
            dist = self
×
524
        sigma = dist.sigma
1✔
525
        mu = dist.mu
1✔
526
        name = r'\text{%s}' % name
1✔
527
        return r'${} \sim \text{{Normal}}(\mathit{{mu}}={},~\mathit{{sigma}}={})$'.format(name,
1✔
528
                                                                get_variable_name(mu),
529
                                                                get_variable_name(sigma))
530

531
    def logcdf(self, value):
8✔
532
        """
533
        Compute the log of the cumulative distribution function for Normal distribution
534
        at the specified value.
535

536
        Parameters
537
        ----------
538
        value: numeric
539
            Value(s) for which log CDF is calculated. If the log CDF for multiple
540
            values are desired the values must be provided in a numpy array or theano tensor.
541

542
        Returns
543
        -------
544
        TensorVariable
545
        """
546
        return normal_lcdf(self.mu, self.sigma, value)
1✔
547

548

549
class TruncatedNormal(BoundedContinuous):
8✔
550
    R"""
551
    Univariate truncated normal log-likelihood.
552

553
    The pdf of this distribution is
554

555
    .. math::
556

557
       f(x;\mu ,\sigma ,a,b)={\frac {\phi ({\frac {x-\mu }{\sigma }})}{
558
       \sigma \left(\Phi ({\frac {b-\mu }{\sigma }})-\Phi ({\frac {a-\mu }{\sigma }})\right)}}
559

560
    Truncated normal distribution can be parameterized either in terms of precision
561
    or standard deviation. The link between the two parametrizations is
562
    given by
563

564
    .. math::
565

566
       \tau = \dfrac{1}{\sigma^2}
567

568

569
    .. plot::
570

571
        import matplotlib.pyplot as plt
572
        import numpy as np
573
        import scipy.stats as st
574
        plt.style.use('seaborn-darkgrid')
575
        x = np.linspace(-10, 10, 1000)
576
        mus = [0.,  0., 0.]
577
        sigmas = [3.,5.,7.]
578
        a1 = [-3, -5, -5]
579
        b1 = [7, 5, 4]
580
        for mu, sigma, a, b in zip(mus, sigmas,a1,b1):
581
            an, bn = (a - mu) / sigma, (b - mu) / sigma
582
            pdf = st.truncnorm.pdf(x, an,bn, loc=mu, scale=sigma)
583
            plt.plot(x, pdf, label=r'$\mu$ = {}, $\sigma$ = {}, a={}, b={}'.format(mu, sigma, a, b))
584
        plt.xlabel('x', fontsize=12)
585
        plt.ylabel('f(x)', fontsize=12)
586
        plt.legend(loc=1)
587
        plt.show()
588

589
    ========  ==========================================
590
    Support   :math:`x \in [a, b]`
591
    Mean      :math:`\mu +{\frac {\phi (\alpha )-\phi (\beta )}{Z}}\sigma`
592
    Variance  :math:`\sigma ^{2}\left[1+{\frac {\alpha \phi (\alpha )-\beta \phi (\beta )}{Z}}-\left({\frac {\phi (\alpha )-\phi (\beta )}{Z}}\right)^{2}\right]`
593
    ========  ==========================================
594

595
    Parameters
596
    ----------
597
    mu : float
598
        Mean.
599
    sigma : float
600
        Standard deviation (sigma > 0).
601
    lower : float (optional)
602
        Left bound.
603
    upper : float (optional)
604
        Right bound.
605

606
    Examples
607
    --------
608
    .. code-block:: python
609

610
        with pm.Model():
611
            x = pm.TruncatedNormal('x', mu=0, sigma=10, lower=0)
612

613
        with pm.Model():
614
            x = pm.TruncatedNormal('x', mu=0, sigma=10, upper=1)
615

616
        with pm.Model():
617
            x = pm.TruncatedNormal('x', mu=0, sigma=10, lower=0, upper=1)
618

619
    """
620

621
    def __init__(self, mu=0, sigma=None, tau=None, lower=None, upper=None,
8✔
622
                 transform='auto', sd=None, *args, **kwargs):
623
        if sd is not None:
3✔
624
            sigma = sd
×
625
        tau, sigma = get_tau_sigma(tau=tau, sigma=sigma)
3✔
626
        self.sigma = self.sd = tt.as_tensor_variable(sigma)
3✔
627
        self.tau = tt.as_tensor_variable(tau)
3✔
628
        self.lower_check = tt.as_tensor_variable(floatX(lower)) if lower is not None else lower
3✔
629
        self.upper_check = tt.as_tensor_variable(floatX(upper)) if upper is not None else upper
3✔
630
        self.lower = tt.as_tensor_variable(floatX(lower)) if lower is not None else tt.as_tensor_variable(-np.inf)
3✔
631
        self.upper = tt.as_tensor_variable(floatX(upper)) if upper is not None else tt.as_tensor_variable(np.inf)
3✔
632
        self.mu = tt.as_tensor_variable(floatX(mu))
3✔
633

634
        if self.lower_check is None and self.upper_check is None:
3✔
635
            self._defaultval = mu
×
636
        elif self.lower_check is None and self.upper_check is not None:
3✔
637
            self._defaultval = self.upper - 1.
2✔
638
        elif self.lower_check is not None and self.upper_check is None:
3✔
639
            self._defaultval = self.lower + 1.
2✔
640
        else:
641
            self._defaultval = (self.lower + self.upper) / 2
3✔
642

643
        assert_negative_support(sigma, 'sigma', 'TruncatedNormal')
3✔
644
        assert_negative_support(tau, 'tau', 'TruncatedNormal')
3✔
645

646
        super().__init__(defaults=('_defaultval',), transform=transform,
3✔
647
                         lower=lower, upper=upper, *args, **kwargs)
648

649
    def random(self, point=None, size=None):
8✔
650
        """
651
        Draw random values from TruncatedNormal distribution.
652

653
        Parameters
654
        ----------
655
        point : dict, optional
656
            Dict of variable values on which random values are to be
657
            conditioned (uses default point if not specified).
658
        size : int, optional
659
            Desired size of random sample (returns one sample if not
660
            specified).
661

662
        Returns
663
        -------
664
        array
665
        """
666
        mu, sigma, lower, upper = draw_values(
2✔
667
            [self.mu, self.sigma, self.lower, self.upper],
668
            point=point,
669
            size=size
670
        )
671
        return generate_samples(
2✔
672
            self._random,
673
            mu=mu,
674
            sigma=sigma,
675
            lower=lower,
676
            upper=upper,
677
            dist_shape=self.shape,
678
            size=size,
679
        )
680

681
    def _random(self, mu, sigma, lower, upper, size):
8✔
682
        """ Wrapper around stats.truncnorm.rvs that converts TruncatedNormal's
683
        parametrization to scipy.truncnorm. All parameter arrays should have
684
        been broadcasted properly by generate_samples at this point and size is
685
        the scipy.rvs representation.
686
        """
687
        return stats.truncnorm.rvs(
2✔
688
            a=(lower - mu) / sigma,
689
            b=(upper - mu) / sigma,
690
            loc=mu,
691
            scale=sigma,
692
            size=size,
693
        )
694

695
    def logp(self, value):
8✔
696
        """
697
        Calculate log-probability of TruncatedNormal distribution at specified value.
698

699
        Parameters
700
        ----------
701
        value : numeric
702
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
703
            values are desired the values must be provided in a numpy array or theano tensor
704

705
        Returns
706
        -------
707
        TensorVariable
708
        """
709
        mu = self.mu
3✔
710
        sigma = self.sigma
3✔
711

712
        norm = self._normalization()
3✔
713
        logp = Normal.dist(mu=mu, sigma=sigma).logp(value) - norm
3✔
714

715
        bounds = [sigma > 0]
3✔
716
        if self.lower_check is not None:
3✔
717
            bounds.append(value >= self.lower)
3✔
718
        if self.upper_check is not None:
3✔
719
            bounds.append(value <= self.upper)
3✔
720
        return bound(logp, *bounds)
3✔
721

722
    def _normalization(self):
8✔
723
        mu, sigma = self.mu, self.sigma
3✔
724

725
        if self.lower_check is None and self.upper_check is None:
3✔
726
            return 0.
×
727

728
        if self.lower_check is not None and self.upper_check is not None:
3✔
729
            lcdf_a = normal_lcdf(mu, sigma, self.lower)
3✔
730
            lcdf_b = normal_lcdf(mu, sigma, self.upper)
3✔
731
            lsf_a = normal_lccdf(mu, sigma, self.lower)
3✔
732
            lsf_b = normal_lccdf(mu, sigma, self.upper)
3✔
733

734
            return tt.switch(
3✔
735
                self.lower > 0,
736
                logdiffexp(lsf_a, lsf_b),
737
                logdiffexp(lcdf_b, lcdf_a),
738
            )
739

740
        if self.lower_check is not None:
2✔
741
            return normal_lccdf(mu, sigma, self.lower)
2✔
742
        else:
743
            return normal_lcdf(mu, sigma, self.upper)
2✔
744

745
    def _repr_latex_(self, name=None, dist=None):
8✔
746
        if dist is None:
×
747
            dist = self
×
748
        name = r'\text{%s}' % name
×
749
        return (
×
750
            r'${} \sim \text{{TruncatedNormal}}('
751
            '\mathit{{mu}}={},~\mathit{{sigma}}={},a={},b={})$'
752
            .format(
753
                name,
754
                get_variable_name(self.mu),
755
                get_variable_name(self.sigma),
756
                get_variable_name(self.lower),
757
                get_variable_name(self.upper),
758
            )
759
        )
760

761

762
class HalfNormal(PositiveContinuous):
8✔
763
    R"""
764
    Half-normal log-likelihood.
765

766
    The pdf of this distribution is
767

768
    .. math::
769

770
       f(x \mid \tau) =
771
           \sqrt{\frac{2\tau}{\pi}}
772
           \exp\left(\frac{-x^2 \tau}{2}\right)
773

774
       f(x \mid \sigma) =
775
           \sqrt{\frac{2}{\pi\sigma^2}}
776
           \exp\left(\frac{-x^2}{2\sigma^2}\right)
777

778
    .. note::
779

780
       The parameters ``sigma``/``tau`` (:math:`\sigma`/:math:`\tau`) refer to
781
       the standard deviation/precision of the unfolded normal distribution, for
782
       the standard deviation of the half-normal distribution, see below. For
783
       the half-normal, they are just two parameterisation :math:`\sigma^2
784
       \equiv \frac{1}{\tau}` of a scale parameter
785

786
    .. plot::
787

788
        import matplotlib.pyplot as plt
789
        import numpy as np
790
        import scipy.stats as st
791
        plt.style.use('seaborn-darkgrid')
792
        x = np.linspace(0, 5, 200)
793
        for sigma in [0.4, 1., 2.]:
794
            pdf = st.halfnorm.pdf(x, scale=sigma)
795
            plt.plot(x, pdf, label=r'$\sigma$ = {}'.format(sigma))
796
        plt.xlabel('x', fontsize=12)
797
        plt.ylabel('f(x)', fontsize=12)
798
        plt.legend(loc=1)
799
        plt.show()
800

801
    ========  ==========================================
802
    Support   :math:`x \in [0, \infty)`
803
    Mean      :math:`\sqrt{\dfrac{2}{\tau \pi}}` or :math:`\dfrac{\sigma \sqrt{2}}{\sqrt{\pi}}`
804
    Variance  :math:`\dfrac{1}{\tau}\left(1 - \dfrac{2}{\pi}\right)` or :math:`\sigma^2\left(1 - \dfrac{2}{\pi}\right)`
805
    ========  ==========================================
806

807
    Parameters
808
    ----------
809
    sigma : float
810
        Scale parameter :math:`sigma` (``sigma`` > 0) (only required if ``tau`` is not specified).
811
    tau : float
812
        Precision :math:`tau` (tau > 0) (only required if sigma is not specified).
813

814
    Examples
815
    --------
816
    .. code-block:: python
817

818
        with pm.Model():
819
            x = pm.HalfNormal('x', sigma=10)
820

821
        with pm.Model():
822
            x = pm.HalfNormal('x', tau=1/15)
823
    """
824

825
    def __init__(self, sigma=None, tau=None, sd=None, *args, **kwargs):
8✔
826
        if sd is not None:
6✔
827
            sigma = sd
4✔
828

829
        super().__init__(*args, **kwargs)
6✔
830
        tau, sigma = get_tau_sigma(tau=tau, sigma=sigma)
6✔
831

832
        self.sigma = self.sd = sigma = tt.as_tensor_variable(sigma)
6✔
833
        self.tau = tau = tt.as_tensor_variable(tau)
6✔
834

835
        self.mean = tt.sqrt(2 / (np.pi * self.tau))
6✔
836
        self.variance = (1. - 2 / np.pi) / self.tau
6✔
837

838
        assert_negative_support(tau, 'tau', 'HalfNormal')
6✔
839
        assert_negative_support(sigma, 'sigma', 'HalfNormal')
6✔
840

841
    def random(self, point=None, size=None):
8✔
842
        """
843
        Draw random values from HalfNormal distribution.
844

845
        Parameters
846
        ----------
847
        point : dict, optional
848
            Dict of variable values on which random values are to be
849
            conditioned (uses default point if not specified).
850
        size : int, optional
851
            Desired size of random sample (returns one sample if not
852
            specified).
853

854
        Returns
855
        -------
856
        array
857
        """
858
        sigma = draw_values([self.sigma], point=point)[0]
6✔
859
        return generate_samples(stats.halfnorm.rvs, loc=0., scale=sigma,
6✔
860
                                dist_shape=self.shape,
861
                                size=size)
862

863
    def logp(self, value):
8✔
864
        """
865
        Calculate log-probability of HalfNormal distribution at specified value.
866

867
        Parameters
868
        ----------
869
        value : numeric
870
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
871
            values are desired the values must be provided in a numpy array or theano tensor
872

873
        Returns
874
        -------
875
        TensorVariable
876
        """
877
        tau = self.tau
6✔
878
        sigma = self.sigma
6✔
879
        return bound(-0.5 * tau * value**2 + 0.5 * tt.log(tau * 2. / np.pi),
6✔
880
                     value >= 0,
881
                     tau > 0, sigma > 0)
882

883
    def _repr_latex_(self, name=None, dist=None):
8✔
884
        if dist is None:
1✔
885
            dist = self
×
886
        sigma = dist.sigma
1✔
887
        name = r'\text{%s}' % name
1✔
888
        return r'${} \sim \text{{HalfNormal}}(\mathit{{sigma}}={})$'.format(name,
1✔
889
                                                                         get_variable_name(sigma))
890

891
    def logcdf(self, value):
8✔
892
        """
893
        Compute the log of the cumulative distribution function for HalfNormal distribution
894
        at the specified value.
895

896
        Parameters
897
        ----------
898
        value: numeric
899
            Value(s) for which log CDF is calculated. If the log CDF for multiple
900
            values are desired the values must be provided in a numpy array or theano tensor.
901

902
        Returns
903
        -------
904
        TensorVariable
905
        """
906
        sigma = self.sigma
1✔
907
        z = zvalue(value, mu=0, sigma=sigma)
1✔
908
        return tt.switch(
1✔
909
            tt.lt(z, -1.0),
910
            tt.log(tt.erfcx(-z / tt.sqrt(2.))) - tt.sqr(z),
911
            tt.log1p(-tt.erfc(z / tt.sqrt(2.)))
912
        )
913

914

915
class Wald(PositiveContinuous):
8✔
916
    R"""
917
    Wald log-likelihood.
918

919
    The pdf of this distribution is
920

921
    .. math::
922

923
       f(x \mid \mu, \lambda) =
924
           \left(\frac{\lambda}{2\pi}\right)^{1/2} x^{-3/2}
925
           \exp\left\{
926
               -\frac{\lambda}{2x}\left(\frac{x-\mu}{\mu}\right)^2
927
           \right\}
928

929
    .. plot::
930

931
        import matplotlib.pyplot as plt
932
        import numpy as np
933
        import scipy.stats as st
934
        plt.style.use('seaborn-darkgrid')
935
        x = np.linspace(0, 3, 500)
936
        mus = [1., 1., 1., 3.]
937
        lams = [1., .2, 3., 1.]
938
        for mu, lam in zip(mus, lams):
939
            pdf = st.invgauss.pdf(x, mu/lam, scale=lam)
940
            plt.plot(x, pdf, label=r'$\mu$ = {}, $\lambda$ = {}'.format(mu, lam))
941
        plt.xlabel('x', fontsize=12)
942
        plt.ylabel('f(x)', fontsize=12)
943
        plt.legend(loc=1)
944
        plt.show()
945

946
    ========  =============================
947
    Support   :math:`x \in (0, \infty)`
948
    Mean      :math:`\mu`
949
    Variance  :math:`\dfrac{\mu^3}{\lambda}`
950
    ========  =============================
951

952
    Wald distribution can be parameterized either in terms of lam or phi.
953
    The link between the two parametrizations is given by
954

955
    .. math::
956

957
       \phi = \dfrac{\lambda}{\mu}
958

959
    Parameters
960
    ----------
961
    mu : float, optional
962
        Mean of the distribution (mu > 0).
963
    lam : float, optional
964
        Relative precision (lam > 0).
965
    phi : float, optional
966
        Alternative shape parameter (phi > 0).
967
    alpha : float, optional
968
        Shift/location parameter (alpha >= 0).
969

970
    Notes
971
    -----
972
    To instantiate the distribution specify any of the following
973

974
    - only mu (in this case lam will be 1)
975
    - mu and lam
976
    - mu and phi
977
    - lam and phi
978

979
    References
980
    ----------
981
    .. [Tweedie1957] Tweedie, M. C. K. (1957).
982
       Statistical Properties of Inverse Gaussian Distributions I.
983
       The Annals of Mathematical Statistics, Vol. 28, No. 2, pp. 362-377
984

985
    .. [Michael1976] Michael, J. R., Schucany, W. R. and Hass, R. W. (1976).
986
        Generating Random Variates Using Transformations with Multiple Roots.
987
        The American Statistician, Vol. 30, No. 2, pp. 88-90
988

989
    .. [Giner2016] Göknur Giner, Gordon K. Smyth (2016)
990
       statmod: Probability Calculations for the Inverse Gaussian Distribution
991
    """
992

993
    def __init__(self, mu=None, lam=None, phi=None, alpha=0., *args, **kwargs):
8✔
994
        super().__init__(*args, **kwargs)
3✔
995
        mu, lam, phi = self.get_mu_lam_phi(mu, lam, phi)
3✔
996
        self.alpha = alpha = tt.as_tensor_variable(floatX(alpha))
3✔
997
        self.mu = mu = tt.as_tensor_variable(floatX(mu))
3✔
998
        self.lam = lam = tt.as_tensor_variable(floatX(lam))
3✔
999
        self.phi = phi = tt.as_tensor_variable(floatX(phi))
3✔
1000

1001
        self.mean = self.mu + self.alpha
3✔
1002
        self.mode = self.mu * (tt.sqrt(1. + (1.5 * self.mu / self.lam)**2)
3✔
1003
                               - 1.5 * self.mu / self.lam) + self.alpha
1004
        self.variance = (self.mu**3) / self.lam
3✔
1005

1006
        assert_negative_support(phi, 'phi', 'Wald')
3✔
1007
        assert_negative_support(mu, 'mu', 'Wald')
3✔
1008
        assert_negative_support(lam, 'lam', 'Wald')
3✔
1009

1010
    def get_mu_lam_phi(self, mu, lam, phi):
8✔
1011
        if mu is None:
3✔
1012
            if lam is not None and phi is not None:
×
1013
                return lam / phi, lam, phi
×
1014
        else:
1015
            if lam is None:
3✔
1016
                if phi is None:
1✔
1017
                    return mu, 1., 1. / mu
1✔
1018
                else:
1019
                    return mu, mu * phi, phi
1✔
1020
            else:
1021
                if phi is None:
3✔
1022
                    return mu, lam, lam / mu
3✔
1023

1024
        raise ValueError('Wald distribution must specify either mu only, '
×
1025
                         'mu and lam, mu and phi, or lam and phi.')
1026

1027
    def _random(self, mu, lam, alpha, size=None):
8✔
1028
        v = np.random.normal(size=size)**2
2✔
1029
        value = (mu + (mu**2) * v / (2. * lam) - mu / (2. * lam)
2✔
1030
                 * np.sqrt(4. * mu * lam * v + (mu * v)**2))
1031
        z = np.random.uniform(size=size)
2✔
1032
        i = np.floor(z - mu / (mu + value)) * 2 + 1
2✔
1033
        value = (value**-i) * (mu**(i + 1))
2✔
1034
        return value + alpha
2✔
1035

1036
    def random(self, point=None, size=None):
8✔
1037
        """
1038
        Draw random values from Wald distribution.
1039

1040
        Parameters
1041
        ----------
1042
        point : dict, optional
1043
            Dict of variable values on which random values are to be
1044
            conditioned (uses default point if not specified).
1045
        size : int, optional
1046
            Desired size of random sample (returns one sample if not
1047
            specified).
1048

1049
        Returns
1050
        -------
1051
        array
1052
        """
1053
        mu, lam, alpha = draw_values([self.mu, self.lam, self.alpha],
2✔
1054
                                     point=point, size=size)
1055
        return generate_samples(self._random,
2✔
1056
                                mu, lam, alpha,
1057
                                dist_shape=self.shape,
1058
                                size=size)
1059

1060
    def logp(self, value):
8✔
1061
        """
1062
        Calculate log-probability of Wald distribution at specified value.
1063

1064
        Parameters
1065
        ----------
1066
        value : numeric
1067
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
1068
            values are desired the values must be provided in a numpy array or theano tensor
1069

1070
        Returns
1071
        -------
1072
        TensorVariable
1073
        """
1074
        mu = self.mu
3✔
1075
        lam = self.lam
3✔
1076
        alpha = self.alpha
3✔
1077
        # value *must* be iid. Otherwise this is wrong.
1078
        return bound(logpow(lam / (2. * np.pi), 0.5)
3✔
1079
                     - logpow(value - alpha, 1.5)
1080
                     - (0.5 * lam / (value - alpha)
1081
                        * ((value - alpha - mu) / mu)**2),
1082
                     # XXX these two are redundant. Please, check.
1083
                     value > 0, value - alpha > 0,
1084
                     mu > 0, lam > 0, alpha >= 0)
1085

1086
    def _repr_latex_(self, name=None, dist=None):
8✔
1087
        if dist is None:
×
1088
            dist = self
×
1089
        lam = dist.lam
×
1090
        mu = dist.mu
×
1091
        alpha = dist.alpha
×
1092
        name = r'\text{%s}' % name
×
1093
        return r'${} \sim \text{{Wald}}(\mathit{{mu}}={},~\mathit{{lam}}={},~\mathit{{alpha}}={})$'.format(name,
×
1094
                                                                get_variable_name(mu),
1095
                                                                get_variable_name(lam),
1096
                                                                get_variable_name(alpha))
1097

1098
    def logcdf(self, value):
8✔
1099
        """
1100
        Compute the log of the cumulative distribution function for Wald distribution
1101
        at the specified value.
1102

1103
        Parameters
1104
        ----------
1105
        value: numeric
1106
            Value(s) for which log CDF is calculated. If the log CDF for multiple
1107
            values are desired the values must be provided in a numpy array or theano tensor.
1108

1109
        Returns
1110
        -------
1111
        TensorVariable
1112
        """
1113
        # Distribution parameters
1114
        mu = self.mu
1✔
1115
        lam = self.lam
1✔
1116
        alpha = self.alpha
1✔
1117

1118
        value -= alpha
1✔
1119
        q = value / mu
1✔
1120
        l = lam * mu
1✔
1121
        r = tt.sqrt(value * lam)
1✔
1122

1123
        a = normal_lcdf(0, 1, (q - 1.)/r)
1✔
1124
        b = 2./l + normal_lcdf(0, 1, -(q + 1.)/r)
1✔
1125
        return tt.switch(
1✔
1126
            (
1127
                # Left limit
1128
                tt.lt(value, 0) |
1129
                (tt.eq(value, 0) & tt.gt(mu, 0) & tt.lt(lam, np.inf)) |
1130
                (tt.lt(value, mu) & tt.eq(lam, 0))
1131
            ),
1132
            -np.inf,
1133
            tt.switch(
1134
                (
1135
                    # Right limit
1136
                    tt.eq(value, np.inf) |
1137
                    (tt.eq(lam, 0) & tt.gt(value, mu)) |
1138
                    (tt.gt(value, 0) & tt.eq(lam, np.inf)) |
1139
                    # Degenerate distribution
1140
                    (
1141
                        tt.lt(mu, np.inf) &
1142
                        tt.eq(mu, value) &
1143
                        tt.eq(lam, 0)
1144
                    ) |
1145
                    (tt.eq(value, 0) & tt.eq(lam, np.inf))
1146
                ),
1147
                0,
1148
                a + tt.log1p(tt.exp(b - a))
1149
            )
1150
        )
1151

1152

1153
class Beta(UnitContinuous):
8✔
1154
    R"""
1155
    Beta log-likelihood.
1156

1157
    The pdf of this distribution is
1158

1159
    .. math::
1160

1161
       f(x \mid \alpha, \beta) =
1162
           \frac{x^{\alpha - 1} (1 - x)^{\beta - 1}}{B(\alpha, \beta)}
1163

1164
    .. plot::
1165

1166
        import matplotlib.pyplot as plt
1167
        import numpy as np
1168
        import scipy.stats as st
1169
        plt.style.use('seaborn-darkgrid')
1170
        x = np.linspace(0, 1, 200)
1171
        alphas = [.5, 5., 1., 2., 2.]
1172
        betas = [.5, 1., 3., 2., 5.]
1173
        for a, b in zip(alphas, betas):
1174
            pdf = st.beta.pdf(x, a, b)
1175
            plt.plot(x, pdf, label=r'$\alpha$ = {}, $\beta$ = {}'.format(a, b))
1176
        plt.xlabel('x', fontsize=12)
1177
        plt.ylabel('f(x)', fontsize=12)
1178
        plt.ylim(0, 4.5)
1179
        plt.legend(loc=9)
1180
        plt.show()
1181

1182
    ========  ==============================================================
1183
    Support   :math:`x \in (0, 1)`
1184
    Mean      :math:`\dfrac{\alpha}{\alpha + \beta}`
1185
    Variance  :math:`\dfrac{\alpha \beta}{(\alpha+\beta)^2(\alpha+\beta+1)}`
1186
    ========  ==============================================================
1187

1188
    Beta distribution can be parameterized either in terms of alpha and
1189
    beta or mean and standard deviation. The link between the two
1190
    parametrizations is given by
1191

1192
    .. math::
1193

1194
       \alpha &= \mu \kappa \\
1195
       \beta  &= (1 - \mu) \kappa
1196

1197
       \text{where } \kappa = \frac{\mu(1-\mu)}{\sigma^2} - 1
1198

1199
    Parameters
1200
    ----------
1201
    alpha : float
1202
        alpha > 0.
1203
    beta : float
1204
        beta > 0.
1205
    mu : float
1206
        Alternative mean (0 < mu < 1).
1207
    sigma : float
1208
        Alternative standard deviation (0 < sigma < sqrt(mu * (1 - mu))).
1209

1210
    Notes
1211
    -----
1212
    Beta distribution is a conjugate prior for the parameter :math:`p` of
1213
    the binomial distribution.
1214
    """
1215

1216
    def __init__(self, alpha=None, beta=None, mu=None, sigma=None,
8✔
1217
                 sd=None, *args, **kwargs):
1218
        super().__init__(*args, **kwargs)
8✔
1219
        if sd is not None:
8✔
1220
            sigma = sd
×
1221
        alpha, beta = self.get_alpha_beta(alpha, beta, mu, sigma)
8✔
1222
        self.alpha = alpha = tt.as_tensor_variable(floatX(alpha))
8✔
1223
        self.beta = beta = tt.as_tensor_variable(floatX(beta))
8✔
1224

1225
        self.mean = self.alpha / (self.alpha + self.beta)
8✔
1226
        self.variance = self.alpha * self.beta / (
8✔
1227
            (self.alpha + self.beta)**2 * (self.alpha + self.beta + 1))
1228

1229
        assert_negative_support(alpha, 'alpha', 'Beta')
8✔
1230
        assert_negative_support(beta, 'beta', 'Beta')
8✔
1231

1232
    def get_alpha_beta(self, alpha=None, beta=None, mu=None, sigma=None):
8✔
1233
        if (alpha is not None) and (beta is not None):
8✔
1234
            pass
8✔
1235
        elif (mu is not None) and (sigma is not None):
1✔
1236
            kappa = mu * (1 - mu) / sigma**2 - 1
1✔
1237
            alpha = mu * kappa
1✔
1238
            beta = (1 - mu) * kappa
1✔
1239
        else:
1240
            raise ValueError('Incompatible parameterization. Either use alpha '
×
1241
                             'and beta, or mu and sigma to specify distribution.')
1242

1243
        return alpha, beta
8✔
1244

1245
    def random(self, point=None, size=None):
8✔
1246
        """
1247
        Draw random values from Beta distribution.
1248

1249
        Parameters
1250
        ----------
1251
        point : dict, optional
1252
            Dict of variable values on which random values are to be
1253
            conditioned (uses default point if not specified).
1254
        size : int, optional
1255
            Desired size of random sample (returns one sample if not
1256
            specified).
1257

1258
        Returns
1259
        -------
1260
        array
1261
        """
1262
        alpha, beta = draw_values([self.alpha, self.beta],
3✔
1263
                                  point=point, size=size)
1264
        return generate_samples(stats.beta.rvs, alpha, beta,
3✔
1265
                                dist_shape=self.shape,
1266
                                size=size)
1267

1268
    def logp(self, value):
8✔
1269
        """
1270
        Calculate log-probability of Beta distribution at specified value.
1271

1272
        Parameters
1273
        ----------
1274
        value : numeric
1275
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
1276
            values are desired the values must be provided in a numpy array or theano tensor
1277

1278
        Returns
1279
        -------
1280
        TensorVariable
1281
        """
1282
        alpha = self.alpha
8✔
1283
        beta = self.beta
8✔
1284

1285
        logval = tt.log(value)
8✔
1286
        log1pval = tt.log1p(-value)
8✔
1287
        logp = (tt.switch(tt.eq(alpha, 1), 0, (alpha - 1) * logval)
8✔
1288
                + tt.switch(tt.eq(beta, 1), 0, (beta - 1) * log1pval)
1289
                - betaln(alpha, beta))
1290

1291
        return bound(logp,
8✔
1292
                     value >= 0, value <= 1,
1293
                     alpha > 0, beta > 0)
1294

1295
    def logcdf(self, value):
8✔
1296
        """
1297
        Compute the log of the cumulative distribution function for Beta distribution
1298
        at the specified value.
1299

1300
        Parameters
1301
        ----------
1302
        value: numeric
1303
            Value(s) for which log CDF is calculated. If the log CDF for multiple
1304
            values are desired the values must be provided in a numpy array or theano tensor.
1305

1306
        Returns
1307
        -------
1308
        TensorVariable
1309
        """
1310
        value = floatX(tt.as_tensor(value))
1✔
1311
        a = floatX(tt.as_tensor(self.alpha))
1✔
1312
        b = floatX(tt.as_tensor(self.beta))
1✔
1313
        return tt.switch(
1✔
1314
            tt.le(value, 0),
1315
            -np.inf,
1316
            tt.switch(
1317
                tt.ge(value, 1),
1318
                0,
1319
                tt.log(incomplete_beta(a, b, value))
1320
            )
1321
        )
1322

1323
    def _repr_latex_(self, name=None, dist=None):
8✔
1324
        if dist is None:
×
1325
            dist = self
×
1326
        alpha = dist.alpha
×
1327
        beta = dist.beta
×
1328
        name = r'\text{%s}' % name
×
1329
        return r'${} \sim \text{{Beta}}(\mathit{{alpha}}={},~\mathit{{beta}}={})$'.format(name,
×
1330
                                                                get_variable_name(alpha),
1331
                                                                get_variable_name(beta))
1332

1333
class Kumaraswamy(UnitContinuous):
8✔
1334
    R"""
1335
    Kumaraswamy log-likelihood.
1336

1337
    The pdf of this distribution is
1338

1339
    .. math::
1340

1341
       f(x \mid a, b) =
1342
           abx^{a-1}(1-x^a)^{b-1}
1343

1344
    .. plot::
1345

1346
        import matplotlib.pyplot as plt
1347
        import numpy as np
1348
        plt.style.use('seaborn-darkgrid')
1349
        x = np.linspace(0, 1, 200)
1350
        a_s = [.5, 5., 1., 2., 2.]
1351
        b_s = [.5, 1., 3., 2., 5.]
1352
        for a, b in zip(a_s, b_s):
1353
            pdf = a * b * x ** (a - 1) * (1 - x ** a) ** (b - 1)
1354
            plt.plot(x, pdf, label=r'$a$ = {}, $b$ = {}'.format(a, b))
1355
        plt.xlabel('x', fontsize=12)
1356
        plt.ylabel('f(x)', fontsize=12)
1357
        plt.ylim(0, 3.)
1358
        plt.legend(loc=9)
1359
        plt.show()
1360

1361
    ========  ==============================================================
1362
    Support   :math:`x \in (0, 1)`
1363
    Mean      :math:`b B(1 + \tfrac{1}{a}, b)`
1364
    Variance  :math:`b B(1 + \tfrac{2}{a}, b) - (b B(1 + \tfrac{1}{a}, b))^2`
1365
    ========  ==============================================================
1366

1367
    Parameters
1368
    ----------
1369
    a : float
1370
        a > 0.
1371
    b : float
1372
        b > 0.
1373
    """
1374

1375
    def __init__(self, a, b, *args, **kwargs):
8✔
1376
        super().__init__(*args, **kwargs)
3✔
1377

1378
        self.a = a = tt.as_tensor_variable(floatX(a))
3✔
1379
        self.b = b = tt.as_tensor_variable(floatX(b))
3✔
1380

1381
        ln_mean = tt.log(b) + tt.gammaln(1 + 1 / a) + tt.gammaln(b) - tt.gammaln(1 + 1 / a + b)
3✔
1382
        self.mean = tt.exp(ln_mean)
3✔
1383
        ln_2nd_raw_moment = tt.log(b) + tt.gammaln(1 + 2 / a) + tt.gammaln(b) - tt.gammaln(1 + 2 / a + b)
3✔
1384
        self.variance = tt.exp(ln_2nd_raw_moment) - self.mean ** 2
3✔
1385

1386
        assert_negative_support(a, 'a', 'Kumaraswamy')
3✔
1387
        assert_negative_support(b, 'b', 'Kumaraswamy')
3✔
1388

1389
    def _random(self, a, b, size=None):
8✔
1390
        u = np.random.uniform(size=size)
2✔
1391
        return (1 - (1 - u) ** (1 / b)) ** (1 / a)
2✔
1392

1393
    def random(self, point=None, size=None):
8✔
1394
        """
1395
        Draw random values from Kumaraswamy distribution.
1396

1397
        Parameters
1398
        ----------
1399
        point : dict, optional
1400
            Dict of variable values on which random values are to be
1401
            conditioned (uses default point if not specified).
1402
        size : int, optional
1403
            Desired size of random sample (returns one sample if not
1404
            specified).
1405

1406
        Returns
1407
        -------
1408
        array
1409
        """
1410
        a, b = draw_values([self.a, self.b],
2✔
1411
                           point=point, size=size)
1412
        return generate_samples(self._random, a, b,
2✔
1413
                                dist_shape=self.shape,
1414
                                size=size)
1415

1416
    def logp(self, value):
8✔
1417
        """
1418
        Calculate log-probability of Kumaraswamy distribution at specified value.
1419

1420
        Parameters
1421
        ----------
1422
        value : numeric
1423
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
1424
            values are desired the values must be provided in a numpy array or theano tensor
1425

1426
        Returns
1427
        -------
1428
        TensorVariable
1429
        """
1430
        a = self.a
3✔
1431
        b = self.b
3✔
1432

1433
        logp = tt.log(a) + tt.log(b) + (a - 1) * tt.log(value) + (b - 1) * tt.log(1 - value ** a)
3✔
1434

1435
        return bound(logp,
3✔
1436
                     value >= 0, value <= 1,
1437
                     a > 0, b > 0)
1438

1439
    def _repr_latex_(self, name=None, dist=None):
8✔
1440
        if dist is None:
×
1441
            dist = self
×
1442
        a = dist.a
×
1443
        b = dist.b
×
1444
        name = r'\text{%s}' % name
×
1445
        return r'${} \sim \text{{Kumaraswamy}}(\mathit{{a}}={},~\mathit{{b}}={})$'.format(name,
×
1446
                                                                                          get_variable_name(a),
1447
                                                                                          get_variable_name(b))
1448

1449

1450
class Exponential(PositiveContinuous):
8✔
1451
    R"""
1452
    Exponential log-likelihood.
1453

1454
    The pdf of this distribution is
1455

1456
    .. math::
1457

1458
       f(x \mid \lambda) = \lambda \exp\left\{ -\lambda x \right\}
1459

1460
    .. plot::
1461

1462
        import matplotlib.pyplot as plt
1463
        import numpy as np
1464
        import scipy.stats as st
1465
        plt.style.use('seaborn-darkgrid')
1466
        x = np.linspace(0, 3, 100)
1467
        for lam in [0.5, 1., 2.]:
1468
            pdf = st.expon.pdf(x, scale=1.0/lam)
1469
            plt.plot(x, pdf, label=r'$\lambda$ = {}'.format(lam))
1470
        plt.xlabel('x', fontsize=12)
1471
        plt.ylabel('f(x)', fontsize=12)
1472
        plt.legend(loc=1)
1473
        plt.show()
1474

1475
    ========  ============================
1476
    Support   :math:`x \in [0, \infty)`
1477
    Mean      :math:`\dfrac{1}{\lambda}`
1478
    Variance  :math:`\dfrac{1}{\lambda^2}`
1479
    ========  ============================
1480

1481
    Parameters
1482
    ----------
1483
    lam : float
1484
        Rate or inverse scale (lam > 0)
1485
    """
1486

1487
    def __init__(self, lam, *args, **kwargs):
8✔
1488
        super().__init__(*args, **kwargs)
6✔
1489
        self.lam = lam = tt.as_tensor_variable(floatX(lam))
6✔
1490
        self.mean = 1. / self.lam
6✔
1491
        self.median = self.mean * tt.log(2)
6✔
1492
        self.mode = tt.zeros_like(self.lam)
6✔
1493

1494
        self.variance = self.lam**-2
6✔
1495

1496
        assert_negative_support(lam, 'lam', 'Exponential')
6✔
1497

1498
    def random(self, point=None, size=None):
8✔
1499
        """
1500
        Draw random values from Exponential distribution.
1501

1502
        Parameters
1503
        ----------
1504
        point : dict, optional
1505
            Dict of variable values on which random values are to be
1506
            conditioned (uses default point if not specified).
1507
        size : int, optional
1508
            Desired size of random sample (returns one sample if not
1509
            specified).
1510

1511
        Returns
1512
        -------
1513
        array
1514
        """
1515
        lam = draw_values([self.lam], point=point, size=size)[0]
3✔
1516
        return generate_samples(np.random.exponential, scale=1. / lam,
3✔
1517
                                dist_shape=self.shape,
1518
                                size=size)
1519

1520
    def logp(self, value):
8✔
1521
        """
1522
        Calculate log-probability of Exponential distribution at specified value.
1523

1524
        Parameters
1525
        ----------
1526
        value : numeric
1527
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
1528
            values are desired the values must be provided in a numpy array or theano tensor
1529

1530
        Returns
1531
        -------
1532
        TensorVariable
1533
        """
1534
        lam = self.lam
6✔
1535
        return bound(tt.log(lam) - lam * value, value >= 0, lam > 0)
6✔
1536

1537
    def _repr_latex_(self, name=None, dist=None):
8✔
1538
        if dist is None:
×
1539
            dist = self
×
1540
        lam = dist.lam
×
1541
        name = r'\text{%s}' % name
×
1542
        return r'${} \sim \text{{Exponential}}(\mathit{{lam}}={})$'.format(name,
×
1543
                                                                get_variable_name(lam))
1544

1545
    def logcdf(self, value):
8✔
1546
        r"""
1547
        Compute the log of cumulative distribution function for the Exponential distribution
1548
        at the specified value.
1549

1550
        References
1551
        ----------
1552
        .. [Machler2012] Martin Mächler (2012).
1553
            "Accurately computing :math:`\log(1-\exp(-\mid a \mid))` Assessed by the Rmpfr
1554
            package"
1555

1556
        Parameters
1557
        ----------
1558
        value: numeric
1559
            Value(s) for which log CDF is calculated. If the log CDF for multiple
1560
            values are desired the values must be provided in a numpy array or theano tensor.
1561

1562
        Returns
1563
        -------
1564
        TensorVariable
1565
        """
1566
        value = floatX(tt.as_tensor(value))
1✔
1567
        lam = self.lam
1✔
1568
        a = lam * value
1✔
1569
        return tt.switch(
1✔
1570
            tt.le(value, 0.0),
1571
            -np.inf,
1572
            tt.switch(
1573
                tt.le(a, tt.log(2.0)),
1574
                tt.log(-tt.expm1(-a)),
1575
                tt.log1p(-tt.exp(-a)),
1576
            )
1577
        )
1578

1579

1580
class Laplace(Continuous):
8✔
1581
    R"""
1582
    Laplace log-likelihood.
1583

1584
    The pdf of this distribution is
1585

1586
    .. math::
1587

1588
       f(x \mid \mu, b) =
1589
           \frac{1}{2b} \exp \left\{ - \frac{|x - \mu|}{b} \right\}
1590

1591
    .. plot::
1592

1593
        import matplotlib.pyplot as plt
1594
        import numpy as np
1595
        import scipy.stats as st
1596
        plt.style.use('seaborn-darkgrid')
1597
        x = np.linspace(-10, 10, 1000)
1598
        mus = [0., 0., 0., -5.]
1599
        bs = [1., 2., 4., 4.]
1600
        for mu, b in zip(mus, bs):
1601
            pdf = st.laplace.pdf(x, loc=mu, scale=b)
1602
            plt.plot(x, pdf, label=r'$\mu$ = {}, $b$ = {}'.format(mu, b))
1603
        plt.xlabel('x', fontsize=12)
1604
        plt.ylabel('f(x)', fontsize=12)
1605
        plt.legend(loc=1)
1606
        plt.show()
1607

1608
    ========  ========================
1609
    Support   :math:`x \in \mathbb{R}`
1610
    Mean      :math:`\mu`
1611
    Variance  :math:`2 b^2`
1612
    ========  ========================
1613

1614
    Parameters
1615
    ----------
1616
    mu : float
1617
        Location parameter.
1618
    b : float
1619
        Scale parameter (b > 0).
1620
    """
1621

1622
    def __init__(self, mu, b, *args, **kwargs):
8✔
1623
        super().__init__(*args, **kwargs)
3✔
1624
        self.b = b = tt.as_tensor_variable(floatX(b))
3✔
1625
        self.mean = self.median = self.mode = self.mu = mu = tt.as_tensor_variable(floatX(mu))
3✔
1626

1627
        self.variance = 2 * self.b**2
3✔
1628

1629
        assert_negative_support(b, 'b', 'Laplace')
3✔
1630

1631
    def random(self, point=None, size=None):
8✔
1632
        """
1633
        Draw random values from Laplace distribution.
1634

1635
        Parameters
1636
        ----------
1637
        point : dict, optional
1638
            Dict of variable values on which random values are to be
1639
            conditioned (uses default point if not specified).
1640
        size : int, optional
1641
            Desired size of random sample (returns one sample if not
1642
            specified).
1643

1644
        Returns
1645
        -------
1646
        array
1647
        """
1648
        mu, b = draw_values([self.mu, self.b], point=point, size=size)
2✔
1649
        return generate_samples(np.random.laplace, mu, b,
2✔
1650
                                dist_shape=self.shape,
1651
                                size=size)
1652

1653
    def logp(self, value):
8✔
1654
        """
1655
        Calculate log-probability of Laplace distribution at specified value.
1656

1657
        Parameters
1658
        ----------
1659
        value : numeric
1660
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
1661
            values are desired the values must be provided in a numpy array or theano tensor
1662

1663
        Returns
1664
        -------
1665
        TensorVariable
1666
        """
1667
        mu = self.mu
3✔
1668
        b = self.b
3✔
1669

1670
        return -tt.log(2 * b) - abs(value - mu) / b
3✔
1671

1672
    def _repr_latex_(self, name=None, dist=None):
8✔
1673
        if dist is None:
×
1674
            dist = self
×
1675
        b = dist.b
×
1676
        mu = dist.mu
×
1677
        name = r'\text{%s}' % name
×
1678
        return r'${} \sim \text{{Laplace}}(\mathit{{mu}}={},~\mathit{{b}}={})$'.format(name,
×
1679
                                                                get_variable_name(mu),
1680
                                                                get_variable_name(b))
1681

1682
    def logcdf(self, value):
8✔
1683
        """
1684
        Compute the log of the cumulative distribution function for Laplace distribution
1685
        at the specified value.
1686

1687
        Parameters
1688
        ----------
1689
        value: numeric
1690
            Value(s) for which log CDF is calculated. If the log CDF for multiple
1691
            values are desired the values must be provided in a numpy array or theano tensor.
1692

1693
        Returns
1694
        -------
1695
        TensorVariable
1696
        """
1697
        a = self.mu
1✔
1698
        b = self.b
1✔
1699
        y = (value - a) / b
1✔
1700
        return tt.switch(
1✔
1701
            tt.le(value, a),
1702
            tt.log(0.5) + y,
1703
            tt.switch(
1704
                tt.gt(y, 1),
1705
                tt.log1p(-0.5 * tt.exp(-y)),
1706
                tt.log(1 - 0.5 * tt.exp(-y))
1707
            )
1708
        )
1709

1710

1711
class Lognormal(PositiveContinuous):
8✔
1712
    R"""
1713
    Log-normal log-likelihood.
1714

1715
    Distribution of any random variable whose logarithm is normally
1716
    distributed. A variable might be modeled as log-normal if it can
1717
    be thought of as the multiplicative product of many small
1718
    independent factors.
1719

1720
    The pdf of this distribution is
1721

1722
    .. math::
1723

1724
       f(x \mid \mu, \tau) =
1725
           \frac{1}{x} \sqrt{\frac{\tau}{2\pi}}
1726
           \exp\left\{ -\frac{\tau}{2} (\ln(x)-\mu)^2 \right\}
1727

1728
    .. plot::
1729

1730
        import matplotlib.pyplot as plt
1731
        import numpy as np
1732
        import scipy.stats as st
1733
        plt.style.use('seaborn-darkgrid')
1734
        x = np.linspace(0, 3, 100)
1735
        mus = [0., 0., 0.]
1736
        sigmas = [.25, .5, 1.]
1737
        for mu, sigma in zip(mus, sigmas):
1738
            pdf = st.lognorm.pdf(x, sigma, scale=np.exp(mu))
1739
            plt.plot(x, pdf, label=r'$\mu$ = {}, $\sigma$ = {}'.format(mu, sigma))
1740
        plt.xlabel('x', fontsize=12)
1741
        plt.ylabel('f(x)', fontsize=12)
1742
        plt.legend(loc=1)
1743
        plt.show()
1744

1745
    ========  =========================================================================
1746
    Support   :math:`x \in [0, \infty)`
1747
    Mean      :math:`\exp\{\mu + \frac{1}{2\tau}\}`
1748
    Variance  :math:`(\exp\{\frac{1}{\tau}\} - 1) \times \exp\{2\mu + \frac{1}{\tau}\}`
1749
    ========  =========================================================================
1750

1751
    Parameters
1752
    ----------
1753
    mu : float
1754
        Location parameter.
1755
    sigma : float
1756
        Standard deviation. (sigma > 0). (only required if tau is not specified).
1757
    tau : float
1758
        Scale parameter (tau > 0). (only required if sigma is not specified).
1759

1760
    Examples
1761
    --------
1762

1763
    .. code-block:: python
1764

1765
        # Example to show that we pass in only ``sigma`` or ``tau`` but not both.
1766
        with pm.Model():
1767
            x = pm.Lognormal('x', mu=2, sigma=30)
1768

1769
        with pm.Model():
1770
            x = pm.Lognormal('x', mu=2, tau=1/100)
1771
    """
1772

1773
    def __init__(self, mu=0, sigma=None, tau=None, sd=None, *args, **kwargs):
8✔
1774
        super().__init__(*args, **kwargs)
5✔
1775
        if sd is not None:
5✔
1776
            sigma = sd
×
1777

1778
        tau, sigma = get_tau_sigma(tau=tau, sigma=sigma)
5✔
1779

1780
        self.mu = mu = tt.as_tensor_variable(floatX(mu))
5✔
1781
        self.tau = tau = tt.as_tensor_variable(tau)
5✔
1782
        self.sigma = self.sd = sigma = tt.as_tensor_variable(sigma)
5✔
1783

1784
        self.mean = tt.exp(self.mu + 1. / (2 * self.tau))
5✔
1785
        self.median = tt.exp(self.mu)
5✔
1786
        self.mode = tt.exp(self.mu - 1. / self.tau)
5✔
1787
        self.variance = (tt.exp(1. / self.tau) - 1) * tt.exp(2 * self.mu + 1. / self.tau)
5✔
1788

1789
        assert_negative_support(tau, 'tau', 'Lognormal')
5✔
1790
        assert_negative_support(sigma, 'sigma', 'Lognormal')
5✔
1791

1792
    def _random(self, mu, tau, size=None):
8✔
1793
        samples = np.random.normal(size=size)
2✔
1794
        return np.exp(mu + (tau**-0.5) * samples)
2✔
1795

1796
    def random(self, point=None, size=None):
8✔
1797
        """
1798
        Draw random values from Lognormal distribution.
1799

1800
        Parameters
1801
        ----------
1802
        point : dict, optional
1803
            Dict of variable values on which random values are to be
1804
            conditioned (uses default point if not specified).
1805
        size : int, optional
1806
            Desired size of random sample (returns one sample if not
1807
            specified).
1808

1809
        Returns
1810
        -------
1811
        array
1812
        """
1813
        mu, tau = draw_values([self.mu, self.tau], point=point, size=size)
2✔
1814
        return generate_samples(self._random, mu, tau,
2✔
1815
                                dist_shape=self.shape,
1816
                                size=size)
1817

1818
    def logp(self, value):
8✔
1819
        """
1820
        Calculate log-probability of Lognormal distribution at specified value.
1821

1822
        Parameters
1823
        ----------
1824
        value : numeric
1825
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
1826
            values are desired the values must be provided in a numpy array or theano tensor
1827

1828
        Returns
1829
        -------
1830
        TensorVariable
1831
        """
1832
        mu = self.mu
5✔
1833
        tau = self.tau
5✔
1834
        return bound(-0.5 * tau * (tt.log(value) - mu)**2
5✔
1835
                     + 0.5 * tt.log(tau / (2. * np.pi))
1836
                     - tt.log(value),
1837
                     tau > 0)
1838

1839
    def _repr_latex_(self, name=None, dist=None):
8✔
1840
        if dist is None:
×
1841
            dist = self
×
1842
        tau = dist.tau
×
1843
        mu = dist.mu
×
1844
        name = r'\text{%s}' % name
×
1845
        return r'${} \sim \text{{Lognormal}}(\mathit{{mu}}={},~\mathit{{tau}}={})$'.format(name,
×
1846
                                                                get_variable_name(mu),
1847
                                                                get_variable_name(tau))
1848

1849
    def logcdf(self, value):
8✔
1850
        """
1851
        Compute the log of the cumulative distribution function for Lognormal distribution
1852
        at the specified value.
1853

1854
        Parameters
1855
        ----------
1856
        value: numeric
1857
            Value(s) for which log CDF is calculated. If the log CDF for multiple
1858
            values are desired the values must be provided in a numpy array or theano tensor.
1859

1860
        Returns
1861
        -------
1862
        TensorVariable
1863
        """
1864
        mu = self.mu
1✔
1865
        sigma = self.sigma
1✔
1866
        z = zvalue(tt.log(value), mu=mu, sigma=sigma)
1✔
1867

1868
        return tt.switch(
1✔
1869
            tt.le(value, 0),
1870
            -np.inf,
1871
            tt.switch(
1872
                tt.lt(z, -1.0),
1873
                tt.log(tt.erfcx(-z / tt.sqrt(2.)) / 2.) -
1874
                tt.sqr(z) / 2,
1875
                tt.log1p(-tt.erfc(z / tt.sqrt(2.)) / 2.)
1876
            )
1877
        )
1878

1879

1880
class StudentT(Continuous):
8✔
1881
    R"""
1882
    Student's T log-likelihood.
1883

1884
    Describes a normal variable whose precision is gamma distributed.
1885
    If only nu parameter is passed, this specifies a standard (central)
1886
    Student's T.
1887

1888
    The pdf of this distribution is
1889

1890
    .. math::
1891

1892
       f(x|\mu,\lambda,\nu) =
1893
           \frac{\Gamma(\frac{\nu + 1}{2})}{\Gamma(\frac{\nu}{2})}
1894
           \left(\frac{\lambda}{\pi\nu}\right)^{\frac{1}{2}}
1895
           \left[1+\frac{\lambda(x-\mu)^2}{\nu}\right]^{-\frac{\nu+1}{2}}
1896

1897
    .. plot::
1898

1899
        import matplotlib.pyplot as plt
1900
        import numpy as np
1901
        import scipy.stats as st
1902
        plt.style.use('seaborn-darkgrid')
1903
        x = np.linspace(-8, 8, 200)
1904
        mus = [0., 0., -2., -2.]
1905
        sigmas = [1., 1., 1., 2.]
1906
        dfs = [1., 5., 5., 5.]
1907
        for mu, sigma, df in zip(mus, sigmas, dfs):
1908
            pdf = st.t.pdf(x, df, loc=mu, scale=sigma)
1909
            plt.plot(x, pdf, label=r'$\mu$ = {}, $\sigma$ = {}, $\nu$ = {}'.format(mu, sigma, df))
1910
        plt.xlabel('x', fontsize=12)
1911
        plt.ylabel('f(x)', fontsize=12)
1912
        plt.legend(loc=1)
1913
        plt.show()
1914

1915
    ========  ========================
1916
    Support   :math:``x \in \mathbb{R}``
1917
    ========  ========================
1918

1919
    Parameters
1920
    ----------
1921
    nu : float
1922
        Degrees of freedom, also known as normality parameter (nu > 0).
1923
    mu : float
1924
        Location parameter.
1925
    sigma : float
1926
        Scale parameter (sigma > 0). Converges to the standard deviation as nu
1927
        increases. (only required if lam is not specified)
1928
    lam : float
1929
        Scale parameter (lam > 0). Converges to the precision as nu
1930
        increases. (only required if sigma is not specified)
1931

1932
    Examples
1933
    --------
1934
    .. code-block:: python
1935

1936
        with pm.Model():
1937
            x = pm.StudentT('x', nu=15, mu=0, sigma=10)
1938

1939
        with pm.Model():
1940
            x = pm.StudentT('x', nu=15, mu=0, lam=1/23)
1941
    """
1942

1943
    def __init__(self, nu, mu=0, lam=None, sigma=None, sd=None, *args, **kwargs):
8✔
1944
        super().__init__(*args, **kwargs)
5✔
1945
        super(StudentT, self).__init__(*args, **kwargs)
5✔
1946
        if sd is not None:
5✔
1947
            sigma = sd
×
1948
        self.nu = nu = tt.as_tensor_variable(floatX(nu))
5✔
1949
        lam, sigma = get_tau_sigma(tau=lam, sigma=sigma)
5✔
1950
        self.lam = lam = tt.as_tensor_variable(lam)
5✔
1951
        self.sigma = self.sd = sigma = tt.as_tensor_variable(sigma)
5✔
1952
        self.mean = self.median = self.mode = self.mu = mu = tt.as_tensor_variable(mu)
5✔
1953

1954
        self.variance = tt.switch((nu > 2) * 1,
5✔
1955
                                  (1 / self.lam) * (nu / (nu - 2)),
1956
                                  np.inf)
1957

1958
        assert_negative_support(lam, 'lam (sigma)', 'StudentT')
5✔
1959
        assert_negative_support(nu, 'nu', 'StudentT')
5✔
1960

1961
    def random(self, point=None, size=None):
8✔
1962
        """
1963
        Draw random values from StudentT distribution.
1964

1965
        Parameters
1966
        ----------
1967
        point : dict, optional
1968
            Dict of variable values on which random values are to be
1969
            conditioned (uses default point if not specified).
1970
        size : int, optional
1971
            Desired size of random sample (returns one sample if not
1972
            specified).
1973

1974
        Returns
1975
        -------
1976
        array
1977
        """
1978
        nu, mu, lam = draw_values([self.nu, self.mu, self.lam],
2✔
1979
                                  point=point, size=size)
1980
        return generate_samples(stats.t.rvs, nu, loc=mu, scale=lam**-0.5,
2✔
1981
                                dist_shape=self.shape,
1982
                                size=size)
1983

1984
    def logp(self, value):
8✔
1985
        """
1986
        Calculate log-probability of StudentT distribution at specified value.
1987

1988
        Parameters
1989
        ----------
1990
        value : numeric
1991
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
1992
            values are desired the values must be provided in a numpy array or theano tensor
1993

1994
        Returns
1995
        -------
1996
        TensorVariable
1997
        """
1998
        nu = self.nu
5✔
1999
        mu = self.mu
5✔
2000
        lam = self.lam
5✔
2001
        sigma = self.sigma
5✔
2002

2003
        return bound(gammaln((nu + 1.0) / 2.0)
5✔
2004
                     + .5 * tt.log(lam / (nu * np.pi))
2005
                     - gammaln(nu / 2.0)
2006
                     - (nu + 1.0) / 2.0 * tt.log1p(lam * (value - mu)**2 / nu),
2007
                     lam > 0, nu > 0, sigma > 0)
2008

2009
    def _repr_latex_(self, name=None, dist=None):
8✔
2010
        if dist is None:
×
2011
            dist = self
×
2012
        nu = dist.nu
×
2013
        mu = dist.mu
×
2014
        lam = dist.lam
×
2015
        name = r'\text{%s}' % name
×
2016
        return r'${} \sim \text{{StudentT}}(\mathit{{nu}}={},~\mathit{{mu}}={},~\mathit{{lam}}={})$'.format(name,
×
2017
                                                                get_variable_name(nu),
2018
                                                                get_variable_name(mu),
2019
                                                                get_variable_name(lam))
2020

2021
    def logcdf(self, value):
8✔
2022
        """
2023
        Compute the log of the cumulative distribution function for Student's T distribution
2024
        at the specified value.
2025

2026
        Parameters
2027
        ----------
2028
        value: numeric
2029
            Value(s) for which log CDF is calculated. If the log CDF for multiple
2030
            values are desired the values must be provided in a numpy array or theano tensor.
2031

2032
        Returns
2033
        -------
2034
        TensorVariable
2035
        """
2036
        nu = self.nu
1✔
2037
        mu = self.mu
1✔
2038
        sigma = self.sigma
1✔
2039
        t = (value - mu)/sigma
1✔
2040
        sqrt_t2_nu = tt.sqrt(t**2 + nu)
1✔
2041
        x = (t + sqrt_t2_nu)/(2.0 * sqrt_t2_nu)
1✔
2042
        return tt.log(incomplete_beta(nu/2., nu/2., x))
1✔
2043

2044

2045
class Pareto(Continuous):
8✔
2046
    R"""
2047
    Pareto log-likelihood.
2048

2049
    Often used to characterize wealth distribution, or other examples of the
2050
    80/20 rule.
2051

2052
    The pdf of this distribution is
2053

2054
    .. math::
2055

2056
       f(x \mid \alpha, m) = \frac{\alpha m^{\alpha}}{x^{\alpha+1}}
2057

2058
    .. plot::
2059

2060
        import matplotlib.pyplot as plt
2061
        import numpy as np
2062
        import scipy.stats as st
2063
        plt.style.use('seaborn-darkgrid')
2064
        x = np.linspace(0, 4, 1000)
2065
        alphas = [1., 2., 5., 5.]
2066
        ms = [1., 1., 1., 2.]
2067
        for alpha, m in zip(alphas, ms):
2068
            pdf = st.pareto.pdf(x, alpha, scale=m)
2069
            plt.plot(x, pdf, label=r'$\alpha$ = {}, m = {}'.format(alpha, m))
2070
        plt.xlabel('x', fontsize=12)
2071
        plt.ylabel('f(x)', fontsize=12)
2072
        plt.legend(loc=1)
2073
        plt.show()
2074

2075
    ========  =============================================================
2076
    Support   :math:`x \in [m, \infty)`
2077
    Mean      :math:`\dfrac{\alpha m}{\alpha - 1}` for :math:`\alpha \ge 1`
2078
    Variance  :math:`\dfrac{m \alpha}{(\alpha - 1)^2 (\alpha - 2)}`
2079
              for :math:`\alpha > 2`
2080
    ========  =============================================================
2081

2082
    Parameters
2083
    ----------
2084
    alpha : float
2085
        Shape parameter (alpha > 0).
2086
    m : float
2087
        Scale parameter (m > 0).
2088
    """
2089

2090
    def __init__(self, alpha, m, transform='lowerbound', *args, **kwargs):
8✔
2091
        self.alpha = alpha = tt.as_tensor_variable(floatX(alpha))
3✔
2092
        self.m = m = tt.as_tensor_variable(floatX(m))
3✔
2093

2094
        self.mean = tt.switch(tt.gt(alpha, 1), alpha *
3✔
2095
                              m / (alpha - 1.), np.inf)
2096
        self.median = m * 2.**(1. / alpha)
3✔
2097
        self.variance = tt.switch(
3✔
2098
            tt.gt(alpha, 2),
2099
            (alpha * m**2) / ((alpha - 2.) * (alpha - 1.)**2),
2100
            np.inf)
2101

2102
        assert_negative_support(alpha, 'alpha', 'Pareto')
3✔
2103
        assert_negative_support(m, 'm', 'Pareto')
3✔
2104

2105
        if transform == 'lowerbound':
3✔
2106
            transform = transforms.lowerbound(self.m)
1✔
2107
        super().__init__(transform=transform, *args, **kwargs)
3✔
2108

2109
    def _random(self, alpha, m, size=None):
8✔
2110
        u = np.random.uniform(size=size)
2✔
2111
        return m * (1. - u)**(-1. / alpha)
2✔
2112

2113
    def random(self, point=None, size=None):
8✔
2114
        """
2115
        Draw random values from Pareto distribution.
2116

2117
        Parameters
2118
        ----------
2119
        point : dict, optional
2120
            Dict of variable values on which random values are to be
2121
            conditioned (uses default point if not specified).
2122
        size : int, optional
2123
            Desired size of random sample (returns one sample if not
2124
            specified).
2125

2126
        Returns
2127
        -------
2128
        array
2129
        """
2130
        alpha, m = draw_values([self.alpha, self.m],
2✔
2131
                               point=point, size=size)
2132
        return generate_samples(self._random, alpha, m,
2✔
2133
                                dist_shape=self.shape,
2134
                                size=size)
2135

2136
    def logp(self, value):
8✔
2137
        """
2138
        Calculate log-probability of Pareto distribution at specified value.
2139

2140
        Parameters
2141
        ----------
2142
        value : numeric
2143
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
2144
            values are desired the values must be provided in a numpy array or theano tensor
2145

2146
        Returns
2147
        -------
2148
        TensorVariable
2149
        """
2150
        alpha = self.alpha
3✔
2151
        m = self.m
3✔
2152
        return bound(tt.log(alpha) + logpow(m, alpha)
3✔
2153
                     - logpow(value, alpha + 1),
2154
                     value >= m, alpha > 0, m > 0)
2155

2156
    def _repr_latex_(self, name=None, dist=None):
8✔
2157
        if dist is None:
×
2158
            dist = self
×
2159
        alpha = dist.alpha
×
2160
        m = dist.m
×
2161
        name = r'\text{%s}' % name
×
2162
        return r'${} \sim \text{{Pareto}}(\mathit{{alpha}}={},~\mathit{{m}}={})$'.format(name,
×
2163
                                                                get_variable_name(alpha),
2164
                                                                get_variable_name(m))
2165

2166
    def logcdf(self, value):
8✔
2167
        """
2168
        Compute the log of the cumulative distribution function for Pareto distribution
2169
        at the specified value.
2170

2171
        Parameters
2172
        ----------
2173
        value: numeric
2174
            Value(s) for which log CDF is calculated. If the log CDF for multiple
2175
            values are desired the values must be provided in a numpy array or theano tensor.
2176

2177
        Returns
2178
        -------
2179
        TensorVariable
2180
        """
2181
        m = self.m
1✔
2182
        alpha = self.alpha
1✔
2183
        arg = (m / value) ** alpha
1✔
2184
        return tt.switch(
1✔
2185
            tt.lt(value, m),
2186
            -np.inf,
2187
            tt.switch(
2188
                tt.le(arg, 1e-5),
2189
                tt.log1p(-arg),
2190
                tt.log(1 - arg)
2191
            )
2192
        )
2193

2194

2195
class Cauchy(Continuous):
8✔
2196
    R"""
2197
    Cauchy log-likelihood.
2198

2199
    Also known as the Lorentz or the Breit-Wigner distribution.
2200

2201
    The pdf of this distribution is
2202

2203
    .. math::
2204

2205
       f(x \mid \alpha, \beta) =
2206
           \frac{1}{\pi \beta [1 + (\frac{x-\alpha}{\beta})^2]}
2207

2208
    .. plot::
2209

2210
        import matplotlib.pyplot as plt
2211
        import numpy as np
2212
        import scipy.stats as st
2213
        plt.style.use('seaborn-darkgrid')
2214
        x = np.linspace(-5, 5, 500)
2215
        alphas = [0., 0., 0., -2.]
2216
        betas = [.5, 1., 2., 1.]
2217
        for a, b in zip(alphas, betas):
2218
            pdf = st.cauchy.pdf(x, loc=a, scale=b)
2219
            plt.plot(x, pdf, label=r'$\alpha$ = {}, $\beta$ = {}'.format(a, b))
2220
        plt.xlabel('x', fontsize=12)
2221
        plt.ylabel('f(x)', fontsize=12)
2222
        plt.legend(loc=1)
2223
        plt.show()
2224

2225
    ========  ========================
2226
    Support   :math:`x \in \mathbb{R}`
2227
    Mode      :math:`\alpha`
2228
    Mean      undefined
2229
    Variance  undefined
2230
    ========  ========================
2231

2232
    Parameters
2233
    ----------
2234
    alpha : float
2235
        Location parameter
2236
    beta : float
2237
        Scale parameter > 0
2238
    """
2239

2240
    def __init__(self, alpha, beta, *args, **kwargs):
8✔
2241
        super().__init__(*args, **kwargs)
3✔
2242
        self.median = self.mode = self.alpha = tt.as_tensor_variable(floatX(alpha))
3✔
2243
        self.beta = tt.as_tensor_variable(floatX(beta))
3✔
2244

2245
        assert_negative_support(beta, 'beta', 'Cauchy')
3✔
2246

2247
    def _random(self, alpha, beta, size=None):
8✔
2248
        u = np.random.uniform(size=size)
2✔
2249
        return alpha + beta * np.tan(np.pi * (u - 0.5))
2✔
2250

2251
    def random(self, point=None, size=None):
8✔
2252
        """
2253
        Draw random values from Cauchy distribution.
2254

2255
        Parameters
2256
        ----------
2257
        point : dict, optional
2258
            Dict of variable values on which random values are to be
2259
            conditioned (uses default point if not specified).
2260
        size : int, optional
2261
            Desired size of random sample (returns one sample if not
2262
            specified).
2263

2264
        Returns
2265
        -------
2266
        array
2267
        """
2268
        alpha, beta = draw_values([self.alpha, self.beta],
2✔
2269
                                  point=point, size=size)
2270
        return generate_samples(self._random, alpha, beta,
2✔
2271
                                dist_shape=self.shape,
2272
                                size=size)
2273

2274
    def logp(self, value):
8✔
2275
        """
2276
        Calculate log-probability of Cauchy distribution at specified value.
2277

2278
        Parameters
2279
        ----------
2280
        value : numeric
2281
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
2282
            values are desired the values must be provided in a numpy array or theano tensor
2283

2284
        Returns
2285
        -------
2286
        TensorVariable
2287
        """
2288
        alpha = self.alpha
3✔
2289
        beta = self.beta
3✔
2290
        return bound(- tt.log(np.pi) - tt.log(beta)
3✔
2291
                     - tt.log1p(((value - alpha) / beta)**2),
2292
                     beta > 0)
2293

2294
    def _repr_latex_(self, name=None, dist=None):
8✔
2295
        if dist is None:
×
2296
            dist = self
×
2297
        alpha = dist.alpha
×
2298
        beta = dist.beta
×
2299
        name = r'\text{%s}' % name
×
2300
        return r'${} \sim \text{{Cauchy}}(\mathit{{alpha}}={},~\mathit{{beta}}={})$'.format(name,
×
2301
                                                                get_variable_name(alpha),
2302
                                                                get_variable_name(beta))
2303

2304
    def logcdf(self, value):
8✔
2305
        """
2306
        Compute the log of the cumulative distribution function for Cauchy distribution
2307
        at the specified value.
2308

2309
        Parameters
2310
        ----------
2311
        value: numeric
2312
            Value(s) for which log CDF is calculated. If the log CDF for multiple
2313
            values are desired the values must be provided in a numpy array or theano tensor.
2314

2315
        Returns
2316
        -------
2317
        TensorVariable
2318
        """
2319
        return tt.log(
1✔
2320
            0.5 + tt.arctan((value - self.alpha) / self.beta) / np.pi
2321
        )
2322

2323

2324
class HalfCauchy(PositiveContinuous):
8✔
2325
    R"""
2326
    Half-Cauchy log-likelihood.
2327

2328
    The pdf of this distribution is
2329

2330
    .. math::
2331

2332
       f(x \mid \beta) = \frac{2}{\pi \beta [1 + (\frac{x}{\beta})^2]}
2333

2334
    .. plot::
2335

2336
        import matplotlib.pyplot as plt
2337
        import numpy as np
2338
        import scipy.stats as st
2339
        plt.style.use('seaborn-darkgrid')
2340
        x = np.linspace(0, 5, 200)
2341
        for b in [0.5, 1.0, 2.0]:
2342
            pdf = st.cauchy.pdf(x, scale=b)
2343
            plt.plot(x, pdf, label=r'$\beta$ = {}'.format(b))
2344
        plt.xlabel('x', fontsize=12)
2345
        plt.ylabel('f(x)', fontsize=12)
2346
        plt.legend(loc=1)
2347
        plt.show()
2348

2349
    ========  ========================
2350
    Support   :math:`x \in [0, \infty)`
2351
    Mode      0
2352
    Mean      undefined
2353
    Variance  undefined
2354
    ========  ========================
2355

2356
    Parameters
2357
    ----------
2358
    beta : float
2359
        Scale parameter (beta > 0).
2360
    """
2361

2362
    def __init__(self, beta, *args, **kwargs):
8✔
2363
        super().__init__(*args, **kwargs)
8✔
2364
        self.mode = tt.as_tensor_variable(0)
8✔
2365
        self.median = self.beta = tt.as_tensor_variable(floatX(beta))
8✔
2366

2367
        assert_negative_support(beta, 'beta', 'HalfCauchy')
8✔
2368

2369
    def _random(self, beta, size=None):
8✔
2370
        u = np.random.uniform(size=size)
2✔
2371
        return beta * np.abs(np.tan(np.pi * (u - 0.5)))
2✔
2372

2373
    def random(self, point=None, size=None):
8✔
2374
        """
2375
        Draw random values from HalfCauchy distribution.
2376

2377
        Parameters
2378
        ----------
2379
        point : dict, optional
2380
            Dict of variable values on which random values are to be
2381
            conditioned (uses default point if not specified).
2382
        size : int, optional
2383
            Desired size of random sample (returns one sample if not
2384
            specified).
2385

2386
        Returns
2387
        -------
2388
        array
2389
        """
2390
        beta = draw_values([self.beta], point=point, size=size)[0]
2✔
2391
        return generate_samples(self._random, beta,
2✔
2392
                                dist_shape=self.shape,
2393
                                size=size)
2394

2395
    def logp(self, value):
8✔
2396
        """
2397
        Calculate log-probability of HalfCauchy distribution at specified value.
2398

2399
        Parameters
2400
        ----------
2401
        value : numeric
2402
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
2403
            values are desired the values must be provided in a numpy array or theano tensor
2404

2405
        Returns
2406
        -------
2407
        TensorVariable
2408
        """
2409
        beta = self.beta
6✔
2410
        return bound(tt.log(2) - tt.log(np.pi) - tt.log(beta)
6✔
2411
                     - tt.log1p((value / beta)**2),
2412
                     value >= 0, beta > 0)
2413

2414
    def _repr_latex_(self, name=None, dist=None):
8✔
2415
        if dist is None:
×
2416
            dist = self
×
2417
        beta = dist.beta
×
2418
        name = r'\text{%s}' % name
×
2419
        return r'${} \sim \text{{HalfCauchy}}(\mathit{{beta}}={})$'.format(name,
×
2420
                                                                get_variable_name(beta))
2421

2422
    def logcdf(self, value):
8✔
2423
        """
2424
        Compute the log of the cumulative distribution function for HalfCauchy distribution
2425
        at the specified value.
2426

2427
        Parameters
2428
        ----------
2429
        value: numeric
2430
            Value(s) for which log CDF is calculated. If the log CDF for multiple
2431
            values are desired the values must be provided in a numpy array or theano tensor.
2432

2433
        Returns
2434
        -------
2435
        TensorVariable
2436
        """
2437
        return tt.switch(
1✔
2438
            tt.le(value, 0),
2439
            -np.inf,
2440
            tt.log(
2441
                2 * tt.arctan(value / self.beta) / np.pi
2442
            ))
2443

2444

2445
class Gamma(PositiveContinuous):
8✔
2446
    R"""
2447
    Gamma log-likelihood.
2448

2449
    Represents the sum of alpha exponentially distributed random variables,
2450
    each of which has mean beta.
2451

2452
    The pdf of this distribution is
2453

2454
    .. math::
2455

2456
       f(x \mid \alpha, \beta) =
2457
           \frac{\beta^{\alpha}x^{\alpha-1}e^{-\beta x}}{\Gamma(\alpha)}
2458

2459
    .. plot::
2460

2461
        import matplotlib.pyplot as plt
2462
        import numpy as np
2463
        import scipy.stats as st
2464
        plt.style.use('seaborn-darkgrid')
2465
        x = np.linspace(0, 20, 200)
2466
        alphas = [1., 2., 3., 7.5]
2467
        betas = [.5, .5, 1., 1.]
2468
        for a, b in zip(alphas, betas):
2469
            pdf = st.gamma.pdf(x, a, scale=1.0/b)
2470
            plt.plot(x, pdf, label=r'$\alpha$ = {}, $\beta$ = {}'.format(a, b))
2471
        plt.xlabel('x', fontsize=12)
2472
        plt.ylabel('f(x)', fontsize=12)
2473
        plt.legend(loc=1)
2474
        plt.show()
2475

2476
    ========  ===============================
2477
    Support   :math:`x \in (0, \infty)`
2478
    Mean      :math:`\dfrac{\alpha}{\beta}`
2479
    Variance  :math:`\dfrac{\alpha}{\beta^2}`
2480
    ========  ===============================
2481

2482
    Gamma distribution can be parameterized either in terms of alpha and
2483
    beta or mean and standard deviation. The link between the two
2484
    parametrizations is given by
2485

2486
    .. math::
2487

2488
       \alpha &= \frac{\mu^2}{\sigma^2} \\
2489
       \beta &= \frac{\mu}{\sigma^2}
2490

2491
    Parameters
2492
    ----------
2493
    alpha : float
2494
        Shape parameter (alpha > 0).
2495
    beta : float
2496
        Rate parameter (beta > 0).
2497
    mu : float
2498
        Alternative shape parameter (mu > 0).
2499
    sigma : float
2500
        Alternative scale parameter (sigma > 0).
2501
    """
2502

2503
    def __init__(self, alpha=None, beta=None, mu=None, sigma=None,
8✔
2504
                 sd=None, *args, **kwargs):
2505
        super().__init__(*args, **kwargs)
6✔
2506
        if sd is not None:
6✔
2507
            sigma = sd
×
2508

2509
        alpha, beta = self.get_alpha_beta(alpha, beta, mu, sigma)
6✔
2510
        self.alpha = alpha = tt.as_tensor_variable(floatX(alpha))
6✔
2511
        self.beta = beta = tt.as_tensor_variable(floatX(beta))
6✔
2512
        self.mean = alpha / beta
6✔
2513
        self.mode = tt.maximum((alpha - 1) / beta, 0)
6✔
2514
        self.variance = alpha / beta**2
6✔
2515

2516
        assert_negative_support(alpha, 'alpha', 'Gamma')
6✔
2517
        assert_negative_support(beta, 'beta', 'Gamma')
6✔
2518

2519
    def get_alpha_beta(self, alpha=None, beta=None, mu=None, sigma=None):
8✔
2520
        if (alpha is not None) and (beta is not None):
6✔
2521
            pass
6✔
2522
        elif (mu is not None) and (sigma is not None):
3✔
2523
            alpha = mu**2 / sigma**2
3✔
2524
            beta = mu / sigma**2
3✔
2525
        else:
2526
            raise ValueError('Incompatible parameterization. Either use '
×
2527
                             'alpha and beta, or mu and sigma to specify '
2528
                             'distribution.')
2529

2530
        return alpha, beta
6✔
2531

2532
    def random(self, point=None, size=None):
8✔
2533
        """
2534
        Draw random values from Gamma distribution.
2535

2536
        Parameters
2537
        ----------
2538
        point : dict, optional
2539
            Dict of variable values on which random values are to be
2540
            conditioned (uses default point if not specified).
2541
        size : int, optional
2542
            Desired size of random sample (returns one sample if not
2543
            specified).
2544

2545
        Returns
2546
        -------
2547
        array
2548
        """
2549
        alpha, beta = draw_values([self.alpha, self.beta],
3✔
2550
                                  point=point, size=size)
2551
        return generate_samples(stats.gamma.rvs, alpha, scale=1. / beta,
3✔
2552
                                dist_shape=self.shape,
2553
                                size=size)
2554

2555
    def logp(self, value):
8✔
2556
        """
2557
        Calculate log-probability of Gamma distribution at specified value.
2558

2559
        Parameters
2560
        ----------
2561
        value : numeric
2562
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
2563
            values are desired the values must be provided in a numpy array or theano tensor
2564

2565
        Returns
2566
        -------
2567
        TensorVariable
2568
        """
2569
        alpha = self.alpha
6✔
2570
        beta = self.beta
6✔
2571
        return bound(
6✔
2572
            -gammaln(alpha) + logpow(
2573
                beta, alpha) - beta * value + logpow(value, alpha - 1),
2574
            value >= 0,
2575
            alpha > 0,
2576
            beta > 0)
2577

2578
    def logcdf(self, value):
8✔
2579
        """
2580
        Compute the log of the cumulative distribution function for Gamma distribution
2581
        at the specified value.
2582

2583
        Parameters
2584
        ----------
2585
        value: numeric
2586
            Value(s) for which log CDF is calculated. If the log CDF for multiple
2587
            values are desired the values must be provided in a numpy array or theano tensor.
2588

2589
        Returns
2590
        -------
2591
        TensorVariable
2592
        """
2593
        alpha = self.alpha
1✔
2594
        beta = self.beta
1✔
2595
        return bound(
1✔
2596
            tt.log(tt.gammainc(alpha, beta * value)),
2597
            value >= 0,
2598
            alpha > 0,
2599
            beta > 0)
2600

2601
    def _repr_latex_(self, name=None, dist=None):
8✔
2602
        if dist is None:
×
2603
            dist = self
×
2604
        beta = dist.beta
×
2605
        alpha = dist.alpha
×
2606
        name = r'\text{%s}' % name
×
2607
        return r'${} \sim \text{{Gamma}}(\mathit{{alpha}}={},~\mathit{{beta}}={})$'.format(name,
×
2608
                                                                get_variable_name(alpha),
2609
                                                                get_variable_name(beta))
2610

2611

2612
class InverseGamma(PositiveContinuous):
8✔
2613
    R"""
2614
    Inverse gamma log-likelihood, the reciprocal of the gamma distribution.
2615

2616
    The pdf of this distribution is
2617

2618
    .. math::
2619

2620
       f(x \mid \alpha, \beta) =
2621
           \frac{\beta^{\alpha}}{\Gamma(\alpha)} x^{-\alpha - 1}
2622
           \exp\left(\frac{-\beta}{x}\right)
2623

2624
    .. plot::
2625

2626
        import matplotlib.pyplot as plt
2627
        import numpy as np
2628
        import scipy.stats as st
2629
        plt.style.use('seaborn-darkgrid')
2630
        x = np.linspace(0, 3, 500)
2631
        alphas = [1., 2., 3., 3.]
2632
        betas = [1., 1., 1., .5]
2633
        for a, b in zip(alphas, betas):
2634
            pdf = st.invgamma.pdf(x, a, scale=b)
2635
            plt.plot(x, pdf, label=r'$\alpha$ = {}, $\beta$ = {}'.format(a, b))
2636
        plt.xlabel('x', fontsize=12)
2637
        plt.ylabel('f(x)', fontsize=12)
2638
        plt.legend(loc=1)
2639
        plt.show()
2640

2641
    ========  ======================================================
2642
    Support   :math:`x \in (0, \infty)`
2643
    Mean      :math:`\dfrac{\beta}{\alpha-1}` for :math:`\alpha > 1`
2644
    Variance  :math:`\dfrac{\beta^2}{(\alpha-1)^2(\alpha - 2)}`
2645
              for :math:`\alpha > 2`
2646
    ========  ======================================================
2647

2648
    Parameters
2649
    ----------
2650
    alpha : float
2651
        Shape parameter (alpha > 0).
2652
    beta : float
2653
        Scale parameter (beta > 0).
2654
    mu : float
2655
        Alternative shape parameter (mu > 0).
2656
    sigma : float
2657
        Alternative scale parameter (sigma > 0).
2658
    """
2659

2660
    def __init__(self, alpha=None, beta=None, mu=None, sigma=None, sd=None,
8✔
2661
                 *args, **kwargs):
2662
        super().__init__(*args, defaults=('mode',), **kwargs)
5✔
2663

2664
        if sd is not None:
5✔
2665
            sigma = sd
×
2666

2667
        alpha, beta = InverseGamma._get_alpha_beta(alpha, beta, mu, sigma)
5✔
2668
        self.alpha = alpha = tt.as_tensor_variable(floatX(alpha))
5✔
2669
        self.beta = beta = tt.as_tensor_variable(floatX(beta))
5✔
2670

2671
        self.mean = self._calculate_mean()
5✔
2672
        self.mode = beta / (alpha + 1.)
5✔
2673
        self.variance = tt.switch(tt.gt(alpha, 2),
5✔
2674
                                  (beta**2) / ((alpha - 2) * (alpha - 1.)**2),
2675
                                  np.inf)
2676
        assert_negative_support(alpha, 'alpha', 'InverseGamma')
5✔
2677
        assert_negative_support(beta, 'beta', 'InverseGamma')
5✔
2678

2679
    def _calculate_mean(self):
8✔
2680
        m = self.beta / (self.alpha - 1.)
5✔
2681
        try:
5✔
2682
            return (self.alpha > 1) * m or np.inf
5✔
2683
        except ValueError:  # alpha is an array
×
2684
            m[self.alpha <= 1] = np.inf
×
2685
            return m
×
2686

2687
    @staticmethod
8✔
2688
    def _get_alpha_beta(alpha, beta, mu, sigma):
2689
        if (alpha is not None):
5✔
2690
            if (beta is not None):
5✔
2691
                pass
5✔
2692
            else:
2693
                beta = 1
×
2694
        elif (mu is not None) and (sigma is not None):
1✔
2695
            alpha = (2 * sigma**2 + mu**2)/sigma**2
1✔
2696
            beta = mu * (mu**2 + sigma**2) / sigma**2
1✔
2697
        else:
2698
            raise ValueError('Incompatible parameterization. Either use '
×
2699
                             'alpha and (optionally) beta, or mu and sigma to specify '
2700
                             'distribution.')
2701

2702
        return alpha, beta
5✔
2703

2704
    def random(self, point=None, size=None):
8✔
2705
        """
2706
        Draw random values from InverseGamma distribution.
2707

2708
        Parameters
2709
        ----------
2710
        point : dict, optional
2711
            Dict of variable values on which random values are to be
2712
            conditioned (uses default point if not specified).
2713
        size : int, optional
2714
            Desired size of random sample (returns one sample if not
2715
            specified).
2716

2717
        Returns
2718
        -------
2719
        array
2720
        """
2721
        alpha, beta = draw_values([self.alpha, self.beta],
4✔
2722
                                  point=point, size=size)
2723
        return generate_samples(stats.invgamma.rvs, a=alpha, scale=beta,
4✔
2724
                                dist_shape=self.shape,
2725
                                size=size)
2726

2727
    def logp(self, value):
8✔
2728
        """
2729
        Calculate log-probability of InverseGamma distribution at specified value.
2730

2731
        Parameters
2732
        ----------
2733
        value : numeric
2734
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
2735
            values are desired the values must be provided in a numpy array or theano tensor
2736

2737
        Returns
2738
        -------
2739
        TensorVariable
2740
        """
2741
        alpha = self.alpha
5✔
2742
        beta = self.beta
5✔
2743
        return bound(logpow(beta, alpha) - gammaln(alpha) - beta / value
5✔
2744
                     + logpow(value, -alpha - 1),
2745
                     value > 0, alpha > 0, beta > 0)
2746

2747
    def _repr_latex_(self, name=None, dist=None):
8✔
2748
        if dist is None:
×
2749
            dist = self
×
2750
        beta = dist.beta
×
2751
        alpha = dist.alpha
×
2752
        name = r'\text{%s}' % name
×
2753
        return r'${} \sim \text{{InverseGamma}}(\mathit{{alpha}}={},~\mathit{{beta}}={})$'.format(name,
×
2754
                                                                get_variable_name(alpha),
2755
                                                                get_variable_name(beta))
2756

2757

2758
class ChiSquared(Gamma):
8✔
2759
    R"""
2760
    :math:`\chi^2` log-likelihood.
2761

2762
    The pdf of this distribution is
2763

2764
    .. math::
2765

2766
       f(x \mid \nu) = \frac{x^{(\nu-2)/2}e^{-x/2}}{2^{\nu/2}\Gamma(\nu/2)}
2767

2768
    .. plot::
2769

2770
        import matplotlib.pyplot as plt
2771
        import numpy as np
2772
        import scipy.stats as st
2773
        plt.style.use('seaborn-darkgrid')
2774
        x = np.linspace(0, 15, 200)
2775
        for df in [1, 2, 3, 6, 9]:
2776
            pdf = st.chi2.pdf(x, df)
2777
            plt.plot(x, pdf, label=r'$\nu$ = {}'.format(df))
2778
        plt.xlabel('x', fontsize=12)
2779
        plt.ylabel('f(x)', fontsize=12)
2780
        plt.ylim(0, 0.6)
2781
        plt.legend(loc=1)
2782
        plt.show()
2783

2784
    ========  ===============================
2785
    Support   :math:`x \in [0, \infty)`
2786
    Mean      :math:`\nu`
2787
    Variance  :math:`2 \nu`
2788
    ========  ===============================
2789

2790
    Parameters
2791
    ----------
2792
    nu : int
2793
        Degrees of freedom (nu > 0).
2794
    """
2795

2796
    def __init__(self, nu, *args, **kwargs):
8✔
2797
        self.nu = nu = tt.as_tensor_variable(floatX(nu))
5✔
2798
        super().__init__(alpha=nu / 2., beta=0.5, *args, **kwargs)
5✔
2799

2800
    def _repr_latex_(self, name=None, dist=None):
8✔
2801
        if dist is None:
×
2802
            dist = self
×
2803
        nu = dist.nu
×
2804
        name = r'\text{%s}' % name
×
2805
        return r'${} \sim \Chi^2(\mathit{{nu}}={})$'.format(name,
×
2806
                                                            get_variable_name(nu))
2807

2808

2809
class Weibull(PositiveContinuous):
8✔
2810
    R"""
2811
    Weibull log-likelihood.
2812

2813
    The pdf of this distribution is
2814

2815
    .. math::
2816

2817
       f(x \mid \alpha, \beta) =
2818
           \frac{\alpha x^{\alpha - 1}
2819
           \exp(-(\frac{x}{\beta})^{\alpha})}{\beta^\alpha}
2820

2821
    .. plot::
2822

2823
        import matplotlib.pyplot as plt
2824
        import numpy as np
2825
        import scipy.stats as st
2826
        plt.style.use('seaborn-darkgrid')
2827
        x = np.linspace(0, 3, 200)
2828
        alphas = [.5, 1., 1.5, 5., 5.]
2829
        betas = [1., 1., 1., 1.,  2]
2830
        for a, b in zip(alphas, betas):
2831
            pdf = st.weibull_min.pdf(x, a, scale=b)
2832
            plt.plot(x, pdf, label=r'$\alpha$ = {}, $\beta$ = {}'.format(a, b))
2833
        plt.xlabel('x', fontsize=12)
2834
        plt.ylabel('f(x)', fontsize=12)
2835
        plt.ylim(0, 2.5)
2836
        plt.legend(loc=1)
2837
        plt.show()
2838

2839
    ========  ====================================================
2840
    Support   :math:`x \in [0, \infty)`
2841
    Mean      :math:`\beta \Gamma(1 + \frac{1}{\alpha})`
2842
    Variance  :math:`\beta^2 \Gamma(1 + \frac{2}{\alpha} - \mu^2/\beta^2)`
2843
    ========  ====================================================
2844

2845
    Parameters
2846
    ----------
2847
    alpha : float
2848
        Shape parameter (alpha > 0).
2849
    beta : float
2850
        Scale parameter (beta > 0).
2851
    """
2852

2853
    def __init__(self, alpha, beta, *args, **kwargs):
8✔
2854
        super().__init__(*args, **kwargs)
3✔
2855
        self.alpha = alpha = tt.as_tensor_variable(floatX(alpha))
3✔
2856
        self.beta = beta = tt.as_tensor_variable(floatX(beta))
3✔
2857
        self.mean = beta * tt.exp(gammaln(1 + 1. / alpha))
3✔
2858
        self.median = beta * tt.exp(gammaln(tt.log(2)))**(1. / alpha)
3✔
2859
        self.variance = (beta**2) * \
3✔
2860
            tt.exp(gammaln(1 + 2. / alpha - self.mean**2))
2861
        self.mode = tt.switch(alpha >= 1,
3✔
2862
                              beta * ((alpha - 1)/alpha) ** (1 / alpha),
2863
                              0)  # Reference: https://en.wikipedia.org/wiki/Weibull_distribution
2864

2865
        assert_negative_support(alpha, 'alpha', 'Weibull')
3✔
2866
        assert_negative_support(beta, 'beta', 'Weibull')
3✔
2867

2868
    def random(self, point=None, size=None):
8✔
2869
        """
2870
        Draw random values from Weibull distribution.
2871

2872
        Parameters
2873
        ----------
2874
        point : dict, optional
2875
            Dict of variable values on which random values are to be
2876
            conditioned (uses default point if not specified).
2877
        size : int, optional
2878
            Desired size of random sample (returns one sample if not
2879
            specified).
2880

2881
        Returns
2882
        -------
2883
        array
2884
        """
2885
        alpha, beta = draw_values([self.alpha, self.beta],
2✔
2886
                                  point=point, size=size)
2887

2888
        def _random(a, b, size=None):
2✔
2889
            return b * (-np.log(np.random.uniform(size=size)))**(1 / a)
2✔
2890

2891
        return generate_samples(_random, alpha, beta,
2✔
2892
                                dist_shape=self.shape,
2893
                                size=size)
2894

2895
    def logp(self, value):
8✔
2896
        """
2897
        Calculate log-probability of Weibull distribution at specified value.
2898

2899
        Parameters
2900
        ----------
2901
        value : numeric
2902
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
2903
            values are desired the values must be provided in a numpy array or theano tensor
2904

2905
        Returns
2906
        -------
2907
        TensorVariable
2908
        """
2909
        alpha = self.alpha
3✔
2910
        beta = self.beta
3✔
2911
        return bound(tt.log(alpha) - tt.log(beta)
3✔
2912
                     + (alpha - 1) * tt.log(value / beta)
2913
                     - (value / beta)**alpha,
2914
                     value >= 0, alpha > 0, beta > 0)
2915

2916
    def _repr_latex_(self, name=None, dist=None):
8✔
2917
        if dist is None:
×
2918
            dist = self
×
2919
        beta = dist.beta
×
2920
        alpha = dist.alpha
×
2921
        name = r'\text{%s}' % name
×
2922
        return r'${} \sim \text{{Weibull}}(\mathit{{alpha}}={},~\mathit{{beta}}={})$'.format(name,
×
2923
                                                                get_variable_name(alpha),
2924
                                                                get_variable_name(beta))
2925

2926
    def logcdf(self, value):
8✔
2927
        """
2928
        Compute the log of the cumulative distribution function for Weibull distribution
2929
        at the specified value.
2930

2931
        References
2932
        ----------
2933
        .. [Machler2012] Martin Mächler (2012).
2934
            "Accurately computing `\log(1-\exp(- \mid a \mid))` Assessed by the Rmpfr
2935
            package"
2936

2937
        Parameters
2938
        ----------
2939
        value: numeric
2940
            Value(s) for which log CDF is calculated. If the log CDF for multiple
2941
            values are desired the values must be provided in a numpy array or theano tensor.
2942

2943
        Returns
2944
        -------
2945
        TensorVariable
2946
        """
2947
        alpha = self.alpha
×
2948
        beta = self.beta
×
2949
        a = (value / beta)**alpha
×
2950
        return tt.switch(
×
2951
            tt.le(value, 0.0),
2952
            -np.inf,
2953
            tt.switch(
2954
                tt.le(a, tt.log(2.0)),
2955
                tt.log(-tt.expm1(-a)),
2956
                tt.log1p(-tt.exp(-a)))
2957
        )
2958

2959

2960
class HalfStudentT(PositiveContinuous):
8✔
2961
    R"""
2962
    Half Student's T log-likelihood
2963

2964
    The pdf of this distribution is
2965

2966
    .. math::
2967

2968
        f(x \mid \sigma,\nu) =
2969
            \frac{2\;\Gamma\left(\frac{\nu+1}{2}\right)}
2970
            {\Gamma\left(\frac{\nu}{2}\right)\sqrt{\nu\pi\sigma^2}}
2971
            \left(1+\frac{1}{\nu}\frac{x^2}{\sigma^2}\right)^{-\frac{\nu+1}{2}}
2972

2973
    .. plot::
2974

2975
        import matplotlib.pyplot as plt
2976
        import numpy as np
2977
        import scipy.stats as st
2978
        plt.style.use('seaborn-darkgrid')
2979
        x = np.linspace(0, 5, 200)
2980
        sigmas = [1., 1., 2., 1.]
2981
        nus = [.5, 1., 1., 30.]
2982
        for sigma, nu in zip(sigmas, nus):
2983
            pdf = st.t.pdf(x, df=nu, loc=0, scale=sigma)
2984
            plt.plot(x, pdf, label=r'$\sigma$ = {}, $\nu$ = {}'.format(sigma, nu))
2985
        plt.xlabel('x', fontsize=12)
2986
        plt.ylabel('f(x)', fontsize=12)
2987
        plt.legend(loc=1)
2988
        plt.show()
2989

2990
    ========  ========================
2991
    Support   :math:`x \in [0, \infty)`
2992
    ========  ========================
2993

2994
    Parameters
2995
    ----------
2996
    nu : float
2997
        Degrees of freedom, also known as normality parameter (nu > 0).
2998
    sigma : float
2999
        Scale parameter (sigma > 0). Converges to the standard deviation as nu
3000
        increases. (only required if lam is not specified)
3001
    lam : float
3002
        Scale parameter (lam > 0). Converges to the precision as nu
3003
        increases. (only required if sigma is not specified)
3004

3005
    Examples
3006
    --------
3007
    .. code-block:: python
3008

3009
        # Only pass in one of lam or sigma, but not both.
3010
        with pm.Model():
3011
            x = pm.HalfStudentT('x', sigma=10, nu=10)
3012

3013
        with pm.Model():
3014
            x = pm.HalfStudentT('x', lam=4, nu=10)
3015
    """
3016

3017
    def __init__(self, nu=1, sigma=None, lam=None, sd=None,
8✔
3018
                 *args, **kwargs):
3019
        super().__init__(*args, **kwargs)
1✔
3020
        if sd is not None:
1✔
3021
            sigma = sd
×
3022

3023
        self.mode = tt.as_tensor_variable(0)
1✔
3024
        lam, sigma = get_tau_sigma(lam, sigma)
1✔
3025
        self.median = tt.as_tensor_variable(sigma)
1✔
3026
        self.sigma = self.sd = tt.as_tensor_variable(sigma)
1✔
3027
        self.lam = tt.as_tensor_variable(lam)
1✔
3028
        self.nu = nu = tt.as_tensor_variable(floatX(nu))
1✔
3029

3030
        assert_negative_support(sigma, 'sigma', 'HalfStudentT')
1✔
3031
        assert_negative_support(lam, 'lam', 'HalfStudentT')
1✔
3032
        assert_negative_support(nu, 'nu', 'HalfStudentT')
1✔
3033

3034
    def random(self, point=None, size=None):
8✔
3035
        """
3036
        Draw random values from HalfStudentT distribution.
3037

3038
        Parameters
3039
        ----------
3040
        point : dict, optional
3041
            Dict of variable values on which random values are to be
3042
            conditioned (uses default point if not specified).
3043
        size : int, optional
3044
            Desired size of random sample (returns one sample if not
3045
            specified).
3046

3047
        Returns
3048
        -------
3049
        array
3050
        """
3051
        nu, sigma = draw_values([self.nu, self.sigma], point=point, size=size)
×
3052
        return np.abs(generate_samples(stats.t.rvs, nu, loc=0, scale=sigma,
×
3053
                                       dist_shape=self.shape,
3054
                                       size=size))
3055

3056
    def logp(self, value):
8✔
3057
        """
3058
        Calculate log-probability of HalfStudentT distribution at specified value.
3059

3060
        Parameters
3061
        ----------
3062
        value : numeric
3063
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
3064
            values are desired the values must be provided in a numpy array or theano tensor
3065

3066
        Returns
3067
        -------
3068
        TensorVariable
3069
        """
3070
        nu = self.nu
1✔
3071
        sigma = self.sigma
1✔
3072
        lam = self.lam
1✔
3073

3074
        return bound(tt.log(2) + gammaln((nu + 1.0) / 2.0)
1✔
3075
                     - gammaln(nu / 2.0)
3076
                     - .5 * tt.log(nu * np.pi * sigma**2)
3077
                     - (nu + 1.0) / 2.0 * tt.log1p(value ** 2 / (nu * sigma**2)),
3078
                     sigma > 0, lam > 0, nu > 0, value >= 0)
3079

3080
    def _repr_latex_(self, name=None, dist=None):
8✔
3081
        if dist is None:
×
3082
            dist = self
×
3083
        nu = dist.nu
×
3084
        sigma = dist.sigma
×
3085
        name = r'\text{%s}' % name
×
3086
        return r'${} \sim \text{{HalfStudentT}}(\mathit{{nu}}={},~\mathit{{sigma}}={})$'.format(name,
×
3087
                                                                get_variable_name(nu),
3088
                                                                get_variable_name(sigma))
3089

3090

3091
class ExGaussian(Continuous):
8✔
3092
    R"""
3093
    Exponentially modified Gaussian log-likelihood.
3094

3095
    Results from the convolution of a normal distribution with an exponential
3096
    distribution.
3097

3098
    The pdf of this distribution is
3099

3100
    .. math::
3101

3102
       f(x \mid \mu, \sigma, \tau) =
3103
           \frac{1}{\nu}\;
3104
           \exp\left\{\frac{\mu-x}{\nu}+\frac{\sigma^2}{2\nu^2}\right\}
3105
           \Phi\left(\frac{x-\mu}{\sigma}-\frac{\sigma}{\nu}\right)
3106

3107
    where :math:`\Phi` is the cumulative distribution function of the
3108
    standard normal distribution.
3109

3110
    .. plot::
3111

3112
        import matplotlib.pyplot as plt
3113
        import numpy as np
3114
        import scipy.stats as st
3115
        plt.style.use('seaborn-darkgrid')
3116
        x = np.linspace(-6, 9, 200)
3117
        mus = [0., -2., 0., -3.]
3118
        sigmas = [1., 1., 3., 1.]
3119
        nus = [1., 1., 1., 4.]
3120
        for mu, sigma, nu in zip(mus, sigmas, nus):
3121
            pdf = st.exponnorm.pdf(x, nu/sigma, loc=mu, scale=sigma)
3122
            plt.plot(x, pdf, label=r'$\mu$ = {}, $\sigma$ = {}, $\nu$ = {}'.format(mu, sigma, nu))
3123
        plt.xlabel('x', fontsize=12)
3124
        plt.ylabel('f(x)', fontsize=12)
3125
        plt.legend(loc=1)
3126
        plt.show()
3127

3128
    ========  ========================
3129
    Support   :math:`x \in \mathbb{R}`
3130
    Mean      :math:`\mu + \nu`
3131
    Variance  :math:`\sigma^2 + \nu^2`
3132
    ========  ========================
3133

3134
    Parameters
3135
    ----------
3136
    mu : float
3137
        Mean of the normal distribution.
3138
    sigma : float
3139
        Standard deviation of the normal distribution (sigma > 0).
3140
    nu : float
3141
        Mean of the exponential distribution (nu > 0).
3142

3143
    References
3144
    ----------
3145
    .. [Rigby2005] Rigby R.A. and Stasinopoulos D.M. (2005).
3146
        "Generalized additive models for location, scale and shape"
3147
        Applied Statististics., 54, part 3, pp 507-554.
3148

3149
    .. [Lacouture2008] Lacouture, Y. and Couseanou, D. (2008).
3150
        "How to use MATLAB to fit the ex-Gaussian and other probability
3151
        functions to a distribution of response times".
3152
        Tutorials in Quantitative Methods for Psychology,
3153
        Vol. 4, No. 1, pp 35-45.
3154
    """
3155

3156
    def __init__(self, mu=0., sigma=None, nu=None, sd=None,
8✔
3157
                 *args, **kwargs):
3158
        super().__init__(*args, **kwargs)
3✔
3159

3160
        if sd is not None:
3✔
3161
            sigma = sd
×
3162

3163
        self.mu = mu = tt.as_tensor_variable(floatX(mu))
3✔
3164
        self.sigma = self.sd = sigma = tt.as_tensor_variable(floatX(sigma))
3✔
3165
        self.nu = nu = tt.as_tensor_variable(floatX(nu))
3✔
3166
        self.mean = mu + nu
3✔
3167
        self.variance = (sigma**2) + (nu**2)
3✔
3168

3169
        assert_negative_support(sigma, 'sigma', 'ExGaussian')
3✔
3170
        assert_negative_support(nu, 'nu', 'ExGaussian')
3✔
3171

3172
    def random(self, point=None, size=None):
8✔
3173
        """
3174
        Draw random values from ExGaussian distribution.
3175

3176
        Parameters
3177
        ----------
3178
        point : dict, optional
3179
            Dict of variable values on which random values are to be
3180
            conditioned (uses default point if not specified).
3181
        size : int, optional
3182
            Desired size of random sample (returns one sample if not
3183
            specified).
3184

3185
        Returns
3186
        -------
3187
        array
3188
        """
3189
        mu, sigma, nu = draw_values([self.mu, self.sigma, self.nu],
2✔
3190
                                    point=point, size=size)
3191

3192
        def _random(mu, sigma, nu, size=None):
2✔
3193
            return (np.random.normal(mu, sigma, size=size)
2✔
3194
                    + np.random.exponential(scale=nu, size=size))
3195

3196
        return generate_samples(_random, mu, sigma, nu,
2✔
3197
                                dist_shape=self.shape,
3198
                                size=size)
3199

3200
    def logp(self, value):
8✔
3201
        """
3202
        Calculate log-probability of ExGaussian distribution at specified value.
3203

3204
        Parameters
3205
        ----------
3206
        value : numeric
3207
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
3208
            values are desired the values must be provided in a numpy array or theano tensor
3209

3210
        Returns
3211
        -------
3212
        TensorVariable
3213
        """
3214
        mu = self.mu
3✔
3215
        sigma = self.sigma
3✔
3216
        nu = self.nu
3✔
3217

3218
        # This condition suggested by exGAUS.R from gamlss
3219
        lp = tt.switch(tt.gt(nu,  0.05 * sigma),
3✔
3220
                       - tt.log(nu) + (mu - value) / nu + 0.5 * (sigma / nu)**2
3221
                       + logpow(std_cdf((value - mu) / sigma - sigma / nu), 1.),
3222
                       - tt.log(sigma * tt.sqrt(2 * np.pi))
3223
                       - 0.5 * ((value - mu) / sigma)**2)
3224
        return bound(lp, sigma > 0., nu > 0.)
3✔
3225

3226
    def _repr_latex_(self, name=None, dist=None):
8✔
3227
        if dist is None:
×
3228
            dist = self
×
3229
        sigma = dist.sigma
×
3230
        mu = dist.mu
×
3231
        nu = dist.nu
×
3232
        name = r'\text{%s}' % name
×
3233
        return r'${} \sim \text{{ExGaussian}}(\mathit{{mu}}={},~\mathit{{sigma}}={},~\mathit{{nu}}={})$'.format(name,
×
3234
                                                                get_variable_name(mu),
3235
                                                                get_variable_name(sigma),
3236
                                                                get_variable_name(nu))
3237

3238
    def logcdf(self, value):
8✔
3239
        """
3240
        Compute the log of the cumulative distribution function for ExGaussian distribution
3241
        at the specified value.
3242

3243
        References
3244
        ----------
3245
        .. [Rigby2005] R.A. Rigby (2005).
3246
           "Generalized additive models for location, scale and shape"
3247
           https://doi.org/10.1111/j.1467-9876.2005.00510.x
3248

3249
        Parameters
3250
        ----------
3251
        value: numeric
3252
            Value(s) for which log CDF is calculated. If the log CDF for multiple
3253
            values are desired the values must be provided in a numpy array or theano tensor.
3254

3255
        Returns
3256
        -------
3257
        TensorVariable
3258
        """
3259
        mu = self.mu
1✔
3260
        sigma = self.sigma
1✔
3261
        sigma_2 = sigma**2
1✔
3262
        nu = self.nu
1✔
3263
        z = value - mu - sigma_2/nu
1✔
3264
        return tt.switch(
1✔
3265
            tt.gt(nu, 0.05 * sigma),
3266
            tt.log(std_cdf((value - mu)/sigma) -
3267
                   std_cdf(z/sigma) * tt.exp(
3268
                       ((mu + (sigma_2/nu))**2 -
3269
                        (mu**2) -
3270
                        2 * value * ((sigma_2)/nu))/(2 * sigma_2))),
3271
            normal_lcdf(mu, sigma, value))
3272

3273

3274
class VonMises(Continuous):
8✔
3275
    R"""
3276
    Univariate VonMises log-likelihood.
3277

3278
    The pdf of this distribution is
3279

3280
    .. math::
3281

3282
        f(x \mid \mu, \kappa) =
3283
            \frac{e^{\kappa\cos(x-\mu)}}{2\pi I_0(\kappa)}
3284

3285
    where :math:`I_0` is the modified Bessel function of order 0.
3286

3287
    .. plot::
3288

3289
        import matplotlib.pyplot as plt
3290
        import numpy as np
3291
        import scipy.stats as st
3292
        plt.style.use('seaborn-darkgrid')
3293
        x = np.linspace(-np.pi, np.pi, 200)
3294
        mus = [0., 0., 0.,  -2.5]
3295
        kappas = [.01, 0.5,  4., 2.]
3296
        for mu, kappa in zip(mus, kappas):
3297
            pdf = st.vonmises.pdf(x, kappa, loc=mu)
3298
            plt.plot(x, pdf, label=r'$\mu$ = {}, $\kappa$ = {}'.format(mu, kappa))
3299
        plt.xlabel('x', fontsize=12)
3300
        plt.ylabel('f(x)', fontsize=12)
3301
        plt.legend(loc=1)
3302
        plt.show()
3303

3304
    ========  ==========================================
3305
    Support   :math:`x \in [-\pi, \pi]`
3306
    Mean      :math:`\mu`
3307
    Variance  :math:`1-\frac{I_1(\kappa)}{I_0(\kappa)}`
3308
    ========  ==========================================
3309

3310
    Parameters
3311
    ----------
3312
    mu : float
3313
        Mean.
3314
    kappa : float
3315
        Concentration (\frac{1}{kappa} is analogous to \sigma^2).
3316
    """
3317

3318
    def __init__(self, mu=0.0, kappa=None, transform='circular',
8✔
3319
                 *args, **kwargs):
3320
        if transform == 'circular':
3✔
3321
            transform = transforms.Circular()
×
3322
        super().__init__(transform=transform, *args, **kwargs)
3✔
3323
        self.mean = self.median = self.mode = self.mu = mu = tt.as_tensor_variable(floatX(mu))
3✔
3324
        self.kappa = kappa = tt.as_tensor_variable(floatX(kappa))
3✔
3325

3326
        assert_negative_support(kappa, 'kappa', 'VonMises')
3✔
3327

3328
    def random(self, point=None, size=None):
8✔
3329
        """
3330
        Draw random values from VonMises distribution.
3331

3332
        Parameters
3333
        ----------
3334
        point : dict, optional
3335
            Dict of variable values on which random values are to be
3336
            conditioned (uses default point if not specified).
3337
        size : int, optional
3338
            Desired size of random sample (returns one sample if not
3339
            specified).
3340

3341
        Returns
3342
        -------
3343
        array
3344
        """
3345
        mu, kappa = draw_values([self.mu, self.kappa],
2✔
3346
                                point=point, size=size)
3347
        return generate_samples(stats.vonmises.rvs, loc=mu, kappa=kappa,
2✔
3348
                                dist_shape=self.shape,
3349
                                size=size)
3350

3351
    def logp(self, value):
8✔
3352
        """
3353
        Calculate log-probability of VonMises distribution at specified value.
3354

3355
        Parameters
3356
        ----------
3357
        value : numeric
3358
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
3359
            values are desired the values must be provided in a numpy array or theano tensor
3360

3361
        Returns
3362
        -------
3363
        TensorVariable
3364
        """
3365
        mu = self.mu
3✔
3366
        kappa = self.kappa
3✔
3367
        return bound(kappa * tt.cos(mu - value) - (tt.log(2 * np.pi) + log_i0(kappa)),
3✔
3368
                     kappa > 0, value >= -np.pi, value <= np.pi)
3369

3370
    def _repr_latex_(self, name=None, dist=None):
8✔
3371
        if dist is None:
×
3372
            dist = self
×
3373
        kappa = dist.kappa
×
3374
        mu = dist.mu
×
3375
        name = r'\text{%s}' % name
×
3376
        return r'${} \sim \text{{VonMises}}(\mathit{{mu}}={},~\mathit{{kappa}}={})$'.format(name,
×
3377
                                                                get_variable_name(mu),
3378
                                                                get_variable_name(kappa))
3379

3380

3381

3382
class SkewNormal(Continuous):
8✔
3383
    R"""
3384
    Univariate skew-normal log-likelihood.
3385

3386
     The pdf of this distribution is
3387

3388
    .. math::
3389

3390
       f(x \mid \mu, \tau, \alpha) =
3391
       2 \Phi((x-\mu)\sqrt{\tau}\alpha) \phi(x,\mu,\tau)
3392

3393
    .. plot::
3394

3395
        import matplotlib.pyplot as plt
3396
        import numpy as np
3397
        import scipy.stats as st
3398
        plt.style.use('seaborn-darkgrid')
3399
        x = np.linspace(-4, 4, 200)
3400
        for alpha in [-6, 0, 6]:
3401
            pdf = st.skewnorm.pdf(x, alpha, loc=0, scale=1)
3402
            plt.plot(x, pdf, label=r'$\mu$ = {}, $\sigma$ = {}, $\alpha$ = {}'.format(0, 1, alpha))
3403
        plt.xlabel('x', fontsize=12)
3404
        plt.ylabel('f(x)', fontsize=12)
3405
        plt.legend(loc=1)
3406
        plt.show()
3407

3408
    ========  ==========================================
3409
    Support   :math:`x \in \mathbb{R}`
3410
    Mean      :math:`\mu + \sigma \sqrt{\frac{2}{\pi}} \frac {\alpha }{{\sqrt {1+\alpha ^{2}}}}`
3411
    Variance  :math:`\sigma^2 \left(  1-\frac{2\alpha^2}{(\alpha^2+1) \pi} \right)`
3412
    ========  ==========================================
3413

3414
    Skew-normal distribution can be parameterized either in terms of precision
3415
    or standard deviation. The link between the two parametrizations is
3416
    given by
3417

3418
    .. math::
3419
       \tau = \dfrac{1}{\sigma^2}
3420

3421
    Parameters
3422
    ----------
3423
    mu : float
3424
        Location parameter.
3425
    sigma : float
3426
        Scale parameter (sigma > 0).
3427
    tau : float
3428
        Alternative scale parameter (tau > 0).
3429
    alpha : float
3430
        Skewness parameter.
3431

3432
    Notes
3433
    -----
3434
    When alpha=0 we recover the Normal distribution and mu becomes the mean,
3435
    tau the precision and sigma the standard deviation. In the limit of alpha
3436
    approaching plus/minus infinite we get a half-normal distribution.
3437

3438
    """
3439

3440
    def __init__(self, mu=0.0, sigma=None, tau=None, alpha=1, sd=None,
8✔
3441
                 *args, **kwargs):
3442
        super().__init__(*args, **kwargs)
3✔
3443

3444
        if sd is not None:
3✔
3445
            sigma = sd
×
3446

3447
        tau, sigma = get_tau_sigma(tau=tau, sigma=sigma)
3✔
3448
        self.mu = mu = tt.as_tensor_variable(floatX(mu))
3✔
3449
        self.tau = tt.as_tensor_variable(tau)
3✔
3450
        self.sigma = self.sd = tt.as_tensor_variable(sigma)
3✔
3451

3452
        self.alpha = alpha = tt.as_tensor_variable(floatX(alpha))
3✔
3453

3454
        self.mean = mu + self.sigma * (2 / np.pi)**0.5 * alpha / (1 + alpha**2)**0.5
3✔
3455
        self.variance = self.sigma**2 * (1 - (2 * alpha**2) / ((1 + alpha**2) * np.pi))
3✔
3456

3457
        assert_negative_support(tau, 'tau', 'SkewNormal')
3✔
3458
        assert_negative_support(sigma, 'sigma', 'SkewNormal')
3✔
3459

3460
    def random(self, point=None, size=None):
8✔
3461
        """
3462
        Draw random values from SkewNormal distribution.
3463

3464
        Parameters
3465
        ----------
3466
        point : dict, optional
3467
            Dict of variable values on which random values are to be
3468
            conditioned (uses default point if not specified).
3469
        size : int, optional
3470
            Desired size of random sample (returns one sample if not
3471
            specified).
3472

3473
        Returns
3474
        -------
3475
        array
3476
        """
3477
        mu, tau, _, alpha = draw_values(
2✔
3478
            [self.mu, self.tau, self.sigma, self.alpha], point=point, size=size)
3479
        return generate_samples(stats.skewnorm.rvs,
2✔
3480
                                a=alpha, loc=mu, scale=tau**-0.5,
3481
                                dist_shape=self.shape,
3482
                                size=size)
3483

3484
    def logp(self, value):
8✔
3485
        """
3486
        Calculate log-probability of SkewNormal distribution at specified value.
3487

3488
        Parameters
3489
        ----------
3490
        value : numeric
3491
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
3492
            values are desired the values must be provided in a numpy array or theano tensor
3493

3494
        Returns
3495
        -------
3496
        TensorVariable
3497
        """
3498
        tau = self.tau
3✔
3499
        sigma = self.sigma
3✔
3500
        mu = self.mu
3✔
3501
        alpha = self.alpha
3✔
3502
        return bound(
3✔
3503
            tt.log(1 +
3504
                   tt.erf(((value - mu) * tt.sqrt(tau) * alpha) / tt.sqrt(2)))
3505
            + (-tau * (value - mu)**2
3506
               + tt.log(tau / np.pi / 2.)) / 2.,
3507
            tau > 0, sigma > 0)
3508

3509
    def _repr_latex_(self, name=None, dist=None):
8✔
3510
        if dist is None:
×
3511
            dist = self
×
3512
        sigma = dist.sigma
×
3513
        mu = dist.mu
×
3514
        alpha = dist.alpha
×
3515
        name = r'\text{%s}' % name
×
3516
        return r'${} \sim \text{{Skew-Normal}}(\mathit{{mu}}={},~\mathit{{sigma}}={},~\mathit{{alpha}}={})$'.format(name,
×
3517
                                                                get_variable_name(mu),
3518
                                                                get_variable_name(sigma),
3519
                                                                get_variable_name(alpha))
3520

3521

3522
class Triangular(BoundedContinuous):
8✔
3523
    R"""
3524
    Continuous Triangular log-likelihood
3525

3526
    The pdf of this distribution is
3527

3528
    .. math::
3529

3530
       \begin{cases}
3531
         0 & \text{for } x < a, \\
3532
         \frac{2(x-a)}{(b-a)(c-a)} & \text{for } a \le x < c, \\[4pt]
3533
         \frac{2}{b-a}             & \text{for } x = c, \\[4pt]
3534
         \frac{2(b-x)}{(b-a)(b-c)} & \text{for } c < x \le b, \\[4pt]
3535
         0 & \text{for } b < x.
3536
        \end{cases}
3537

3538
    .. plot::
3539

3540
        import matplotlib.pyplot as plt
3541
        import numpy as np
3542
        import scipy.stats as st
3543
        plt.style.use('seaborn-darkgrid')
3544
        x = np.linspace(-2, 10, 500)
3545
        lowers = [0., -1, 2]
3546
        cs = [2., 0., 6.5]
3547
        uppers = [4., 1, 8]
3548
        for lower, c, upper in zip(lowers, cs, uppers):
3549
            scale = upper - lower
3550
            c_ = (c - lower) / scale
3551
            pdf = st.triang.pdf(x, loc=lower, c=c_, scale=scale)
3552
            plt.plot(x, pdf, label='lower = {}, c = {}, upper = {}'.format(lower,
3553
                                                                           c,
3554
                                                                           upper))
3555
        plt.xlabel('x', fontsize=12)
3556
        plt.ylabel('f(x)', fontsize=12)
3557
        plt.legend(loc=1)
3558
        plt.show()
3559

3560
    ========  ============================================================================
3561
    Support   :math:`x \in [lower, upper]`
3562
    Mean      :math:`\dfrac{lower + upper + c}{3}`
3563
    Variance  :math:`\dfrac{upper^2 + lower^2 +c^2 - lower*upper - lower*c - upper*c}{18}`
3564
    ========  ============================================================================
3565

3566
    Parameters
3567
    ----------
3568
    lower : float
3569
        Lower limit.
3570
    c: float
3571
        mode
3572
    upper : float
3573
        Upper limit.
3574
    """
3575

3576
    def __init__(self, lower=0, upper=1, c=0.5,
8✔
3577
                 *args, **kwargs):
3578
        self.median = self.mean = self.c = c = tt.as_tensor_variable(floatX(c))
3✔
3579
        self.lower = lower = tt.as_tensor_variable(floatX(lower))
3✔
3580
        self.upper = upper = tt.as_tensor_variable(floatX(upper))
3✔
3581

3582
        super().__init__(lower=lower, upper=upper, *args, **kwargs)
3✔
3583

3584
    def random(self, point=None, size=None):
8✔
3585
        """
3586
        Draw random values from Triangular distribution.
3587

3588
        Parameters
3589
        ----------
3590
        point : dict, optional
3591
            Dict of variable values on which random values are to be
3592
            conditioned (uses default point if not specified).
3593
        size : int, optional
3594
            Desired size of random sample (returns one sample if not
3595
            specified).
3596

3597
        Returns
3598
        -------
3599
        array
3600
        """
3601
        c, lower, upper = draw_values([self.c, self.lower, self.upper],
2✔
3602
                                      point=point, size=size)
3603
        return generate_samples(self._random, c=c, lower=lower, upper=upper,
2✔
3604
                                size=size, dist_shape=self.shape)
3605

3606
    def _random(self, c, lower, upper, size):
8✔
3607
        """ Wrapper around stats.triang.rvs that converts Triangular's
3608
        parametrization to scipy.triang. All parameter arrays should have
3609
        been broadcasted properly by generate_samples at this point and size is
3610
        the scipy.rvs representation.
3611
        """
3612
        scale = upper - lower
2✔
3613
        return stats.triang.rvs(
2✔
3614
            c=(c - lower) / scale,
3615
            loc=lower,
3616
            scale=scale,
3617
            size=size,
3618
        )
3619

3620
    def logp(self, value):
8✔
3621
        """
3622
        Calculate log-probability of Triangular distribution at specified value.
3623

3624
        Parameters
3625
        ----------
3626
        value : numeric
3627
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
3628
            values are desired the values must be provided in a numpy array or theano tensor
3629

3630
        Returns
3631
        -------
3632
        TensorVariable
3633
        """
3634
        c = self.c
3✔
3635
        lower = self.lower
3✔
3636
        upper = self.upper
3✔
3637
        return tt.switch(alltrue_elemwise([lower <= value, value < c]),
3✔
3638
                         tt.log(2 * (value - lower) / ((upper - lower) * (c - lower))),
3639
                         tt.switch(tt.eq(value, c),
3640
                                   tt.log(2 / (upper - lower)),
3641
                                   tt.switch(alltrue_elemwise([c < value, value <= upper]),
3642
                                             tt.log(2 * (upper - value) / ((upper - lower) * (upper - c))),
3643
                                             np.inf)))
3644

3645
    def _repr_latex_(self, name=None, dist=None):
8✔
3646
        if dist is None:
×
3647
            dist = self
×
3648
        lower = dist.lower
×
3649
        upper = dist.upper
×
3650
        c = dist.c
×
3651
        name = r'\text{%s}' % name
×
3652
        return r'${} \sim \text{{Triangular}}(\mathit{{c}}={},~\mathit{{lower}}={},~\mathit{{upper}}={})$'.format(name,
×
3653
                                                                get_variable_name(c),
3654
                                                                get_variable_name(lower),
3655
                                                                get_variable_name(upper))
3656

3657
    def logcdf(self, value):
8✔
3658
        """
3659
        Compute the log of the cumulative distribution function for Triangular distribution
3660
        at the specified value.
3661

3662
        Parameters
3663
        ----------
3664
        value: numeric
3665
            Value(s) for which log CDF is calculated. If the log CDF for multiple
3666
            values are desired the values must be provided in a numpy array or theano tensor.
3667

3668
        Returns
3669
        -------
3670
        TensorVariable
3671
        """
3672
        l = self.lower
1✔
3673
        u = self.upper
1✔
3674
        c = self.c
1✔
3675
        return tt.switch(
1✔
3676
            tt.le(value, l),
3677
            -np.inf,
3678
            tt.switch(
3679
                tt.le(value, c),
3680
                tt.log(((value - l) ** 2) / ((u - l) * (c - l))),
3681
                tt.switch(
3682
                    tt.lt(value, u),
3683
                    tt.log1p(-((u - value) ** 2) / ((u - l) * (u - c))),
3684
                    0
3685
                )
3686
            )
3687
        )
3688

3689

3690
class Gumbel(Continuous):
8✔
3691
    R"""
3692
        Univariate Gumbel log-likelihood
3693

3694
    The pdf of this distribution is
3695

3696
    .. math::
3697

3698
       f(x \mid \mu, \beta) = \frac{1}{\beta}e^{-(z + e^{-z})}
3699

3700
    where
3701

3702
    .. math::
3703

3704
        z = \frac{x - \mu}{\beta}.
3705

3706
    .. plot::
3707

3708
        import matplotlib.pyplot as plt
3709
        import numpy as np
3710
        import scipy.stats as st
3711
        plt.style.use('seaborn-darkgrid')
3712
        x = np.linspace(-10, 20, 200)
3713
        mus = [0., 4., -1.]
3714
        betas = [2., 2., 4.]
3715
        for mu, beta in zip(mus, betas):
3716
            pdf = st.gumbel_r.pdf(x, loc=mu, scale=beta)
3717
            plt.plot(x, pdf, label=r'$\mu$ = {}, $\beta$ = {}'.format(mu, beta))
3718
        plt.xlabel('x', fontsize=12)
3719
        plt.ylabel('f(x)', fontsize=12)
3720
        plt.legend(loc=1)
3721
        plt.show()
3722

3723

3724
    ========  ==========================================
3725
    Support   :math:`x \in \mathbb{R}`
3726
    Mean      :math:`\mu + \beta\gamma`, where :math:`\gamma` is the Euler-Mascheroni constant
3727
    Variance  :math:`\frac{\pi^2}{6} \beta^2`
3728
    ========  ==========================================
3729

3730
    Parameters
3731
    ----------
3732
    mu : float
3733
        Location parameter.
3734
    beta : float
3735
        Scale parameter (beta > 0).
3736
    """
3737

3738
    def __init__(self, mu=0, beta=1.0, **kwargs):
8✔
3739
        self.mu = tt.as_tensor_variable(floatX(mu))
3✔
3740
        self.beta = tt.as_tensor_variable(floatX(beta))
3✔
3741

3742
        assert_negative_support(beta, 'beta', 'Gumbel')
3✔
3743

3744
        self.mean = self.mu + self.beta * np.euler_gamma
3✔
3745
        self.median = self.mu - self.beta * tt.log(tt.log(2))
3✔
3746
        self.mode = self.mu
3✔
3747
        self.variance = (np.pi ** 2 / 6.0) * self.beta ** 2
3✔
3748

3749
        super().__init__(**kwargs)
3✔
3750

3751
    def random(self, point=None, size=None):
8✔
3752
        """
3753
        Draw random values from Gumbel distribution.
3754

3755
        Parameters
3756
        ----------
3757
        point : dict, optional
3758
            Dict of variable values on which random values are to be
3759
            conditioned (uses default point if not specified).
3760
        size : int, optional
3761
            Desired size of random sample (returns one sample if not
3762
            specified).
3763

3764
        Returns
3765
        -------
3766
        array
3767
        """
3768
        mu, sigma = draw_values([self.mu, self.beta], point=point, size=size)
2✔
3769
        return generate_samples(stats.gumbel_r.rvs, loc=mu, scale=sigma,
2✔
3770
                                dist_shape=self.shape,
3771
                                size=size)
3772

3773
    def logp(self, value):
8✔
3774
        """
3775
        Calculate log-probability of Gumbel distribution at specified value.
3776

3777
        Parameters
3778
        ----------
3779
        value : numeric
3780
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
3781
            values are desired the values must be provided in a numpy array or theano tensor
3782

3783
        Returns
3784
        -------
3785
        TensorVariable
3786
        """
3787
        scaled = (value - self.mu) / self.beta
3✔
3788
        return bound(-scaled - tt.exp(-scaled) - tt.log(self.beta), self.beta > 0)
3✔
3789

3790
    def _repr_latex_(self, name=None, dist=None):
8✔
3791
        if dist is None:
×
3792
            dist = self
×
3793
        beta = dist.beta
×
3794
        mu = dist.mu
×
3795
        name = r'\text{%s}' % name
×
3796
        return r'${} \sim \text{{Gumbel}}(\mathit{{mu}}={},~\mathit{{beta}}={})$'.format(name,
×
3797
                                                                get_variable_name(mu),
3798
                                                                get_variable_name(beta))
3799

3800
    def logcdf(self, value):
8✔
3801
        """
3802
        Compute the log of the cumulative distribution function for Gumbel distribution
3803
        at the specified value.
3804

3805
        Parameters
3806
        ----------
3807
        value: numeric
3808
            Value(s) for which log CDF is calculated. If the log CDF for multiple
3809
            values are desired the values must be provided in a numpy array or theano tensor.
3810

3811
        Returns
3812
        -------
3813
        TensorVariable
3814
        """
3815
        beta = self.beta
1✔
3816
        mu = self.mu
1✔
3817

3818
        return -tt.exp(-(value - mu)/beta)
1✔
3819

3820

3821
class Rice(PositiveContinuous):
8✔
3822
    R"""
3823
    Rice distribution.
3824

3825
    .. math::
3826

3827
       f(x\mid \nu ,\sigma )=
3828
       {\frac  {x}{\sigma ^{2}}}\exp
3829
       \left({\frac  {-(x^{2}+\nu ^{2})}{2\sigma ^{2}}}\right)I_{0}\left({\frac  {x\nu }{\sigma ^{2}}}\right),
3830

3831
    ========  ==============================================================
3832
    Support   :math:`x \in (0, \infty)`
3833
    Mean      :math:`\sigma {\sqrt  {\pi /2}}\,\,L_{{1/2}}(-\nu ^{2}/2\sigma ^{2})`
3834
    Variance  :math:`2\sigma ^{2}+\nu ^{2}-{\frac  {\pi \sigma ^{2}}{2}}L_{{1/2}}^{2}\left({\frac  {-\nu ^{2}}{2\sigma ^{2}}}\right)`
3835
    ========  ==============================================================
3836

3837

3838
    Parameters
3839
    ----------
3840
    nu : float
3841
        noncentrality parameter.
3842
    sigma : float
3843
        scale parameter.
3844
    b : float
3845
        shape parameter (alternative to nu).
3846

3847
    Notes
3848
    -----
3849
    The distribution :math:`\mathrm{Rice}\left(|\nu|,\sigma\right)` is the
3850
    distribution of :math:`R=\sqrt{X^2+Y^2}` where :math:`X\sim N(\nu \cos{\theta}, \sigma^2)`,
3851
    :math:`Y\sim N(\nu \sin{\theta}, \sigma^2)` are independent and for any
3852
    real :math:`\theta`.
3853

3854
    The distribution is defined with either nu or b.
3855
    The link between the two parametrizations is given by
3856

3857
    .. math::
3858

3859
       b = \dfrac{\nu}{\sigma}
3860

3861
    """
3862

3863
    def __init__(self, nu=None, sigma=None, b=None, sd=None, *args, **kwargs):
8✔
3864
        super().__init__(*args, **kwargs)
3✔
3865
        if sd is not None:
3✔
3866
            sigma = sd
×
3867

3868
        nu, b, sigma = self.get_nu_b(nu, b, sigma)
3✔
3869
        self.nu = nu = tt.as_tensor_variable(floatX(nu))
3✔
3870
        self.sigma = self.sd = sigma = tt.as_tensor_variable(floatX(sigma))
3✔
3871
        self.b = b = tt.as_tensor_variable(floatX(b))
3✔
3872
        self.mean = sigma * np.sqrt(np.pi / 2) * tt.exp((-nu**2 / (2 * sigma**2)) / 2) * ((1 - (-nu**2 / (2 * sigma**2)))
3✔
3873
                                 * tt.i0(-(-nu**2 / (2 * sigma**2)) / 2) - (-nu**2 / (2 * sigma**2)) * tt.i1(-(-nu**2 / (2 * sigma**2)) / 2))
3874
        self.variance = 2 * sigma**2 + nu**2 - (np.pi * sigma**2 / 2) * (tt.exp((-nu**2 / (2 * sigma**2)) / 2) * ((1 - (-nu**2 / (
3✔
3875
            2 * sigma**2))) * tt.i0(-(-nu**2 / (2 * sigma**2)) / 2) - (-nu**2 / (2 * sigma**2)) * tt.i1(-(-nu**2 / (2 * sigma**2)) / 2)))**2
3876

3877
    def get_nu_b(self, nu, b, sigma):
8✔
3878
        if sigma is None:
3✔
3879
            sigma = 1.
×
3880
        if nu is None and b is not None:
3✔
3881
            nu = b * sigma
1✔
3882
            return nu, b, sigma
1✔
3883
        elif nu is not None and b is None:
3✔
3884
            b = nu / sigma
3✔
3885
            return nu, b, sigma
3✔
3886
        raise ValueError('Rice distribution must specify either nu'
×
3887
                         ' or b.')
3888

3889
    def random(self, point=None, size=None):
8✔
3890
        """
3891
        Draw random values from Rice distribution.
3892

3893
        Parameters
3894
        ----------
3895
        point : dict, optional
3896
            Dict of variable values on which random values are to be
3897
            conditioned (uses default point if not specified).
3898
        size : int, optional
3899
            Desired size of random sample (returns one sample if not
3900
            specified).
3901

3902
        Returns
3903
        -------
3904
        array
3905
        """
3906
        nu, sigma = draw_values([self.nu, self.sigma],
2✔
3907
                             point=point, size=size)
3908
        return generate_samples(self._random, nu=nu, sigma=sigma,
2✔
3909
                                dist_shape=self.shape, size=size)
3910

3911
    def _random(self, nu, sigma, size):
8✔
3912
        """ Wrapper around stats.rice.rvs that converts Rice's
3913
        parametrization to scipy.rice. All parameter arrays should have
3914
        been broadcasted properly by generate_samples at this point and size is
3915
        the scipy.rvs representation.
3916
        """
3917
        return stats.rice.rvs(
2✔
3918
            b=nu / sigma,
3919
            scale=sigma,
3920
            size=size,
3921
        )
3922

3923
    def logp(self, value):
8✔
3924
        """
3925
        Calculate log-probability of Rice distribution at specified value.
3926

3927
        Parameters
3928
        ----------
3929
        value : numeric
3930
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
3931
            values are desired the values must be provided in a numpy array or theano tensor
3932

3933
        Returns
3934
        -------
3935
        TensorVariable
3936
        """
3937
        nu = self.nu
3✔
3938
        sigma = self.sigma
3✔
3939
        b = self.b
3✔
3940
        x = value / sigma
3✔
3941
        return bound(tt.log(x * tt.exp((-(x - b) * (x - b)) / 2) * i0e(x * b) / sigma),
3✔
3942
                     sigma >= 0,
3943
                     nu >= 0,
3944
                     value > 0,
3945
                     )
3946

3947

3948
class Logistic(Continuous):
8✔
3949
    R"""
3950
    Logistic log-likelihood.
3951

3952
    The pdf of this distribution is
3953

3954
    .. math::
3955

3956
       f(x \mid \mu, s) =
3957
           \frac{\exp\left(-\frac{x - \mu}{s}\right)}{s \left(1 + \exp\left(-\frac{x - \mu}{s}\right)\right)^2}
3958

3959
    .. plot::
3960

3961
        import matplotlib.pyplot as plt
3962
        import numpy as np
3963
        import scipy.stats as st
3964
        plt.style.use('seaborn-darkgrid')
3965
        x = np.linspace(-5, 5, 200)
3966
        mus = [0., 0., 0., -2.]
3967
        ss = [.4, 1., 2., .4]
3968
        for mu, s in zip(mus, ss):
3969
            pdf = st.logistic.pdf(x, loc=mu, scale=s)
3970
            plt.plot(x, pdf, label=r'$\mu$ = {}, $s$ = {}'.format(mu, s))
3971
        plt.xlabel('x', fontsize=12)
3972
        plt.ylabel('f(x)', fontsize=12)
3973
        plt.legend(loc=1)
3974
        plt.show()
3975

3976
    ========  ==========================================
3977
    Support   :math:`x \in \mathbb{R}`
3978
    Mean      :math:`\mu`
3979
    Variance  :math:`\frac{s^2 \pi^2}{3}`
3980
    ========  ==========================================
3981

3982

3983
    Parameters
3984
    ----------
3985
    mu : float
3986
        Mean.
3987
    s : float
3988
        Scale (s > 0).
3989
    """
3990

3991
    def __init__(self, mu=0., s=1., *args, **kwargs):
8✔
3992
        super().__init__(*args, **kwargs)
3✔
3993

3994
        self.mu = tt.as_tensor_variable(floatX(mu))
3✔
3995
        self.s = tt.as_tensor_variable(floatX(s))
3✔
3996

3997
        self.mean = self.mode = mu
3✔
3998
        self.variance = s**2 * np.pi**2 / 3.
3✔
3999

4000
    def logp(self, value):
8✔
4001
        """
4002
        Calculate log-probability of Logistic distribution at specified value.
4003

4004
        Parameters
4005
        ----------
4006
        value : numeric
4007
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
4008
            values are desired the values must be provided in a numpy array or theano tensor
4009

4010
        Returns
4011
        -------
4012
        TensorVariable
4013
        """
4014
        mu = self.mu
3✔
4015
        s = self.s
3✔
4016

4017
        return bound(
3✔
4018
            -(value - mu) / s - tt.log(s) - 2 * tt.log1p(tt.exp(-(value - mu) / s)), s > 0)
4019

4020
    def random(self, point=None, size=None):
8✔
4021
        """
4022
        Draw random values from Logistic distribution.
4023

4024
        Parameters
4025
        ----------
4026
        point : dict, optional
4027
            Dict of variable values on which random values are to be
4028
            conditioned (uses default point if not specified).
4029
        size : int, optional
4030
            Desired size of random sample (returns one sample if not
4031
            specified).
4032

4033
        Returns
4034
        -------
4035
        array
4036
        """
4037
        mu, s = draw_values([self.mu, self.s], point=point, size=size)
2✔
4038

4039
        return generate_samples(
2✔
4040
            stats.logistic.rvs,
4041
            loc=mu, scale=s,
4042
            dist_shape=self.shape,
4043
            size=size)
4044

4045
    def _repr_latex_(self, name=None, dist=None):
8✔
4046
        if dist is None:
×
4047
            dist = self
×
4048
        mu = dist.mu
×
4049
        s = dist.s
×
4050
        name = r'\text{%s}' % name
×
4051
        return r'${} \sim \text{{Logistic}}(\mathit{{mu}}={},~\mathit{{s}}={})$'.format(name,
×
4052
                                                                get_variable_name(mu),
4053
                                                                get_variable_name(s))
4054

4055
    def logcdf(self, value):
8✔
4056
        """
4057
        Compute the log of the cumulative distribution function for Logistic distribution
4058
        at the specified value.
4059

4060
        References
4061
        ----------
4062
        .. [Machler2012] Martin Mächler (2012).
4063
            "Accurately computing :math:  `\log(1-\exp(- \mid a \mid<))` Assessed by the Rmpfr
4064
            package"
4065

4066
        Parameters
4067
        ----------
4068
        value: numeric
4069
            Value(s) for which log CDF is calculated. If the log CDF for multiple
4070
            values are desired the values must be provided in a numpy array or theano tensor.
4071

4072
        Returns
4073
        -------
4074
        TensorVariable
4075
        """
4076
        mu = self.mu
1✔
4077
        s = self.s
1✔
4078
        a = -(value - mu)/s
1✔
4079
        return - tt.switch(
1✔
4080
            tt.le(a, -37),
4081
            tt.exp(a),
4082
            tt.switch(
4083
                tt.le(a, 18),
4084
                tt.log1p(tt.exp(a)),
4085
                tt.switch(
4086
                    tt.le(a, 33.3),
4087
                    tt.exp(-a) + a,
4088
                    a)))
4089

4090

4091
class LogitNormal(UnitContinuous):
8✔
4092
    R"""
4093
    Logit-Normal log-likelihood.
4094

4095
    The pdf of this distribution is
4096

4097
    .. math::
4098
       f(x \mid \mu, \tau) =
4099
           \frac{1}{x(1-x)} \sqrt{\frac{\tau}{2\pi}}
4100
           \exp\left\{ -\frac{\tau}{2} (logit(x)-\mu)^2 \right\}
4101

4102

4103
    .. plot::
4104

4105
        import matplotlib.pyplot as plt
4106
        import numpy as np
4107
        import scipy.stats as st
4108
        from scipy.special import logit
4109
        plt.style.use('seaborn-darkgrid')
4110
        x = np.linspace(0.0001, 0.9999, 500)
4111
        mus = [0., 0., 0., 1.]
4112
        sigmas = [0.3, 1., 2., 1.]
4113
        for mu, sigma in  zip(mus, sigmas):
4114
            pdf = st.norm.pdf(logit(x), loc=mu, scale=sigma) * 1/(x * (1-x))
4115
            plt.plot(x, pdf, label=r'$\mu$ = {}, $\sigma$ = {}'.format(mu, sigma))
4116
            plt.legend(loc=1)
4117
        plt.show()
4118

4119
    ========  ==========================================
4120
    Support   :math:`x \in (0, 1)`
4121
    Mean      no analytical solution
4122
    Variance  no analytical solution
4123
    ========  ==========================================
4124

4125
    Parameters
4126
    ----------
4127
    mu : float
4128
        Location parameter.
4129
    sigma : float
4130
        Scale parameter (sigma > 0).
4131
    tau : float
4132
        Scale parameter (tau > 0).
4133
    """
4134

4135
    def __init__(self, mu=0, sigma=None, tau=None, sd=None, **kwargs):
8✔
4136
        if sd is not None:
3✔
4137
            sigma = sd
×
4138
        self.mu = mu = tt.as_tensor_variable(floatX(mu))
3✔
4139
        tau, sigma = get_tau_sigma(tau=tau, sigma=sigma)
3✔
4140
        self.sigma = self.sd = tt.as_tensor_variable(sigma)
3✔
4141
        self.tau = tau = tt.as_tensor_variable(tau)
3✔
4142

4143
        self.median = invlogit(mu)
3✔
4144
        assert_negative_support(sigma, 'sigma', 'LogitNormal')
3✔
4145
        assert_negative_support(tau, 'tau', 'LogitNormal')
3✔
4146

4147
        super().__init__(**kwargs)
3✔
4148

4149
    def random(self, point=None, size=None):
8✔
4150
        """
4151
        Draw random values from LogitNormal distribution.
4152

4153
        Parameters
4154
        ----------
4155
        point : dict, optional
4156
            Dict of variable values on which random values are to be
4157
            conditioned (uses default point if not specified).
4158
        size : int, optional
4159
            Desired size of random sample (returns one sample if not
4160
            specified).
4161

4162
        Returns
4163
        -------
4164
        array
4165
        """
4166
        mu, _, sigma = draw_values(
2✔
4167
            [self.mu, self.tau, self.sigma], point=point, size=size)
4168
        return expit(generate_samples(stats.norm.rvs, loc=mu, scale=sigma, dist_shape=self.shape,
2✔
4169
                                      size=size))
4170

4171
    def logp(self, value):
8✔
4172
        """
4173
        Calculate log-probability of LogitNormal distribution at specified value.
4174

4175
        Parameters
4176
        ----------
4177
        value : numeric
4178
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
4179
            values are desired the values must be provided in a numpy array or theano tensor
4180

4181
        Returns
4182
        -------
4183
        TensorVariable
4184
        """
4185
        sigma = self.sigma
3✔
4186
        mu = self.mu
3✔
4187
        tau = self.tau
3✔
4188
        return bound(-0.5 * tau * (logit(value) - mu) ** 2
3✔
4189
                     + 0.5 * tt.log(tau / (2. * np.pi))
4190
                     - tt.log(value * (1 - value)), value > 0, value < 1, tau > 0)
4191

4192
    def _repr_latex_(self, name=None, dist=None):
8✔
4193
        if dist is None:
×
4194
            dist = self
×
4195
        sigma = dist.sigma
×
4196
        mu = dist.mu
×
4197
        name = r'\text{%s}' % name
×
4198
        return r'${} \sim \text{{LogitNormal}}(\mathit{{mu}}={},~\mathit{{sigma}}={})$'.format(name,
×
4199
                                                                get_variable_name(mu),
4200
                                                                get_variable_name(sigma))
4201

4202

4203
class Interpolated(BoundedContinuous):
8✔
4204
    R"""
4205
    Univariate probability distribution defined as a linear interpolation
4206
    of probability density function evaluated on some lattice of points.
4207

4208
    The lattice can be uneven, so the steps between different points can have
4209
    different size and it is possible to vary the precision between regions
4210
    of the support.
4211

4212
    The probability density function values don not have to be normalized, as the
4213
    interpolated density is any way normalized to make the total probability
4214
    equal to $1$.
4215

4216
    Both parameters ``x_points`` and values ``pdf_points`` are not variables, but
4217
    plain array-like objects, so they are constant and cannot be sampled.
4218

4219
    ========  ===========================================
4220
    Support   :math:`x \in [x\_points[0], x\_points[-1]]`
4221
    ========  ===========================================
4222

4223
    Parameters
4224
    ----------
4225
    x_points : array-like
4226
        A monotonically growing list of values
4227
    pdf_points : array-like
4228
        Probability density function evaluated on lattice ``x_points``
4229
    """
4230

4231
    def __init__(self, x_points, pdf_points, *args, **kwargs):
8✔
4232
        self.lower = lower = tt.as_tensor_variable(x_points[0])
3✔
4233
        self.upper = upper = tt.as_tensor_variable(x_points[-1])
3✔
4234

4235
        super().__init__(lower=lower, upper=upper, *args, **kwargs)
3✔
4236

4237
        interp = InterpolatedUnivariateSpline(
3✔
4238
            x_points, pdf_points, k=1, ext='zeros')
4239
        Z = interp.integral(x_points[0], x_points[-1])
3✔
4240

4241
        self.Z = tt.as_tensor_variable(Z)
3✔
4242
        self.interp_op = SplineWrapper(interp)
3✔
4243
        self.x_points = x_points
3✔
4244
        self.pdf_points = pdf_points / Z
3✔
4245
        self.cdf_points = interp.antiderivative()(x_points) / Z
3✔
4246

4247
        self.median = self._argcdf(0.5)
3✔
4248

4249
    def _argcdf(self, p):
8✔
4250
        pdf = self.pdf_points
3✔
4251
        cdf = self.cdf_points
3✔
4252
        x = self.x_points
3✔
4253

4254
        index = np.searchsorted(cdf, p) - 1
3✔
4255
        slope = (pdf[index + 1] - pdf[index]) / (x[index + 1] - x[index])
3✔
4256

4257
        return x[index] + np.where(
3✔
4258
            np.abs(slope) <= 1e-8,
4259
            np.where(
4260
                np.abs(pdf[index]) <= 1e-8,
4261
                np.zeros(index.shape),
4262
                (p - cdf[index]) / pdf[index]
4263
            ),
4264
            (-pdf[index] + np.sqrt(pdf[index] ** 2 + 2 * slope * (p - cdf[index]))) / slope
4265
        )
4266

4267
    def _random(self, size=None):
8✔
4268
        return self._argcdf(np.random.uniform(size=size))
×
4269

4270
    def random(self, size=None):
8✔
4271
        """
4272
        Draw random values from Interpolated distribution.
4273

4274
        Parameters
4275
        ----------
4276
        size : int, optional
4277
            Desired size of random sample (returns one sample if not
4278
            specified).
4279

4280
        Returns
4281
        -------
4282
        array
4283
        """
4284
        return generate_samples(self._random,
×
4285
                                dist_shape=self.shape,
4286
                                size=size)
4287

4288
    def logp(self, value):
8✔
4289
        """
4290
        Calculate log-probability of Interpolated distribution at specified value.
4291

4292
        Parameters
4293
        ----------
4294
        value : numeric
4295
            Value(s) for which log-probability is calculated. If the log probabilities for multiple
4296
            values are desired the values must be provided in a numpy array or theano tensor
4297

4298
        Returns
4299
        -------
4300
        TensorVariable
4301
        """
4302
        return tt.log(self.interp_op(value) / self.Z)
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc