Repository: scipy/scipy
Pull request: TST Duplicate tests for geometric mean
closes #9467 

This is first version for PR.
@Kai-Striega please review the commit.

I changed the structure for testing(Geometric mean only).
If this structure is looks fine, I will apply for Harmonic mean test.

Problem statement:
TST: Duplicate tests for Geometric and Harmonic Means
The Geometric and Harmonic means have two sets of test cases in ``test_stats``. Ideally, the tests for related functions should occur in one place, not spread across multiple suites. I think these tests should be reviewed and combined into a single test suit where possible. 

For reference, the tests covering the geometric mean are  ``TestGMean`` and ``GeoMeanTestCase`` / ``TestGeoMean(GeoMeanTestCase)``. And for the harmonic mean ``TestHMean`` and ``HarMeanTestCase`` / ``TestHarMean(HarMeanTestCase)``

Hints from issue discussion:
Hello,
Should I take this issue?
If no problem, I will follow that.
Go ahead, it will be nice to have it done.

Commit message: Update test_stats.py

- test method is changed (assert_allclose())
- test_ma is moved to test_mstats_basic.py

Patch to review:
diff --git a/scipy/stats/tests/test_stats.py b/scipy/stats/tests/test_stats.py
index f38339533d83..b94c26890e18 100644
--- a/scipy/stats/tests/test_stats.py
+++ b/scipy/stats/tests/test_stats.py
@@ -1231,52 +1231,6 @@ def test_relfreq():
     assert_array_almost_equal(relfreqs, relfreqs2)
 
 
-class TestGMean(object):
-
-    def test_1D_list(self):
-        a = (1,2,3,4)
-        actual = stats.gmean(a)
-        desired = power(1*2*3*4,1./4.)
-        assert_almost_equal(actual, desired,decimal=14)
-
-        desired1 = stats.gmean(a,axis=-1)
-        assert_almost_equal(actual, desired1, decimal=14)
-
-    def test_1D_array(self):
-        a = array((1,2,3,4), float32)
-        actual = stats.gmean(a)
-        desired = power(1*2*3*4,1./4.)
-        assert_almost_equal(actual, desired, decimal=7)
-
-        desired1 = stats.gmean(a,axis=-1)
-        assert_almost_equal(actual, desired1, decimal=7)
-
-    def test_2D_array_default(self):
-        a = array(((1,2,3,4),
-                   (1,2,3,4),
-                   (1,2,3,4)))
-        actual = stats.gmean(a)
-        desired = array((1,2,3,4))
-        assert_array_almost_equal(actual, desired, decimal=14)
-
-        desired1 = stats.gmean(a,axis=0)
-        assert_array_almost_equal(actual, desired1, decimal=14)
-
-    def test_2D_array_dim1(self):
-        a = array(((1,2,3,4),
-                   (1,2,3,4),
-                   (1,2,3,4)))
-        actual = stats.gmean(a, axis=1)
-        v = power(1*2*3*4,1./4.)
-        desired = array((v,v,v))
-        assert_array_almost_equal(actual, desired, decimal=14)
-
-    def test_large_values(self):
-        a = array([1e100, 1e200, 1e300])
-        actual = stats.gmean(a)
-        assert_approx_equal(actual, 1e200, significant=13)
-
-
 class TestHMean(object):
     def test_1D_list(self):
         a = (1,2,3,4)
@@ -3723,122 +3677,129 @@ def do(self, a, b, axis=None, dtype=None):
         assert_equal(x.dtype, dtype)
 
 
-class GeoMeanTestCase:
-    def test_1dlist(self):
+class StatsTestMethod(object):
+    def equal_test(self, array_like, desired, axis=None, dtype=None, significant=7):
+        # Note this doesn't test when axis is not specified
+        rtol = np.float_power(10, -1.0 * significant)
+        x = stats.gmean(array_like, axis=axis, dtype=dtype)
+        assert_allclose(x, desired, rtol=rtol)
+        assert_equal(x.dtype, dtype)
+
+
+class TestGeoMean(StatsTestMethod):
+    def test_1d_list(self):
         #  Test a 1d list
         a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
         b = 45.2872868812
