Repository: scipy/scipy

Issue:
Wrong / contradictory documentation of stats.mannwhitneyu (Trac #1289)
_Original ticket http://projects.scipy.org/scipy/ticket/1289 on 2010-09-27 by trac user Wrzlprmft, assigned to unknown._

```
    help(mannwhitneyu)
```

states:
“Use only when the n in each condition is < 20”
while [http://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.mannwhitneyu.html the online documentation] says:
“Use only when the number of observation in each sample is > 20”

If I am not mistaken, the second restriction (“<20”) is closer to the truth. However it applies only to the second return value (prob). The first return value (u) can be correctly and easily calculated for all inputs and SciPy appears to do so. For small inputs (<20) it can only be evaluated using a table of critical values (or calculating those critical values), while for larger one can use an approximative normal distribution to calculate the significance level (prob).



Discussion:

Resolving patch:
diff --git a/scipy/stats/distributions.py b/scipy/stats/distributions.py
--- a/scipy/stats/distributions.py
+++ b/scipy/stats/distributions.py
@@ -533,20 +533,46 @@ class rv_generic(object):
 
     """
     def _fix_loc_scale(self, args, loc, scale=1):
+        """Parse args/kwargs input to other methods."""
+        args, loc, scale, kwarg3 = self._fix_loc_scale_kwarg3(args, loc, scale,
+                                                              None, None)
+        if kwarg3 is not None:
+            # 3 positional args
+            raise TypeError("Too many input arguments.")
+
+        return args, loc, scale
+
+    def _fix_loc_scale_kwarg3(self, args, loc, scale=1,
+                              kwarg3=1, kwarg3_default=None):
+        """Parse args/kwargs input to methods with a third kwarg.
+
+        At the moment these methods are ``stats`` and ``rvs``.
+        """
         N = len(args)
         if N > self.numargs:
             if N == self.numargs + 1 and loc is None:
                 # loc is given without keyword
                 loc = args[-1]
-            if N == self.numargs + 2 and scale is None:
+            elif N == self.numargs + 2 and loc is None and scale is None:
                 # loc and scale given without keyword
                 loc, scale = args[-2:]
+            elif N == self.numargs + 3 and loc is None and scale is None \
+                                       and kwarg3 is None:
+                # loc, scale and a third argument
+                loc, scale, kwarg3 = args[-3:]
+            else:
+                raise TypeError("Too many input arguments.")
+
             args = args[:self.numargs]
+
         if scale is None:
             scale = 1.0
         if loc is None:
             loc = 0.0
-        return args, loc, scale
+        if kwarg3 is None:
+            kwarg3 = kwarg3_default
+
+        return args, loc, scale, kwarg3
 
     def _fix_loc(self, args, loc):
         args, loc, scale = self._fix_loc_scale(args, loc)
@@ -562,25 +588,26 @@ def rvs(self,*args,**kwds):
         ----------
         arg1, arg2, arg3,... : array_like
             The shape parameter(s) for the distribution (see docstring of the
-            instance object for more information)
+            instance object for more information).
         loc : array_like, optional
-            location parameter (default=0)
+            Location parameter (default=0).
         scale : array_like, optional
-            scale parameter (default=1)
+            Scale parameter (default=1).
         size : int or tuple of ints, optional
-            defining number of random variates (default=1)
+            Defining number of random variates (default=1).
 
         Returns
         -------
-        rvs : array_like
-            random variates of given `size`
+        rvs : ndarray or scalar
+            Random variates of given `size`.
 
         """
         kwd_names = ['loc', 'scale', 'size', 'discrete']
         loc, scale, size, discrete = map(kwds.get, kwd_names,
                                          [None]*len(kwd_names))
 
-        args, loc, scale = self._fix_loc_scale(args, loc, scale)
+        args, loc, scale, size = self._fix_loc_scale_kwarg3(args, loc, scale,
+                                                            size)
         cond = logical_and(self._argcheck(*args),(scale >= 0))
         if not all(cond):
             raise ValueError("Domain error in arguments.")
@@ -1495,7 +1522,7 @@ def stats(self,*args,**kwds):
             location parameter (default=0)
         scale : array_like, optional
             scale parameter (default=1)
-        moments : string, optional
+        moments : str, optional
             composed of letters ['mvsk'] defining which moments to compute:
             'm' = mean,
             'v' = variance,
@@ -1510,25 +1537,11 @@ def stats(self,*args,**kwds):
 
         """
         loc,scale,moments=map(kwds.get,['loc','scale','moments'])
+        args, loc, scale, moments = self._fix_loc_scale_kwarg3(args, loc,
+                                                    scale, moments, 'mv')
 
-        N = len(args)
-        if N > self.numargs:
-            if N == self.numargs + 1 and loc is None:
-                # loc is given without keyword
-                loc = args[-1]
-            if N == self.numargs + 2 and scale is None:
-                # loc and scale given without keyword
-                loc, scale = args[-2:]
-            if N == self.numargs + 3 and moments is None:
-                # loc, scale, and moments
-                loc, scale, moments = args[-3:]
-            args = args[:self.numargs]
-        if scale is None: scale = 1.0
-        if loc is None: loc = 0.0
-        if moments is None: moments = 'mv'
-
-        loc,scale = map(asarray,(loc,scale))
-        args = tuple(map(asarray,args))
+        loc, scale = map(asarray, (loc, scale))
+        args = tuple(map(asarray, args))
         cond = self._argcheck(*args) & (scale > 0) & (loc==loc)
 
         signature = inspect.getargspec(get_method_function(self._stats))
@@ -1771,7 +1784,8 @@ def fit(self, data, *args, **kwds):
         """
         Narg = len(args)
         if Narg > self.numargs:
-            raise ValueError("Too many input arguments.")
+            raise TypeError("Too many input arguments.")
+
         start = [None]*2
         if (Narg < self.numargs) or not ('loc' in kwds and
                                          'scale' in kwds):
@@ -5763,11 +5777,12 @@ def rvs(self, *args, **kwargs):
         loc : array_like, optional
             Location parameter (default=0).
         size : int or tuple of ints, optional
-            Defining number of random variates (default=1).
+            Defining number of random variates (default=1).  Note that `size`
+            has to be given as keyword, not as positional argument.
 
         Returns
         -------
-        rvs : array_like
+        rvs : ndarray or scalar
             Random variates of given `size`.
 
         """
@@ -6136,11 +6151,17 @@ def stats(self, *args, **kwds):
         if N > self.numargs:
             if N == self.numargs + 1 and loc is None:  # loc is given without keyword
                 loc = args[-1]
-            if N == self.numargs + 2 and moments is None: # loc, scale, and moments
+            elif N == self.numargs + 2 and moments is None: # loc, scale, and moments
                 loc, moments = args[-2:]
+            else:
+                raise TypeError("Too many input arguments.")
+
             args = args[:self.numargs]
-        if loc is None: loc = 0.0
-        if moments is None: moments = 'mv'
+
+        if loc is None:
+            loc = 0.0
+        if moments is None:
+            moments = 'mv'
 
         loc = asarray(loc)
         args = tuple(map(asarray,args))


Test patch:
diff --git a/scipy/stats/tests/test_distributions.py b/scipy/stats/tests/test_distributions.py
--- a/scipy/stats/tests/test_distributions.py
+++ b/scipy/stats/tests/test_distributions.py
@@ -5,7 +5,7 @@
 
 from numpy.testing import TestCase, run_module_suite, assert_equal, \
     assert_array_equal, assert_almost_equal, assert_array_almost_equal, \
-    assert_allclose, assert_, rand, dec
+    assert_allclose, assert_, assert_raises, rand, dec
 from numpy.testing.utils import WarningManager
 
 
@@ -16,6 +16,7 @@
 from scipy.stats.distributions import argsreduce
 import warnings
 
+
 def kolmogorov_check(diststr, args=(), N=20, significance=0.01):
     qtest = stats.ksoneisf(significance, N)
     cdf = eval('stats.'+diststr+'.cdf')
@@ -954,5 +955,43 @@ def test_hypergeom_interval_1802():
     assert_equal(stats.hypergeom.ppf(1, 100, 100, 8), 8)
 
 
+def test_distribution_too_many_args():
+    # Check that a TypeError is raised when too many args are given to a method
+    # Regression test for ticket 1815.
+    x = np.linspace(0.1, 0.7, num=5)
+    assert_raises(TypeError, stats.gamma.pdf, x, 2, 3, loc=1.0)
+    assert_raises(TypeError, stats.gamma.pdf, x, 2, 3, 4, loc=1.0)
+    assert_raises(TypeError, stats.gamma.pdf, x, 2, 3, 4, 5)
+    assert_raises(TypeError, stats.gamma.pdf, x, 2, 3, loc=1.0, scale=0.5)
+    assert_raises(TypeError, stats.gamma.rvs, 2., 3, loc=1.0, scale=0.5)
+    assert_raises(TypeError, stats.gamma.cdf, x, 2., 3, loc=1.0, scale=0.5)
+    assert_raises(TypeError, stats.gamma.ppf, x, 2., 3, loc=1.0, scale=0.5)
+    assert_raises(TypeError, stats.gamma.stats, 2., 3, loc=1.0, scale=0.5)
+    assert_raises(TypeError, stats.gamma.entropy, 2., 3, loc=1.0, scale=0.5)
+    assert_raises(TypeError, stats.gamma.fit, x, 2., 3, loc=1.0, scale=0.5)
+
+    # These should not give errors
+    stats.gamma.pdf(x, 2, 3)  # loc=3
+    stats.gamma.pdf(x, 2, 3, 4)  # loc=3, scale=4
+    stats.gamma.stats(2., 3)
+    stats.gamma.stats(2., 3, 4)
+    stats.gamma.stats(2., 3, 4, 'mv')
+    stats.gamma.rvs(2., 3, 4, 5)
+    stats.gamma.fit(stats.gamma.rvs(2., size=7), 2.)
+
+    # Also for a discrete distribution
+    stats.geom.pmf(x, 2, loc=3)  # no error, loc=3
+    assert_raises(TypeError, stats.geom.pmf, x, 2, 3, 4)
+    assert_raises(TypeError, stats.geom.pmf, x, 2, 3, loc=4)
+
+    # And for distributions with 0, 2 and 3 args respectively
+    assert_raises(TypeError, stats.expon.pdf, x, 3, loc=1.0)
+    assert_raises(TypeError, stats.exponweib.pdf, x, 3, 4, 5, loc=1.0)
+    assert_raises(TypeError, stats.exponweib.pdf, x, 3, 4, 5, 0.1, 0.1)
+    assert_raises(TypeError, stats.ncf.pdf, x, 3, 4, 5, 6, loc=1.0)
+    assert_raises(TypeError, stats.ncf.pdf, x, 3, 4, 5, 6, 1.0, scale=0.5)
+    stats.ncf.pdf(x, 3, 4, 5, 6, 1.0)  # 3 args, plus loc/scale
+
+
 if __name__ == "__main__":
     run_module_suite()