Repository: scipy/scipy

Issue:
Yeo-Johnson power transformation fails with integer input data
My issue is about [scipy.stats.yeojohnson](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.yeojohnson.html). A ValueError is raised when the input data has an integer type, because `Integers to negative integer powers are not allowed.`

### Reproducing code example:

```python
import numpy as np
from scipy.stats import yeojohnson


yeojohnson(np.arange(10))
```
which fails, while it works if the data type is np.float64:
```python
yeojohnson(np.arange(10).astype('float64))
```

### Error message:
<!-- If any, paste the *full* error message inside a code block
as above (starting from line Traceback)
-->

```python
ValueError                                Traceback (most recent call last)
<ipython-input-4-2cd13f92b547> in <module>
----> 1 yeojohnson(np.arange(10))

~/anaconda3/lib/python3.7/site-packages/scipy/stats/morestats.py in yeojohnson(x, lmbda)
   1345 
   1346     # if lmbda=None, find the lmbda that maximizes the log-likelihood function.
-> 1347     lmax = yeojohnson_normmax(x)
   1348     y = _yeojohnson_transform(x, lmax)
   1349 

~/anaconda3/lib/python3.7/site-packages/scipy/stats/morestats.py in yeojohnson_normmax(x, brack)
   1518         return -yeojohnson_llf(lmbda, data)
   1519 
-> 1520     return optimize.brent(_neg_llf, brack=brack, args=(x,))
   1521 
   1522 

~/anaconda3/lib/python3.7/site-packages/scipy/optimize/optimize.py in brent(func, args, brack, tol, full_output, maxiter)
   2107     options = {'xtol': tol,
   2108                'maxiter': maxiter}
-> 2109     res = _minimize_scalar_brent(func, brack, args, **options)
   2110     if full_output:
   2111         return res['x'], res['fun'], res['nit'], res['nfev']

~/anaconda3/lib/python3.7/site-packages/scipy/optimize/optimize.py in _minimize_scalar_brent(func, brack, args, xtol, maxiter, **unknown_options)
   2139                   full_output=True, maxiter=maxiter)
   2140     brent.set_bracket(brack)
-> 2141     brent.optimize()
   2142     x, fval, nit, nfev = brent.get_result(full_output=True)
   2143     return OptimizeResult(fun=fval, x=x, nit=nit, nfev=nfev,

~/anaconda3/lib/python3.7/site-packages/scipy/optimize/optimize.py in optimize(self)
   1923         # set up for optimization
   1924         func = self.func
-> 1925         xa, xb, xc, fa, fb, fc, funcalls = self.get_bracket_info()
   1926         _mintol = self._mintol
   1927         _cg = self._cg

~/anaconda3/lib/python3.7/site-packages/scipy/optimize/optimize.py in get_bracket_info(self)
   1900         elif len(brack) == 2:
   1901             xa, xb, xc, fa, fb, fc, funcalls = bracket(func, xa=brack[0],
-> 1902                                                        xb=brack[1], args=args)
   1903         elif len(brack) == 3:
   1904             xa, xb, xc = brack

~/anaconda3/lib/python3.7/site-packages/scipy/optimize/optimize.py in bracket(func, xa, xb, args, grow_limit, maxiter)
   2322     _gold = 1.618034  # golden ratio: (1.0+sqrt(5.0))/2.0
   2323     _verysmall_num = 1e-21
-> 2324     fa = func(*(xa,) + args)
   2325     fb = func(*(xb,) + args)
   2326     if (fa < fb):                      # Switch so fa > fb

~/anaconda3/lib/python3.7/site-packages/scipy/stats/morestats.py in _neg_llf(lmbda, data)
   1516 
   1517     def _neg_llf(lmbda, data):
-> 1518         return -yeojohnson_llf(lmbda, data)
   1519 
   1520     return optimize.brent(_neg_llf, brack=brack, args=(x,))

~/anaconda3/lib/python3.7/site-packages/scipy/stats/morestats.py in yeojohnson_llf(lmb, data)
   1461         return np.nan
   1462 
-> 1463     trans = _yeojohnson_transform(data, lmb)
   1464 
   1465     loglike = -n_samples / 2 * np.log(trans.var(axis=0))

~/anaconda3/lib/python3.7/site-packages/scipy/stats/morestats.py in _yeojohnson_transform(x, lmbda)
   1362         out[pos] = np.log1p(x[pos])
   1363     else:  # lmbda != 0
-> 1364         out[pos] = (np.power(x[pos] + 1, lmbda) - 1) / lmbda
   1365 
   1366     # when x < 0

ValueError: Integers to negative integer powers are not allowed.
```

### Scipy/Numpy/Python version information:

```python
import sys, scipy, numpy; print(scipy.__version__, numpy.__version__, sys.version_info)
1.3.0 1.16.4 sys.version_info(major=3, minor=7, micro=1, releaselevel='final', serial=0)
```

### Proposed solution

The first operation is to convert `x` as np.array:
https://github.com/scipy/scipy/blob/e1e44d12637997606b1bcc0c6de232349e11eee0/scipy/stats/morestats.py#L1339
I think that it can be expected to have float data when doing a Yeo-Johnson power transformation, so we could just modify the dtype by adding `dtype='float64'` (or maybe find the lowest value among `float16`, `float32` and `float64`).

I would like to work on this if possible.


Discussion:

Resolving patch:
diff --git a/scipy/stats/morestats.py b/scipy/stats/morestats.py
--- a/scipy/stats/morestats.py
+++ b/scipy/stats/morestats.py
@@ -1340,6 +1340,13 @@ def yeojohnson(x, lmbda=None):
     if x.size == 0:
         return x
 
+    if np.issubdtype(x.dtype, np.complexfloating):
+        raise ValueError('Yeo-Johnson transformation is not defined for '
+                         'complex numbers.')
+
+    if np.issubdtype(x.dtype, np.integer):
+        x = x.astype(np.float64, copy=False)
+
     if lmbda is not None:
         return _yeojohnson_transform(x, lmbda)
 


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
@@ -1512,6 +1512,23 @@ def test_array_like(self):
         xt2, _ = stats.yeojohnson(list(x))
         assert_allclose(xt1, xt2, rtol=1e-12)
 
+    @pytest.mark.parametrize('dtype', [np.complex64, np.complex128])
+    def test_input_dtype_complex(self, dtype):
+        x = np.arange(6, dtype=dtype)
+        err_msg = ('Yeo-Johnson transformation is not defined for complex '
+                   'numbers.')
+        with pytest.raises(ValueError, match=err_msg):
+            stats.yeojohnson(x)
+
+    @pytest.mark.parametrize('dtype', [np.int8, np.uint8, np.int16, np.int32])
+    def test_input_dtype_integer(self, dtype):
+        x_int = np.arange(8, dtype=dtype)
+        x_float = np.arange(8, dtype=np.float64)
+        xt_int, lmbda_int = stats.yeojohnson(x_int)
+        xt_float, lmbda_float = stats.yeojohnson(x_float)
+        assert_allclose(xt_int, xt_float, rtol=1e-7)
+        assert_allclose(lmbda_int, lmbda_float, rtol=1e-7)
+
 
 class TestYeojohnsonNormmax(object):
     def setup_method(self):