Repository: phobson/paramnormal

Issue:
add exponential distribution
seems pretty critical

Discussion:

Patch:
diff --git a/docs/tutorial/fitting.ipynb b/docs/tutorial/fitting.ipynb
index b5434fa..364bccb 100644
--- a/docs/tutorial/fitting.ipynb
+++ b/docs/tutorial/fitting.ipynb
@@ -11,6 +11,17 @@
     "Again, we'll demonstrate with a lognormal distribution and compare parameter estimatation with scipy."
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "%matplotlib inline"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -28,9 +39,7 @@
     "clean_bkgd = {'axes.facecolor':'none', 'figure.facecolor':'none'}\n",
     "seaborn.set(style='ticks', rc=clean_bkgd)\n",
     "\n",
-    "import paramnormal\n",
-    "\n",
-    "%matplotlib inline"
+    "import paramnormal"
    ]
   },
   {
diff --git a/docs/tutorial/overview.ipynb b/docs/tutorial/overview.ipynb
index 26e7506..d3f0fc6 100644
--- a/docs/tutorial/overview.ipynb
+++ b/docs/tutorial/overview.ipynb
@@ -11,6 +11,17 @@
     "The main problem that `paramnormal` is trying to solve is that sometimes, creating a probability distribution using these parameters (and others) in `scipy.stats` can be confusing. Also the parameters in `numpy.random` can be inconsistently named (admittedly, just a minor inconvenience). "
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "%matplotlib inline"
+   ]
+  },
   {
    "cell_type": "code",
    "execution_count": null,
@@ -147,11 +158,7 @@
    },
    "outputs": [],
    "source": [
-    "distributions = filter(\n",
-    "    lambda d: not d.startswith('__') and d not in ['BaseDist_Mixin', 'stats', 'namedtuple', 'numpy', 'utils'], \n",
-    "    dir(paramnormal.paramnormal)\n",
-    ")\n",
-    "for d in distributions:\n",
+    "for d in paramnormal.paramnormal.__all__:\n",
     "    print(d)"
    ]
   },
diff --git a/paramnormal/paramnormal.py b/paramnormal/paramnormal.py
index 6faae1a..7228ea0 100644
--- a/paramnormal/paramnormal.py
+++ b/paramnormal/paramnormal.py
@@ -439,7 +439,7 @@ class beta(BaseDist_Mixin):
 
     >>> # silly fake data
     >>> numpy.random.seed(0)
-    >>> pn.beta(alpha=2, beta=5).rvs(size=37)
+    >>> data = pn.beta(alpha=2, beta=5).rvs(size=37)
     >>> # pretend `data` is unknown and we want to fit a dist. to it
     >>> pn.beta.fit(data)
     params(alpha=1.6784891179355, beta=4.2459121691279, loc=0, scale=1)
@@ -526,7 +526,7 @@ class gamma(BaseDist_Mixin):
 
     >>> # silly fake data
     >>> numpy.random.seed(0)
-    >>> pn.gamma(k=2, θ=5).rvs(size=37)
+    >>> data = pn.gamma(k=2, θ=5).rvs(size=37)
     >>> # pretend `data` is unknown and we want to fit a dist. to it
     >>> pn.gamma.fit(data)
     params(k=1.3379069223213478, loc=0, theta=7.5830062081633587)
@@ -607,7 +607,7 @@ class chi_squared(BaseDist_Mixin):
 
     >>> # silly fake data
     >>> numpy.random.seed(0)
-    >>> pn.chi_squared(k=2).rvs(size=37)
+    >>> data = pn.chi_squared(k=2).rvs(size=37)
     >>> # pretend `data` is unknown and we want to fit a dist. to it
     >>> pn.chi_squared.fit(data)
     params(k=2.2668945312500028, loc=0, scale=1)
@@ -652,8 +652,8 @@ class pareto(BaseDist_Mixin):
         Use scipy's maximum likelihood estimation methods to estimate
         the parameters of the data's distribution. By default, `loc`
         and `scale` are fixed at 0 and 1, respectively. Thus, only
-        `alpha` and `beta` are estimated unless `loc` or `scale` are
-        explicitly set to `None`.
+        `alpha` is estimated unless `loc` or `scale` are explicitly
+        set to `None`.
     from_params(params)
         Create a new distribution instances from the namedtuple result
         of the :meth:`~fit` method.
@@ -668,7 +668,7 @@ class pareto(BaseDist_Mixin):
         respectively.
 
         .. note ::
-           When fitting a beta distribution to a dataset, this will
+           When fitting a pareto distribution to a dataset, this will
            be fixed at its default value unless you explicitly set
            it to other values. Set to `None` if you wish that it be
            estimated entirely from scratch.
@@ -688,7 +688,7 @@ class pareto(BaseDist_Mixin):
 
     >>> # silly fake data
     >>> numpy.random.seed(0)
-    >>> pn.pareto(alpha=2).rvs(size=37)
+    >>> data = pn.pareto(alpha=2).rvs(size=37)
     >>> # pretend `data` is unknown and we want to fit a dist. to it
     >>> pn.pareto.fit(data)
     params(alpha=1.7850585937500019, loc=0, scale=1)
@@ -699,7 +699,7 @@ class pareto(BaseDist_Mixin):
 
     References
     ----------
