Repository: scipy/scipy

Issue:
BUG: Wrong limit and no warning in stats.t for df=np.inf
### Describe your issue.

Scipy's stats module for Student's t distribution does not have the correct limit of a Gaussian distribution if the degree-of-freedom (`df`) parameter is set to `np.inf`. Most importantly, Scipy does not print a warning nor does it raise an error, instead it simply yields a completely wrong result.

While I think it would make sense to branch for `df=np.inf`, it certainly does not make sense to let the user plug in values that produce bogus results.

### Reproducing Code Example

```python
In [1]: import numpy as np

In [2]: from scipy.stats import norm as stats_norm

In [3]: from scipy.stats import t as stats_t

In [4]: stats_t.sf(loc=3., df=99999., x=2., scale=1.)
Out[4]: 0.8413435362058463

In [5]: stats_t.sf(loc=3., df=np.inf, x=2., scale=1.)
Out[5]: 0.5
```


### Error message

```shell
None
```


### SciPy/NumPy/Python version information

1.7.1 1.21.2 sys.version_info(major=3, minor=9, micro=7, releaselevel='final', serial=0)


Discussion:

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
@@ -5881,20 +5881,25 @@ def _rvs(self, df, size=None, random_state=None):
         return random_state.standard_t(df, size=size)
 
     def _pdf(self, x, df):
-        #                                gamma((df+1)/2)
-        # t.pdf(x, df) = ---------------------------------------------------
-        #                sqrt(pi*df) * gamma(df/2) * (1+x**2/df)**((df+1)/2)
-        r = np.asarray(df*1.0)
-        Px = (np.exp(sc.gammaln((r+1)/2)-sc.gammaln(r/2))
-              / (np.sqrt(r*np.pi)*(1+(x**2)/r)**((r+1)/2)))
-
-        return Px
+        return _lazywhere(
+            df == np.inf, (x, df),
+            f=lambda x, df: norm._pdf(x),
+            f2=lambda x, df: (
+                np.exp(sc.gammaln((df+1)/2)-sc.gammaln(df/2))
+                / (np.sqrt(df*np.pi)*(1+(x**2)/df)**((df+1)/2))
+            )
+        )
 
     def _logpdf(self, x, df):
-        r = df*1.0
-        lPx = (sc.gammaln((r+1)/2) - sc.gammaln(r/2)
-               - (0.5*np.log(r*np.pi) + (r+1)/2*np.log(1+(x**2)/r)))
-        return lPx
+        return _lazywhere(
+            df == np.inf, (x, df),
+            f=lambda x, df: norm._logpdf(x),
+            f2=lambda x, df: (
+                sc.gammaln((df+1)/2) - sc.gammaln(df/2)
+                - (0.5*np.log(df*np.pi)
+                   + (df+1)/2*np.log(1+(x**2)/df))
+            )
+        )
 
     def _cdf(self, x, df):
         return sc.stdtr(df, x)
@@ -5909,19 +5914,34 @@ def _isf(self, q, df):
         return -sc.stdtrit(df, q)
 
     def _stats(self, df):
+        # infinite df -> normal distribution (0.0, 1.0, 0.0, 0.0)
+        infinite_df = np.isposinf(df)
+
         mu = np.where(df > 1, 0.0, np.inf)
-        mu2 = _lazywhere(df > 2, (df,),
-                         lambda df: df / (df-2.0),
-                         np.inf)
-        mu2 = np.where(df <= 1, np.nan, mu2)
+
+        condlist = ((df > 1) & (df <= 2),
+                    (df > 2) & np.isfinite(df),
+                    infinite_df)
+        choicelist = (lambda df: np.broadcast_to(np.inf, df.shape),
+                      lambda df: df / (df-2.0),
+                      lambda df: np.broadcast_to(1, df.shape))
+        mu2 = _lazyselect(condlist, choicelist, (df,), np.nan)
+
         g1 = np.where(df > 3, 0.0, np.nan)
