Repository: scipy/scipy

Issue:
BUG: stats: inconsistency in docs and behavior of gmean and hmean
There are some problems with `gmean` and `hmean`:

**`stats.gmean`**
-   The `axis` argument can be a tuple, so the docstring should include this possibility.
  (The ability to handle a tuple might have happened "by accident" when numpy added this
  feature.)
-   In the notes, it says "float64 intermediate and return values are used for integer
  inputs."  This is not true, and in fact it contradicts the documentation of the
  `dtype` argument.  (But perhaps it _should_ be true.)
  
  For example, when the input data type is `uint8`, the data type of the result is `float16`:
  
  ```
  In [47]: x = np.array([1, 2, 3], dtype=np.uint8)
  
  In [48]: g = gmean(x)
  
  In [49]: g.dtype
  Out[49]: dtype('float16')
  ```
  
  This behavior _also_ contradicts what is stated in the description of the `dtype` argument.
-   A spurious warning is generated when the input contains 0.  There is nothing wrong
  with taking the geometric mean of set of values containing 0, so no warning should
  occur.
  
  ```
  In [50]: gmean([1, 0, 2])
  /Users/warren/miniconda3scipy/lib/python3.5/site-packages/scipy-0.19.0.dev0+ac75879-py3.5-macosx-10.6-x86_64.egg/scipy/stats/stats.py:306: RuntimeWarning: divide by zero encountered in log
    log_a = np.log(np.array(a, dtype=dtype))
  Out[50]: 0.0
  ```
  
  The return value, 0.0, is correct.
-   The notes also say: "Use masked arrays to ignore any non-finite values in the input or that
  arise in the calculations such as Not a Number and infinity because masked
  arrays automatically mask any non-finite values."  That last clause is not true: masked arrays do not _automatically_ mask non-finite values.  This entire statement could be dropped, or at least shortened to the version that is in the `hmean` docstring.
- The docstring needs some examples, including one where `axis` is a tuple.

**`stats.hmean`**
-   `axis` should accept a tuple; see the note above about `gmean`.
-   The description of the handling of the data type when `dtype` is None is not correct.
  The `hmean` docstring has the same note about using `float64` as `gmean`'s
  docstring.  Apparently the `hmean` code actually agrees with that part of the documentation. 



Discussion:
I think we'll want to treat this as as part of gh-14651. The `axis` tuple stuff is already handled, and all stats functions should have the same policies regarding `dtype`, so this inconsistency should be considered in the broader context.
@mdhaber @WarrenWeckesser AFAICT the problem of the spurious warning when the inputs to `gmean` include 0 remains; can this issue be reopened?
@anntzer What version of scipy are you testing with, and what code is not behaving as you expect?
As of 1.8.1 and 1.9.0rc2:
```
In [1]: scipy.stats.gmean([0, 1, 2])
/usr/lib/python3.10/site-packages/scipy/stats/_stats_py.py:279: RuntimeWarning: divide by zero encountered in log
  log_a = np.log(np.array(a, dtype=dtype))
Out[1]: numpy.float64(0.0)
```
As noted by @WarrenWeckesser above, the warning is spurious.
Thanks @anntzer. I must not have seen that part of the issue. The rest (including `dtype`) is part of gh-14651.

@tupui (or @anntzer) I'm flying now but if you have a moment to address this (probably just `np.errstate`), I can review.

Resolving patch:
diff --git a/scipy/stats/_stats_py.py b/scipy/stats/_stats_py.py
--- a/scipy/stats/_stats_py.py
+++ b/scipy/stats/_stats_py.py
@@ -318,14 +318,15 @@ def gmean(a, axis=0, dtype=None, weights=None):
     """
     if not isinstance(a, np.ndarray):
         # if not an ndarray object attempt to convert it
-        log_a = np.log(np.array(a, dtype=dtype))
+        a = np.array(a, dtype=dtype)
     elif dtype:
         # Must change the default dtype allowing array type
         if isinstance(a, np.ma.MaskedArray):
-            log_a = np.log(np.ma.asarray(a, dtype=dtype))
+            a = np.ma.asarray(a, dtype=dtype)
         else:
-            log_a = np.log(np.asarray(a, dtype=dtype))
-    else:
+            a = np.asarray(a, dtype=dtype)
+
+    with np.errstate(divide='ignore'):
         log_a = np.log(a)
 
     if weights is not None:


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
@@ -5645,6 +5645,11 @@ def check_equal_pmean(array_like, exp, desired, axis=None, dtype=None,
 
 
 class TestHarMean:
+    def test_0(self):
+        a = [1, 0, 2]
+        desired = 0
+        check_equal_hmean(a, desired)
+
     def test_1d_list(self):
         #  Test a 1d list
         a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
@@ -5739,6 +5744,11 @@ def test_weights_masked_1d_array(self):
 
 
 class TestGeoMean:
+    def test_0(self):
+        a = [1, 0, 2]
+        desired = 0
+        check_equal_gmean(a, desired)
+
     def test_1d_list(self):
         #  Test a 1d list
         a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]