-    http://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.chi2.html
+    http://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.pareto.html
     https://en.wikipedia.org/wiki/pareto_distribution
 
     See Also
@@ -721,3 +721,95 @@ class pareto(BaseDist_Mixin):
         else:
             key = 'b'
         return {key: alpha, loc_key: loc, scale_key: scale}
+
+
+class exponential(BaseDist_Mixin):
+    """
+    Create and fit data to an exponential distribution.
+
+    Methods
+    -------
+    fit(data, **guesses)
+        Use scipy's maximum likelihood estimation methods to estimate
+        the parameters of the data's distribution. By default, `loc`
+        isfixed at 0. Thus, only `lambda_` is estimated unless `loc` is
+        explicitly set to `None`.
+    from_params(params)
+        Create a new distribution instances from the namedtuple result
+        of the :meth:`~fit` method.
+
+    Parameters
+    ----------
+    lambda_ : float
+        The shape parameter of the distribution.
+    loc : float, optional
+        Location parameter of the distribution. This default to, and
+        should probably be left at, 0,
+
+        .. note ::
+           When fitting an exponential distribution to a dataset, this
+           will be fixed at its default value unless you explicitly set
+           it to other values. Set to `None` if you wish that it be
+           estimated entirely from scratch.
+
+    Examples
+    --------
+    >>> import numpy
+    >>> import paramnormal as pn
+    >>> numpy.random.seed(0)
+    >>> pn.exponential(lambda_=2).rvs(size=3)
+    array([ 0.39793725,  0.62796538,  0.46161157])
+
+    >>> # you can also use greek letters
+    >>> numpy.random.seed(0)
+    >>> pn.exponential(λ=2).rvs(size=3)
+    array([ 0.39793725,  0.62796538,  0.46161157])
+
+    >>> # silly fake data
+    >>> numpy.random.seed(0)
+    >>> data = pn.exponential(λ=2).rvs(size=37)
+    >>> # pretend `data` is unknown and we want to fit a dist. to it
+    >>> pn.exponential.fit(data)
+    params(lambda_=1.7849050026146085, loc=0)
+
+    >>> # include `loc` in the estimate
+    >>> pn.exponential.fit(data, loc=None)
+    params(lambda_=1.8154701618164411, loc=0.0094842718426853996)
+
+    References
+    ----------
+    http://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.expon.html
+    https://en.wikipedia.org/wiki/exponential_distribution
+
+    See Also
+    --------
+    scipy.stats.expon
+    numpy.random.exponential
+
+    """
+    dist = stats.expon
+    param_template = namedtuple('params', ['lambda_', 'loc'])
+
+    @staticmethod
+    @utils.greco_deco
+    def _process_args(lambda_=None, loc=0, fit=False):
+        loc_key, scale_key = utils._get_loc_scale_keys(fit=fit)
+        return {loc_key: loc, scale_key: lambda_**-1 if lambda_ is not None else lambda_}
+
+    @classmethod
+    def fit(cls, data, **guesses):
+        params = cls._fit(data, **guesses)
+        return cls.param_template(loc=params[0], lambda_=params[1]**-1)
+
+
+__all__ = [
+    'normal',
+    'lognormal',
+    'weibull',
+    'alpha',
+    'beta',
+    'gamma',
+    'chi_squared',
+    'pareto',
+    'exponential',
+]
\ No newline at end of file
diff --git a/paramnormal/utils.py b/paramnormal/utils.py
index fd3df0e..da50755 100644
--- a/paramnormal/utils.py
+++ b/paramnormal/utils.py
@@ -8,7 +8,8 @@ SYMBOLS = {
     'α': 'alpha',
     'β': 'beta',
     'γ': 'gamma',
-    'θ': 'theta'
+    'λ': 'lambda_',
+    'θ': 'theta',
 }
 
 


Test patch:
diff --git a/paramnormal/tests/test_paramnormal.py b/paramnormal/tests/test_paramnormal.py
index 8fde0c3..73de100 100644
--- a/paramnormal/tests/test_paramnormal.py
+++ b/paramnormal/tests/test_paramnormal.py
@@ -342,3 +342,36 @@ class Test_pareto(CheckDist_Mixin):
             (params.loc, 0),
             (params.scale, 1),
         )
+
+
+class Test_exponential(CheckDist_Mixin):
+    def setup(self):
+        self.dist = paramnormal.exponential
+        self.cargs = []
+        self.ckwds = dict(lambda_=2)
+
+        self.np_rand_fxn = numpy.random.exponential
+        self.npargs = [0.5]
+        self.npkwds = dict()
+
+    def test_process_args(self):
+        nt.assert_dict_equal(
+            self.dist._process_args(lambda_=2.0),
+            dict(loc=0, scale=0.5)
+        )
+
+        nt.assert_dict_equal(
+            self.dist._process_args(lambda_=2.0, fit=True),
+            dict(floc=0, fscale=0.5)
+        )
+
+    @seed
+    def test_fit(self):
+        data = numpy.random.exponential(0.5, size=37)
+        params = self.dist.fit(data)
+        check_params(
+            (params.lambda_, 1.7849050026146085),
+            (params.loc, 0),
+        )
+
+