Repository: scipy/scipy

Issue:
Use RandomState for gaussian_kde.resample
Currently, `scipy.stats.gaussian_kde.resample` use  `numpy.random.choice` and  `numpy.multivariate_normal`. However, no `RandomState` option is available for these two functions. It will be much better if one can use `RandomState` for the resampling method.



Discussion:
PR welcome

Resolving patch:
diff --git a/scipy/stats/kde.py b/scipy/stats/kde.py
--- a/scipy/stats/kde.py
+++ b/scipy/stats/kde.py
@@ -27,12 +27,12 @@
 from scipy import linalg, special
 from scipy.special import logsumexp
 from scipy._lib._numpy_compat import cov
+from scipy._lib._util import check_random_state
 
 from numpy import (asarray, atleast_2d, reshape, zeros, newaxis, dot, exp, pi,
                    sqrt, ravel, power, atleast_1d, squeeze, sum, transpose,
                    ones)
 import numpy as np
-from numpy.random import choice, multivariate_normal
 
 # Local imports.
 from . import mvn
@@ -437,7 +437,7 @@ def integrate_kde(self, other):
 
         return result
 
-    def resample(self, size=None):
+    def resample(self, size=None, seed=None):
         """
         Randomly sample a dataset from the estimated pdf.
 
@@ -447,6 +447,14 @@ def resample(self, size=None):
             The number of samples to draw.  If not provided, then the size is
             the same as the effective number of samples in the underlying
             dataset.
+        seed : None or int or `np.random.RandomState`, optional
+            If `seed` is None, random variates are drawn by the RandomState
+            singleton used by np.random.
+            If `seed` is an int, a new `np.random.RandomState` instance is used,
+            seeded with seed.
+            If `seed` is already a `np.random.RandomState instance`, then that
+            `np.random.RandomState` instance is used.
+            Specify `seed` for reproducible drawing of random variates.
 
         Returns
         -------
@@ -457,9 +465,11 @@ def resample(self, size=None):
         if size is None:
             size = int(self.neff)
 
-        norm = transpose(multivariate_normal(zeros((self.d,), float),
-                                             self.covariance, size=size))
-        indices = choice(self.n, size=size, p=self.weights)
+        random_state = check_random_state(seed)
+        norm = transpose(random_state.multivariate_normal(
+            zeros((self.d,), float), self.covariance, size=size
+        ))
+        indices = random_state.choice(self.n, size=size, p=self.weights)
         means = self.dataset[:, indices]
 
         return means + norm


Test patch:
diff --git a/scipy/stats/tests/test_kdeoth.py b/scipy/stats/tests/test_kdeoth.py
--- a/scipy/stats/tests/test_kdeoth.py
+++ b/scipy/stats/tests/test_kdeoth.py
@@ -390,3 +390,46 @@ def test_weights_integer():
     assert_allclose(pdf_i.evaluate(xn),
                     pdf_f.evaluate(xn), atol=1e-14, rtol=1e-14)
 
+
+def test_seed():
+    # Test the seed option of the resample method
+    def test_seed_sub(gkde_trail):
+        n_sample = 200
+        # The results should be different without using seed
+        samp1 = gkde_trail.resample(n_sample)
+        samp2 = gkde_trail.resample(n_sample)
+        assert_raises(
+            AssertionError, assert_allclose, samp1, samp2, atol=1e-13
+        )
+        # Use integer seed
+        seed = 831
+        samp1 = gkde_trail.resample(n_sample, seed=seed)
+        samp2 = gkde_trail.resample(n_sample, seed=seed)
+        assert_allclose(samp1, samp2, atol=1e-13)
+        # Use RandomState
+        rstate1 = np.random.RandomState(seed=138)
+        samp1 = gkde_trail.resample(n_sample, seed=rstate1)
+        rstate2 = np.random.RandomState(seed=138)
+        samp2 = gkde_trail.resample(n_sample, seed=rstate2)
+        assert_allclose(samp1, samp2, atol=1e-13)
+
+    np.random.seed(8765678)
+    n_basesample = 500
+    wn = np.random.rand(n_basesample)
+    # Test 1D case
+    xn_1d = np.random.randn(n_basesample)
+
+    gkde_1d = stats.gaussian_kde(xn_1d)
+    test_seed_sub(gkde_1d)
+    gkde_1d_weighted = stats.gaussian_kde(xn_1d, weights=wn)
+    test_seed_sub(gkde_1d_weighted)
+
+    # Test 2D case 
+    mean = np.array([1.0, 3.0])
+    covariance = np.array([[1.0, 2.0], [2.0, 6.0]])
+    xn_2d = np.random.multivariate_normal(mean, covariance, size=n_basesample).T
+
+    gkde_2d = stats.gaussian_kde(xn_2d)
+    test_seed_sub(gkde_2d)
+    gkde_2d_weighted = stats.gaussian_kde(xn_2d, weights=wn)
+    test_seed_sub(gkde_2d_weighted)