-        self.do(a, b)
+        self.equal_test(a, b)
 
-    def test_1darray(self):
+        a = [1, 2, 3, 4]
+        desired = power(1 * 2 * 3 * 4, 1. / 4.)
+        self.equal_test(a, desired, significant=14)
+
+        desired1 = stats.gmean(a, axis=-1)
+        self.equal_test(a, desired1, axis=-1)
+
+    def test_1d_array(self):
         #  Test a 1d array
         a = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
         b = 45.2872868812
-        self.do(a, b)
+        self.equal_test(a, b)
 
-    def test_1dma(self):
-        #  Test a 1d masked array
-        a = np.ma.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
-        b = 45.2872868812
-        self.do(a, b)
+        a = array([1, 2, 3, 4], float32)
+        desired = power(1 * 2 * 3 * 4, 1. / 4.)
+        self.equal_test(a, desired, dtype=float32)
 
-    def test_1dmavalue(self):
-        #  Test a 1d masked array with a masked value
-        a = np.ma.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 100], mask=[0,0,0,0,0,0,0,0,0,1])
-        b = 41.4716627439
-        self.do(a, b)
+        desired1 = stats.gmean(a, axis=-1)
+        self.equal_test(a, desired1, axis=-1, dtype=float32)
 
     # Note the next tests use axis=None as default, not axis=0
-    def test_2dlist(self):
+    def test_2d_list(self):
         #  Test a 2d list
         a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
         b = 52.8885199
-        self.do(a, b)
+        self.equal_test(a, b)
 
-    def test_2darray(self):
+    def test_2d_array(self):
         #  Test a 2d array
         a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
         b = 52.8885199
-        self.do(np.array(a), b)
+        self.equal_test(array(a), b)
 
-    def test_2dma(self):
-        #  Test a 2d masked array
-        a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
-        b = 52.8885199
-        self.do(np.ma.array(a), b)
-
-    def test_2daxis0(self):
+    def test_2d_axis0(self):
         #  Test a 2d list with axis=0
         a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
         b = np.array([35.56893304, 49.32424149, 61.3579244, 72.68482371])
-        self.do(a, b, axis=0)
+        self.equal_test(a, b, axis=0)
 
-    def test_2daxis1(self):
+        a = array([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]])
+        desired = array([1, 2, 3, 4])
+        self.equal_test(a, desired, axis=0, significant=14)
+
+        a = array([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]])
+        desired = stats.gmean(a, axis=0)
+        self.equal_test(a, desired, axis=0, significant=14)
+
+    def test_2d_axis1(self):
         #  Test a 2d list with axis=1
         a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
         b = np.array([22.13363839, 64.02171746, 104.40086817])
-        self.do(a, b, axis=1)
+        self.equal_test(a, b, axis=1)
 
-    def test_2dmatrixdaxis0(self):
+        a = array([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]])
+        v = power(1 * 2 * 3 * 4, 1. / 4.)
+        desired = array([v, v, v])
+        self.equal_test(a, desired, axis=1, significant=14)
+
+    def test_2d_matrix_axis0(self):
         #  Test a 2d list with axis=0
         a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
         b = np.matrix([[35.56893304, 49.32424149, 61.3579244, 72.68482371]])
-        self.do(np.matrix(a), b, axis=0)
+        self.equal_test(np.matrix(a), b, axis=0)
 
