From c0707404f1749439a0a5a0ff60a299042feb762e Mon Sep 17 00:00:00 2001 From: Chris Fonnesbeck Date: Sat, 22 Oct 2016 15:13:54 -0500 Subject: [PATCH 1/2] Added warnings for non-positive support in positive parameters for continuous distributions --- pymc3/distributions/continuous.py | 60 ++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/pymc3/distributions/continuous.py b/pymc3/distributions/continuous.py index bf6ce60ab5..346325d016 100644 --- a/pymc3/distributions/continuous.py +++ b/pymc3/distributions/continuous.py @@ -38,6 +38,23 @@ def __init__(self, transform=transforms.logodds, *args, **kwargs): super(UnitContinuous, self).__init__( transform=transform, *args, **kwargs) +def assert_nonpos_support(var, label): + # Checks for evidence of positive support for a variable + try: + # Transformed distribution + support = np.isfinite(var.transformed.distribution.dist.logp(0).tag.test_value) + except AttributeError: + try: + # Untransformed distribution + support = np.isfinite(var.distribution.logp(0).tag.test_value) + except AttributeError: + # Otherwise no direct evidence of non-positive support + support = False + + if np.any(support): + warnings.warn(('The variable specified for {} has non-positive support,'.format(label), + 'likely making it unsuitable for this parameter.')) + def get_tau_sd(tau=None, sd=None): """ @@ -185,7 +202,7 @@ def __init__(self, *args, **kwargs): # called to display a warning we have to fetch the args and # kwargs manually. After a certain period we should revert # back to the old calling signature. - + if len(args) == 1: mu = args[0] sd = kwargs.pop('sd', None) @@ -205,6 +222,8 @@ def __init__(self, *args, **kwargs): self.mean = self.median = self.mode = self.mu = mu self.tau, self.sd = get_tau_sd(tau=tau, sd=sd) self.variance = 1. / self.tau + + assert_nonpos_support(self.tau, 'tau (sd)') super(Normal, self).__init__(**kwargs) @@ -252,6 +271,8 @@ def __init__(self, sd=None, tau=None, *args, **kwargs): self.tau, self.sd = get_tau_sd(tau=tau, sd=sd) self.mean = tt.sqrt(2 / (np.pi * self.tau)) self.variance = (1. - 2 / np.pi) / self.tau + + assert_nonpos_support(self.tau, 'tau (sd)') def random(self, point=None, size=None, repeat=None): sd = draw_values([self.sd], point=point) @@ -331,6 +352,8 @@ def __init__(self, mu=None, lam=None, phi=None, alpha=0., *args, **kwargs): self.mode = self.mu * (tt.sqrt(1. + (1.5 * self.mu / self.lam)**2) - 1.5 * self.mu / self.lam) + alpha self.variance = (self.mu**3) / self.lam + + assert_nonpos_support(self.phi, 'phi (or mu, lam)') def get_mu_lam_phi(self, mu, lam, phi): if mu is None: @@ -433,6 +456,9 @@ def __init__(self, alpha=None, beta=None, mu=None, sd=None, self.mean = alpha / (alpha + beta) self.variance = alpha * beta / ( (alpha + beta)**2 * (alpha + beta + 1)) + + assert_nonpos_support(self.alpha, 'alpha') + assert_nonpos_support(self.beta, 'beta') def get_alpha_beta(self, alpha=None, beta=None, mu=None, sd=None): if (alpha is not None) and (beta is not None): @@ -492,6 +518,8 @@ def __init__(self, lam, *args, **kwargs): self.mode = 0 self.variance = lam**-2 + + assert_nonpos_support(self.lam, 'lam') def random(self, point=None, size=None, repeat=None): lam = draw_values([self.lam], point=point) @@ -533,6 +561,8 @@ def __init__(self, mu, b, *args, **kwargs): self.mean = self.median = self.mode = self.mu = mu self.variance = 2 * b**2 + + assert_nonpos_support(self.b, 'b') def random(self, point=None, size=None, repeat=None): mu, b = draw_values([self.mu, self.b], point=point) @@ -586,6 +616,8 @@ def __init__(self, mu=0, sd=None, tau=None, *args, **kwargs): self.median = tt.exp(mu) self.mode = tt.exp(mu - 1. / self.tau) self.variance = (tt.exp(1. / self.tau) - 1) * tt.exp(2 * mu + 1. / self.tau) + + assert_nonpos_support(self.tau, 'tau (sd)') def _random(self, mu, tau, size=None): samples = np.random.normal(size=size) @@ -644,6 +676,9 @@ def __init__(self, nu, mu=0, lam=None, sd=None, *args, **kwargs): self.variance = tt.switch((nu > 2) * 1, (1 / self.lam) * (nu / (nu - 2)), np.inf) + + assert_nonpos_support(self.lam, 'lam (sd)') + assert_nonpos_support(self.nu, 'nu') def random(self, point=None, size=None, repeat=None): nu, mu, lam = draw_values([self.nu, self.mu, self.lam], @@ -702,6 +737,10 @@ def __init__(self, alpha, m, *args, **kwargs): tt.gt(alpha, 2), (alpha * m**2) / ((alpha - 2.) * (alpha - 1.)**2), np.inf) + + assert_nonpos_support(self.alpha, 'alpha') + assert_nonpos_support(self.m, 'm') + def _random(self, alpha, m, size=None): u = np.random.uniform(size=size) @@ -752,6 +791,8 @@ def __init__(self, alpha, beta, *args, **kwargs): super(Cauchy, self).__init__(*args, **kwargs) self.median = self.mode = self.alpha = alpha self.beta = beta + + assert_nonpos_support(self.beta, 'beta') def _random(self, alpha, beta, size=None): u = np.random.uniform(size=size) @@ -798,6 +839,8 @@ def __init__(self, beta, *args, **kwargs): self.mode = 0 self.median = beta self.beta = beta + + assert_nonpos_support(self.beta, 'beta') def _random(self, beta, size=None): u = np.random.uniform(size=size) @@ -864,6 +907,9 @@ def __init__(self, alpha=None, beta=None, mu=None, sd=None, self.mean = alpha / beta self.mode = tt.maximum((alpha - 1) / beta, 0) self.variance = alpha / beta**2 + + assert_nonpos_support(self.alpha, 'alpha') + assert_nonpos_support(self.beta, 'beta') def get_alpha_beta(self, alpha=None, beta=None, mu=None, sd=None): if (alpha is not None) and (beta is not None): @@ -931,6 +977,8 @@ def __init__(self, alpha, beta=1, *args, **kwargs): self.variance = tt.switch(tt.gt(alpha, 2), (beta**2) / (alpha * (alpha - 1.)**2), np.inf) + assert_nonpos_support(self.alpha, 'alpha') + assert_nonpos_support(self.beta, 'beta') def _calculate_mean(self): m = self.beta / (self.alpha - 1.) @@ -1013,6 +1061,9 @@ def __init__(self, alpha, beta, *args, **kwargs): self.median = beta * tt.exp(gammaln(tt.log(2)))**(1. / alpha) self.variance = (beta**2) * \ tt.exp(gammaln(1 + 2. / alpha - self.mean**2)) + + assert_nonpos_support(self.alpha, 'alpha') + assert_nonpos_support(self.beta, 'beta') def random(self, point=None, size=None, repeat=None): alpha, beta = draw_values([self.alpha, self.beta], @@ -1199,6 +1250,9 @@ def __init__(self, mu, sigma, nu, *args, **kwargs): self.nu = nu self.mean = mu + nu self.variance = (sigma**2) + (nu**2) + + assert_nonpos_support(self.sigma, 'sigma') + assert_nonpos_support(self.nu, 'nu') def random(self, point=None, size=None, repeat=None): mu, sigma, nu = draw_values([self.mu, self.sigma, self.nu], @@ -1257,6 +1311,8 @@ def __init__(self, mu=0.0, kappa=None, transform='circular', if transform == 'circular': self.transform = transforms.Circular() + + assert_nonpos_support(self.kappa, 'kappa') def random(self, point=None, size=None, repeat=None): mu, kappa = draw_values([self.mu, self.kappa], @@ -1312,6 +1368,8 @@ def __init__(self, mu=0.0, sd=None, tau=None, alpha=1, *args, **kwargs): self.alpha = alpha self.mean = mu + self.sd * (2 / np.pi)**0.5 * alpha / (1 + alpha**2)**0.5 self.variance = self.sd**2 * (1 - (2 * alpha**2) / ((1 + alpha**2) * np.pi)) + + assert_nonpos_support(self.tau, 'tau (sd)') def random(self, point=None, size=None, repeat=None): mu, tau, sd, alpha = draw_values( From 253cbdecb68760020c5449b9a04bd5417ccd7102 Mon Sep 17 00:00:00 2001 From: Chris Fonnesbeck Date: Sat, 22 Oct 2016 15:55:58 -0500 Subject: [PATCH 2/2] Improved message from assert_nonpos_support --- pymc3/distributions/continuous.py | 63 ++++++++++++++++++------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/pymc3/distributions/continuous.py b/pymc3/distributions/continuous.py index 346325d016..3e2c13d2f5 100644 --- a/pymc3/distributions/continuous.py +++ b/pymc3/distributions/continuous.py @@ -38,8 +38,10 @@ def __init__(self, transform=transforms.logodds, *args, **kwargs): super(UnitContinuous, self).__init__( transform=transform, *args, **kwargs) -def assert_nonpos_support(var, label): +def assert_nonpos_support(var, label, distname): # Checks for evidence of positive support for a variable + if var is None: + return try: # Transformed distribution support = np.isfinite(var.transformed.distribution.dist.logp(0).tag.test_value) @@ -52,8 +54,9 @@ def assert_nonpos_support(var, label): support = False if np.any(support): - warnings.warn(('The variable specified for {} has non-positive support,'.format(label), - 'likely making it unsuitable for this parameter.')) + msg = "The variable specified for {0} has non-positive support for {1}, ".format(label, distname) + msg += "likely making it unsuitable for this parameter." + warnings.warn(msg) def get_tau_sd(tau=None, sd=None): @@ -223,7 +226,8 @@ def __init__(self, *args, **kwargs): self.tau, self.sd = get_tau_sd(tau=tau, sd=sd) self.variance = 1. / self.tau - assert_nonpos_support(self.tau, 'tau (sd)') + assert_nonpos_support(sd, 'sd', 'Normal') + assert_nonpos_support(tau, 'tau', 'Normal') super(Normal, self).__init__(**kwargs) @@ -272,7 +276,8 @@ def __init__(self, sd=None, tau=None, *args, **kwargs): self.mean = tt.sqrt(2 / (np.pi * self.tau)) self.variance = (1. - 2 / np.pi) / self.tau - assert_nonpos_support(self.tau, 'tau (sd)') + assert_nonpos_support(tau, 'tau', 'HalfNormal') + assert_nonpos_support(sd, 'sd', 'HalfNormal') def random(self, point=None, size=None, repeat=None): sd = draw_values([self.sd], point=point) @@ -353,7 +358,9 @@ def __init__(self, mu=None, lam=None, phi=None, alpha=0., *args, **kwargs): - 1.5 * self.mu / self.lam) + alpha self.variance = (self.mu**3) / self.lam - assert_nonpos_support(self.phi, 'phi (or mu, lam)') + assert_nonpos_support(phi, 'phi', 'Wald') + assert_nonpos_support(mu, 'mu', 'Wald') + assert_nonpos_support(lam, 'lam', 'Wald') def get_mu_lam_phi(self, mu, lam, phi): if mu is None: @@ -457,8 +464,8 @@ def __init__(self, alpha=None, beta=None, mu=None, sd=None, self.variance = alpha * beta / ( (alpha + beta)**2 * (alpha + beta + 1)) - assert_nonpos_support(self.alpha, 'alpha') - assert_nonpos_support(self.beta, 'beta') + assert_nonpos_support(alpha, 'alpha', 'Beta') + assert_nonpos_support(beta, 'beta', 'Beta') def get_alpha_beta(self, alpha=None, beta=None, mu=None, sd=None): if (alpha is not None) and (beta is not None): @@ -519,7 +526,7 @@ def __init__(self, lam, *args, **kwargs): self.variance = lam**-2 - assert_nonpos_support(self.lam, 'lam') + assert_nonpos_support(lam, 'lam', 'Exponential') def random(self, point=None, size=None, repeat=None): lam = draw_values([self.lam], point=point) @@ -562,7 +569,7 @@ def __init__(self, mu, b, *args, **kwargs): self.variance = 2 * b**2 - assert_nonpos_support(self.b, 'b') + assert_nonpos_support(b, 'b', 'Laplace') def random(self, point=None, size=None, repeat=None): mu, b = draw_values([self.mu, self.b], point=point) @@ -617,7 +624,8 @@ def __init__(self, mu=0, sd=None, tau=None, *args, **kwargs): self.mode = tt.exp(mu - 1. / self.tau) self.variance = (tt.exp(1. / self.tau) - 1) * tt.exp(2 * mu + 1. / self.tau) - assert_nonpos_support(self.tau, 'tau (sd)') + assert_nonpos_support(tau, 'tau', 'Lognormal') + assert_nonpos_support(sd, 'sd', 'Lognormal') def _random(self, mu, tau, size=None): samples = np.random.normal(size=size) @@ -677,8 +685,8 @@ def __init__(self, nu, mu=0, lam=None, sd=None, *args, **kwargs): (1 / self.lam) * (nu / (nu - 2)), np.inf) - assert_nonpos_support(self.lam, 'lam (sd)') - assert_nonpos_support(self.nu, 'nu') + assert_nonpos_support(lam, 'lam (sd)', 'StudentT') + assert_nonpos_support(nu, 'nu', 'StudentT') def random(self, point=None, size=None, repeat=None): nu, mu, lam = draw_values([self.nu, self.mu, self.lam], @@ -738,8 +746,8 @@ def __init__(self, alpha, m, *args, **kwargs): (alpha * m**2) / ((alpha - 2.) * (alpha - 1.)**2), np.inf) - assert_nonpos_support(self.alpha, 'alpha') - assert_nonpos_support(self.m, 'm') + assert_nonpos_support(alpha, 'alpha', 'Pareto') + assert_nonpos_support(m, 'm', 'Pareto') def _random(self, alpha, m, size=None): @@ -792,7 +800,7 @@ def __init__(self, alpha, beta, *args, **kwargs): self.median = self.mode = self.alpha = alpha self.beta = beta - assert_nonpos_support(self.beta, 'beta') + assert_nonpos_support(beta, 'beta', 'Cauchy') def _random(self, alpha, beta, size=None): u = np.random.uniform(size=size) @@ -840,7 +848,7 @@ def __init__(self, beta, *args, **kwargs): self.median = beta self.beta = beta - assert_nonpos_support(self.beta, 'beta') + assert_nonpos_support(beta, 'beta', 'HalfCauchy') def _random(self, beta, size=None): u = np.random.uniform(size=size) @@ -908,8 +916,8 @@ def __init__(self, alpha=None, beta=None, mu=None, sd=None, self.mode = tt.maximum((alpha - 1) / beta, 0) self.variance = alpha / beta**2 - assert_nonpos_support(self.alpha, 'alpha') - assert_nonpos_support(self.beta, 'beta') + assert_nonpos_support(alpha, 'alpha', 'Gamma') + assert_nonpos_support(beta, 'beta', 'Gamma') def get_alpha_beta(self, alpha=None, beta=None, mu=None, sd=None): if (alpha is not None) and (beta is not None): @@ -977,8 +985,8 @@ def __init__(self, alpha, beta=1, *args, **kwargs): self.variance = tt.switch(tt.gt(alpha, 2), (beta**2) / (alpha * (alpha - 1.)**2), np.inf) - assert_nonpos_support(self.alpha, 'alpha') - assert_nonpos_support(self.beta, 'beta') + assert_nonpos_support(alpha, 'alpha', 'InverseGamma') + assert_nonpos_support(beta, 'beta', 'InverseGamma') def _calculate_mean(self): m = self.beta / (self.alpha - 1.) @@ -1062,8 +1070,8 @@ def __init__(self, alpha, beta, *args, **kwargs): self.variance = (beta**2) * \ tt.exp(gammaln(1 + 2. / alpha - self.mean**2)) - assert_nonpos_support(self.alpha, 'alpha') - assert_nonpos_support(self.beta, 'beta') + assert_nonpos_support(alpha, 'alpha', 'Weibull') + assert_nonpos_support(beta, 'beta', 'Weibull') def random(self, point=None, size=None, repeat=None): alpha, beta = draw_values([self.alpha, self.beta], @@ -1251,8 +1259,8 @@ def __init__(self, mu, sigma, nu, *args, **kwargs): self.mean = mu + nu self.variance = (sigma**2) + (nu**2) - assert_nonpos_support(self.sigma, 'sigma') - assert_nonpos_support(self.nu, 'nu') + assert_nonpos_support(sigma, 'sigma', 'ExGaussian') + assert_nonpos_support(nu, 'nu', 'ExGaussian') def random(self, point=None, size=None, repeat=None): mu, sigma, nu = draw_values([self.mu, self.sigma, self.nu], @@ -1312,7 +1320,7 @@ def __init__(self, mu=0.0, kappa=None, transform='circular', if transform == 'circular': self.transform = transforms.Circular() - assert_nonpos_support(self.kappa, 'kappa') + assert_nonpos_support(kappa, 'kappa', 'VonMises') def random(self, point=None, size=None, repeat=None): mu, kappa = draw_values([self.mu, self.kappa], @@ -1369,7 +1377,8 @@ def __init__(self, mu=0.0, sd=None, tau=None, alpha=1, *args, **kwargs): self.mean = mu + self.sd * (2 / np.pi)**0.5 * alpha / (1 + alpha**2)**0.5 self.variance = self.sd**2 * (1 - (2 * alpha**2) / ((1 + alpha**2) * np.pi)) - assert_nonpos_support(self.tau, 'tau (sd)') + assert_nonpos_support(tau, 'tau', 'SkewNormal') + assert_nonpos_support(sd, 'sd', 'SkewNormal') def random(self, point=None, size=None, repeat=None): mu, tau, sd, alpha = draw_values(