test_resampling.py 66 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651
  1. import numpy as np
  2. import pytest
  3. from scipy.stats import bootstrap, monte_carlo_test, permutation_test
  4. from numpy.testing import assert_allclose, assert_equal, suppress_warnings
  5. from scipy import stats
  6. from scipy import special
  7. from .. import _resampling as _resampling
  8. from scipy._lib._util import rng_integers
  9. from scipy.optimize import root
  10. def test_bootstrap_iv():
  11. message = "`data` must be a sequence of samples."
  12. with pytest.raises(ValueError, match=message):
  13. bootstrap(1, np.mean)
  14. message = "`data` must contain at least one sample."
  15. with pytest.raises(ValueError, match=message):
  16. bootstrap(tuple(), np.mean)
  17. message = "each sample in `data` must contain two or more observations..."
  18. with pytest.raises(ValueError, match=message):
  19. bootstrap(([1, 2, 3], [1]), np.mean)
  20. message = ("When `paired is True`, all samples must have the same length ")
  21. with pytest.raises(ValueError, match=message):
  22. bootstrap(([1, 2, 3], [1, 2, 3, 4]), np.mean, paired=True)
  23. message = "`vectorized` must be `True`, `False`, or `None`."
  24. with pytest.raises(ValueError, match=message):
  25. bootstrap(1, np.mean, vectorized='ekki')
  26. message = "`axis` must be an integer."
  27. with pytest.raises(ValueError, match=message):
  28. bootstrap(([1, 2, 3],), np.mean, axis=1.5)
  29. message = "could not convert string to float"
  30. with pytest.raises(ValueError, match=message):
  31. bootstrap(([1, 2, 3],), np.mean, confidence_level='ni')
  32. message = "`n_resamples` must be a non-negative integer."
  33. with pytest.raises(ValueError, match=message):
  34. bootstrap(([1, 2, 3],), np.mean, n_resamples=-1000)
  35. message = "`n_resamples` must be a non-negative integer."
  36. with pytest.raises(ValueError, match=message):
  37. bootstrap(([1, 2, 3],), np.mean, n_resamples=1000.5)
  38. message = "`batch` must be a positive integer or None."
  39. with pytest.raises(ValueError, match=message):
  40. bootstrap(([1, 2, 3],), np.mean, batch=-1000)
  41. message = "`batch` must be a positive integer or None."
  42. with pytest.raises(ValueError, match=message):
  43. bootstrap(([1, 2, 3],), np.mean, batch=1000.5)
  44. message = "`method` must be in"
  45. with pytest.raises(ValueError, match=message):
  46. bootstrap(([1, 2, 3],), np.mean, method='ekki')
  47. message = "`bootstrap_result` must have attribute `bootstrap_distribution'"
  48. with pytest.raises(ValueError, match=message):
  49. bootstrap(([1, 2, 3],), np.mean, bootstrap_result=10)
  50. message = "Either `bootstrap_result.bootstrap_distribution.size`"
  51. with pytest.raises(ValueError, match=message):
  52. bootstrap(([1, 2, 3],), np.mean, n_resamples=0)
  53. message = "'herring' cannot be used to seed a"
  54. with pytest.raises(ValueError, match=message):
  55. bootstrap(([1, 2, 3],), np.mean, random_state='herring')
  56. @pytest.mark.parametrize("method", ['basic', 'percentile', 'BCa'])
  57. @pytest.mark.parametrize("axis", [0, 1, 2])
  58. def test_bootstrap_batch(method, axis):
  59. # for one-sample statistics, batch size shouldn't affect the result
  60. np.random.seed(0)
  61. x = np.random.rand(10, 11, 12)
  62. res1 = bootstrap((x,), np.mean, batch=None, method=method,
  63. random_state=0, axis=axis, n_resamples=100)
  64. res2 = bootstrap((x,), np.mean, batch=10, method=method,
  65. random_state=0, axis=axis, n_resamples=100)
  66. assert_equal(res2.confidence_interval.low, res1.confidence_interval.low)
  67. assert_equal(res2.confidence_interval.high, res1.confidence_interval.high)
  68. assert_equal(res2.standard_error, res1.standard_error)
  69. @pytest.mark.parametrize("method", ['basic', 'percentile', 'BCa'])
  70. def test_bootstrap_paired(method):
  71. # test that `paired` works as expected
  72. np.random.seed(0)
  73. n = 100
  74. x = np.random.rand(n)
  75. y = np.random.rand(n)
  76. def my_statistic(x, y, axis=-1):
  77. return ((x-y)**2).mean(axis=axis)
  78. def my_paired_statistic(i, axis=-1):
  79. a = x[i]
  80. b = y[i]
  81. res = my_statistic(a, b)
  82. return res
  83. i = np.arange(len(x))
  84. res1 = bootstrap((i,), my_paired_statistic, random_state=0)
  85. res2 = bootstrap((x, y), my_statistic, paired=True, random_state=0)
  86. assert_allclose(res1.confidence_interval, res2.confidence_interval)
  87. assert_allclose(res1.standard_error, res2.standard_error)
  88. @pytest.mark.parametrize("method", ['basic', 'percentile', 'BCa'])
  89. @pytest.mark.parametrize("axis", [0, 1, 2])
  90. @pytest.mark.parametrize("paired", [True, False])
  91. def test_bootstrap_vectorized(method, axis, paired):
  92. # test that paired is vectorized as expected: when samples are tiled,
  93. # CI and standard_error of each axis-slice is the same as those of the
  94. # original 1d sample
  95. np.random.seed(0)
  96. def my_statistic(x, y, z, axis=-1):
  97. return x.mean(axis=axis) + y.mean(axis=axis) + z.mean(axis=axis)
  98. shape = 10, 11, 12
  99. n_samples = shape[axis]
  100. x = np.random.rand(n_samples)
  101. y = np.random.rand(n_samples)
  102. z = np.random.rand(n_samples)
  103. res1 = bootstrap((x, y, z), my_statistic, paired=paired, method=method,
  104. random_state=0, axis=0, n_resamples=100)
  105. assert (res1.bootstrap_distribution.shape
  106. == res1.standard_error.shape + (100,))
  107. reshape = [1, 1, 1]
  108. reshape[axis] = n_samples
  109. x = np.broadcast_to(x.reshape(reshape), shape)
  110. y = np.broadcast_to(y.reshape(reshape), shape)
  111. z = np.broadcast_to(z.reshape(reshape), shape)
  112. res2 = bootstrap((x, y, z), my_statistic, paired=paired, method=method,
  113. random_state=0, axis=axis, n_resamples=100)
  114. assert_allclose(res2.confidence_interval.low,
  115. res1.confidence_interval.low)
  116. assert_allclose(res2.confidence_interval.high,
  117. res1.confidence_interval.high)
  118. assert_allclose(res2.standard_error, res1.standard_error)
  119. result_shape = list(shape)
  120. result_shape.pop(axis)
  121. assert_equal(res2.confidence_interval.low.shape, result_shape)
  122. assert_equal(res2.confidence_interval.high.shape, result_shape)
  123. assert_equal(res2.standard_error.shape, result_shape)
  124. @pytest.mark.parametrize("method", ['basic', 'percentile', 'BCa'])
  125. def test_bootstrap_against_theory(method):
  126. # based on https://www.statology.org/confidence-intervals-python/
  127. data = stats.norm.rvs(loc=5, scale=2, size=5000, random_state=0)
  128. alpha = 0.95
  129. dist = stats.t(df=len(data)-1, loc=np.mean(data), scale=stats.sem(data))
  130. expected_interval = dist.interval(confidence=alpha)
  131. expected_se = dist.std()
  132. res = bootstrap((data,), np.mean, n_resamples=5000,
  133. confidence_level=alpha, method=method,
  134. random_state=0)
  135. assert_allclose(res.confidence_interval, expected_interval, rtol=5e-4)
  136. assert_allclose(res.standard_error, expected_se, atol=3e-4)
  137. tests_R = {"basic": (23.77, 79.12),
  138. "percentile": (28.86, 84.21),
  139. "BCa": (32.31, 91.43)}
  140. @pytest.mark.parametrize("method, expected", tests_R.items())
  141. def test_bootstrap_against_R(method, expected):
  142. # Compare against R's "boot" library
  143. # library(boot)
  144. # stat <- function (x, a) {
  145. # mean(x[a])
  146. # }
  147. # x <- c(10, 12, 12.5, 12.5, 13.9, 15, 21, 22,
  148. # 23, 34, 50, 81, 89, 121, 134, 213)
  149. # # Use a large value so we get a few significant digits for the CI.
  150. # n = 1000000
  151. # bootresult = boot(x, stat, n)
  152. # result <- boot.ci(bootresult)
  153. # print(result)
  154. x = np.array([10, 12, 12.5, 12.5, 13.9, 15, 21, 22,
  155. 23, 34, 50, 81, 89, 121, 134, 213])
  156. res = bootstrap((x,), np.mean, n_resamples=1000000, method=method,
  157. random_state=0)
  158. assert_allclose(res.confidence_interval, expected, rtol=0.005)
  159. tests_against_itself_1samp = {"basic": 1780,
  160. "percentile": 1784,
  161. "BCa": 1784}
  162. def test_multisample_BCa_against_R():
  163. # Because bootstrap is stochastic, it's tricky to test against reference
  164. # behavior. Here, we show that SciPy's BCa CI matches R wboot's BCa CI
  165. # much more closely than the other SciPy CIs do.
  166. # arbitrary skewed data
  167. x = [0.75859206, 0.5910282, -0.4419409, -0.36654601,
  168. 0.34955357, -1.38835871, 0.76735821]
  169. y = [1.41186073, 0.49775975, 0.08275588, 0.24086388,
  170. 0.03567057, 0.52024419, 0.31966611, 1.32067634]
  171. # a multi-sample statistic for which the BCa CI tends to be different
  172. # from the other CIs
  173. def statistic(x, y, axis):
  174. s1 = stats.skew(x, axis=axis)
  175. s2 = stats.skew(y, axis=axis)
  176. return s1 - s2
  177. # compute confidence intervals using each method
  178. rng = np.random.default_rng(468865032284792692)
  179. res_basic = stats.bootstrap((x, y), statistic, method='basic',
  180. batch=100, random_state=rng)
  181. res_percent = stats.bootstrap((x, y), statistic, method='percentile',
  182. batch=100, random_state=rng)
  183. res_bca = stats.bootstrap((x, y), statistic, method='bca',
  184. batch=100, random_state=rng)
  185. # compute midpoints so we can compare just one number for each
  186. mid_basic = np.mean(res_basic.confidence_interval)
  187. mid_percent = np.mean(res_percent.confidence_interval)
  188. mid_bca = np.mean(res_bca.confidence_interval)
  189. # reference for BCA CI computed using R wboot package:
  190. # library(wBoot)
  191. # library(moments)
  192. # x = c(0.75859206, 0.5910282, -0.4419409, -0.36654601,
  193. # 0.34955357, -1.38835871, 0.76735821)
  194. # y = c(1.41186073, 0.49775975, 0.08275588, 0.24086388,
  195. # 0.03567057, 0.52024419, 0.31966611, 1.32067634)
  196. # twoskew <- function(x1, y1) {skewness(x1) - skewness(y1)}
  197. # boot.two.bca(x, y, skewness, conf.level = 0.95,
  198. # R = 9999, stacked = FALSE)
  199. mid_wboot = -1.5519
  200. # compute percent difference relative to wboot BCA method
  201. diff_basic = (mid_basic - mid_wboot)/abs(mid_wboot)
  202. diff_percent = (mid_percent - mid_wboot)/abs(mid_wboot)
  203. diff_bca = (mid_bca - mid_wboot)/abs(mid_wboot)
  204. # SciPy's BCa CI midpoint is much closer than that of the other methods
  205. assert diff_basic < -0.15
  206. assert diff_percent > 0.15
  207. assert abs(diff_bca) < 0.03
  208. def test_BCa_acceleration_against_reference():
  209. # Compare the (deterministic) acceleration parameter for a multi-sample
  210. # problem against a reference value. The example is from [1], but Efron's
  211. # value seems inaccurate. Straightorward code for computing the
  212. # reference acceleration (0.011008228344026734) is available at:
  213. # https://github.com/scipy/scipy/pull/16455#issuecomment-1193400981
  214. y = np.array([10, 27, 31, 40, 46, 50, 52, 104, 146])
  215. z = np.array([16, 23, 38, 94, 99, 141, 197])
  216. def statistic(z, y, axis=0):
  217. return np.mean(z, axis=axis) - np.mean(y, axis=axis)
  218. data = [z, y]
  219. res = stats.bootstrap(data, statistic)
  220. axis = -1
  221. alpha = 0.95
  222. theta_hat_b = res.bootstrap_distribution
  223. batch = 100
  224. _, _, a_hat = _resampling._bca_interval(data, statistic, axis, alpha,
  225. theta_hat_b, batch)
  226. assert_allclose(a_hat, 0.011008228344026734)
  227. @pytest.mark.parametrize("method, expected",
  228. tests_against_itself_1samp.items())
  229. def test_bootstrap_against_itself_1samp(method, expected):
  230. # The expected values in this test were generated using bootstrap
  231. # to check for unintended changes in behavior. The test also makes sure
  232. # that bootstrap works with multi-sample statistics and that the
  233. # `axis` argument works as expected / function is vectorized.
  234. np.random.seed(0)
  235. n = 100 # size of sample
  236. n_resamples = 999 # number of bootstrap resamples used to form each CI
  237. confidence_level = 0.9
  238. # The true mean is 5
  239. dist = stats.norm(loc=5, scale=1)
  240. stat_true = dist.mean()
  241. # Do the same thing 2000 times. (The code is fully vectorized.)
  242. n_replications = 2000
  243. data = dist.rvs(size=(n_replications, n))
  244. res = bootstrap((data,),
  245. statistic=np.mean,
  246. confidence_level=confidence_level,
  247. n_resamples=n_resamples,
  248. batch=50,
  249. method=method,
  250. axis=-1)
  251. ci = res.confidence_interval
  252. # ci contains vectors of lower and upper confidence interval bounds
  253. ci_contains_true = np.sum((ci[0] < stat_true) & (stat_true < ci[1]))
  254. assert ci_contains_true == expected
  255. # ci_contains_true is not inconsistent with confidence_level
  256. pvalue = stats.binomtest(ci_contains_true, n_replications,
  257. confidence_level).pvalue
  258. assert pvalue > 0.1
  259. tests_against_itself_2samp = {"basic": 892,
  260. "percentile": 890}
  261. @pytest.mark.parametrize("method, expected",
  262. tests_against_itself_2samp.items())
  263. def test_bootstrap_against_itself_2samp(method, expected):
  264. # The expected values in this test were generated using bootstrap
  265. # to check for unintended changes in behavior. The test also makes sure
  266. # that bootstrap works with multi-sample statistics and that the
  267. # `axis` argument works as expected / function is vectorized.
  268. np.random.seed(0)
  269. n1 = 100 # size of sample 1
  270. n2 = 120 # size of sample 2
  271. n_resamples = 999 # number of bootstrap resamples used to form each CI
  272. confidence_level = 0.9
  273. # The statistic we're interested in is the difference in means
  274. def my_stat(data1, data2, axis=-1):
  275. mean1 = np.mean(data1, axis=axis)
  276. mean2 = np.mean(data2, axis=axis)
  277. return mean1 - mean2
  278. # The true difference in the means is -0.1
  279. dist1 = stats.norm(loc=0, scale=1)
  280. dist2 = stats.norm(loc=0.1, scale=1)
  281. stat_true = dist1.mean() - dist2.mean()
  282. # Do the same thing 1000 times. (The code is fully vectorized.)
  283. n_replications = 1000
  284. data1 = dist1.rvs(size=(n_replications, n1))
  285. data2 = dist2.rvs(size=(n_replications, n2))
  286. res = bootstrap((data1, data2),
  287. statistic=my_stat,
  288. confidence_level=confidence_level,
  289. n_resamples=n_resamples,
  290. batch=50,
  291. method=method,
  292. axis=-1)
  293. ci = res.confidence_interval
  294. # ci contains vectors of lower and upper confidence interval bounds
  295. ci_contains_true = np.sum((ci[0] < stat_true) & (stat_true < ci[1]))
  296. assert ci_contains_true == expected
  297. # ci_contains_true is not inconsistent with confidence_level
  298. pvalue = stats.binomtest(ci_contains_true, n_replications,
  299. confidence_level).pvalue
  300. assert pvalue > 0.1
  301. @pytest.mark.parametrize("method", ["basic", "percentile"])
  302. @pytest.mark.parametrize("axis", [0, 1])
  303. def test_bootstrap_vectorized_3samp(method, axis):
  304. def statistic(*data, axis=0):
  305. # an arbitrary, vectorized statistic
  306. return sum((sample.mean(axis) for sample in data))
  307. def statistic_1d(*data):
  308. # the same statistic, not vectorized
  309. for sample in data:
  310. assert sample.ndim == 1
  311. return statistic(*data, axis=0)
  312. np.random.seed(0)
  313. x = np.random.rand(4, 5)
  314. y = np.random.rand(4, 5)
  315. z = np.random.rand(4, 5)
  316. res1 = bootstrap((x, y, z), statistic, vectorized=True,
  317. axis=axis, n_resamples=100, method=method, random_state=0)
  318. res2 = bootstrap((x, y, z), statistic_1d, vectorized=False,
  319. axis=axis, n_resamples=100, method=method, random_state=0)
  320. assert_allclose(res1.confidence_interval, res2.confidence_interval)
  321. assert_allclose(res1.standard_error, res2.standard_error)
  322. @pytest.mark.xfail_on_32bit("Failure is not concerning; see gh-14107")
  323. @pytest.mark.parametrize("method", ["basic", "percentile", "BCa"])
  324. @pytest.mark.parametrize("axis", [0, 1])
  325. def test_bootstrap_vectorized_1samp(method, axis):
  326. def statistic(x, axis=0):
  327. # an arbitrary, vectorized statistic
  328. return x.mean(axis=axis)
  329. def statistic_1d(x):
  330. # the same statistic, not vectorized
  331. assert x.ndim == 1
  332. return statistic(x, axis=0)
  333. np.random.seed(0)
  334. x = np.random.rand(4, 5)
  335. res1 = bootstrap((x,), statistic, vectorized=True, axis=axis,
  336. n_resamples=100, batch=None, method=method,
  337. random_state=0)
  338. res2 = bootstrap((x,), statistic_1d, vectorized=False, axis=axis,
  339. n_resamples=100, batch=10, method=method,
  340. random_state=0)
  341. assert_allclose(res1.confidence_interval, res2.confidence_interval)
  342. assert_allclose(res1.standard_error, res2.standard_error)
  343. @pytest.mark.parametrize("method", ["basic", "percentile", "BCa"])
  344. def test_bootstrap_degenerate(method):
  345. data = 35 * [10000.]
  346. if method == "BCa":
  347. with np.errstate(invalid='ignore'):
  348. msg = "The BCa confidence interval cannot be calculated"
  349. with pytest.warns(stats.DegenerateDataWarning, match=msg):
  350. res = bootstrap([data, ], np.mean, method=method)
  351. assert_equal(res.confidence_interval, (np.nan, np.nan))
  352. else:
  353. res = bootstrap([data, ], np.mean, method=method)
  354. assert_equal(res.confidence_interval, (10000., 10000.))
  355. assert_equal(res.standard_error, 0)
  356. @pytest.mark.parametrize("method", ["basic", "percentile", "BCa"])
  357. def test_bootstrap_gh15678(method):
  358. # Check that gh-15678 is fixed: when statistic function returned a Python
  359. # float, method="BCa" failed when trying to add a dimension to the float
  360. rng = np.random.default_rng(354645618886684)
  361. dist = stats.norm(loc=2, scale=4)
  362. data = dist.rvs(size=100, random_state=rng)
  363. data = (data,)
  364. res = bootstrap(data, stats.skew, method=method, n_resamples=100,
  365. random_state=np.random.default_rng(9563))
  366. # this always worked because np.apply_along_axis returns NumPy data type
  367. ref = bootstrap(data, stats.skew, method=method, n_resamples=100,
  368. random_state=np.random.default_rng(9563), vectorized=False)
  369. assert_allclose(res.confidence_interval, ref.confidence_interval)
  370. assert_allclose(res.standard_error, ref.standard_error)
  371. assert isinstance(res.standard_error, np.float64)
  372. def test_bootstrap_min():
  373. # Check that gh-15883 is fixed: percentileofscore should
  374. # behave according to the 'mean' behavior and not trigger nan for BCa
  375. rng = np.random.default_rng(1891289180021102)
  376. dist = stats.norm(loc=2, scale=4)
  377. data = dist.rvs(size=100, random_state=rng)
  378. true_min = np.min(data)
  379. data = (data,)
  380. res = bootstrap(data, np.min, method="BCa", n_resamples=100,
  381. random_state=np.random.default_rng(3942))
  382. assert true_min == res.confidence_interval.low
  383. res2 = bootstrap(-np.array(data), np.max, method="BCa", n_resamples=100,
  384. random_state=np.random.default_rng(3942))
  385. assert_allclose(-res.confidence_interval.low,
  386. res2.confidence_interval.high)
  387. assert_allclose(-res.confidence_interval.high,
  388. res2.confidence_interval.low)
  389. @pytest.mark.parametrize("additional_resamples", [0, 1000])
  390. def test_re_boostrap(additional_resamples):
  391. # Test behavior of parameter `bootstrap_result`
  392. rng = np.random.default_rng(8958153316228384)
  393. x = rng.random(size=100)
  394. n1 = 1000
  395. n2 = additional_resamples
  396. n3 = n1 + additional_resamples
  397. rng = np.random.default_rng(296689032789913033)
  398. res = stats.bootstrap((x,), np.mean, n_resamples=n1, random_state=rng,
  399. confidence_level=0.95, method='percentile')
  400. res = stats.bootstrap((x,), np.mean, n_resamples=n2, random_state=rng,
  401. confidence_level=0.90, method='BCa',
  402. bootstrap_result=res)
  403. rng = np.random.default_rng(296689032789913033)
  404. ref = stats.bootstrap((x,), np.mean, n_resamples=n3, random_state=rng,
  405. confidence_level=0.90, method='BCa')
  406. assert_allclose(res.standard_error, ref.standard_error, rtol=1e-14)
  407. assert_allclose(res.confidence_interval, ref.confidence_interval,
  408. rtol=1e-14)
  409. def test_jackknife_resample():
  410. shape = 3, 4, 5, 6
  411. np.random.seed(0)
  412. x = np.random.rand(*shape)
  413. y = next(_resampling._jackknife_resample(x))
  414. for i in range(shape[-1]):
  415. # each resample is indexed along second to last axis
  416. # (last axis is the one the statistic will be taken over / consumed)
  417. slc = y[..., i, :]
  418. expected = np.delete(x, i, axis=-1)
  419. assert np.array_equal(slc, expected)
  420. y2 = np.concatenate(list(_resampling._jackknife_resample(x, batch=2)),
  421. axis=-2)
  422. assert np.array_equal(y2, y)
  423. @pytest.mark.parametrize("rng_name", ["RandomState", "default_rng"])
  424. def test_bootstrap_resample(rng_name):
  425. rng = getattr(np.random, rng_name, None)
  426. if rng is None:
  427. pytest.skip(f"{rng_name} not available.")
  428. rng1 = rng(0)
  429. rng2 = rng(0)
  430. n_resamples = 10
  431. shape = 3, 4, 5, 6
  432. np.random.seed(0)
  433. x = np.random.rand(*shape)
  434. y = _resampling._bootstrap_resample(x, n_resamples, random_state=rng1)
  435. for i in range(n_resamples):
  436. # each resample is indexed along second to last axis
  437. # (last axis is the one the statistic will be taken over / consumed)
  438. slc = y[..., i, :]
  439. js = rng_integers(rng2, 0, shape[-1], shape[-1])
  440. expected = x[..., js]
  441. assert np.array_equal(slc, expected)
  442. @pytest.mark.parametrize("score", [0, 0.5, 1])
  443. @pytest.mark.parametrize("axis", [0, 1, 2])
  444. def test_percentile_of_score(score, axis):
  445. shape = 10, 20, 30
  446. np.random.seed(0)
  447. x = np.random.rand(*shape)
  448. p = _resampling._percentile_of_score(x, score, axis=-1)
  449. def vectorized_pos(a, score, axis):
  450. return np.apply_along_axis(stats.percentileofscore, axis, a, score)
  451. p2 = vectorized_pos(x, score, axis=-1)/100
  452. assert_allclose(p, p2, 1e-15)
  453. def test_percentile_along_axis():
  454. # the difference between _percentile_along_axis and np.percentile is that
  455. # np.percentile gets _all_ the qs for each axis slice, whereas
  456. # _percentile_along_axis gets the q corresponding with each axis slice
  457. shape = 10, 20
  458. np.random.seed(0)
  459. x = np.random.rand(*shape)
  460. q = np.random.rand(*shape[:-1]) * 100
  461. y = _resampling._percentile_along_axis(x, q)
  462. for i in range(shape[0]):
  463. res = y[i]
  464. expected = np.percentile(x[i], q[i], axis=-1)
  465. assert_allclose(res, expected, 1e-15)
  466. @pytest.mark.parametrize("axis", [0, 1, 2])
  467. def test_vectorize_statistic(axis):
  468. # test that _vectorize_statistic vectorizes a statistic along `axis`
  469. def statistic(*data, axis):
  470. # an arbitrary, vectorized statistic
  471. return sum((sample.mean(axis) for sample in data))
  472. def statistic_1d(*data):
  473. # the same statistic, not vectorized
  474. for sample in data:
  475. assert sample.ndim == 1
  476. return statistic(*data, axis=0)
  477. # vectorize the non-vectorized statistic
  478. statistic2 = _resampling._vectorize_statistic(statistic_1d)
  479. np.random.seed(0)
  480. x = np.random.rand(4, 5, 6)
  481. y = np.random.rand(4, 1, 6)
  482. z = np.random.rand(1, 5, 6)
  483. res1 = statistic(x, y, z, axis=axis)
  484. res2 = statistic2(x, y, z, axis=axis)
  485. assert_allclose(res1, res2)
  486. @pytest.mark.parametrize("method", ["basic", "percentile", "BCa"])
  487. def test_vector_valued_statistic(method):
  488. # Generate 95% confidence interval around MLE of normal distribution
  489. # parameters. Repeat 100 times, each time on sample of size 100.
  490. # Check that confidence interval contains true parameters ~95 times.
  491. # Confidence intervals are estimated and stochastic; a test failure
  492. # does not necessarily indicate that something is wrong. More important
  493. # than values of `counts` below is that the shapes of the outputs are
  494. # correct.
  495. rng = np.random.default_rng(2196847219)
  496. params = 1, 0.5
  497. sample = stats.norm.rvs(*params, size=(100, 100), random_state=rng)
  498. def statistic(data, axis):
  499. return np.asarray([np.mean(data, axis),
  500. np.std(data, axis, ddof=1)])
  501. res = bootstrap((sample,), statistic, method=method, axis=-1,
  502. n_resamples=9999, batch=200)
  503. counts = np.sum((res.confidence_interval.low.T < params)
  504. & (res.confidence_interval.high.T > params),
  505. axis=0)
  506. assert np.all(counts >= 90)
  507. assert np.all(counts <= 100)
  508. assert res.confidence_interval.low.shape == (2, 100)
  509. assert res.confidence_interval.high.shape == (2, 100)
  510. assert res.standard_error.shape == (2, 100)
  511. assert res.bootstrap_distribution.shape == (2, 100, 9999)
  512. @pytest.mark.slow
  513. @pytest.mark.filterwarnings('ignore::RuntimeWarning')
  514. def test_vector_valued_statistic_gh17715():
  515. # gh-17715 reported a mistake introduced in the extension of BCa to
  516. # multi-sample statistics; a `len` should have been `.shape[-1]`. Check
  517. # that this is resolved.
  518. rng = np.random.default_rng(141921000979291141)
  519. def concordance(x, y, axis):
  520. xm = x.mean(axis)
  521. ym = y.mean(axis)
  522. cov = ((x - xm[..., None]) * (y - ym[..., None])).mean(axis)
  523. return (2 * cov) / (x.var(axis) + y.var(axis) + (xm - ym) ** 2)
  524. def statistic(tp, tn, fp, fn, axis):
  525. actual = tp + fp
  526. expected = tp + fn
  527. return np.nan_to_num(concordance(actual, expected, axis))
  528. def statistic_extradim(*args, axis):
  529. return statistic(*args, axis)[np.newaxis, ...]
  530. data = [[4, 0, 0, 2], # (tp, tn, fp, fn)
  531. [2, 1, 2, 1],
  532. [0, 6, 0, 0],
  533. [0, 6, 3, 0],
  534. [0, 8, 1, 0]]
  535. data = np.array(data).T
  536. res = bootstrap(data, statistic_extradim, random_state=rng, paired=True)
  537. ref = bootstrap(data, statistic, random_state=rng, paired=True)
  538. assert_allclose(res.confidence_interval.low[0],
  539. ref.confidence_interval.low, atol=1e-15)
  540. assert_allclose(res.confidence_interval.high[0],
  541. ref.confidence_interval.high, atol=1e-15)
  542. # --- Test Monte Carlo Hypothesis Test --- #
  543. class TestMonteCarloHypothesisTest:
  544. atol = 2.5e-2 # for comparing p-value
  545. def rvs(self, rvs_in, rs):
  546. return lambda *args, **kwds: rvs_in(*args, random_state=rs, **kwds)
  547. def test_input_validation(self):
  548. # test that the appropriate error messages are raised for invalid input
  549. def stat(x):
  550. return stats.skewnorm(x).statistic
  551. message = "`axis` must be an integer."
  552. with pytest.raises(ValueError, match=message):
  553. monte_carlo_test([1, 2, 3], stats.norm.rvs, stat, axis=1.5)
  554. message = "`vectorized` must be `True`, `False`, or `None`."
  555. with pytest.raises(ValueError, match=message):
  556. monte_carlo_test([1, 2, 3], stats.norm.rvs, stat, vectorized=1.5)
  557. message = "`rvs` must be callable."
  558. with pytest.raises(TypeError, match=message):
  559. monte_carlo_test([1, 2, 3], None, stat)
  560. message = "`statistic` must be callable."
  561. with pytest.raises(TypeError, match=message):
  562. monte_carlo_test([1, 2, 3], stats.norm.rvs, None)
  563. message = "`n_resamples` must be a positive integer."
  564. with pytest.raises(ValueError, match=message):
  565. monte_carlo_test([1, 2, 3], stats.norm.rvs, stat,
  566. n_resamples=-1000)
  567. message = "`n_resamples` must be a positive integer."
  568. with pytest.raises(ValueError, match=message):
  569. monte_carlo_test([1, 2, 3], stats.norm.rvs, stat,
  570. n_resamples=1000.5)
  571. message = "`batch` must be a positive integer or None."
  572. with pytest.raises(ValueError, match=message):
  573. monte_carlo_test([1, 2, 3], stats.norm.rvs, stat, batch=-1000)
  574. message = "`batch` must be a positive integer or None."
  575. with pytest.raises(ValueError, match=message):
  576. monte_carlo_test([1, 2, 3], stats.norm.rvs, stat, batch=1000.5)
  577. message = "`alternative` must be in..."
  578. with pytest.raises(ValueError, match=message):
  579. monte_carlo_test([1, 2, 3], stats.norm.rvs, stat,
  580. alternative='ekki')
  581. def test_batch(self):
  582. # make sure that the `batch` parameter is respected by checking the
  583. # maximum batch size provided in calls to `statistic`
  584. rng = np.random.default_rng(23492340193)
  585. x = rng.random(10)
  586. def statistic(x, axis):
  587. batch_size = 1 if x.ndim == 1 else len(x)
  588. statistic.batch_size = max(batch_size, statistic.batch_size)
  589. statistic.counter += 1
  590. return stats.skewtest(x, axis=axis).statistic
  591. statistic.counter = 0
  592. statistic.batch_size = 0
  593. kwds = {'sample': x, 'statistic': statistic,
  594. 'n_resamples': 1000, 'vectorized': True}
  595. kwds['rvs'] = self.rvs(stats.norm.rvs, np.random.default_rng(32842398))
  596. res1 = monte_carlo_test(batch=1, **kwds)
  597. assert_equal(statistic.counter, 1001)
  598. assert_equal(statistic.batch_size, 1)
  599. kwds['rvs'] = self.rvs(stats.norm.rvs, np.random.default_rng(32842398))
  600. statistic.counter = 0
  601. res2 = monte_carlo_test(batch=50, **kwds)
  602. assert_equal(statistic.counter, 21)
  603. assert_equal(statistic.batch_size, 50)
  604. kwds['rvs'] = self.rvs(stats.norm.rvs, np.random.default_rng(32842398))
  605. statistic.counter = 0
  606. res3 = monte_carlo_test(**kwds)
  607. assert_equal(statistic.counter, 2)
  608. assert_equal(statistic.batch_size, 1000)
  609. assert_equal(res1.pvalue, res3.pvalue)
  610. assert_equal(res2.pvalue, res3.pvalue)
  611. @pytest.mark.parametrize('axis', range(-3, 3))
  612. def test_axis(self, axis):
  613. # test that Nd-array samples are handled correctly for valid values
  614. # of the `axis` parameter
  615. rng = np.random.default_rng(2389234)
  616. norm_rvs = self.rvs(stats.norm.rvs, rng)
  617. size = [2, 3, 4]
  618. size[axis] = 100
  619. x = norm_rvs(size=size)
  620. expected = stats.skewtest(x, axis=axis)
  621. def statistic(x, axis):
  622. return stats.skewtest(x, axis=axis).statistic
  623. res = monte_carlo_test(x, norm_rvs, statistic, vectorized=True,
  624. n_resamples=20000, axis=axis)
  625. assert_allclose(res.statistic, expected.statistic)
  626. assert_allclose(res.pvalue, expected.pvalue, atol=self.atol)
  627. @pytest.mark.parametrize('alternative', ("less", "greater"))
  628. @pytest.mark.parametrize('a', np.linspace(-0.5, 0.5, 5)) # skewness
  629. def test_against_ks_1samp(self, alternative, a):
  630. # test that monte_carlo_test can reproduce pvalue of ks_1samp
  631. rng = np.random.default_rng(65723433)
  632. x = stats.skewnorm.rvs(a=a, size=30, random_state=rng)
  633. expected = stats.ks_1samp(x, stats.norm.cdf, alternative=alternative)
  634. def statistic1d(x):
  635. return stats.ks_1samp(x, stats.norm.cdf, mode='asymp',
  636. alternative=alternative).statistic
  637. norm_rvs = self.rvs(stats.norm.rvs, rng)
  638. res = monte_carlo_test(x, norm_rvs, statistic1d,
  639. n_resamples=1000, vectorized=False,
  640. alternative=alternative)
  641. assert_allclose(res.statistic, expected.statistic)
  642. if alternative == 'greater':
  643. assert_allclose(res.pvalue, expected.pvalue, atol=self.atol)
  644. elif alternative == 'less':
  645. assert_allclose(1-res.pvalue, expected.pvalue, atol=self.atol)
  646. @pytest.mark.parametrize('hypotest', (stats.skewtest, stats.kurtosistest))
  647. @pytest.mark.parametrize('alternative', ("less", "greater", "two-sided"))
  648. @pytest.mark.parametrize('a', np.linspace(-2, 2, 5)) # skewness
  649. def test_against_normality_tests(self, hypotest, alternative, a):
  650. # test that monte_carlo_test can reproduce pvalue of normality tests
  651. rng = np.random.default_rng(85723405)
  652. x = stats.skewnorm.rvs(a=a, size=150, random_state=rng)
  653. expected = hypotest(x, alternative=alternative)
  654. def statistic(x, axis):
  655. return hypotest(x, axis=axis).statistic
  656. norm_rvs = self.rvs(stats.norm.rvs, rng)
  657. res = monte_carlo_test(x, norm_rvs, statistic, vectorized=True,
  658. alternative=alternative)
  659. assert_allclose(res.statistic, expected.statistic)
  660. assert_allclose(res.pvalue, expected.pvalue, atol=self.atol)
  661. @pytest.mark.parametrize('a', np.arange(-2, 3)) # skewness parameter
  662. def test_against_normaltest(self, a):
  663. # test that monte_carlo_test can reproduce pvalue of normaltest
  664. rng = np.random.default_rng(12340513)
  665. x = stats.skewnorm.rvs(a=a, size=150, random_state=rng)
  666. expected = stats.normaltest(x)
  667. def statistic(x, axis):
  668. return stats.normaltest(x, axis=axis).statistic
  669. norm_rvs = self.rvs(stats.norm.rvs, rng)
  670. res = monte_carlo_test(x, norm_rvs, statistic, vectorized=True,
  671. alternative='greater')
  672. assert_allclose(res.statistic, expected.statistic)
  673. assert_allclose(res.pvalue, expected.pvalue, atol=self.atol)
  674. @pytest.mark.parametrize('a', np.linspace(-0.5, 0.5, 5)) # skewness
  675. def test_against_cramervonmises(self, a):
  676. # test that monte_carlo_test can reproduce pvalue of cramervonmises
  677. rng = np.random.default_rng(234874135)
  678. x = stats.skewnorm.rvs(a=a, size=30, random_state=rng)
  679. expected = stats.cramervonmises(x, stats.norm.cdf)
  680. def statistic1d(x):
  681. return stats.cramervonmises(x, stats.norm.cdf).statistic
  682. norm_rvs = self.rvs(stats.norm.rvs, rng)
  683. res = monte_carlo_test(x, norm_rvs, statistic1d,
  684. n_resamples=1000, vectorized=False,
  685. alternative='greater')
  686. assert_allclose(res.statistic, expected.statistic)
  687. assert_allclose(res.pvalue, expected.pvalue, atol=self.atol)
  688. @pytest.mark.parametrize('dist_name', ('norm', 'logistic'))
  689. @pytest.mark.parametrize('i', range(5))
  690. def test_against_anderson(self, dist_name, i):
  691. # test that monte_carlo_test can reproduce results of `anderson`. Note:
  692. # `anderson` does not provide a p-value; it provides a list of
  693. # significance levels and the associated critical value of the test
  694. # statistic. `i` used to index this list.
  695. # find the skewness for which the sample statistic matches one of the
  696. # critical values provided by `stats.anderson`
  697. def fun(a):
  698. rng = np.random.default_rng(394295467)
  699. x = stats.tukeylambda.rvs(a, size=100, random_state=rng)
  700. expected = stats.anderson(x, dist_name)
  701. return expected.statistic - expected.critical_values[i]
  702. with suppress_warnings() as sup:
  703. sup.filter(RuntimeWarning)
  704. sol = root(fun, x0=0)
  705. assert sol.success
  706. # get the significance level (p-value) associated with that critical
  707. # value
  708. a = sol.x[0]
  709. rng = np.random.default_rng(394295467)
  710. x = stats.tukeylambda.rvs(a, size=100, random_state=rng)
  711. expected = stats.anderson(x, dist_name)
  712. expected_stat = expected.statistic
  713. expected_p = expected.significance_level[i]/100
  714. # perform equivalent Monte Carlo test and compare results
  715. def statistic1d(x):
  716. return stats.anderson(x, dist_name).statistic
  717. dist_rvs = self.rvs(getattr(stats, dist_name).rvs, rng)
  718. with suppress_warnings() as sup:
  719. sup.filter(RuntimeWarning)
  720. res = monte_carlo_test(x, dist_rvs,
  721. statistic1d, n_resamples=1000,
  722. vectorized=False, alternative='greater')
  723. assert_allclose(res.statistic, expected_stat)
  724. assert_allclose(res.pvalue, expected_p, atol=2*self.atol)
  725. def test_p_never_zero(self):
  726. # Use biased estimate of p-value to ensure that p-value is never zero
  727. # per monte_carlo_test reference [1]
  728. rng = np.random.default_rng(2190176673029737545)
  729. x = np.zeros(100)
  730. res = monte_carlo_test(x, rng.random, np.mean,
  731. vectorized=True, alternative='less')
  732. assert res.pvalue == 0.0001
  733. class TestPermutationTest:
  734. rtol = 1e-14
  735. def setup_method(self):
  736. self.rng = np.random.default_rng(7170559330470561044)
  737. # -- Input validation -- #
  738. def test_permutation_test_iv(self):
  739. def stat(x, y, axis):
  740. return stats.ttest_ind((x, y), axis).statistic
  741. message = "each sample in `data` must contain two or more ..."
  742. with pytest.raises(ValueError, match=message):
  743. permutation_test(([1, 2, 3], [1]), stat)
  744. message = "`data` must be a tuple containing at least two samples"
  745. with pytest.raises(ValueError, match=message):
  746. permutation_test((1,), stat)
  747. with pytest.raises(TypeError, match=message):
  748. permutation_test(1, stat)
  749. message = "`axis` must be an integer."
  750. with pytest.raises(ValueError, match=message):
  751. permutation_test(([1, 2, 3], [1, 2, 3]), stat, axis=1.5)
  752. message = "`permutation_type` must be in..."
  753. with pytest.raises(ValueError, match=message):
  754. permutation_test(([1, 2, 3], [1, 2, 3]), stat,
  755. permutation_type="ekki")
  756. message = "`vectorized` must be `True`, `False`, or `None`."
  757. with pytest.raises(ValueError, match=message):
  758. permutation_test(([1, 2, 3], [1, 2, 3]), stat, vectorized=1.5)
  759. message = "`n_resamples` must be a positive integer."
  760. with pytest.raises(ValueError, match=message):
  761. permutation_test(([1, 2, 3], [1, 2, 3]), stat, n_resamples=-1000)
  762. message = "`n_resamples` must be a positive integer."
  763. with pytest.raises(ValueError, match=message):
  764. permutation_test(([1, 2, 3], [1, 2, 3]), stat, n_resamples=1000.5)
  765. message = "`batch` must be a positive integer or None."
  766. with pytest.raises(ValueError, match=message):
  767. permutation_test(([1, 2, 3], [1, 2, 3]), stat, batch=-1000)
  768. message = "`batch` must be a positive integer or None."
  769. with pytest.raises(ValueError, match=message):
  770. permutation_test(([1, 2, 3], [1, 2, 3]), stat, batch=1000.5)
  771. message = "`alternative` must be in..."
  772. with pytest.raises(ValueError, match=message):
  773. permutation_test(([1, 2, 3], [1, 2, 3]), stat, alternative='ekki')
  774. message = "'herring' cannot be used to seed a"
  775. with pytest.raises(ValueError, match=message):
  776. permutation_test(([1, 2, 3], [1, 2, 3]), stat,
  777. random_state='herring')
  778. # -- Test Parameters -- #
  779. @pytest.mark.parametrize('random_state', [np.random.RandomState,
  780. np.random.default_rng])
  781. @pytest.mark.parametrize('permutation_type',
  782. ['pairings', 'samples', 'independent'])
  783. def test_batch(self, permutation_type, random_state):
  784. # make sure that the `batch` parameter is respected by checking the
  785. # maximum batch size provided in calls to `statistic`
  786. x = self.rng.random(10)
  787. y = self.rng.random(10)
  788. def statistic(x, y, axis):
  789. batch_size = 1 if x.ndim == 1 else len(x)
  790. statistic.batch_size = max(batch_size, statistic.batch_size)
  791. statistic.counter += 1
  792. return np.mean(x, axis=axis) - np.mean(y, axis=axis)
  793. statistic.counter = 0
  794. statistic.batch_size = 0
  795. kwds = {'n_resamples': 1000, 'permutation_type': permutation_type,
  796. 'vectorized': True}
  797. res1 = stats.permutation_test((x, y), statistic, batch=1,
  798. random_state=random_state(0), **kwds)
  799. assert_equal(statistic.counter, 1001)
  800. assert_equal(statistic.batch_size, 1)
  801. statistic.counter = 0
  802. res2 = stats.permutation_test((x, y), statistic, batch=50,
  803. random_state=random_state(0), **kwds)
  804. assert_equal(statistic.counter, 21)
  805. assert_equal(statistic.batch_size, 50)
  806. statistic.counter = 0
  807. res3 = stats.permutation_test((x, y), statistic, batch=1000,
  808. random_state=random_state(0), **kwds)
  809. assert_equal(statistic.counter, 2)
  810. assert_equal(statistic.batch_size, 1000)
  811. assert_equal(res1.pvalue, res3.pvalue)
  812. assert_equal(res2.pvalue, res3.pvalue)
  813. @pytest.mark.parametrize('random_state', [np.random.RandomState,
  814. np.random.default_rng])
  815. @pytest.mark.parametrize('permutation_type, exact_size',
  816. [('pairings', special.factorial(3)**2),
  817. ('samples', 2**3),
  818. ('independent', special.binom(6, 3))])
  819. def test_permutations(self, permutation_type, exact_size, random_state):
  820. # make sure that the `permutations` parameter is respected by checking
  821. # the size of the null distribution
  822. x = self.rng.random(3)
  823. y = self.rng.random(3)
  824. def statistic(x, y, axis):
  825. return np.mean(x, axis=axis) - np.mean(y, axis=axis)
  826. kwds = {'permutation_type': permutation_type,
  827. 'vectorized': True}
  828. res = stats.permutation_test((x, y), statistic, n_resamples=3,
  829. random_state=random_state(0), **kwds)
  830. assert_equal(res.null_distribution.size, 3)
  831. res = stats.permutation_test((x, y), statistic, **kwds)
  832. assert_equal(res.null_distribution.size, exact_size)
  833. # -- Randomized Permutation Tests -- #
  834. # To get reasonable accuracy, these next three tests are somewhat slow.
  835. # Originally, I had them passing for all combinations of permutation type,
  836. # alternative, and RNG, but that takes too long for CI. Instead, split
  837. # into three tests, each testing a particular combination of the three
  838. # parameters.
  839. def test_randomized_test_against_exact_both(self):
  840. # check that the randomized and exact tests agree to reasonable
  841. # precision for permutation_type='both
  842. alternative, rng = 'less', 0
  843. nx, ny, permutations = 8, 9, 24000
  844. assert special.binom(nx + ny, nx) > permutations
  845. x = stats.norm.rvs(size=nx)
  846. y = stats.norm.rvs(size=ny)
  847. data = x, y
  848. def statistic(x, y, axis):
  849. return np.mean(x, axis=axis) - np.mean(y, axis=axis)
  850. kwds = {'vectorized': True, 'permutation_type': 'independent',
  851. 'batch': 100, 'alternative': alternative, 'random_state': rng}
  852. res = permutation_test(data, statistic, n_resamples=permutations,
  853. **kwds)
  854. res2 = permutation_test(data, statistic, n_resamples=np.inf, **kwds)
  855. assert res.statistic == res2.statistic
  856. assert_allclose(res.pvalue, res2.pvalue, atol=1e-2)
  857. @pytest.mark.slow()
  858. def test_randomized_test_against_exact_samples(self):
  859. # check that the randomized and exact tests agree to reasonable
  860. # precision for permutation_type='samples'
  861. alternative, rng = 'greater', None
  862. nx, ny, permutations = 15, 15, 32000
  863. assert 2**nx > permutations
  864. x = stats.norm.rvs(size=nx)
  865. y = stats.norm.rvs(size=ny)
  866. data = x, y
  867. def statistic(x, y, axis):
  868. return np.mean(x - y, axis=axis)
  869. kwds = {'vectorized': True, 'permutation_type': 'samples',
  870. 'batch': 100, 'alternative': alternative, 'random_state': rng}
  871. res = permutation_test(data, statistic, n_resamples=permutations,
  872. **kwds)
  873. res2 = permutation_test(data, statistic, n_resamples=np.inf, **kwds)
  874. assert res.statistic == res2.statistic
  875. assert_allclose(res.pvalue, res2.pvalue, atol=1e-2)
  876. def test_randomized_test_against_exact_pairings(self):
  877. # check that the randomized and exact tests agree to reasonable
  878. # precision for permutation_type='pairings'
  879. alternative, rng = 'two-sided', self.rng
  880. nx, ny, permutations = 8, 8, 40000
  881. assert special.factorial(nx) > permutations
  882. x = stats.norm.rvs(size=nx)
  883. y = stats.norm.rvs(size=ny)
  884. data = [x]
  885. def statistic1d(x):
  886. return stats.pearsonr(x, y)[0]
  887. statistic = _resampling._vectorize_statistic(statistic1d)
  888. kwds = {'vectorized': True, 'permutation_type': 'samples',
  889. 'batch': 100, 'alternative': alternative, 'random_state': rng}
  890. res = permutation_test(data, statistic, n_resamples=permutations,
  891. **kwds)
  892. res2 = permutation_test(data, statistic, n_resamples=np.inf, **kwds)
  893. assert res.statistic == res2.statistic
  894. assert_allclose(res.pvalue, res2.pvalue, atol=1e-2)
  895. @pytest.mark.parametrize('alternative', ('less', 'greater'))
  896. # Different conventions for two-sided p-value here VS ttest_ind.
  897. # Eventually, we can add multiple options for the two-sided alternative
  898. # here in permutation_test.
  899. @pytest.mark.parametrize('permutations', (30, 1e9))
  900. @pytest.mark.parametrize('axis', (0, 1, 2))
  901. def test_against_permutation_ttest(self, alternative, permutations, axis):
  902. # check that this function and ttest_ind with permutations give
  903. # essentially identical results.
  904. x = np.arange(3*4*5).reshape(3, 4, 5)
  905. y = np.moveaxis(np.arange(4)[:, None, None], 0, axis)
  906. rng1 = np.random.default_rng(4337234444626115331)
  907. res1 = stats.ttest_ind(x, y, permutations=permutations, axis=axis,
  908. random_state=rng1, alternative=alternative)
  909. def statistic(x, y, axis):
  910. return stats.ttest_ind(x, y, axis=axis).statistic
  911. rng2 = np.random.default_rng(4337234444626115331)
  912. res2 = permutation_test((x, y), statistic, vectorized=True,
  913. n_resamples=permutations,
  914. alternative=alternative, axis=axis,
  915. random_state=rng2)
  916. assert_allclose(res1.statistic, res2.statistic, rtol=self.rtol)
  917. assert_allclose(res1.pvalue, res2.pvalue, rtol=self.rtol)
  918. # -- Independent (Unpaired) Sample Tests -- #
  919. @pytest.mark.parametrize('alternative', ("less", "greater", "two-sided"))
  920. def test_against_ks_2samp(self, alternative):
  921. x = self.rng.normal(size=4, scale=1)
  922. y = self.rng.normal(size=5, loc=3, scale=3)
  923. expected = stats.ks_2samp(x, y, alternative=alternative, mode='exact')
  924. def statistic1d(x, y):
  925. return stats.ks_2samp(x, y, mode='asymp',
  926. alternative=alternative).statistic
  927. # ks_2samp is always a one-tailed 'greater' test
  928. # it's the statistic that changes (D+ vs D- vs max(D+, D-))
  929. res = permutation_test((x, y), statistic1d, n_resamples=np.inf,
  930. alternative='greater', random_state=self.rng)
  931. assert_allclose(res.statistic, expected.statistic, rtol=self.rtol)
  932. assert_allclose(res.pvalue, expected.pvalue, rtol=self.rtol)
  933. @pytest.mark.parametrize('alternative', ("less", "greater", "two-sided"))
  934. def test_against_ansari(self, alternative):
  935. x = self.rng.normal(size=4, scale=1)
  936. y = self.rng.normal(size=5, scale=3)
  937. # ansari has a different convention for 'alternative'
  938. alternative_correspondence = {"less": "greater",
  939. "greater": "less",
  940. "two-sided": "two-sided"}
  941. alternative_scipy = alternative_correspondence[alternative]
  942. expected = stats.ansari(x, y, alternative=alternative_scipy)
  943. def statistic1d(x, y):
  944. return stats.ansari(x, y).statistic
  945. res = permutation_test((x, y), statistic1d, n_resamples=np.inf,
  946. alternative=alternative, random_state=self.rng)
  947. assert_allclose(res.statistic, expected.statistic, rtol=self.rtol)
  948. assert_allclose(res.pvalue, expected.pvalue, rtol=self.rtol)
  949. @pytest.mark.parametrize('alternative', ("less", "greater", "two-sided"))
  950. def test_against_mannwhitneyu(self, alternative):
  951. x = stats.uniform.rvs(size=(3, 5, 2), loc=0, random_state=self.rng)
  952. y = stats.uniform.rvs(size=(3, 5, 2), loc=0.05, random_state=self.rng)
  953. expected = stats.mannwhitneyu(x, y, axis=1, alternative=alternative)
  954. def statistic(x, y, axis):
  955. return stats.mannwhitneyu(x, y, axis=axis).statistic
  956. res = permutation_test((x, y), statistic, vectorized=True,
  957. n_resamples=np.inf, alternative=alternative,
  958. axis=1, random_state=self.rng)
  959. assert_allclose(res.statistic, expected.statistic, rtol=self.rtol)
  960. assert_allclose(res.pvalue, expected.pvalue, rtol=self.rtol)
  961. def test_against_cvm(self):
  962. x = stats.norm.rvs(size=4, scale=1, random_state=self.rng)
  963. y = stats.norm.rvs(size=5, loc=3, scale=3, random_state=self.rng)
  964. expected = stats.cramervonmises_2samp(x, y, method='exact')
  965. def statistic1d(x, y):
  966. return stats.cramervonmises_2samp(x, y,
  967. method='asymptotic').statistic
  968. # cramervonmises_2samp has only one alternative, greater
  969. res = permutation_test((x, y), statistic1d, n_resamples=np.inf,
  970. alternative='greater', random_state=self.rng)
  971. assert_allclose(res.statistic, expected.statistic, rtol=self.rtol)
  972. assert_allclose(res.pvalue, expected.pvalue, rtol=self.rtol)
  973. @pytest.mark.xslow()
  974. @pytest.mark.parametrize('axis', (-1, 2))
  975. def test_vectorized_nsamp_ptype_both(self, axis):
  976. # Test that permutation_test with permutation_type='independent' works
  977. # properly for a 3-sample statistic with nd array samples of different
  978. # (but compatible) shapes and ndims. Show that exact permutation test
  979. # and random permutation tests approximate SciPy's asymptotic pvalues
  980. # and that exact and random permutation test results are even closer
  981. # to one another (than they are to the asymptotic results).
  982. # Three samples, different (but compatible) shapes with different ndims
  983. rng = np.random.default_rng(6709265303529651545)
  984. x = rng.random(size=(3))
  985. y = rng.random(size=(1, 3, 2))
  986. z = rng.random(size=(2, 1, 4))
  987. data = (x, y, z)
  988. # Define the statistic (and pvalue for comparison)
  989. def statistic1d(*data):
  990. return stats.kruskal(*data).statistic
  991. def pvalue1d(*data):
  992. return stats.kruskal(*data).pvalue
  993. statistic = _resampling._vectorize_statistic(statistic1d)
  994. pvalue = _resampling._vectorize_statistic(pvalue1d)
  995. # Calculate the expected results
  996. x2 = np.broadcast_to(x, (2, 3, 3)) # broadcast manually because
  997. y2 = np.broadcast_to(y, (2, 3, 2)) # _vectorize_statistic doesn't
  998. z2 = np.broadcast_to(z, (2, 3, 4))
  999. expected_statistic = statistic(x2, y2, z2, axis=axis)
  1000. expected_pvalue = pvalue(x2, y2, z2, axis=axis)
  1001. # Calculate exact and randomized permutation results
  1002. kwds = {'vectorized': False, 'axis': axis, 'alternative': 'greater',
  1003. 'permutation_type': 'independent', 'random_state': self.rng}
  1004. res = permutation_test(data, statistic1d, n_resamples=np.inf, **kwds)
  1005. res2 = permutation_test(data, statistic1d, n_resamples=1000, **kwds)
  1006. # Check results
  1007. assert_allclose(res.statistic, expected_statistic, rtol=self.rtol)
  1008. assert_allclose(res.statistic, res2.statistic, rtol=self.rtol)
  1009. assert_allclose(res.pvalue, expected_pvalue, atol=6e-2)
  1010. assert_allclose(res.pvalue, res2.pvalue, atol=3e-2)
  1011. # -- Paired-Sample Tests -- #
  1012. @pytest.mark.parametrize('alternative', ("less", "greater", "two-sided"))
  1013. def test_against_wilcoxon(self, alternative):
  1014. x = stats.uniform.rvs(size=(3, 6, 2), loc=0, random_state=self.rng)
  1015. y = stats.uniform.rvs(size=(3, 6, 2), loc=0.05, random_state=self.rng)
  1016. # We'll check both 1- and 2-sample versions of the same test;
  1017. # we expect identical results to wilcoxon in all cases.
  1018. def statistic_1samp_1d(z):
  1019. # 'less' ensures we get the same of two statistics every time
  1020. return stats.wilcoxon(z, alternative='less').statistic
  1021. def statistic_2samp_1d(x, y):
  1022. return stats.wilcoxon(x, y, alternative='less').statistic
  1023. def test_1d(x, y):
  1024. return stats.wilcoxon(x, y, alternative=alternative)
  1025. test = _resampling._vectorize_statistic(test_1d)
  1026. expected = test(x, y, axis=1)
  1027. expected_stat = expected[0]
  1028. expected_p = expected[1]
  1029. kwds = {'vectorized': False, 'axis': 1, 'alternative': alternative,
  1030. 'permutation_type': 'samples', 'random_state': self.rng,
  1031. 'n_resamples': np.inf}
  1032. res1 = permutation_test((x-y,), statistic_1samp_1d, **kwds)
  1033. res2 = permutation_test((x, y), statistic_2samp_1d, **kwds)
  1034. # `wilcoxon` returns a different statistic with 'two-sided'
  1035. assert_allclose(res1.statistic, res2.statistic, rtol=self.rtol)
  1036. if alternative != 'two-sided':
  1037. assert_allclose(res2.statistic, expected_stat, rtol=self.rtol)
  1038. assert_allclose(res2.pvalue, expected_p, rtol=self.rtol)
  1039. assert_allclose(res1.pvalue, res2.pvalue, rtol=self.rtol)
  1040. @pytest.mark.parametrize('alternative', ("less", "greater", "two-sided"))
  1041. def test_against_binomtest(self, alternative):
  1042. x = self.rng.integers(0, 2, size=10)
  1043. x[x == 0] = -1
  1044. # More naturally, the test would flip elements between 0 and one.
  1045. # However, permutation_test will flip the _signs_ of the elements.
  1046. # So we have to work with +1/-1 instead of 1/0.
  1047. def statistic(x, axis=0):
  1048. return np.sum(x > 0, axis=axis)
  1049. k, n, p = statistic(x), 10, 0.5
  1050. expected = stats.binomtest(k, n, p, alternative=alternative)
  1051. res = stats.permutation_test((x,), statistic, vectorized=True,
  1052. permutation_type='samples',
  1053. n_resamples=np.inf, random_state=self.rng,
  1054. alternative=alternative)
  1055. assert_allclose(res.pvalue, expected.pvalue, rtol=self.rtol)
  1056. # -- Exact Association Tests -- #
  1057. def test_against_kendalltau(self):
  1058. x = self.rng.normal(size=6)
  1059. y = x + self.rng.normal(size=6)
  1060. expected = stats.kendalltau(x, y, method='exact')
  1061. def statistic1d(x):
  1062. return stats.kendalltau(x, y, method='asymptotic').statistic
  1063. # kendalltau currently has only one alternative, two-sided
  1064. res = permutation_test((x,), statistic1d, permutation_type='pairings',
  1065. n_resamples=np.inf, random_state=self.rng)
  1066. assert_allclose(res.statistic, expected.statistic, rtol=self.rtol)
  1067. assert_allclose(res.pvalue, expected.pvalue, rtol=self.rtol)
  1068. @pytest.mark.parametrize('alternative', ('less', 'greater', 'two-sided'))
  1069. def test_against_fisher_exact(self, alternative):
  1070. def statistic(x,):
  1071. return np.sum((x == 1) & (y == 1))
  1072. # x and y are binary random variables with some dependence
  1073. rng = np.random.default_rng(6235696159000529929)
  1074. x = (rng.random(7) > 0.6).astype(float)
  1075. y = (rng.random(7) + 0.25*x > 0.6).astype(float)
  1076. tab = stats.contingency.crosstab(x, y)[1]
  1077. res = permutation_test((x,), statistic, permutation_type='pairings',
  1078. n_resamples=np.inf, alternative=alternative,
  1079. random_state=rng)
  1080. res2 = stats.fisher_exact(tab, alternative=alternative)
  1081. assert_allclose(res.pvalue, res2[1])
  1082. @pytest.mark.xslow()
  1083. @pytest.mark.parametrize('axis', (-2, 1))
  1084. def test_vectorized_nsamp_ptype_samples(self, axis):
  1085. # Test that permutation_test with permutation_type='samples' works
  1086. # properly for a 3-sample statistic with nd array samples of different
  1087. # (but compatible) shapes and ndims. Show that exact permutation test
  1088. # reproduces SciPy's exact pvalue and that random permutation test
  1089. # approximates it.
  1090. x = self.rng.random(size=(2, 4, 3))
  1091. y = self.rng.random(size=(1, 4, 3))
  1092. z = self.rng.random(size=(2, 4, 1))
  1093. x = stats.rankdata(x, axis=axis)
  1094. y = stats.rankdata(y, axis=axis)
  1095. z = stats.rankdata(z, axis=axis)
  1096. y = y[0] # to check broadcast with different ndim
  1097. data = (x, y, z)
  1098. def statistic1d(*data):
  1099. return stats.page_trend_test(data, ranked=True,
  1100. method='asymptotic').statistic
  1101. def pvalue1d(*data):
  1102. return stats.page_trend_test(data, ranked=True,
  1103. method='exact').pvalue
  1104. statistic = _resampling._vectorize_statistic(statistic1d)
  1105. pvalue = _resampling._vectorize_statistic(pvalue1d)
  1106. expected_statistic = statistic(*np.broadcast_arrays(*data), axis=axis)
  1107. expected_pvalue = pvalue(*np.broadcast_arrays(*data), axis=axis)
  1108. # Let's forgive this use of an integer seed, please.
  1109. kwds = {'vectorized': False, 'axis': axis, 'alternative': 'greater',
  1110. 'permutation_type': 'pairings', 'random_state': 0}
  1111. res = permutation_test(data, statistic1d, n_resamples=np.inf, **kwds)
  1112. res2 = permutation_test(data, statistic1d, n_resamples=5000, **kwds)
  1113. assert_allclose(res.statistic, expected_statistic, rtol=self.rtol)
  1114. assert_allclose(res.statistic, res2.statistic, rtol=self.rtol)
  1115. assert_allclose(res.pvalue, expected_pvalue, rtol=self.rtol)
  1116. assert_allclose(res.pvalue, res2.pvalue, atol=3e-2)
  1117. # -- Test Against External References -- #
  1118. tie_case_1 = {'x': [1, 2, 3, 4], 'y': [1.5, 2, 2.5],
  1119. 'expected_less': 0.2000000000,
  1120. 'expected_2sided': 0.4, # 2*expected_less
  1121. 'expected_Pr_gte_S_mean': 0.3428571429, # see note below
  1122. 'expected_statistic': 7.5,
  1123. 'expected_avg': 9.142857, 'expected_std': 1.40698}
  1124. tie_case_2 = {'x': [111, 107, 100, 99, 102, 106, 109, 108],
  1125. 'y': [107, 108, 106, 98, 105, 103, 110, 105, 104],
  1126. 'expected_less': 0.1555738379,
  1127. 'expected_2sided': 0.3111476758,
  1128. 'expected_Pr_gte_S_mean': 0.2969971205, # see note below
  1129. 'expected_statistic': 32.5,
  1130. 'expected_avg': 38.117647, 'expected_std': 5.172124}
  1131. @pytest.mark.xslow() # only the second case is slow, really
  1132. @pytest.mark.parametrize('case', (tie_case_1, tie_case_2))
  1133. def test_with_ties(self, case):
  1134. """
  1135. Results above from SAS PROC NPAR1WAY, e.g.
  1136. DATA myData;
  1137. INPUT X Y;
  1138. CARDS;
  1139. 1 1
  1140. 1 2
  1141. 1 3
  1142. 1 4
  1143. 2 1.5
  1144. 2 2
  1145. 2 2.5
  1146. ods graphics on;
  1147. proc npar1way AB data=myData;
  1148. class X;
  1149. EXACT;
  1150. run;
  1151. ods graphics off;
  1152. Note: SAS provides Pr >= |S-Mean|, which is different from our
  1153. definition of a two-sided p-value.
  1154. """
  1155. x = case['x']
  1156. y = case['y']
  1157. expected_statistic = case['expected_statistic']
  1158. expected_less = case['expected_less']
  1159. expected_2sided = case['expected_2sided']
  1160. expected_Pr_gte_S_mean = case['expected_Pr_gte_S_mean']
  1161. expected_avg = case['expected_avg']
  1162. expected_std = case['expected_std']
  1163. def statistic1d(x, y):
  1164. return stats.ansari(x, y).statistic
  1165. with np.testing.suppress_warnings() as sup:
  1166. sup.filter(UserWarning, "Ties preclude use of exact statistic")
  1167. res = permutation_test((x, y), statistic1d, n_resamples=np.inf,
  1168. alternative='less')
  1169. res2 = permutation_test((x, y), statistic1d, n_resamples=np.inf,
  1170. alternative='two-sided')
  1171. assert_allclose(res.statistic, expected_statistic, rtol=self.rtol)
  1172. assert_allclose(res.pvalue, expected_less, atol=1e-10)
  1173. assert_allclose(res2.pvalue, expected_2sided, atol=1e-10)
  1174. assert_allclose(res2.null_distribution.mean(), expected_avg, rtol=1e-6)
  1175. assert_allclose(res2.null_distribution.std(), expected_std, rtol=1e-6)
  1176. # SAS provides Pr >= |S-Mean|; might as well check against that, too
  1177. S = res.statistic
  1178. mean = res.null_distribution.mean()
  1179. n = len(res.null_distribution)
  1180. Pr_gte_S_mean = np.sum(np.abs(res.null_distribution-mean)
  1181. >= np.abs(S-mean))/n
  1182. assert_allclose(expected_Pr_gte_S_mean, Pr_gte_S_mean)
  1183. @pytest.mark.parametrize('alternative, expected_pvalue',
  1184. (('less', 0.9708333333333),
  1185. ('greater', 0.05138888888889),
  1186. ('two-sided', 0.1027777777778)))
  1187. def test_against_spearmanr_in_R(self, alternative, expected_pvalue):
  1188. """
  1189. Results above from R cor.test, e.g.
  1190. options(digits=16)
  1191. x <- c(1.76405235, 0.40015721, 0.97873798,
  1192. 2.2408932, 1.86755799, -0.97727788)
  1193. y <- c(2.71414076, 0.2488, 0.87551913,
  1194. 2.6514917, 2.01160156, 0.47699563)
  1195. cor.test(x, y, method = "spearm", alternative = "t")
  1196. """
  1197. # data comes from
  1198. # np.random.seed(0)
  1199. # x = stats.norm.rvs(size=6)
  1200. # y = x + stats.norm.rvs(size=6)
  1201. x = [1.76405235, 0.40015721, 0.97873798,
  1202. 2.2408932, 1.86755799, -0.97727788]
  1203. y = [2.71414076, 0.2488, 0.87551913,
  1204. 2.6514917, 2.01160156, 0.47699563]
  1205. expected_statistic = 0.7714285714285715
  1206. def statistic1d(x):
  1207. return stats.spearmanr(x, y).statistic
  1208. res = permutation_test((x,), statistic1d, permutation_type='pairings',
  1209. n_resamples=np.inf, alternative=alternative)
  1210. assert_allclose(res.statistic, expected_statistic, rtol=self.rtol)
  1211. assert_allclose(res.pvalue, expected_pvalue, atol=1e-13)
  1212. @pytest.mark.parametrize("batch", (-1, 0))
  1213. def test_batch_generator_iv(self, batch):
  1214. with pytest.raises(ValueError, match="`batch` must be positive."):
  1215. list(_resampling._batch_generator([1, 2, 3], batch))
  1216. batch_generator_cases = [(range(0), 3, []),
  1217. (range(6), 3, [[0, 1, 2], [3, 4, 5]]),
  1218. (range(8), 3, [[0, 1, 2], [3, 4, 5], [6, 7]])]
  1219. @pytest.mark.parametrize("iterable, batch, expected",
  1220. batch_generator_cases)
  1221. def test_batch_generator(self, iterable, batch, expected):
  1222. got = list(_resampling._batch_generator(iterable, batch))
  1223. assert got == expected
  1224. def test_finite_precision_statistic(self):
  1225. # Some statistics return numerically distinct values when the values
  1226. # should be equal in theory. Test that `permutation_test` accounts
  1227. # for this in some way.
  1228. x = [1, 2, 4, 3]
  1229. y = [2, 4, 6, 8]
  1230. def statistic(x, y):
  1231. return stats.pearsonr(x, y)[0]
  1232. res = stats.permutation_test((x, y), statistic, vectorized=False,
  1233. permutation_type='pairings')
  1234. r, pvalue, null = res.statistic, res.pvalue, res.null_distribution
  1235. correct_p = 2 * np.sum(null >= r - 1e-14) / len(null)
  1236. assert pvalue == correct_p == 1/3
  1237. # Compare against other exact correlation tests using R corr.test
  1238. # options(digits=16)
  1239. # x = c(1, 2, 4, 3)
  1240. # y = c(2, 4, 6, 8)
  1241. # cor.test(x, y, alternative = "t", method = "spearman") # 0.333333333
  1242. # cor.test(x, y, alternative = "t", method = "kendall") # 0.333333333
  1243. def test_all_partitions_concatenated():
  1244. # make sure that _all_paritions_concatenated produces the correct number
  1245. # of partitions of the data into samples of the given sizes and that
  1246. # all are unique
  1247. n = np.array([3, 2, 4], dtype=int)
  1248. nc = np.cumsum(n)
  1249. all_partitions = set()
  1250. counter = 0
  1251. for partition_concatenated in _resampling._all_partitions_concatenated(n):
  1252. counter += 1
  1253. partitioning = np.split(partition_concatenated, nc[:-1])
  1254. all_partitions.add(tuple([frozenset(i) for i in partitioning]))
  1255. expected = np.product([special.binom(sum(n[i:]), sum(n[i+1:]))
  1256. for i in range(len(n)-1)])
  1257. assert_equal(counter, expected)
  1258. assert_equal(len(all_partitions), expected)
  1259. @pytest.mark.parametrize('fun_name',
  1260. ['bootstrap', 'permutation_test', 'monte_carlo_test'])
  1261. def test_parameter_vectorized(fun_name):
  1262. # Check that parameter `vectorized` is working as desired for all
  1263. # resampling functions. Results don't matter; just don't fail asserts.
  1264. rng = np.random.default_rng(75245098234592)
  1265. sample = rng.random(size=10)
  1266. def rvs(size): # needed by `monte_carlo_test`
  1267. return stats.norm.rvs(size=size, random_state=rng)
  1268. fun_options = {'bootstrap': {'data': (sample,), 'random_state': rng,
  1269. 'method': 'percentile'},
  1270. 'permutation_test': {'data': (sample,), 'random_state': rng,
  1271. 'permutation_type': 'samples'},
  1272. 'monte_carlo_test': {'sample': sample, 'rvs': rvs}}
  1273. common_options = {'n_resamples': 100}
  1274. fun = getattr(stats, fun_name)
  1275. options = fun_options[fun_name]
  1276. options.update(common_options)
  1277. def statistic(x, axis):
  1278. assert x.ndim > 1 or np.array_equal(x, sample)
  1279. return np.mean(x, axis=axis)
  1280. fun(statistic=statistic, vectorized=None, **options)
  1281. fun(statistic=statistic, vectorized=True, **options)
  1282. def statistic(x):
  1283. assert x.ndim == 1
  1284. return np.mean(x)
  1285. fun(statistic=statistic, vectorized=None, **options)
  1286. fun(statistic=statistic, vectorized=False, **options)