Repository: scipy/scipy

Issue:
wilcoxon() rejection direction is unclear for one-sided test
<<Please describe the issue in detail here, and for bug reports fill in the fields below.>>
As mentioned [here](https://github.com/scipy/scipy/issues/2625) by @abhinavkgp :

> ... it is still not possible to determine one-sided result (whether x-y>0 or y-x>0) using this two-sided statistic, due to the following:
> 
> Code:
> T = min(r_plus, r_minus)
> r_plus = np.sum((d > 0) * r, axis=0) #Always positive
> r_minus = np.sum((d < 0) * r, axis=0) #Always positive
> 
> Reasoning:
> So, if you swap x and y, T will still be the same, z statistics will still be the same
> 
> Final Thoughts:
> We should also aim to return in some way that whether r_plus was used or r_minus was used (through a flag maybe) in order to know the result of a one-sided tailed test


### Reproducing code example:
```
A = [10,10,10,10,15,11]
B = [30,30,30,30,31,32]

wilcoxon(A, B) == wilcoxon(B,A)
```

### Scipy/Numpy/Python version information:
```
1.0.1 1.14.3 sys.version_info(major=3, minor=6, micro=5, releaselevel='final', serial=0)
```


Discussion:
The test only returns the two-sided p-value, so the order of x and y does not matter. I don't think there is a bug.

Is this a feature request to have a one-sided test?
Once could transform the p-value directly into a one-sided p-value by diving by 2. So, yes, it is either a feature request (like in R: alternative which can be 'two.sided', 'greater' or 'less') or a request for providing a flag for rejection direction in case of a one-sided test.

Resolving patch:
diff --git a/scipy/stats/morestats.py b/scipy/stats/morestats.py
--- a/scipy/stats/morestats.py
+++ b/scipy/stats/morestats.py
@@ -2704,7 +2704,8 @@ def mood(x, y, axis=0):
 WilcoxonResult = namedtuple('WilcoxonResult', ('statistic', 'pvalue'))
 
 
-def wilcoxon(x, y=None, zero_method="wilcox", correction=False):
+def wilcoxon(x, y=None, zero_method="wilcox", correction=False,
+             alternative="two-sided"):
     """
     Calculate the Wilcoxon signed-rank test.
 
@@ -2721,37 +2722,94 @@ def wilcoxon(x, y=None, zero_method="wilcox", correction=False):
         The second set of measurements.  If `y` is not given, then the `x`
         array is considered to be the differences between the two sets of
         measurements.
-    zero_method : string, {"pratt", "wilcox", "zsplit"}, optional
+    zero_method : {"pratt", "wilcox", "zsplit"}, optional. Default is "wilcox".
         "pratt":
-            Pratt treatment: includes zero-differences in the ranking process
-            (more conservative)
+            includes zero-differences in the ranking process,
+            but drops the ranks of the zeros, see [4]_, (more conservative)
         "wilcox":
-            Wilcox treatment: discards all zero-differences
+            discards all zero-differences, the default
         "zsplit":
-            Zero rank split: just like Pratt, but splitting the zero rank
-            between positive and negative ones
+            includes zero-differences in the ranking process and split the
+            zero rank between positive and negative ones
     correction : bool, optional
         If True, apply continuity correction by adjusting the Wilcoxon rank
         statistic by 0.5 towards the mean value when computing the
         z-statistic.  Default is False.
+    alternative : {"two-sided", "greater", "less"}, optional
+        The alternative hypothesis to be tested, see Notes. Default is
+        "two-sided".
 
     Returns
     -------
     statistic : float
-        The sum of the ranks of the differences above or below zero, whichever
-        is smaller.
+        If `alternative` is "two-sided", the sum of the ranks of the
+        differences above or below zero, whichever is smaller.
+        Otherwise the sum of the ranks of the differences above zero.
     pvalue : float
-        The two-sided p-value for the test.
+        The p-value for the test depending on `alternative`.
+
+    See Also
+    --------
+    kruskal, mannwhitneyu
 
     Notes
     -----
-    Because the normal approximation is used for the calculations, the
-    samples used should be large.  A typical rule is to require that
-    n > 20.
+    The test has been introduced in [4]_. Given n independent samples
+    (xi, yi) from a bivariate distribution (i.e. paired samples),
+    it computes the differences di = xi - yi. One assumption of the test
+    is that the differences are symmetric, see [2]_.
+    The two-sided test has the null hypothesis that the median of the
+    differences is zero against the alternative that it is different from
+    zero. The one-sided test has the null that the median is positive against
+    the alternative that the it is negative (``alternative == 'less'``),
+    or vice versa (``alternative == 'greater.'``).
+
+    The test uses a normal approximation to derive the p-value. A typical rule
+    is to require that n > 20. For smaller n, exact tables can be used to find
+    critical values.
 
     References
     ----------
     .. [1] https://en.wikipedia.org/wiki/Wilcoxon_signed-rank_test
+    .. [2] Conover, W.J., Practical Nonparametric Statistics, 1971.
+    .. [3] Pratt, J.W., Remarks on Zeros and Ties in the Wilcoxon Signed
+       Rank Procedures, Journal of the American Statistical Association,
+       Vol. 54, 1959, pp. 655-667. :doi:`10.1080/01621459.1959.10501526`
+    .. [4] Wilcoxon, F., Individual Comparisons by Ranking Methods,
+       Biometrics Bulletin, Vol. 1, 1945, pp. 80-83. :doi:`10.2307/3001968`
+
+    Examples
+    --------
+    In [4]_, the differences in height between cross- and self-fertilized
+    corn plants is given as follows:
+
+    >>> d = [6, 8, 14, 16, 23, 24, 28, 29, 41, -48, 49, 56, 60, -67, 75]
+
+    Cross-fertilized plants appear to be be higher. To test the null
+    hypothesis that there is no height difference, we can apply the
+    two-sided test:
+
+    >>> from scipy.stats import wilcoxon
+    >>> w, p = wilcoxon(d)
+    >>> w, p
+    (24.0, 0.04088813291185591)
+
+    Hence, we would reject the null hypothesis at a confidence level of 5%,
+    concluding that there is a difference in height between the groups.
+    To confirm that the median of the differences can be assumed to be
+    positive, we use:
+
+    >>> w, p = wilcoxon(d, alternative='greater')
+    >>> w, p
+    (96.0, 0.020444066455927955)
+
+    This shows that the null hypothesis that the median is negative can be
+    rejected at a confidence level of 5% in favor of the alternative that
+    the median is greater than zero. The p-value based on the approximation
+    is within the range of 0.019 and 0.054 given in [2]_.
+    Note that the statistic changed to 96 in the one-sided case (the sum
+    of ranks of positive differences) whereas it is 24 in the two-sided
+    case (the minimum of sum of ranks above and below zero).
 
     """
 
@@ -2759,12 +2817,16 @@ def wilcoxon(x, y=None, zero_method="wilcox", correction=False):
         raise ValueError("Zero method should be either 'wilcox' "
                          "or 'pratt' or 'zsplit'")
 
+    if alternative not in ["two-sided", "less", "greater"]:
+        raise ValueError("Alternative must be either 'two-sided', "
+                         "'greater' or 'less'")
+
     if y is None:
         d = asarray(x)
     else:
         x, y = map(asarray, (x, y))
         if len(x) != len(y):
-            raise ValueError('Unequal N in wilcoxon.  Aborting.')
+            raise ValueError('Unequal N in wilcoxon. Aborting.')
         d = x - y
 
     if zero_method == "wilcox":
@@ -2773,7 +2835,7 @@ def wilcoxon(x, y=None, zero_method="wilcox", correction=False):
 
     count = len(d)
     if count < 10:
-        warnings.warn("Warning: sample size too small for normal approximation.")
+        warnings.warn("Sample size too small for normal approximation.")
 
     r = stats.rankdata(abs(d))
     r_plus = np.sum((d > 0) * r, axis=0)
@@ -2784,7 +2846,17 @@ def wilcoxon(x, y=None, zero_method="wilcox", correction=False):
         r_plus += r_zero / 2.
         r_minus += r_zero / 2.
 
-    T = min(r_plus, r_minus)
+    # return min for two-sided test, but r_plus for one-sided test
+    # the literature is not consistent here
+    # r_plus is more informative since r_plus + r_minus = count*(count+1)/2,
+    # i.e. the sum of the ranks, so r_minus and the min can be inferred
+    # (If alternative='pratt', r_plus + r_minus = count*(count+1)/2 - r_zero.)
+    # [3] uses the r_plus for the one-sided test, keep min for two-sided test
+    # to keep backwards compatability
+    if alternative == "two-sided":
+        T = min(r_plus, r_minus)
+    else:
+        T = r_plus
     mn = count * (count + 1.) * 0.25
     se = count * (count + 1.) * (2. * count + 1.)
 
@@ -2797,9 +2869,27 @@ def wilcoxon(x, y=None, zero_method="wilcox", correction=False):
         se -= 0.5 * (repnum * (repnum * repnum - 1)).sum()
 
     se = sqrt(se / 24)
-    correction = 0.5 * int(bool(correction)) * np.sign(T - mn)
-    z = (T - mn - correction) / se
-    prob = 2. * distributions.norm.sf(abs(z))
+
+    # apply continuity correction if applicable
+    d = 0
+    if correction:
+        if alternative == "two-sided":
+            d = 0.5 * np.sign(T - mn)
+        elif alternative == "less":
+            d = -0.5
+        else:
+            d = 0.5
+
+    # compute statistic and p-value using normal approximation
+    z = (T - mn - d) / se
+    if alternative == "two-sided":
+        prob = 2. * distributions.norm.sf(abs(z))
+    elif alternative == "greater":
+        # large T = r_plus indicates x is greater than y; i.e.
+        # accept alternative in that case and return small p-value (sf)
+        prob = distributions.norm.sf(z)
+    else:
+        prob = distributions.norm.cdf(z)
 
     return WilcoxonResult(T, prob)
 


Test patch:
diff --git a/scipy/stats/tests/test_morestats.py b/scipy/stats/tests/test_morestats.py
--- a/scipy/stats/tests/test_morestats.py
+++ b/scipy/stats/tests/test_morestats.py
@@ -903,21 +903,114 @@ def test_array_of_size_one(self):
                           (np.nan, np.nan, 0.0)))
 
 
