From 7f129af1f7a2fb69e49718b2aa55b5da6d2c00d4 Mon Sep 17 00:00:00 2001 From: aloctavodia Date: Fri, 16 Dec 2022 16:57:33 -0300 Subject: [PATCH 1/4] add interactive method --- preliz/distributions/continuous.py | 31 ++++++++------- preliz/distributions/distributions.py | 56 +++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/preliz/distributions/continuous.py b/preliz/distributions/continuous.py index 56f06646..0c0836df 100644 --- a/preliz/distributions/continuous.py +++ b/preliz/distributions/continuous.py @@ -1,6 +1,7 @@ # pylint: disable=too-many-lines # pylint: disable=too-many-instance-attributes # pylint: disable=invalid-name +# pylint: disable=attribute-defined-outside-init """ Continuous probability distributions. """ @@ -87,31 +88,33 @@ def __init__(self, alpha=None, beta=None, mu=None, sigma=None, kappa=None): self.name = "beta" self.dist = stats.beta self.support = (0, 1) - self.params_support = ((eps, np.inf), (eps, np.inf)) - self.alpha, self.beta, self.param_names = self._parametrization( - alpha, beta, mu, sigma, kappa - ) - if self.alpha is not None and self.beta is not None: - self._update(self.alpha, self.beta) + self._parametrization(alpha, beta, mu, sigma, kappa) - def _parametrization(self, alpha, beta, mu, sigma, kappa): + def _parametrization(self, alpha=None, beta=None, mu=None, sigma=None, kappa=None): if mu is None and sigma is None: names = ("alpha", "beta") + self.params_support = ((eps, np.inf), (eps, np.inf)) elif mu is not None and sigma is not None: alpha, beta = self._from_mu_sigma(mu, sigma) names = ("mu", "sigma") + self.params_support = ((eps, 1 - eps), (eps, (mu * (1 - mu)) ** 0.5)) elif mu is not None and kappa is not None: alpha, beta = self._from_mu_kappa(mu, kappa) names = ("mu", "kappa") + self.params_support = ((eps, 1 - eps), (eps, np.inf)) else: raise ValueError( "Incompatible parametrization. Either use alpha " "and beta, or mu and sigma." ) - return alpha, beta, names + self.alpha = alpha + self.beta = beta + self.param_names = names + if self.alpha is not None and self.beta is not None: + self._update(self.alpha, self.beta) def _from_mu_sigma(self, mu, sigma): kappa = mu * (1 - mu) / sigma**2 - 1 @@ -1526,11 +1529,9 @@ def __init__(self, mu=None, sigma=None, tau=None): self.dist = stats.norm self.support = (-np.inf, np.inf) self.params_support = ((-np.inf, np.inf), (eps, np.inf)) - self.mu, self.sigma, self.param_names = self._parametrization(mu, sigma, tau) - if self.mu is not None and self.sigma is not None: - self._update(self.mu, self.sigma) + self._parametrization(mu, sigma, tau) - def _parametrization(self, mu, sigma, tau): + def _parametrization(self, mu=None, sigma=None, tau=None): if sigma is not None and tau is not None: raise ValueError( "Incompatible parametrization. Either use mu and sigma, or mu and tau." @@ -1543,7 +1544,11 @@ def _parametrization(self, mu, sigma, tau): sigma = from_precision(tau) names = ("mu", "tau") - return mu, sigma, names + self.mu = mu + self.sigma = sigma + self.param_names = names + if mu is not None and sigma is not None: + self._update(mu, sigma) def _get_frozen(self): frozen = None diff --git a/preliz/distributions/distributions.py b/preliz/distributions/distributions.py index 49634d2a..4a995adc 100644 --- a/preliz/distributions/distributions.py +++ b/preliz/distributions/distributions.py @@ -4,6 +4,8 @@ # pylint: disable=no-member from collections import namedtuple +from ipywidgets import interact +import ipywidgets as ipyw import numpy as np from ..utils.plot_utils import plot_pdfpmf, plot_cdf, plot_ppf @@ -327,6 +329,60 @@ def plot_ppf( "you need to first define its parameters or use one of the fit methods" ) + def interactive(self, kind="pdf", xlim=None): + """ + Interactive exploration of distributions parameters + + Parameters + ---------- + kind : str: + Type of plot. Available options are `pdf`, `cdf` and `ppf`. + xlim : tuple or str + Values to set the limits of the x-axis. Defaults to None, the values are computed + automatically. Use `auto` for automatic rescaling + """ + + # temporary patch until we migrate all distributions to use + # self.params_report and self.params + try: + params_value = self.params_report + except AttributeError: + params_value = self.params + + args = dict(zip(self.param_names, params_value)) + if xlim is None: + self.__init__(**args) + xlim = self._finite_endpoints("full") + + sliders = {} + for name, value, support in zip(self.param_names, params_value, self.params_support): + lower, upper = support + if np.isfinite(lower): + min_v = lower + else: + min_v = value - 10 + if np.isfinite(upper): + max_v = upper + else: + max_v = value + 10 + + step = (max_v - min_v) / 100 + + sliders[name] = ipyw.FloatSlider(min=min_v, max=max_v, step=step, value=value) + + def plot(**args): + self.__init__(**args) + if kind == "pdf": + ax = self.plot_pdf(legend=False) + elif kind == "cdf": + ax = self.plot_cdf(legend=False) + elif kind == "ppf": + ax = self.plot_ppf(legend=False) + if xlim != "auto": + ax.set_xlim(*xlim) + + interact(plot, **sliders) + class Continuous(Distribution): """Base class for continuous distributions.""" From f29426daf33d7989ef11815702f276bd0c8480bd Mon Sep 17 00:00:00 2001 From: aloctavodia Date: Fri, 16 Dec 2022 18:51:27 -0300 Subject: [PATCH 2/4] add fixed_lims --- preliz/distributions/distributions.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/preliz/distributions/distributions.py b/preliz/distributions/distributions.py index 4a995adc..0b3b38e7 100644 --- a/preliz/distributions/distributions.py +++ b/preliz/distributions/distributions.py @@ -329,7 +329,7 @@ def plot_ppf( "you need to first define its parameters or use one of the fit methods" ) - def interactive(self, kind="pdf", xlim=None): + def interactive(self, kind="pdf", xlim=None, fixed_lim=None): """ Interactive exploration of distributions parameters @@ -337,9 +337,12 @@ def interactive(self, kind="pdf", xlim=None): ---------- kind : str: Type of plot. Available options are `pdf`, `cdf` and `ppf`. - xlim : tuple or str - Values to set the limits of the x-axis. Defaults to None, the values are computed - automatically. Use `auto` for automatic rescaling + fixed_lim : tuple or str + Values to set the limits of the x-axis and y-axis. + Defaults to None, the values are computed automatically. + Use `both` for automatic rescaling of x-axis and y-axis. Or pass a tuple + of 4 elements the first two fox x-axis, the last two for x-axis. The elements + of the tuple can be `None`. """ # temporary patch until we migrate all distributions to use @@ -350,9 +353,15 @@ def interactive(self, kind="pdf", xlim=None): params_value = self.params args = dict(zip(self.param_names, params_value)) - if xlim is None: + + if fixed_lim is None: self.__init__(**args) xlim = self._finite_endpoints("full") + xvals = self.xvals("restricted") + ylim = (0, np.max(self.pdf(xvals) * 1.5)) + if isinstance(fixed_lim, tuple): + xlim = fixed_lim[:2] + ylim = fixed_lim[2:] sliders = {} for name, value, support in zip(self.param_names, params_value, self.params_support): @@ -378,8 +387,10 @@ def plot(**args): ax = self.plot_cdf(legend=False) elif kind == "ppf": ax = self.plot_ppf(legend=False) - if xlim != "auto": + if fixed_lim != "both" and kind != "ppf": ax.set_xlim(*xlim) + if fixed_lim != "both" and kind != "cdf": + ax.set_ylim(*ylim) interact(plot, **sliders) From 0e3a7472496a1d30363b9e43388081be52cc3c86 Mon Sep 17 00:00:00 2001 From: aloctavodia Date: Fri, 16 Dec 2022 18:59:58 -0300 Subject: [PATCH 3/4] pick better name for arguments --- preliz/distributions/distributions.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/preliz/distributions/distributions.py b/preliz/distributions/distributions.py index 0b3b38e7..3b177ea9 100644 --- a/preliz/distributions/distributions.py +++ b/preliz/distributions/distributions.py @@ -329,7 +329,7 @@ def plot_ppf( "you need to first define its parameters or use one of the fit methods" ) - def interactive(self, kind="pdf", xlim=None, fixed_lim=None): + def interactive(self, kind="pdf", xlim=None, fixed_lim="both"): """ Interactive exploration of distributions parameters @@ -337,12 +337,12 @@ def interactive(self, kind="pdf", xlim=None, fixed_lim=None): ---------- kind : str: Type of plot. Available options are `pdf`, `cdf` and `ppf`. - fixed_lim : tuple or str - Values to set the limits of the x-axis and y-axis. - Defaults to None, the values are computed automatically. - Use `both` for automatic rescaling of x-axis and y-axis. Or pass a tuple - of 4 elements the first two fox x-axis, the last two for x-axis. The elements - of the tuple can be `None`. + fixed_lim : str or tuple + Set the limits of the x-axis and/or y-axis. + Defaults to `"both"`, the limits of both axis are fixed. + Use `"auto"` for automatic rescaling of x-axis and y-axis. + Or set them manually by passing a tuple of 4 elements, + the first two fox x-axis, the last two for x-axis. The tuple can have `None`. """ # temporary patch until we migrate all distributions to use @@ -354,12 +354,12 @@ def interactive(self, kind="pdf", xlim=None, fixed_lim=None): args = dict(zip(self.param_names, params_value)) - if fixed_lim is None: + if fixed_lim == "both": self.__init__(**args) xlim = self._finite_endpoints("full") xvals = self.xvals("restricted") ylim = (0, np.max(self.pdf(xvals) * 1.5)) - if isinstance(fixed_lim, tuple): + elif isinstance(fixed_lim, tuple): xlim = fixed_lim[:2] ylim = fixed_lim[2:] @@ -387,9 +387,9 @@ def plot(**args): ax = self.plot_cdf(legend=False) elif kind == "ppf": ax = self.plot_ppf(legend=False) - if fixed_lim != "both" and kind != "ppf": + if fixed_lim != "auto" and kind != "ppf": ax.set_xlim(*xlim) - if fixed_lim != "both" and kind != "cdf": + if fixed_lim != "auto" and kind != "cdf": ax.set_ylim(*ylim) interact(plot, **sliders) From c23ab15cd8e1811b5f6794d312ecc86067b380e2 Mon Sep 17 00:00:00 2001 From: aloctavodia Date: Fri, 16 Dec 2022 20:04:08 -0300 Subject: [PATCH 4/4] add pointinterval and quantile args, add parameter suport to labels --- preliz/distributions/distributions.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/preliz/distributions/distributions.py b/preliz/distributions/distributions.py index 3b177ea9..9e827101 100644 --- a/preliz/distributions/distributions.py +++ b/preliz/distributions/distributions.py @@ -329,7 +329,7 @@ def plot_ppf( "you need to first define its parameters or use one of the fit methods" ) - def interactive(self, kind="pdf", xlim=None, fixed_lim="both"): + def interactive(self, kind="pdf", fixed_lim="both", pointinterval=True, quantiles=None): """ Interactive exploration of distributions parameters @@ -343,6 +343,13 @@ def interactive(self, kind="pdf", xlim=None, fixed_lim="both"): Use `"auto"` for automatic rescaling of x-axis and y-axis. Or set them manually by passing a tuple of 4 elements, the first two fox x-axis, the last two for x-axis. The tuple can have `None`. + pointinterval : bool + Whether to include a plot of the quantiles. Defaults to False. If True the default is to + plot the median and two interquantiles ranges. + quantiles : list + Values of the five quantiles to use when ``pointinterval=True`` if None (default) + the values ``[0.05, 0.25, 0.5, 0.75, 0.95]`` will be used. The number of elements + should be 5, 3, 1 or 0 (in this last case nothing will be plotted). """ # temporary patch until we migrate all distributions to use @@ -377,16 +384,22 @@ def interactive(self, kind="pdf", xlim=None, fixed_lim="both"): step = (max_v - min_v) / 100 - sliders[name] = ipyw.FloatSlider(min=min_v, max=max_v, step=step, value=value) + sliders[name] = ipyw.FloatSlider( + min=min_v, + max=max_v, + step=step, + description=f"{name} ({lower:.0f}, {upper:.0f})", + value=value, + ) def plot(**args): self.__init__(**args) if kind == "pdf": - ax = self.plot_pdf(legend=False) + ax = self.plot_pdf(legend=False, pointinterval=pointinterval, quantiles=quantiles) elif kind == "cdf": - ax = self.plot_cdf(legend=False) + ax = self.plot_cdf(legend=False, pointinterval=pointinterval, quantiles=quantiles) elif kind == "ppf": - ax = self.plot_ppf(legend=False) + ax = self.plot_ppf(legend=False, pointinterval=pointinterval, quantiles=quantiles) if fixed_lim != "auto" and kind != "ppf": ax.set_xlim(*xlim) if fixed_lim != "auto" and kind != "cdf":