-    def test_2dmatrixaxis1(self):
+        a = array([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]])
+        desired = np.matrix([1, 2, 3, 4])
+        self.equal_test(np.matrix(a), desired, axis=0, significant=14)
+
+        a = array([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]])
+        desired = np.matrix(stats.gmean(a, axis=0))
+        self.equal_test(np.matrix(a), desired, axis=0, significant=14)
+
+    def test_2d_matrix_axis1(self):
         #  Test a 2d list with axis=1
         a = [[10, 20, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
         b = np.matrix([[22.13363839, 64.02171746, 104.40086817]]).T
-        self.do(np.matrix(a), b, axis=1)
+        self.equal_test(np.matrix(a), b, axis=1)
+
+        a = array([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]])
+        v = power(1 * 2 * 3 * 4, 1. / 4.)
+        desired = np.matrix([[v], [v], [v]])
+        self.equal_test(np.matrix(a), desired, axis=1, significant=14)
+
+    def test_large_values(self):
+        a = array([1e100, 1e200, 1e300])
+        self.equal_test(a, 1e200, significant=13)
 
-    def test_1dlist0(self):
+    def test_1d_list0(self):
         #  Test a 1d list with zero element
         a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 0]
         b = 0.0  # due to exp(-inf)=0
         olderr = np.seterr(all='ignore')
         try:
-            self.do(a, b)
+            self.equal_test(a, b)
         finally:
             np.seterr(**olderr)
 
-    def test_1darray0(self):
+    def test_1d_array0(self):
         #  Test a 1d array with zero element
         a = np.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 0])
         b = 0.0  # due to exp(-inf)=0
         olderr = np.seterr(all='ignore')
         try:
-            self.do(a, b)
+            self.equal_test(a, b)
         finally:
             np.seterr(**olderr)
 
-    def test_1dma0(self):
-        #  Test a 1d masked array with zero element
-        a = np.ma.array([10, 20, 30, 40, 50, 60, 70, 80, 90, 0])
-        b = 41.4716627439
-        olderr = np.seterr(all='ignore')
-        try:
-            self.do(a, b)
-        finally:
-            np.seterr(**olderr)
-
-    def test_1dmainf(self):
-        #  Test a 1d masked array with negative element
-        a = np.ma.array([10, 20, 30, 40, 50, 60, 70, 80, 90, -1])
-        b = 41.4716627439
-        olderr = np.seterr(all='ignore')
-        try:
-            self.do(a, b)
-        finally:
-            np.seterr(**olderr)
-
-
-class TestGeoMean(GeoMeanTestCase):
-    def do(self, a, b, axis=None, dtype=None):
-        # Note this doesn't test when axis is not specified
-        x = stats.gmean(a, axis=axis, dtype=dtype)
-        assert_almost_equal(b, x)
-        assert_equal(x.dtype, dtype)
-
 
 def test_binomtest():
     # precision tests compared to R for ticket:986


Code review comments:
diff_hunk: @@ -3677,19 +3677,16 @@ def do(self, a, b, axis=None, dtype=None):
         assert_equal(x.dtype, dtype)
 
 
-class GeoMeanTestMethod(object):
-    def equal_test(self, array_like, desired, axis=None, dtype=None, decimal=7):
+class StatsTestMethod(object):
+    def equal_test(self, array_like, desired, axis=None, dtype=None, significant=7):
path: scipy/stats/tests/test_stats.py
text: @user1:
Here and for mstats have you considered using `rtol` as an argument? I think it would be more understandable than asking for `significant` and converting it to a tolerance.

@author:
When I changed the argument of `significant` to `rtol`, I also have concerned this issue. 
but the most of the code in test use the argument of `significant` or `decimal`' for testing a tolerance.

If I change the argument, the other parts of the test code should be changed, in my option.
How about your opinion? If it is needed, I will fix the argument.

@user1:
As it stands the code is already converting to `rtol`:

``` rtol = np.float_power(10, -1.0 * significant)```

This seems to be a very roundabout way of setting the tolerance, and should probably be avoided. Luckily, all the tests pass with the provided tolerance, and so replacing ``significant=7`` with ``rtol=1e-7``  (replace 7 with the actual value) should work.

This should also be applied to the tests in `mstats`.

@author:
OK, I understand.
I fix the argument of `siginificant` to `rtol`.