-def test_wilcoxon_bad_arg():
-    # Raise ValueError when two args of different lengths are given or
-    # zero_method is unknown.
-    assert_raises(ValueError, stats.wilcoxon, [1], [1, 2])
-    assert_raises(ValueError, stats.wilcoxon, [1, 2], [1, 2], "dummy")
+class TestWilcoxon(object):
+    def test_wilcoxon_bad_arg(self):
+        # Raise ValueError when two args of different lengths are given or
+        # zero_method is unknown.
+        assert_raises(ValueError, stats.wilcoxon, [1], [1, 2])
+        assert_raises(ValueError, stats.wilcoxon, [1, 2], [1, 2], "dummy")
+        assert_raises(ValueError, stats.wilcoxon, [1, 2], [1, 2],
+                      alternative="dummy")
+
+    def test_wilcoxon_arg_type(self):
+        # Should be able to accept list as arguments.
+        # Address issue 6070.
+        arr = [1, 2, 3, 0, -1, 3, 1, 2, 1, 1, 2]
+
+        _ = stats.wilcoxon(arr, zero_method="pratt")
+        _ = stats.wilcoxon(arr, zero_method="zsplit")
+        _ = stats.wilcoxon(arr, zero_method="wilcox")
+
+    def test_accuracy_wilcoxon(self):
+        freq = [1, 4, 16, 15, 8, 4, 5, 1, 2]
+        nums = range(-4, 5)
+        x = np.concatenate([[u] * v for u, v in zip(nums, freq)])
+        y = np.zeros(x.size)
+
+        T, p = stats.wilcoxon(x, y, "pratt")
+        assert_allclose(T, 423)
+        assert_allclose(p, 0.00197547303533107)
+
+        T, p = stats.wilcoxon(x, y, "zsplit")
+        assert_allclose(T, 441)
+        assert_allclose(p, 0.0032145343172473055)
+
+        T, p = stats.wilcoxon(x, y, "wilcox")
+        assert_allclose(T, 327)
+        assert_allclose(p, 0.00641346115861)
+
+        # Test the 'correction' option, using values computed in R with:
+        # > wilcox.test(x, y, paired=TRUE, exact=FALSE, correct={FALSE,TRUE})
+        x = np.array([120, 114, 181, 188, 180, 146, 121, 191, 132, 113, 127, 112])
+        y = np.array([133, 143, 119, 189, 112, 199, 198, 113, 115, 121, 142, 187])
+        T, p = stats.wilcoxon(x, y, correction=False)
+        assert_equal(T, 34)
+        assert_allclose(p, 0.6948866, rtol=1e-6)
+        T, p = stats.wilcoxon(x, y, correction=True)
+        assert_equal(T, 34)
+        assert_allclose(p, 0.7240817, rtol=1e-6)
+
+    def test_wilcoxon_result_attributes(self):
+        x = np.array([120, 114, 181, 188, 180, 146, 121, 191, 132, 113, 127, 112])
+        y = np.array([133, 143, 119, 189, 112, 199, 198, 113, 115, 121, 142, 187])
+        res = stats.wilcoxon(x, y, correction=False)
+        attributes = ('statistic', 'pvalue')
+        check_named_results(res, attributes)
+
+    def test_wilcoxon_tie(self):
+        # Regression test for gh-2391.
+        # Corresponding R code is:
+        #   > result = wilcox.test(rep(0.1, 10), exact=FALSE, correct=FALSE)
+        #   > result$p.value
+        #   [1] 0.001565402
+        #   > result = wilcox.test(rep(0.1, 10), exact=FALSE, correct=TRUE)
+        #   > result$p.value
+        #   [1] 0.001904195
+        stat, p = stats.wilcoxon([0.1] * 10)
+        expected_p = 0.001565402
+        assert_equal(stat, 0)
+        assert_allclose(p, expected_p, rtol=1e-6)
+
+        stat, p = stats.wilcoxon([0.1] * 10, correction=True)
+        expected_p = 0.001904195
+        assert_equal(stat, 0)
+        assert_allclose(p, expected_p, rtol=1e-6)
+
+    def test_onesided(self):
+        # tested against "R version 3.4.1 (2017-06-30)"
+        # x <- c(125, 115, 130, 140, 140, 115, 140, 125, 140, 135)
+        # y <- c(110, 122, 125, 120, 140, 124, 123, 137, 135, 145)
+        # cfg <- list(x = x, y = y, paired = TRUE, exact = FALSE)
+        # do.call(wilcox.test, c(cfg, list(alternative = "less", correct = FALSE)))
+        # do.call(wilcox.test, c(cfg, list(alternative = "less", correct = TRUE)))
+        # do.call(wilcox.test, c(cfg, list(alternative = "greater", correct = FALSE)))
+        # do.call(wilcox.test, c(cfg, list(alternative = "greater", correct = TRUE)))
+        x = [125, 115, 130, 140, 140, 115, 140, 125, 140, 135]
+        y = [110, 122, 125, 120, 140, 124, 123, 137, 135, 145]
 