-        g2 = _lazywhere(df > 4, (df,),
-                        lambda df: 6.0 / (df-4.0),
-                        np.inf)
-        g2 = np.where(df <= 2, np.nan, g2)
+
+        condlist = ((df > 2) & (df <= 4),
+                    (df > 4) & np.isfinite(df),
+                    infinite_df)
+        choicelist = (lambda df: np.broadcast_to(np.inf, df.shape),
+                      lambda df: 6.0 / (df-4.0),
+                      lambda df: np.broadcast_to(0, df.shape))
+        g2 = _lazyselect(condlist, choicelist, (df,), np.nan)
+
         return mu, mu2, g1, g2
 
     def _entropy(self, df):
+        if df == np.inf:
+            return norm._entropy()
         half = df/2
         half1 = (df + 1)/2
         return (half1*(sc.digamma(half1) - sc.digamma(half))


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
@@ -2316,6 +2316,53 @@ def test_t_entropy():
     assert_allclose(stats.t.entropy(df), expected, rtol=1e-13)
 
 
+@pytest.mark.parametrize("methname", ["pdf", "logpdf", "cdf",
+                                      "ppf", "sf", "isf"])
+@pytest.mark.parametrize("df_infmask", [[0, 0], [1, 1], [0, 1],
+                                        [[0, 1, 0], [1, 1, 1]],
+                                        [[1, 0], [0, 1]],
+                                        [[0], [1]]])
+def test_t_inf_df(methname, df_infmask):
+    np.random.seed(0)
+    df_infmask = np.asarray(df_infmask, dtype=bool)
+    df = np.random.uniform(0, 10, size=df_infmask.shape)
+    x = np.random.randn(*df_infmask.shape)
+    df[df_infmask] = np.inf
+    t_dist = stats.t(df=df, loc=3, scale=1)
+    t_dist_ref = stats.t(df=df[~df_infmask], loc=3, scale=1)
+    norm_dist = stats.norm(loc=3, scale=1)
+    t_meth = getattr(t_dist, methname)
+    t_meth_ref = getattr(t_dist_ref, methname)
+    norm_meth = getattr(norm_dist, methname)
+    res = t_meth(x)
+    assert_equal(res[df_infmask], norm_meth(x[df_infmask]))
+    assert_equal(res[~df_infmask], t_meth_ref(x[~df_infmask]))
+
+
+@pytest.mark.parametrize("df_infmask", [[0, 0], [1, 1], [0, 1],
+                                        [[0, 1, 0], [1, 1, 1]],
+                                        [[1, 0], [0, 1]],
+                                        [[0], [1]]])
+def test_t_inf_df_stats_entropy(df_infmask):
+    np.random.seed(0)
+    df_infmask = np.asarray(df_infmask, dtype=bool)
+    df = np.random.uniform(0, 10, size=df_infmask.shape)
+    df[df_infmask] = np.inf
+    res = stats.t.stats(df=df, loc=3, scale=1, moments='mvsk')
+    res_ex_inf = stats.norm.stats(loc=3, scale=1, moments='mvsk')
+    res_ex_noinf = stats.t.stats(df=df[~df_infmask], loc=3, scale=1,
+                                 moments='mvsk')
+    for i in range(4):
+        assert_equal(res[i][df_infmask], res_ex_inf[i])
+        assert_equal(res[i][~df_infmask], res_ex_noinf[i])
+
+    res = stats.t.entropy(df=df, loc=3, scale=1)
+    res_ex_inf = stats.norm.entropy(loc=3, scale=1)
+    res_ex_noinf = stats.t.entropy(df=df[~df_infmask], loc=3, scale=1)
+    assert_equal(res[df_infmask], res_ex_inf)
+    assert_equal(res[~df_infmask], res_ex_noinf)
+
+
 class TestRvDiscrete:
     def setup_method(self):
         np.random.seed(1234)