Repository: scipy/scipy

Issue:
Sigma clipping on data with the same value
When I use sigma clipping with an array where all values are the same, everything gets clipped. 

```python
import scipy
scipy.stats.sigmaclip([2,2,2])[0]
```

This is especially problematic if there is one outlier and all other values are the same.

```python
import scipy
scipy.stats.sigmaclip([2]*20+[3])[0]
```

In both examples, I end up with an empty list. 


Discussion:
Astropy doesn't have this problem

```python
> from astropy.stats import sigma_clip
> sigma_clip([2]*20+[3])
masked_array(data=[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
                   2, 2, --],
             mask=[False, False, False, False, False, False, False, False,
                   False, False, False, False, False, False, False, False,
                   False, False, False, False,  True],
       fill_value=999999)
```
According to the documentation, "The output array contains only those elements of the input array c that satisfy the conditions `mean(c) - std(c)*low < c < mean(c) + std(c)*high` "

If `std(c) = 0`, there is no such element, so it is technically correct, but still not what you expect.

One could change strict inequality to `<=` (Looks like this is what astropy does, see Notes here http://docs.astropy.org/en/stable/api/astropy.stats.sigma_clip.html)


Resolving patch:
diff --git a/scipy/stats/stats.py b/scipy/stats/stats.py
--- a/scipy/stats/stats.py
+++ b/scipy/stats/stats.py
@@ -2581,14 +2581,15 @@ def sigmaclip(a, low=4., high=4.):
     """
     Iterative sigma-clipping of array elements.
 
-    The output array contains only those elements of the input array `c`
-    that satisfy the conditions ::
+    Starting from the full sample, all elements outside the critical range are
+    removed, i.e. all elements of the input array `c` that satisfy either of
+    the following conditions ::
 
-        mean(c) - std(c)*low < c < mean(c) + std(c)*high
+        c < mean(c) - std(c)*low
+        c > mean(c) + std(c)*high
 
-    Starting from the full sample, all elements outside the critical range are
-    removed. The iteration continues with a new critical range until no
-    elements are outside the range.
+    The iteration continues with the updated sample until no
+    elements are outside the (updated) range.
 
     Parameters
     ----------
@@ -2639,7 +2640,7 @@ def sigmaclip(a, low=4., high=4.):
         size = c.size
         critlower = c_mean - c_std * low
         critupper = c_mean + c_std * high
-        c = c[(c > critlower) & (c < critupper)]
+        c = c[(c >= critlower) & (c <= critupper)]
         delta = size - c.size
 
     return SigmaclipResult(c, critlower, critupper)


Test patch:
diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py
--- a/scipy/stats/tests/test_stats.py
+++ b/scipy/stats/tests/test_stats.py
@@ -3856,9 +3856,9 @@ def test_trim_mean(self):
         assert_equal(stats.trim_mean([], 0.6), np.nan)
 
 
-class TestSigamClip(object):
+class TestSigmaClip(object):
     def test_sigmaclip1(self):
-        a = np.concatenate((np.linspace(9.5,10.5,31),np.linspace(0,20,5)))
+        a = np.concatenate((np.linspace(9.5, 10.5, 31), np.linspace(0, 20, 5)))
         fact = 4  # default
         c, low, upp = stats.sigmaclip(a)
         assert_(c.min() > low)
@@ -3868,7 +3868,7 @@ def test_sigmaclip1(self):
         assert_equal(c.size, a.size)
 
     def test_sigmaclip2(self):
-        a = np.concatenate((np.linspace(9.5,10.5,31),np.linspace(0,20,5)))
+        a = np.concatenate((np.linspace(9.5, 10.5, 31), np.linspace(0, 20, 5)))
         fact = 1.5
         c, low, upp = stats.sigmaclip(a, fact, fact)
         assert_(c.min() > low)
@@ -3879,14 +3879,15 @@ def test_sigmaclip2(self):
         assert_equal(a.size, 36)  # check original array unchanged
 
     def test_sigmaclip3(self):
-        a = np.concatenate((np.linspace(9.5,10.5,11),np.linspace(-100,-50,3)))
+        a = np.concatenate((np.linspace(9.5, 10.5, 11),
+                            np.linspace(-100, -50, 3)))
         fact = 1.8
         c, low, upp = stats.sigmaclip(a, fact, fact)
         assert_(c.min() > low)
         assert_(c.max() < upp)
         assert_equal(low, c.mean() - fact*c.std())
         assert_equal(upp, c.mean() + fact*c.std())
-        assert_equal(c, np.linspace(9.5,10.5,11))
+        assert_equal(c, np.linspace(9.5, 10.5, 11))
 
     def test_sigmaclip_result_attributes(self):
         a = np.concatenate((np.linspace(9.5, 10.5, 11),
@@ -3896,6 +3897,12 @@ def test_sigmaclip_result_attributes(self):
         attributes = ('clipped', 'lower', 'upper')
         check_named_results(res, attributes)
 
+    def test_std_zero(self):
+        # regression test #8632
+        x = np.ones(10)
+        assert_equal(stats.sigmaclip(x)[0], x)
+
+
 class TestFOneWay(object):
     def test_trivial(self):
         # A trivial test of stats.f_oneway, with F=0.