+        with suppress_warnings() as sup:
+            sup.filter(UserWarning, message="Sample size too small")
+            w, p = stats.wilcoxon(x, y, alternative="less")
+        assert_equal(w, 27)
+        assert_almost_equal(p, 0.7031847, decimal=6)
+
+        with suppress_warnings() as sup:
+            sup.filter(UserWarning, message="Sample size too small")
+            w, p = stats.wilcoxon(x, y, alternative="less", correction=True)
+        assert_equal(w, 27)
+        assert_almost_equal(p, 0.7233656, decimal=6)
 
-def test_wilcoxon_arg_type():
-    # Should be able to accept list as arguments.
-    # Address issue 6070.
-    arr = [1, 2, 3, 0, -1, 3, 1, 2, 1, 1, 2]
+        with suppress_warnings() as sup:
+            sup.filter(UserWarning, message="Sample size too small")
+            w, p = stats.wilcoxon(x, y, alternative="greater")
+        assert_equal(w, 27)
+        assert_almost_equal(p, 0.2968153, decimal=6)
 
-    _ = stats.wilcoxon(arr, zero_method="pratt")
-    _ = stats.wilcoxon(arr, zero_method="zsplit")
-    _ = stats.wilcoxon(arr, zero_method="wilcox")
+        with suppress_warnings() as sup:
+            sup.filter(UserWarning, message="Sample size too small")
+            w, p = stats.wilcoxon(x, y, alternative="greater", correction=True)
+        assert_equal(w, 27)
+        assert_almost_equal(p, 0.3176447, decimal=6)
 
 
 class TestKstat(object):
