Repository: scipy/scipy

Issue:
BUG: scipy.stats.chi.mean returns nan for large df due to use of scipy.special.gamma
### Describe your issue.

For large numbers of degrees of freedom (above around 330 on my machine), scipy.stats.chi.mean (or calculating the mean via scipy.chi.stats) returns NaN, triggering a RuntimeWarning. This appears to be due to scipy.special.gamma returning 'inf' and appears to be easily fixable by substituting scipy.special.gammaln as demonstrated below. For the cases where scipy.stats.chi.mean returns a finite answer, the function below using scipy.special.gammaln produces values consistent with it.

### Reproducing Code Example

```python
from scipy.stats import chi
chi.mean(df=1000)

###############################################
# The above is the minimum reproducing example.
# below demonstrates that using gammaln can
# fix the issue without appearing to 
# sacrifice accuracy
################################################
import numpy as np
from scipy.special import gammaln

chi_mean = lambda df: np.sqrt(2)*np.exp(gammaln(df/2.0+0.5)-gammaln(df/2.0))

for i in np.linspace(1, 1000, 100):
  print(i, chi.mean(df=i), chi_mean(i), (chi.mean(df=i)-chi_mean(i))/chi_mean(i))
```


### Error message

```shell
/usr/local/lib/python3.7/dist-packages/scipy/stats/_continuous_distns.py:1150: RuntimeWarning: invalid value encountered in true_divide
  mu = np.sqrt(2)*sc.gamma(df/2.0+0.5)/sc.gamma(df/2.0)
```


### SciPy/NumPy/Python version information

1.4.1 1.19.5 sys.version_info(major=3, minor=7, micro=12, releaselevel='final', serial=0)


Discussion:
This occurs on master, too.
@johnsmorgan are you interested in submitting a PR?
@mdhaber can I submit a PR if @johnsmorgan doesn't want to work on this?
@mdhaber I'm afraid I don't have time to work on this right now.

Resolving patch:
diff --git a/scipy/stats/_continuous_distns.py b/scipy/stats/_continuous_distns.py
--- a/scipy/stats/_continuous_distns.py
+++ b/scipy/stats/_continuous_distns.py
@@ -1248,7 +1248,7 @@ def _isf(self, q, df):
         return np.sqrt(2*sc.gammainccinv(.5*df, q))
 
     def _stats(self, df):
-        mu = np.sqrt(2)*sc.gamma(df/2.0+0.5)/sc.gamma(df/2.0)
+        mu = np.sqrt(2)*np.exp(sc.gammaln(df/2.0+0.5)-sc.gammaln(df/2.0))
         mu2 = df - mu*mu
         g1 = (2*mu**3.0 + mu*(1-2*df))/np.asarray(np.power(mu2, 1.5))
         g2 = 2*df*(1.0-df)-6*mu**4 + 4*mu**2 * (2*df-1)


Test patch:
diff --git a/scipy/stats/tests/test_distributions.py b/scipy/stats/tests/test_distributions.py
--- a/scipy/stats/tests/test_distributions.py
+++ b/scipy/stats/tests/test_distributions.py
@@ -317,6 +317,9 @@ class TestChi:
     # "Exact" value of chi.sf(10, 4), as computed by Wolfram Alpha with
     #     1 - CDF[ChiDistribution[4], 10]
     CHI_SF_10_4 = 9.83662422461598e-21
+    # "Exact" value of chi.mean(df=1000) as computed by Wolfram Alpha with
+    #       Mean[ChiDistribution[1000]]
+    CHI_MEAN_1000 = 31.614871896980
 
     def test_sf(self):
         s = stats.chi.sf(10, 4)
@@ -326,6 +329,10 @@ def test_isf(self):
         x = stats.chi.isf(self.CHI_SF_10_4, 4)
         assert_allclose(x, 10, rtol=1e-15)
 
+    def test_mean(self):
+        x = stats.chi.mean(df=1000)
+        assert_allclose(x, self.CHI_MEAN_1000, rtol=1e-12)
+
 
 class TestNBinom:
     def setup_method(self):