@@ -1461,63 +1554,6 @@ def test_circfuncs_unit8(self):
         assert_allclose(stats.circvar(x, high=180), 437.45871686, rtol=1e-7)
         assert_allclose(stats.circstd(x, high=180), 20.91551378, rtol=1e-7)
 
-def test_accuracy_wilcoxon():
-    freq = [1, 4, 16, 15, 8, 4, 5, 1, 2]
-    nums = range(-4, 5)
-    x = np.concatenate([[u] * v for u, v in zip(nums, freq)])
-    y = np.zeros(x.size)
-
-    T, p = stats.wilcoxon(x, y, "pratt")
-    assert_allclose(T, 423)
-    assert_allclose(p, 0.00197547303533107)
-
-    T, p = stats.wilcoxon(x, y, "zsplit")
-    assert_allclose(T, 441)
-    assert_allclose(p, 0.0032145343172473055)
-
-    T, p = stats.wilcoxon(x, y, "wilcox")
-    assert_allclose(T, 327)
-    assert_allclose(p, 0.00641346115861)
-
-    # Test the 'correction' option, using values computed in R with:
-    # > wilcox.test(x, y, paired=TRUE, exact=FALSE, correct={FALSE,TRUE})
-    x = np.array([120, 114, 181, 188, 180, 146, 121, 191, 132, 113, 127, 112])
-    y = np.array([133, 143, 119, 189, 112, 199, 198, 113, 115, 121, 142, 187])
-    T, p = stats.wilcoxon(x, y, correction=False)
-    assert_equal(T, 34)
-    assert_allclose(p, 0.6948866, rtol=1e-6)
-    T, p = stats.wilcoxon(x, y, correction=True)
-    assert_equal(T, 34)
-    assert_allclose(p, 0.7240817, rtol=1e-6)
-
-
-def test_wilcoxon_result_attributes():
-    x = np.array([120, 114, 181, 188, 180, 146, 121, 191, 132, 113, 127, 112])
-    y = np.array([133, 143, 119, 189, 112, 199, 198, 113, 115, 121, 142, 187])
-    res = stats.wilcoxon(x, y, correction=False)
-    attributes = ('statistic', 'pvalue')
-    check_named_results(res, attributes)
-
-
-def test_wilcoxon_tie():
-    # Regression test for gh-2391.
-    # Corresponding R code is:
-    #   > result = wilcox.test(rep(0.1, 10), exact=FALSE, correct=FALSE)
-    #   > result$p.value
-    #   [1] 0.001565402
-    #   > result = wilcox.test(rep(0.1, 10), exact=FALSE, correct=TRUE)
-    #   > result$p.value
-    #   [1] 0.001904195
-    stat, p = stats.wilcoxon([0.1] * 10)
-    expected_p = 0.001565402
-    assert_equal(stat, 0)
-    assert_allclose(p, expected_p, rtol=1e-6)
-
-    stat, p = stats.wilcoxon([0.1] * 10, correction=True)
-    expected_p = 0.001904195
-    assert_equal(stat, 0)
-    assert_allclose(p, expected_p, rtol=1e-6)
-
 
 class TestMedianTest(object):