scipy 1.15.3__cp312-cp312-macosx_12_0_arm64.whl → 1.16.0rc2__cp312-cp312-macosx_12_0_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (629) hide show
  1. scipy/.dylibs/libscipy_openblas.dylib +0 -0
  2. scipy/__config__.py +8 -8
  3. scipy/__init__.py +3 -6
  4. scipy/_cyutility.cpython-312-darwin.so +0 -0
  5. scipy/_lib/_array_api.py +486 -161
  6. scipy/_lib/_array_api_compat_vendor.py +9 -0
  7. scipy/_lib/_bunch.py +4 -0
  8. scipy/_lib/_ccallback_c.cpython-312-darwin.so +0 -0
  9. scipy/_lib/_docscrape.py +1 -1
  10. scipy/_lib/_elementwise_iterative_method.py +15 -26
  11. scipy/_lib/_sparse.py +41 -0
  12. scipy/_lib/_test_deprecation_call.cpython-312-darwin.so +0 -0
  13. scipy/_lib/_test_deprecation_def.cpython-312-darwin.so +0 -0
  14. scipy/_lib/_testutils.py +6 -2
  15. scipy/_lib/_util.py +222 -125
  16. scipy/_lib/array_api_compat/__init__.py +4 -4
  17. scipy/_lib/array_api_compat/_internal.py +19 -6
  18. scipy/_lib/array_api_compat/common/__init__.py +1 -1
  19. scipy/_lib/array_api_compat/common/_aliases.py +365 -193
  20. scipy/_lib/array_api_compat/common/_fft.py +94 -64
  21. scipy/_lib/array_api_compat/common/_helpers.py +413 -180
  22. scipy/_lib/array_api_compat/common/_linalg.py +116 -40
  23. scipy/_lib/array_api_compat/common/_typing.py +179 -10
  24. scipy/_lib/array_api_compat/cupy/__init__.py +1 -4
  25. scipy/_lib/array_api_compat/cupy/_aliases.py +61 -41
  26. scipy/_lib/array_api_compat/cupy/_info.py +16 -6
  27. scipy/_lib/array_api_compat/cupy/_typing.py +24 -39
  28. scipy/_lib/array_api_compat/dask/array/__init__.py +6 -3
  29. scipy/_lib/array_api_compat/dask/array/_aliases.py +267 -108
  30. scipy/_lib/array_api_compat/dask/array/_info.py +105 -34
  31. scipy/_lib/array_api_compat/dask/array/fft.py +5 -8
  32. scipy/_lib/array_api_compat/dask/array/linalg.py +21 -22
  33. scipy/_lib/array_api_compat/numpy/__init__.py +13 -15
  34. scipy/_lib/array_api_compat/numpy/_aliases.py +98 -49
  35. scipy/_lib/array_api_compat/numpy/_info.py +36 -16
  36. scipy/_lib/array_api_compat/numpy/_typing.py +27 -43
  37. scipy/_lib/array_api_compat/numpy/fft.py +11 -5
  38. scipy/_lib/array_api_compat/numpy/linalg.py +75 -22
  39. scipy/_lib/array_api_compat/torch/__init__.py +3 -5
  40. scipy/_lib/array_api_compat/torch/_aliases.py +262 -159
  41. scipy/_lib/array_api_compat/torch/_info.py +27 -16
  42. scipy/_lib/array_api_compat/torch/_typing.py +3 -0
  43. scipy/_lib/array_api_compat/torch/fft.py +17 -18
  44. scipy/_lib/array_api_compat/torch/linalg.py +16 -16
  45. scipy/_lib/array_api_extra/__init__.py +26 -3
  46. scipy/_lib/array_api_extra/_delegation.py +171 -0
  47. scipy/_lib/array_api_extra/_lib/__init__.py +1 -0
  48. scipy/_lib/array_api_extra/_lib/_at.py +463 -0
  49. scipy/_lib/array_api_extra/_lib/_backends.py +46 -0
  50. scipy/_lib/array_api_extra/_lib/_funcs.py +937 -0
  51. scipy/_lib/array_api_extra/_lib/_lazy.py +357 -0
  52. scipy/_lib/array_api_extra/_lib/_testing.py +278 -0
  53. scipy/_lib/array_api_extra/_lib/_utils/__init__.py +1 -0
  54. scipy/_lib/array_api_extra/_lib/_utils/_compat.py +74 -0
  55. scipy/_lib/array_api_extra/_lib/_utils/_compat.pyi +45 -0
  56. scipy/_lib/array_api_extra/_lib/_utils/_helpers.py +559 -0
  57. scipy/_lib/array_api_extra/_lib/_utils/_typing.py +10 -0
  58. scipy/_lib/array_api_extra/_lib/_utils/_typing.pyi +105 -0
  59. scipy/_lib/array_api_extra/testing.py +359 -0
  60. scipy/_lib/decorator.py +2 -2
  61. scipy/_lib/doccer.py +1 -7
  62. scipy/_lib/messagestream.cpython-312-darwin.so +0 -0
  63. scipy/_lib/pyprima/__init__.py +212 -0
  64. scipy/_lib/pyprima/cobyla/__init__.py +0 -0
  65. scipy/_lib/pyprima/cobyla/cobyla.py +559 -0
  66. scipy/_lib/pyprima/cobyla/cobylb.py +714 -0
  67. scipy/_lib/pyprima/cobyla/geometry.py +226 -0
  68. scipy/_lib/pyprima/cobyla/initialize.py +215 -0
  69. scipy/_lib/pyprima/cobyla/trustregion.py +492 -0
  70. scipy/_lib/pyprima/cobyla/update.py +289 -0
  71. scipy/_lib/pyprima/common/__init__.py +0 -0
  72. scipy/_lib/pyprima/common/_bounds.py +34 -0
  73. scipy/_lib/pyprima/common/_linear_constraints.py +46 -0
  74. scipy/_lib/pyprima/common/_nonlinear_constraints.py +54 -0
  75. scipy/_lib/pyprima/common/_project.py +173 -0
  76. scipy/_lib/pyprima/common/checkbreak.py +93 -0
  77. scipy/_lib/pyprima/common/consts.py +47 -0
  78. scipy/_lib/pyprima/common/evaluate.py +99 -0
  79. scipy/_lib/pyprima/common/history.py +38 -0
  80. scipy/_lib/pyprima/common/infos.py +30 -0
  81. scipy/_lib/pyprima/common/linalg.py +435 -0
  82. scipy/_lib/pyprima/common/message.py +290 -0
  83. scipy/_lib/pyprima/common/powalg.py +131 -0
  84. scipy/_lib/pyprima/common/preproc.py +277 -0
  85. scipy/_lib/pyprima/common/present.py +5 -0
  86. scipy/_lib/pyprima/common/ratio.py +54 -0
  87. scipy/_lib/pyprima/common/redrho.py +47 -0
  88. scipy/_lib/pyprima/common/selectx.py +296 -0
  89. scipy/_lib/tests/test__util.py +105 -121
  90. scipy/_lib/tests/test_array_api.py +166 -35
  91. scipy/_lib/tests/test_bunch.py +7 -0
  92. scipy/_lib/tests/test_ccallback.py +2 -10
  93. scipy/_lib/tests/test_public_api.py +13 -0
  94. scipy/cluster/_hierarchy.cpython-312-darwin.so +0 -0
  95. scipy/cluster/_optimal_leaf_ordering.cpython-312-darwin.so +0 -0
  96. scipy/cluster/_vq.cpython-312-darwin.so +0 -0
  97. scipy/cluster/hierarchy.py +393 -223
  98. scipy/cluster/tests/test_hierarchy.py +273 -335
  99. scipy/cluster/tests/test_vq.py +45 -61
  100. scipy/cluster/vq.py +39 -35
  101. scipy/conftest.py +263 -157
  102. scipy/constants/_constants.py +4 -1
  103. scipy/constants/tests/test_codata.py +2 -2
  104. scipy/constants/tests/test_constants.py +11 -18
  105. scipy/datasets/_download_all.py +15 -1
  106. scipy/datasets/_fetchers.py +7 -1
  107. scipy/datasets/_utils.py +1 -1
  108. scipy/differentiate/_differentiate.py +25 -25
  109. scipy/differentiate/tests/test_differentiate.py +24 -25
  110. scipy/fft/_basic.py +20 -0
  111. scipy/fft/_helper.py +3 -34
  112. scipy/fft/_pocketfft/helper.py +29 -1
  113. scipy/fft/_pocketfft/tests/test_basic.py +2 -4
  114. scipy/fft/_pocketfft/tests/test_real_transforms.py +4 -4
  115. scipy/fft/_realtransforms.py +13 -0
  116. scipy/fft/tests/test_basic.py +27 -25
  117. scipy/fft/tests/test_fftlog.py +16 -7
  118. scipy/fft/tests/test_helper.py +18 -34
  119. scipy/fft/tests/test_real_transforms.py +8 -10
  120. scipy/fftpack/convolve.cpython-312-darwin.so +0 -0
  121. scipy/fftpack/tests/test_basic.py +2 -4
  122. scipy/fftpack/tests/test_real_transforms.py +8 -9
  123. scipy/integrate/_bvp.py +9 -3
  124. scipy/integrate/_cubature.py +3 -2
  125. scipy/integrate/_dop.cpython-312-darwin.so +0 -0
  126. scipy/integrate/_lsoda.cpython-312-darwin.so +0 -0
  127. scipy/integrate/_ode.py +9 -2
  128. scipy/integrate/_odepack.cpython-312-darwin.so +0 -0
  129. scipy/integrate/_quad_vec.py +21 -29
  130. scipy/integrate/_quadpack.cpython-312-darwin.so +0 -0
  131. scipy/integrate/_quadpack_py.py +11 -7
  132. scipy/integrate/_quadrature.py +3 -3
  133. scipy/integrate/_rules/_base.py +2 -2
  134. scipy/integrate/_tanhsinh.py +48 -47
  135. scipy/integrate/_test_odeint_banded.cpython-312-darwin.so +0 -0
  136. scipy/integrate/_vode.cpython-312-darwin.so +0 -0
  137. scipy/integrate/tests/test__quad_vec.py +0 -6
  138. scipy/integrate/tests/test_banded_ode_solvers.py +85 -0
  139. scipy/integrate/tests/test_cubature.py +21 -35
  140. scipy/integrate/tests/test_quadrature.py +6 -8
  141. scipy/integrate/tests/test_tanhsinh.py +56 -48
  142. scipy/interpolate/__init__.py +70 -58
  143. scipy/interpolate/_bary_rational.py +22 -22
  144. scipy/interpolate/_bsplines.py +119 -66
  145. scipy/interpolate/_cubic.py +65 -50
  146. scipy/interpolate/_dfitpack.cpython-312-darwin.so +0 -0
  147. scipy/interpolate/_dierckx.cpython-312-darwin.so +0 -0
  148. scipy/interpolate/_fitpack.cpython-312-darwin.so +0 -0
  149. scipy/interpolate/_fitpack2.py +9 -6
  150. scipy/interpolate/_fitpack_impl.py +32 -26
  151. scipy/interpolate/_fitpack_repro.py +23 -19
  152. scipy/interpolate/_interpnd.cpython-312-darwin.so +0 -0
  153. scipy/interpolate/_interpolate.py +30 -12
  154. scipy/interpolate/_ndbspline.py +13 -18
  155. scipy/interpolate/_ndgriddata.py +5 -8
  156. scipy/interpolate/_polyint.py +95 -31
  157. scipy/interpolate/_ppoly.cpython-312-darwin.so +0 -0
  158. scipy/interpolate/_rbf.py +2 -2
  159. scipy/interpolate/_rbfinterp.py +1 -1
  160. scipy/interpolate/_rbfinterp_pythran.cpython-312-darwin.so +0 -0
  161. scipy/interpolate/_rgi.py +31 -26
  162. scipy/interpolate/_rgi_cython.cpython-312-darwin.so +0 -0
  163. scipy/interpolate/dfitpack.py +0 -20
  164. scipy/interpolate/interpnd.py +1 -2
  165. scipy/interpolate/tests/test_bary_rational.py +2 -2
  166. scipy/interpolate/tests/test_bsplines.py +97 -1
  167. scipy/interpolate/tests/test_fitpack2.py +39 -1
  168. scipy/interpolate/tests/test_interpnd.py +32 -20
  169. scipy/interpolate/tests/test_interpolate.py +48 -4
  170. scipy/interpolate/tests/test_rgi.py +2 -1
  171. scipy/io/_fast_matrix_market/__init__.py +2 -0
  172. scipy/io/_harwell_boeing/_fortran_format_parser.py +19 -16
  173. scipy/io/_harwell_boeing/hb.py +7 -11
  174. scipy/io/_idl.py +5 -7
  175. scipy/io/_netcdf.py +15 -5
  176. scipy/io/_test_fortran.cpython-312-darwin.so +0 -0
  177. scipy/io/arff/tests/test_arffread.py +3 -3
  178. scipy/io/matlab/__init__.py +5 -3
  179. scipy/io/matlab/_mio.py +4 -1
  180. scipy/io/matlab/_mio5.py +19 -13
  181. scipy/io/matlab/_mio5_utils.cpython-312-darwin.so +0 -0
  182. scipy/io/matlab/_mio_utils.cpython-312-darwin.so +0 -0
  183. scipy/io/matlab/_miobase.py +4 -1
  184. scipy/io/matlab/_streams.cpython-312-darwin.so +0 -0
  185. scipy/io/matlab/tests/test_mio.py +46 -18
  186. scipy/io/matlab/tests/test_mio_funcs.py +1 -1
  187. scipy/io/tests/test_mmio.py +7 -1
  188. scipy/io/tests/test_wavfile.py +41 -0
  189. scipy/io/wavfile.py +57 -10
  190. scipy/linalg/_basic.py +113 -86
  191. scipy/linalg/_cythonized_array_utils.cpython-312-darwin.so +0 -0
  192. scipy/linalg/_decomp.py +22 -9
  193. scipy/linalg/_decomp_cholesky.py +28 -13
  194. scipy/linalg/_decomp_cossin.py +45 -30
  195. scipy/linalg/_decomp_interpolative.cpython-312-darwin.so +0 -0
  196. scipy/linalg/_decomp_ldl.py +4 -1
  197. scipy/linalg/_decomp_lu.py +18 -6
  198. scipy/linalg/_decomp_lu_cython.cpython-312-darwin.so +0 -0
  199. scipy/linalg/_decomp_polar.py +2 -0
  200. scipy/linalg/_decomp_qr.py +6 -2
  201. scipy/linalg/_decomp_qz.py +3 -0
  202. scipy/linalg/_decomp_schur.py +3 -1
  203. scipy/linalg/_decomp_svd.py +13 -2
  204. scipy/linalg/_decomp_update.cpython-312-darwin.so +0 -0
  205. scipy/linalg/_expm_frechet.py +4 -0
  206. scipy/linalg/_fblas.cpython-312-darwin.so +0 -0
  207. scipy/linalg/_flapack.cpython-312-darwin.so +0 -0
  208. scipy/linalg/_linalg_pythran.cpython-312-darwin.so +0 -0
  209. scipy/linalg/_matfuncs.py +187 -4
  210. scipy/linalg/_matfuncs_expm.cpython-312-darwin.so +0 -0
  211. scipy/linalg/_matfuncs_schur_sqrtm.cpython-312-darwin.so +0 -0
  212. scipy/linalg/_matfuncs_sqrtm.py +1 -99
  213. scipy/linalg/_matfuncs_sqrtm_triu.cpython-312-darwin.so +0 -0
  214. scipy/linalg/_procrustes.py +2 -0
  215. scipy/linalg/_sketches.py +17 -6
  216. scipy/linalg/_solve_toeplitz.cpython-312-darwin.so +0 -0
  217. scipy/linalg/_solvers.py +7 -2
  218. scipy/linalg/_special_matrices.py +26 -36
  219. scipy/linalg/cython_blas.cpython-312-darwin.so +0 -0
  220. scipy/linalg/cython_lapack.cpython-312-darwin.so +0 -0
  221. scipy/linalg/lapack.py +22 -2
  222. scipy/linalg/tests/_cython_examples/meson.build +7 -0
  223. scipy/linalg/tests/test_basic.py +31 -16
  224. scipy/linalg/tests/test_batch.py +588 -0
  225. scipy/linalg/tests/test_cythonized_array_utils.py +0 -2
  226. scipy/linalg/tests/test_decomp.py +40 -3
  227. scipy/linalg/tests/test_decomp_cossin.py +14 -0
  228. scipy/linalg/tests/test_decomp_ldl.py +1 -1
  229. scipy/linalg/tests/test_lapack.py +115 -7
  230. scipy/linalg/tests/test_matfuncs.py +157 -102
  231. scipy/linalg/tests/test_procrustes.py +0 -7
  232. scipy/linalg/tests/test_solve_toeplitz.py +1 -1
  233. scipy/linalg/tests/test_special_matrices.py +1 -5
  234. scipy/ndimage/__init__.py +1 -0
  235. scipy/ndimage/_cytest.cpython-312-darwin.so +0 -0
  236. scipy/ndimage/_delegators.py +8 -2
  237. scipy/ndimage/_filters.py +453 -5
  238. scipy/ndimage/_interpolation.py +36 -6
  239. scipy/ndimage/_measurements.py +4 -2
  240. scipy/ndimage/_morphology.py +5 -0
  241. scipy/ndimage/_nd_image.cpython-312-darwin.so +0 -0
  242. scipy/ndimage/_ni_docstrings.py +5 -1
  243. scipy/ndimage/_ni_label.cpython-312-darwin.so +0 -0
  244. scipy/ndimage/_ni_support.py +1 -5
  245. scipy/ndimage/_rank_filter_1d.cpython-312-darwin.so +0 -0
  246. scipy/ndimage/_support_alternative_backends.py +18 -6
  247. scipy/ndimage/tests/test_filters.py +370 -259
  248. scipy/ndimage/tests/test_fourier.py +7 -9
  249. scipy/ndimage/tests/test_interpolation.py +68 -61
  250. scipy/ndimage/tests/test_measurements.py +18 -35
  251. scipy/ndimage/tests/test_morphology.py +143 -131
  252. scipy/ndimage/tests/test_splines.py +1 -3
  253. scipy/odr/__odrpack.cpython-312-darwin.so +0 -0
  254. scipy/optimize/_basinhopping.py +13 -7
  255. scipy/optimize/_bglu_dense.cpython-312-darwin.so +0 -0
  256. scipy/optimize/_bracket.py +17 -24
  257. scipy/optimize/_chandrupatla.py +9 -10
  258. scipy/optimize/_cobyla_py.py +104 -123
  259. scipy/optimize/_constraints.py +14 -10
  260. scipy/optimize/_differentiable_functions.py +371 -230
  261. scipy/optimize/_differentialevolution.py +4 -3
  262. scipy/optimize/_direct.cpython-312-darwin.so +0 -0
  263. scipy/optimize/_dual_annealing.py +1 -1
  264. scipy/optimize/_elementwise.py +1 -4
  265. scipy/optimize/_group_columns.cpython-312-darwin.so +0 -0
  266. scipy/optimize/_lbfgsb.cpython-312-darwin.so +0 -0
  267. scipy/optimize/_lbfgsb_py.py +57 -16
  268. scipy/optimize/_linprog_doc.py +2 -2
  269. scipy/optimize/_linprog_highs.py +2 -2
  270. scipy/optimize/_linprog_ip.py +25 -10
  271. scipy/optimize/_linprog_util.py +14 -16
  272. scipy/optimize/_lsap.cpython-312-darwin.so +0 -0
  273. scipy/optimize/_lsq/common.py +3 -3
  274. scipy/optimize/_lsq/dogbox.py +16 -2
  275. scipy/optimize/_lsq/givens_elimination.cpython-312-darwin.so +0 -0
  276. scipy/optimize/_lsq/least_squares.py +198 -126
  277. scipy/optimize/_lsq/lsq_linear.py +6 -6
  278. scipy/optimize/_lsq/trf.py +35 -8
  279. scipy/optimize/_milp.py +3 -1
  280. scipy/optimize/_minimize.py +105 -36
  281. scipy/optimize/_minpack.cpython-312-darwin.so +0 -0
  282. scipy/optimize/_minpack_py.py +21 -14
  283. scipy/optimize/_moduleTNC.cpython-312-darwin.so +0 -0
  284. scipy/optimize/_nnls.py +20 -21
  285. scipy/optimize/_nonlin.py +34 -3
  286. scipy/optimize/_numdiff.py +288 -110
  287. scipy/optimize/_optimize.py +86 -48
  288. scipy/optimize/_pava_pybind.cpython-312-darwin.so +0 -0
  289. scipy/optimize/_remove_redundancy.py +5 -5
  290. scipy/optimize/_root_scalar.py +1 -1
  291. scipy/optimize/_shgo.py +6 -0
  292. scipy/optimize/_shgo_lib/_complex.py +1 -1
  293. scipy/optimize/_slsqp_py.py +216 -124
  294. scipy/optimize/_slsqplib.cpython-312-darwin.so +0 -0
  295. scipy/optimize/_spectral.py +1 -1
  296. scipy/optimize/_tnc.py +8 -1
  297. scipy/optimize/_trlib/_trlib.cpython-312-darwin.so +0 -0
  298. scipy/optimize/_trustregion.py +20 -6
  299. scipy/optimize/_trustregion_constr/canonical_constraint.py +7 -7
  300. scipy/optimize/_trustregion_constr/equality_constrained_sqp.py +1 -1
  301. scipy/optimize/_trustregion_constr/minimize_trustregion_constr.py +11 -3
  302. scipy/optimize/_trustregion_constr/projections.py +12 -8
  303. scipy/optimize/_trustregion_constr/qp_subproblem.py +9 -9
  304. scipy/optimize/_trustregion_constr/tests/test_projections.py +7 -7
  305. scipy/optimize/_trustregion_constr/tests/test_qp_subproblem.py +77 -77
  306. scipy/optimize/_trustregion_constr/tr_interior_point.py +5 -5
  307. scipy/optimize/_trustregion_exact.py +0 -1
  308. scipy/optimize/_zeros.cpython-312-darwin.so +0 -0
  309. scipy/optimize/_zeros_py.py +97 -17
  310. scipy/optimize/cython_optimize/_zeros.cpython-312-darwin.so +0 -0
  311. scipy/optimize/slsqp.py +0 -1
  312. scipy/optimize/tests/test__basinhopping.py +1 -1
  313. scipy/optimize/tests/test__differential_evolution.py +4 -4
  314. scipy/optimize/tests/test__linprog_clean_inputs.py +5 -3
  315. scipy/optimize/tests/test__numdiff.py +66 -22
  316. scipy/optimize/tests/test__remove_redundancy.py +2 -2
  317. scipy/optimize/tests/test__shgo.py +9 -1
  318. scipy/optimize/tests/test_bracket.py +36 -46
  319. scipy/optimize/tests/test_chandrupatla.py +133 -135
  320. scipy/optimize/tests/test_cobyla.py +74 -45
  321. scipy/optimize/tests/test_constraints.py +1 -1
  322. scipy/optimize/tests/test_differentiable_functions.py +226 -6
  323. scipy/optimize/tests/test_lbfgsb_hessinv.py +22 -0
  324. scipy/optimize/tests/test_least_squares.py +125 -13
  325. scipy/optimize/tests/test_linear_assignment.py +3 -3
  326. scipy/optimize/tests/test_linprog.py +3 -3
  327. scipy/optimize/tests/test_lsq_linear.py +6 -6
  328. scipy/optimize/tests/test_minimize_constrained.py +2 -2
  329. scipy/optimize/tests/test_minpack.py +4 -4
  330. scipy/optimize/tests/test_nnls.py +43 -3
  331. scipy/optimize/tests/test_nonlin.py +36 -0
  332. scipy/optimize/tests/test_optimize.py +95 -17
  333. scipy/optimize/tests/test_slsqp.py +36 -4
  334. scipy/optimize/tests/test_zeros.py +34 -1
  335. scipy/signal/__init__.py +12 -23
  336. scipy/signal/_delegators.py +568 -0
  337. scipy/signal/_filter_design.py +459 -241
  338. scipy/signal/_fir_filter_design.py +262 -90
  339. scipy/signal/_lti_conversion.py +3 -2
  340. scipy/signal/_ltisys.py +118 -91
  341. scipy/signal/_max_len_seq_inner.cpython-312-darwin.so +0 -0
  342. scipy/signal/_peak_finding_utils.cpython-312-darwin.so +0 -0
  343. scipy/signal/_polyutils.py +172 -0
  344. scipy/signal/_short_time_fft.py +519 -70
  345. scipy/signal/_signal_api.py +30 -0
  346. scipy/signal/_signaltools.py +719 -399
  347. scipy/signal/_sigtools.cpython-312-darwin.so +0 -0
  348. scipy/signal/_sosfilt.cpython-312-darwin.so +0 -0
  349. scipy/signal/_spectral_py.py +230 -50
  350. scipy/signal/_spline.cpython-312-darwin.so +0 -0
  351. scipy/signal/_spline_filters.py +108 -68
  352. scipy/signal/_support_alternative_backends.py +73 -0
  353. scipy/signal/_upfirdn.py +4 -1
  354. scipy/signal/_upfirdn_apply.cpython-312-darwin.so +0 -0
  355. scipy/signal/_waveforms.py +2 -11
  356. scipy/signal/_wavelets.py +1 -1
  357. scipy/signal/fir_filter_design.py +1 -0
  358. scipy/signal/spline.py +4 -11
  359. scipy/signal/tests/_scipy_spectral_test_shim.py +2 -171
  360. scipy/signal/tests/test_bsplines.py +114 -79
  361. scipy/signal/tests/test_cont2discrete.py +9 -2
  362. scipy/signal/tests/test_filter_design.py +721 -481
  363. scipy/signal/tests/test_fir_filter_design.py +332 -140
  364. scipy/signal/tests/test_savitzky_golay.py +4 -3
  365. scipy/signal/tests/test_short_time_fft.py +221 -3
  366. scipy/signal/tests/test_signaltools.py +2144 -1348
  367. scipy/signal/tests/test_spectral.py +50 -6
  368. scipy/signal/tests/test_splines.py +161 -96
  369. scipy/signal/tests/test_upfirdn.py +84 -50
  370. scipy/signal/tests/test_waveforms.py +20 -0
  371. scipy/signal/tests/test_windows.py +607 -466
  372. scipy/signal/windows/_windows.py +287 -148
  373. scipy/sparse/__init__.py +23 -4
  374. scipy/sparse/_base.py +270 -108
  375. scipy/sparse/_bsr.py +7 -4
  376. scipy/sparse/_compressed.py +59 -231
  377. scipy/sparse/_construct.py +90 -38
  378. scipy/sparse/_coo.py +115 -181
  379. scipy/sparse/_csc.py +4 -4
  380. scipy/sparse/_csparsetools.cpython-312-darwin.so +0 -0
  381. scipy/sparse/_csr.py +2 -2
  382. scipy/sparse/_data.py +48 -48
  383. scipy/sparse/_dia.py +105 -18
  384. scipy/sparse/_dok.py +0 -23
  385. scipy/sparse/_index.py +4 -4
  386. scipy/sparse/_matrix.py +23 -0
  387. scipy/sparse/_sparsetools.cpython-312-darwin.so +0 -0
  388. scipy/sparse/_sputils.py +37 -22
  389. scipy/sparse/base.py +0 -9
  390. scipy/sparse/bsr.py +0 -14
  391. scipy/sparse/compressed.py +0 -23
  392. scipy/sparse/construct.py +0 -6
  393. scipy/sparse/coo.py +0 -14
  394. scipy/sparse/csc.py +0 -3
  395. scipy/sparse/csgraph/_flow.cpython-312-darwin.so +0 -0
  396. scipy/sparse/csgraph/_matching.cpython-312-darwin.so +0 -0
  397. scipy/sparse/csgraph/_min_spanning_tree.cpython-312-darwin.so +0 -0
  398. scipy/sparse/csgraph/_reordering.cpython-312-darwin.so +0 -0
  399. scipy/sparse/csgraph/_shortest_path.cpython-312-darwin.so +0 -0
  400. scipy/sparse/csgraph/_tools.cpython-312-darwin.so +0 -0
  401. scipy/sparse/csgraph/_traversal.cpython-312-darwin.so +0 -0
  402. scipy/sparse/csgraph/tests/test_matching.py +14 -2
  403. scipy/sparse/csgraph/tests/test_pydata_sparse.py +4 -1
  404. scipy/sparse/csgraph/tests/test_shortest_path.py +83 -27
  405. scipy/sparse/csr.py +0 -5
  406. scipy/sparse/data.py +1 -6
  407. scipy/sparse/dia.py +0 -7
  408. scipy/sparse/dok.py +0 -10
  409. scipy/sparse/linalg/_dsolve/_superlu.cpython-312-darwin.so +0 -0
  410. scipy/sparse/linalg/_dsolve/linsolve.py +9 -0
  411. scipy/sparse/linalg/_dsolve/tests/test_linsolve.py +35 -28
  412. scipy/sparse/linalg/_eigen/arpack/_arpack.cpython-312-darwin.so +0 -0
  413. scipy/sparse/linalg/_eigen/arpack/arpack.py +23 -17
  414. scipy/sparse/linalg/_eigen/lobpcg/lobpcg.py +6 -6
  415. scipy/sparse/linalg/_interface.py +17 -18
  416. scipy/sparse/linalg/_isolve/_gcrotmk.py +4 -4
  417. scipy/sparse/linalg/_isolve/iterative.py +51 -45
  418. scipy/sparse/linalg/_isolve/lgmres.py +6 -6
  419. scipy/sparse/linalg/_isolve/minres.py +5 -5
  420. scipy/sparse/linalg/_isolve/tfqmr.py +7 -7
  421. scipy/sparse/linalg/_isolve/utils.py +2 -8
  422. scipy/sparse/linalg/_matfuncs.py +1 -1
  423. scipy/sparse/linalg/_norm.py +1 -1
  424. scipy/sparse/linalg/_propack/_cpropack.cpython-312-darwin.so +0 -0
  425. scipy/sparse/linalg/_propack/_dpropack.cpython-312-darwin.so +0 -0
  426. scipy/sparse/linalg/_propack/_spropack.cpython-312-darwin.so +0 -0
  427. scipy/sparse/linalg/_propack/_zpropack.cpython-312-darwin.so +0 -0
  428. scipy/sparse/linalg/_special_sparse_arrays.py +39 -38
  429. scipy/sparse/linalg/tests/test_pydata_sparse.py +14 -0
  430. scipy/sparse/tests/test_arithmetic1d.py +5 -2
  431. scipy/sparse/tests/test_base.py +214 -42
  432. scipy/sparse/tests/test_common1d.py +7 -7
  433. scipy/sparse/tests/test_construct.py +1 -1
  434. scipy/sparse/tests/test_coo.py +272 -4
  435. scipy/sparse/tests/test_sparsetools.py +5 -0
  436. scipy/sparse/tests/test_sputils.py +36 -7
  437. scipy/spatial/_ckdtree.cpython-312-darwin.so +0 -0
  438. scipy/spatial/_distance_pybind.cpython-312-darwin.so +0 -0
  439. scipy/spatial/_distance_wrap.cpython-312-darwin.so +0 -0
  440. scipy/spatial/_hausdorff.cpython-312-darwin.so +0 -0
  441. scipy/spatial/_qhull.cpython-312-darwin.so +0 -0
  442. scipy/spatial/_voronoi.cpython-312-darwin.so +0 -0
  443. scipy/spatial/distance.py +49 -42
  444. scipy/spatial/tests/test_distance.py +15 -1
  445. scipy/spatial/tests/test_kdtree.py +1 -0
  446. scipy/spatial/tests/test_qhull.py +7 -2
  447. scipy/spatial/transform/__init__.py +5 -3
  448. scipy/spatial/transform/_rigid_transform.cpython-312-darwin.so +0 -0
  449. scipy/spatial/transform/_rotation.cpython-312-darwin.so +0 -0
  450. scipy/spatial/transform/tests/test_rigid_transform.py +1221 -0
  451. scipy/spatial/transform/tests/test_rotation.py +1213 -832
  452. scipy/spatial/transform/tests/test_rotation_groups.py +3 -3
  453. scipy/spatial/transform/tests/test_rotation_spline.py +29 -8
  454. scipy/special/__init__.py +1 -47
  455. scipy/special/_add_newdocs.py +34 -772
  456. scipy/special/_basic.py +22 -25
  457. scipy/special/_comb.cpython-312-darwin.so +0 -0
  458. scipy/special/_ellip_harm_2.cpython-312-darwin.so +0 -0
  459. scipy/special/_gufuncs.cpython-312-darwin.so +0 -0
  460. scipy/special/_logsumexp.py +67 -58
  461. scipy/special/_orthogonal.pyi +1 -1
  462. scipy/special/_specfun.cpython-312-darwin.so +0 -0
  463. scipy/special/_special_ufuncs.cpython-312-darwin.so +0 -0
  464. scipy/special/_spherical_bessel.py +4 -4
  465. scipy/special/_support_alternative_backends.py +212 -119
  466. scipy/special/_test_internal.cpython-312-darwin.so +0 -0
  467. scipy/special/_testutils.py +4 -4
  468. scipy/special/_ufuncs.cpython-312-darwin.so +0 -0
  469. scipy/special/_ufuncs.pyi +1 -0
  470. scipy/special/_ufuncs.pyx +215 -1400
  471. scipy/special/_ufuncs_cxx.cpython-312-darwin.so +0 -0
  472. scipy/special/_ufuncs_cxx.pxd +2 -15
  473. scipy/special/_ufuncs_cxx.pyx +5 -44
  474. scipy/special/_ufuncs_cxx_defs.h +2 -16
  475. scipy/special/_ufuncs_defs.h +0 -8
  476. scipy/special/cython_special.cpython-312-darwin.so +0 -0
  477. scipy/special/cython_special.pxd +1 -1
  478. scipy/special/tests/_cython_examples/meson.build +10 -1
  479. scipy/special/tests/test_basic.py +153 -20
  480. scipy/special/tests/test_boost_ufuncs.py +3 -0
  481. scipy/special/tests/test_cdflib.py +35 -11
  482. scipy/special/tests/test_gammainc.py +16 -0
  483. scipy/special/tests/test_hyp2f1.py +2 -2
  484. scipy/special/tests/test_log1mexp.py +85 -0
  485. scipy/special/tests/test_logsumexp.py +206 -64
  486. scipy/special/tests/test_mpmath.py +1 -0
  487. scipy/special/tests/test_nan_inputs.py +1 -1
  488. scipy/special/tests/test_orthogonal.py +17 -18
  489. scipy/special/tests/test_sf_error.py +3 -2
  490. scipy/special/tests/test_sph_harm.py +6 -7
  491. scipy/special/tests/test_support_alternative_backends.py +211 -76
  492. scipy/stats/__init__.py +4 -1
  493. scipy/stats/_ansari_swilk_statistics.cpython-312-darwin.so +0 -0
  494. scipy/stats/_axis_nan_policy.py +5 -12
  495. scipy/stats/_biasedurn.cpython-312-darwin.so +0 -0
  496. scipy/stats/_continued_fraction.py +387 -0
  497. scipy/stats/_continuous_distns.py +277 -310
  498. scipy/stats/_correlation.py +1 -1
  499. scipy/stats/_covariance.py +6 -3
  500. scipy/stats/_discrete_distns.py +39 -32
  501. scipy/stats/_distn_infrastructure.py +39 -12
  502. scipy/stats/_distribution_infrastructure.py +900 -238
  503. scipy/stats/_entropy.py +9 -10
  504. scipy/{_lib → stats}/_finite_differences.py +1 -1
  505. scipy/stats/_hypotests.py +83 -50
  506. scipy/stats/_kde.py +53 -49
  507. scipy/stats/_ksstats.py +1 -1
  508. scipy/stats/_levy_stable/__init__.py +7 -15
  509. scipy/stats/_levy_stable/levyst.cpython-312-darwin.so +0 -0
  510. scipy/stats/_morestats.py +118 -73
  511. scipy/stats/_mstats_basic.py +13 -17
  512. scipy/stats/_mstats_extras.py +8 -8
  513. scipy/stats/_multivariate.py +89 -113
  514. scipy/stats/_new_distributions.py +97 -20
  515. scipy/stats/_page_trend_test.py +12 -5
  516. scipy/stats/_probability_distribution.py +265 -43
  517. scipy/stats/_qmc.py +14 -9
  518. scipy/stats/_qmc_cy.cpython-312-darwin.so +0 -0
  519. scipy/stats/_qmvnt.py +16 -95
  520. scipy/stats/_qmvnt_cy.cpython-312-darwin.so +0 -0
  521. scipy/stats/_quantile.py +335 -0
  522. scipy/stats/_rcont/rcont.cpython-312-darwin.so +0 -0
  523. scipy/stats/_resampling.py +4 -29
  524. scipy/stats/_sampling.py +1 -1
  525. scipy/stats/_sobol.cpython-312-darwin.so +0 -0
  526. scipy/stats/_stats.cpython-312-darwin.so +0 -0
  527. scipy/stats/_stats_mstats_common.py +21 -2
  528. scipy/stats/_stats_py.py +550 -476
  529. scipy/stats/_stats_pythran.cpython-312-darwin.so +0 -0
  530. scipy/stats/_unuran/unuran_wrapper.cpython-312-darwin.so +0 -0
  531. scipy/stats/_unuran/unuran_wrapper.pyi +2 -1
  532. scipy/stats/_variation.py +6 -8
  533. scipy/stats/_wilcoxon.py +13 -7
  534. scipy/stats/tests/common_tests.py +6 -4
  535. scipy/stats/tests/test_axis_nan_policy.py +62 -24
  536. scipy/stats/tests/test_continued_fraction.py +173 -0
  537. scipy/stats/tests/test_continuous.py +379 -60
  538. scipy/stats/tests/test_continuous_basic.py +18 -12
  539. scipy/stats/tests/test_discrete_basic.py +14 -8
  540. scipy/stats/tests/test_discrete_distns.py +16 -16
  541. scipy/stats/tests/test_distributions.py +95 -75
  542. scipy/stats/tests/test_entropy.py +40 -48
  543. scipy/stats/tests/test_fit.py +4 -3
  544. scipy/stats/tests/test_hypotests.py +153 -24
  545. scipy/stats/tests/test_kdeoth.py +109 -41
  546. scipy/stats/tests/test_marray.py +289 -0
  547. scipy/stats/tests/test_morestats.py +79 -47
  548. scipy/stats/tests/test_mstats_basic.py +3 -3
  549. scipy/stats/tests/test_multivariate.py +434 -83
  550. scipy/stats/tests/test_qmc.py +13 -10
  551. scipy/stats/tests/test_quantile.py +199 -0
  552. scipy/stats/tests/test_rank.py +119 -112
  553. scipy/stats/tests/test_resampling.py +47 -56
  554. scipy/stats/tests/test_sampling.py +9 -4
  555. scipy/stats/tests/test_stats.py +799 -939
  556. scipy/stats/tests/test_variation.py +8 -6
  557. scipy/version.py +2 -2
  558. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/LICENSE.txt +4 -4
  559. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/METADATA +11 -11
  560. {scipy-1.15.3.dist-info → scipy-1.16.0rc2.dist-info}/RECORD +561 -568
  561. scipy-1.16.0rc2.dist-info/WHEEL +6 -0
  562. scipy/_lib/array_api_extra/_funcs.py +0 -484
  563. scipy/_lib/array_api_extra/_typing.py +0 -8
  564. scipy/interpolate/_bspl.cpython-312-darwin.so +0 -0
  565. scipy/optimize/_cobyla.cpython-312-darwin.so +0 -0
  566. scipy/optimize/_cython_nnls.cpython-312-darwin.so +0 -0
  567. scipy/optimize/_slsqp.cpython-312-darwin.so +0 -0
  568. scipy/spatial/qhull_src/COPYING.txt +0 -38
  569. scipy/special/libsf_error_state.dylib +0 -0
  570. scipy/special/tests/test_log_softmax.py +0 -109
  571. scipy/special/tests/test_xsf_cuda.py +0 -114
  572. scipy/special/xsf/binom.h +0 -89
  573. scipy/special/xsf/cdflib.h +0 -100
  574. scipy/special/xsf/cephes/airy.h +0 -307
  575. scipy/special/xsf/cephes/besselpoly.h +0 -51
  576. scipy/special/xsf/cephes/beta.h +0 -257
  577. scipy/special/xsf/cephes/cbrt.h +0 -131
  578. scipy/special/xsf/cephes/chbevl.h +0 -85
  579. scipy/special/xsf/cephes/chdtr.h +0 -193
  580. scipy/special/xsf/cephes/const.h +0 -87
  581. scipy/special/xsf/cephes/ellie.h +0 -293
  582. scipy/special/xsf/cephes/ellik.h +0 -251
  583. scipy/special/xsf/cephes/ellpe.h +0 -107
  584. scipy/special/xsf/cephes/ellpk.h +0 -117
  585. scipy/special/xsf/cephes/expn.h +0 -260
  586. scipy/special/xsf/cephes/gamma.h +0 -398
  587. scipy/special/xsf/cephes/hyp2f1.h +0 -596
  588. scipy/special/xsf/cephes/hyperg.h +0 -361
  589. scipy/special/xsf/cephes/i0.h +0 -149
  590. scipy/special/xsf/cephes/i1.h +0 -158
  591. scipy/special/xsf/cephes/igam.h +0 -421
  592. scipy/special/xsf/cephes/igam_asymp_coeff.h +0 -195
  593. scipy/special/xsf/cephes/igami.h +0 -313
  594. scipy/special/xsf/cephes/j0.h +0 -225
  595. scipy/special/xsf/cephes/j1.h +0 -198
  596. scipy/special/xsf/cephes/jv.h +0 -715
  597. scipy/special/xsf/cephes/k0.h +0 -164
  598. scipy/special/xsf/cephes/k1.h +0 -163
  599. scipy/special/xsf/cephes/kn.h +0 -243
  600. scipy/special/xsf/cephes/lanczos.h +0 -112
  601. scipy/special/xsf/cephes/ndtr.h +0 -275
  602. scipy/special/xsf/cephes/poch.h +0 -85
  603. scipy/special/xsf/cephes/polevl.h +0 -167
  604. scipy/special/xsf/cephes/psi.h +0 -194
  605. scipy/special/xsf/cephes/rgamma.h +0 -111
  606. scipy/special/xsf/cephes/scipy_iv.h +0 -811
  607. scipy/special/xsf/cephes/shichi.h +0 -248
  608. scipy/special/xsf/cephes/sici.h +0 -224
  609. scipy/special/xsf/cephes/sindg.h +0 -221
  610. scipy/special/xsf/cephes/tandg.h +0 -139
  611. scipy/special/xsf/cephes/trig.h +0 -58
  612. scipy/special/xsf/cephes/unity.h +0 -186
  613. scipy/special/xsf/cephes/zeta.h +0 -172
  614. scipy/special/xsf/config.h +0 -304
  615. scipy/special/xsf/digamma.h +0 -205
  616. scipy/special/xsf/error.h +0 -57
  617. scipy/special/xsf/evalpoly.h +0 -47
  618. scipy/special/xsf/expint.h +0 -266
  619. scipy/special/xsf/hyp2f1.h +0 -694
  620. scipy/special/xsf/iv_ratio.h +0 -173
  621. scipy/special/xsf/lambertw.h +0 -150
  622. scipy/special/xsf/loggamma.h +0 -163
  623. scipy/special/xsf/sici.h +0 -200
  624. scipy/special/xsf/tools.h +0 -427
  625. scipy/special/xsf/trig.h +0 -164
  626. scipy/special/xsf/wright_bessel.h +0 -843
  627. scipy/special/xsf/zlog1.h +0 -35
  628. scipy/stats/_mvn.cpython-312-darwin.so +0 -0
  629. scipy-1.15.3.dist-info/WHEEL +0 -4
@@ -22,7 +22,6 @@ from numpy.testing import (assert_, assert_equal,
22
22
  assert_array_less)
23
23
  import pytest
24
24
  from pytest import raises as assert_raises
25
- import numpy.ma.testutils as mat
26
25
  from numpy import array, arange, float32, power
27
26
  import numpy as np
28
27
 
@@ -40,11 +39,14 @@ from scipy.stats._axis_nan_policy import (_broadcast_concatenate, SmallSampleWar
40
39
  from scipy.stats._stats_py import (_permutation_distribution_t, _chk_asarray, _moment,
41
40
  LinregressResult, _xp_mean, _xp_var, _SimpleChi2)
42
41
  from scipy._lib._util import AxisError
43
- from scipy.conftest import array_api_compatible, skip_xp_invalid_arg
44
- from scipy._lib._array_api import (array_namespace, xp_copy, is_numpy,
45
- is_torch, xp_size, SCIPY_ARRAY_API)
42
+ from scipy.conftest import skip_xp_invalid_arg
43
+ from scipy._lib._array_api import (array_namespace, eager_warns, is_lazy_array,
44
+ is_numpy, is_torch, xp_default_dtype, xp_size,
45
+ SCIPY_ARRAY_API, make_xp_test_case)
46
46
  from scipy._lib._array_api_no_0d import xp_assert_close, xp_assert_equal
47
+ import scipy._lib.array_api_extra as xpx
47
48
 
49
+ lazy_xp_modules = [stats]
48
50
  skip_xp_backends = pytest.mark.skip_xp_backends
49
51
 
50
52
 
@@ -73,19 +75,14 @@ TINY = array([1e-12,2e-12,3e-12,4e-12,5e-12,6e-12,7e-12,8e-12,9e-12], float)
73
75
  ROUND = array([0.5,1.5,2.5,3.5,4.5,5.5,6.5,7.5,8.5], float)
74
76
 
75
77
 
76
- @array_api_compatible
77
- @skip_xp_backends('jax.numpy', reason="JAX doesn't allow item assignment.")
78
- @skip_xp_backends('array_api_strict',
79
- reason=("`array_api_strict.where` `fillvalue` doesn't "
80
- "accept Python floats. See data-apis/array-api#807.")
81
- )
82
- @pytest.mark.usefixtures("skip_xp_backends")
83
78
  class TestTrimmedStats:
84
79
  # TODO: write these tests to handle missing values properly
85
80
  dprec = np.finfo(np.float64).precision
86
81
 
82
+ @make_xp_test_case(stats.tmean)
87
83
  def test_tmean(self, xp):
88
- x = xp.asarray(X.tolist()) # use default dtype of xp
84
+ default_dtype = xp_default_dtype(xp)
85
+ x = xp.asarray(X, dtype=default_dtype)
89
86
 
90
87
  y = stats.tmean(x, (2, 8), (True, True))
91
88
  xp_assert_close(y, xp.asarray(5.0))
@@ -115,8 +112,7 @@ class TestTrimmedStats:
115
112
  y_true = [10.5, 11.5, 9, 10, 11, 12, 13]
116
113
  xp_assert_close(y, xp.asarray(y_true))
117
114
 
118
- x_2d_with_nan = xp_copy(x_2d)
119
- x_2d_with_nan[-1, -3:] = xp.nan
115
+ x_2d_with_nan = xpx.at(x_2d)[-1, -3:].set(xp.nan, copy=True)
120
116
  y = stats.tmean(x_2d_with_nan, limits=(1, 13), axis=0)
121
117
  y_true = [7, 4.5, 5.5, 6.5, xp.nan, xp.nan, xp.nan]
122
118
  xp_assert_close(y, xp.asarray(y_true))
@@ -130,21 +126,22 @@ class TestTrimmedStats:
130
126
  y_true = [4.5, 10, 17, 21, xp.nan, xp.nan, xp.nan, xp.nan, xp.nan]
131
127
  xp_assert_close(y, xp.asarray(y_true))
132
128
 
133
- @skip_xp_backends('cupy',
134
- reason="cupy/cupy#8391")
129
+ @make_xp_test_case(stats.tvar)
130
+ @pytest.mark.filterwarnings(
131
+ "ignore:invalid value encountered in divide:RuntimeWarning:dask"
132
+ )
135
133
  def test_tvar(self, xp):
136
134
  x = xp.asarray(X.tolist()) # use default dtype of xp
137
- xp_test = array_namespace(x) # need array-api-compat var for `correction`
138
135
 
139
136
  y = stats.tvar(x, limits=(2, 8), inclusive=(True, True))
140
137
  xp_assert_close(y, xp.asarray(4.6666666666666661))
141
138
 
142
139
  y = stats.tvar(x, limits=None)
143
- xp_assert_close(y, xp_test.var(x, correction=1))
140
+ xp_assert_close(y, xp.var(x, correction=1))
144
141
 
145
142
  x_2d = xp.reshape(xp.arange(63.), (9, 7))
146
143
  y = stats.tvar(x_2d, axis=None)
147
- xp_assert_close(y, xp_test.var(x_2d, correction=1))
144
+ xp_assert_close(y, xp.var(x_2d, correction=1))
148
145
 
149
146
  y = stats.tvar(x_2d, axis=0)
150
147
  xp_assert_close(y, xp.full((7,), 367.5))
@@ -161,35 +158,35 @@ class TestTrimmedStats:
161
158
  xp_assert_close(y[0], xp.asarray(4.666666666666667))
162
159
  xp_assert_equal(y[1], xp.asarray(xp.nan))
163
160
 
161
+ @make_xp_test_case(stats.tstd)
164
162
  def test_tstd(self, xp):
165
163
  x = xp.asarray(X.tolist()) # use default dtype of xp
166
- xp_test = array_namespace(x) # need array-api-compat std for `correction`
167
164
 
168
165
  y = stats.tstd(x, (2, 8), (True, True))
169
166
  xp_assert_close(y, xp.asarray(2.1602468994692865))
170
167
 
171
168
  y = stats.tstd(x, limits=None)
172
- xp_assert_close(y, xp_test.std(x, correction=1))
169
+ xp_assert_close(y, xp.std(x, correction=1))
173
170
 
171
+ @make_xp_test_case(stats.tmin)
174
172
  def test_tmin(self, xp):
175
- x = xp.arange(10)
176
- xp_assert_equal(stats.tmin(x), xp.asarray(0))
177
- xp_assert_equal(stats.tmin(x, lowerlimit=0), xp.asarray(0))
178
- xp_assert_equal(stats.tmin(x, lowerlimit=0, inclusive=False), xp.asarray(1))
173
+ x = xp.arange(10.)
174
+ xp_assert_equal(stats.tmin(x), xp.asarray(0.))
175
+ xp_assert_equal(stats.tmin(x, lowerlimit=0), xp.asarray(0.))
176
+ xp_assert_equal(stats.tmin(x, lowerlimit=0, inclusive=False), xp.asarray(1.))
179
177
 
180
178
  x = xp.reshape(x, (5, 2))
181
179
  xp_assert_equal(stats.tmin(x, lowerlimit=0, inclusive=False),
182
- xp.asarray([2, 1]))
183
- xp_assert_equal(stats.tmin(x, axis=1), xp.asarray([0, 2, 4, 6, 8]))
184
- xp_assert_equal(stats.tmin(x, axis=None), xp.asarray(0))
180
+ xp.asarray([2., 1.]))
181
+ xp_assert_equal(stats.tmin(x, axis=1), xp.asarray([0., 2., 4., 6., 8.]))
182
+ xp_assert_equal(stats.tmin(x, axis=None), xp.asarray(0.))
185
183
 
186
- x = xp.arange(10.)
187
- x[9] = xp.nan
184
+ x = xpx.at(xp.arange(10.), 9).set(xp.nan)
188
185
  xp_assert_equal(stats.tmin(x), xp.asarray(xp.nan))
189
186
 
190
187
  # check that if a full slice is masked, the output returns a
191
188
  # nan instead of a garbage value.
192
- x = xp.arange(16).reshape(4, 4)
189
+ x = xp.reshape(xp.arange(16), (4, 4))
193
190
  res = stats.tmin(x, lowerlimit=4, axis=1)
194
191
  xp_assert_equal(res, xp.asarray([np.nan, 4, 8, 12]))
195
192
 
@@ -210,20 +207,20 @@ class TestTrimmedStats:
210
207
  with assert_raises(ValueError, match=msg):
211
208
  stats.tmin(x, nan_policy='foobar')
212
209
 
210
+ @make_xp_test_case(stats.tmax)
213
211
  def test_tmax(self, xp):
214
- x = xp.arange(10)
215
- xp_assert_equal(stats.tmax(x), xp.asarray(9))
216
- xp_assert_equal(stats.tmax(x, upperlimit=9), xp.asarray(9))
217
- xp_assert_equal(stats.tmax(x, upperlimit=9, inclusive=False), xp.asarray(8))
212
+ x = xp.arange(10.)
213
+ xp_assert_equal(stats.tmax(x), xp.asarray(9.))
214
+ xp_assert_equal(stats.tmax(x, upperlimit=9), xp.asarray(9.))
215
+ xp_assert_equal(stats.tmax(x, upperlimit=9, inclusive=False), xp.asarray(8.))
218
216
 
219
217
  x = xp.reshape(x, (5, 2))
220
218
  xp_assert_equal(stats.tmax(x, upperlimit=9, inclusive=False),
221
- xp.asarray([8, 7]))
222
- xp_assert_equal(stats.tmax(x, axis=1), xp.asarray([1, 3, 5, 7, 9]))
223
- xp_assert_equal(stats.tmax(x, axis=None), xp.asarray(9))
219
+ xp.asarray([8., 7.]))
220
+ xp_assert_equal(stats.tmax(x, axis=1), xp.asarray([1., 3., 5., 7., 9.]))
221
+ xp_assert_equal(stats.tmax(x, axis=None), xp.asarray(9.))
224
222
 
225
- x = xp.arange(10.)
226
- x[9] = xp.nan
223
+ x = xpx.at(xp.arange(10.), 9).set(xp.nan)
227
224
  xp_assert_equal(stats.tmax(x), xp.asarray(xp.nan))
228
225
 
229
226
  # check that if a full slice is masked, the output returns a
@@ -236,7 +233,7 @@ class TestTrimmedStats:
236
233
 
237
234
  @skip_xp_backends(np_only=True,
238
235
  reason="Only NumPy arrays support scalar input/`nan_policy`.")
239
- def test_tax_scalar_and_nanpolicy(self, xp):
236
+ def test_tmax_scalar_and_nanpolicy(self, xp):
240
237
  assert_equal(stats.tmax(4), 4)
241
238
 
242
239
  x = np.arange(10.)
@@ -251,13 +248,36 @@ class TestTrimmedStats:
251
248
  with assert_raises(ValueError, match=msg):
252
249
  stats.tmax(x, nan_policy='foobar')
253
250
 
251
+ @make_xp_test_case(stats.tmin, stats.tmax)
252
+ def test_tmin_tmax_int_dtype(self, xp):
253
+ x = xp.reshape(xp.arange(10, dtype=xp.int16), (2, 5)).T
254
+
255
+ # When tmin/tmax don't need to inject any NaNs,
256
+ # retain the input dtype. Dask/JAX can't inspect
257
+ # the data so they always return float.
258
+ expect_dtype = xp_default_dtype(xp) if is_lazy_array(x) else x.dtype
259
+ xp_assert_equal(stats.tmin(x), xp.asarray([0, 5], dtype=expect_dtype))
260
+ xp_assert_equal(stats.tmax(x), xp.asarray([4, 9], dtype=expect_dtype))
261
+
262
+ # When they do inject NaNs, all backends behave the same.
263
+ xp_assert_equal(stats.tmin(x, lowerlimit=6), xp.asarray([xp.nan, 6.]))
264
+ xp_assert_equal(stats.tmax(x, upperlimit=3), xp.asarray([3., xp.nan]))
265
+
266
+ @skip_xp_backends(eager_only=True, reason="Only with data-dependent output dtype")
267
+ @make_xp_test_case(stats.tmin, stats.tmax)
268
+ def test_gh_22626(self, xp):
269
+ # Test that `tmin`/`tmax` returns exact result with outrageously large integers
270
+ x = xp.arange(2**62, 2**62+10)
271
+ xp_assert_equal(stats.tmin(x[None, :]), x)
272
+ xp_assert_equal(stats.tmax(x[None, :]), x)
273
+
274
+ @make_xp_test_case(stats.tsem)
254
275
  def test_tsem(self, xp):
255
276
  x = xp.asarray(X.tolist()) # use default dtype of xp
256
- xp_test = array_namespace(x) # need array-api-compat std for `correction`
257
277
 
258
278
  y = stats.tsem(x, limits=(3, 8), inclusive=(False, True))
259
279
  y_ref = xp.asarray([4., 5., 6., 7., 8.])
260
- xp_assert_close(y, xp_test.std(y_ref, correction=1) / xp_size(y_ref)**0.5)
280
+ xp_assert_close(y, xp.std(y_ref, correction=1) / xp_size(y_ref)**0.5)
261
281
  xp_assert_close(stats.tsem(x, limits=[-1, 10]), stats.tsem(x, limits=None))
262
282
 
263
283
 
@@ -376,19 +396,14 @@ class TestPearsonrWilkinson:
376
396
  assert_approx_equal(r,1.0)
377
397
 
378
398
 
379
- @array_api_compatible
380
- @pytest.mark.usefixtures("skip_xp_backends")
381
- @skip_xp_backends(cpu_only=True)
399
+ @make_xp_test_case(stats.pearsonr)
382
400
  class TestPearsonr:
383
- @skip_xp_backends(np_only=True)
384
401
  def test_pearsonr_result_attributes(self):
385
402
  res = stats.pearsonr(X, X)
386
403
  attributes = ('correlation', 'pvalue')
387
404
  check_named_results(res, attributes)
388
405
  assert_equal(res.correlation, res.statistic)
389
406
 
390
- @skip_xp_backends('jax.numpy',
391
- reason='JAX arrays do not support item assignment')
392
407
  def test_r_almost_exactly_pos1(self, xp):
393
408
  a = xp.arange(3.0)
394
409
  r, prob = stats.pearsonr(a, a)
@@ -398,8 +413,6 @@ class TestPearsonr:
398
413
  # square root of the error in r.
399
414
  xp_assert_close(prob, xp.asarray(0.0), atol=np.sqrt(2*np.spacing(1.0)))
400
415
 
401
- @skip_xp_backends('jax.numpy',
402
- reason='JAX arrays do not support item assignment')
403
416
  def test_r_almost_exactly_neg1(self, xp):
404
417
  a = xp.arange(3.0)
405
418
  r, prob = stats.pearsonr(a, -a)
@@ -409,8 +422,6 @@ class TestPearsonr:
409
422
  # square root of the error in r.
410
423
  xp_assert_close(prob, xp.asarray(0.0), atol=np.sqrt(2*np.spacing(1.0)))
411
424
 
412
- @skip_xp_backends('jax.numpy',
413
- reason='JAX arrays do not support item assignment')
414
425
  def test_basic(self, xp):
415
426
  # A basic test, with a correlation coefficient
416
427
  # that is not 1 or -1.
@@ -420,9 +431,6 @@ class TestPearsonr:
420
431
  xp_assert_close(r, xp.asarray(3**0.5/2))
421
432
  xp_assert_close(prob, xp.asarray(1/3))
422
433
 
423
- @skip_xp_backends('jax.numpy',
424
- reason='JAX arrays do not support item assignment',
425
- )
426
434
  def test_constant_input(self, xp):
427
435
  # Zero variance input
428
436
  # See https://github.com/scipy/scipy/issues/3728
@@ -434,9 +442,6 @@ class TestPearsonr:
434
442
  xp_assert_close(r, xp.asarray(xp.nan))
435
443
  xp_assert_close(p, xp.asarray(xp.nan))
436
444
 
437
- @skip_xp_backends('jax.numpy',
438
- reason='JAX arrays do not support item assignment',
439
- )
440
445
  @pytest.mark.parametrize('dtype', ['float32', 'float64'])
441
446
  def test_near_constant_input(self, xp, dtype):
442
447
  npdtype = getattr(np, dtype)
@@ -450,9 +455,6 @@ class TestPearsonr:
450
455
  # (The exact value of r would be 1.)
451
456
  stats.pearsonr(x, y)
452
457
 
453
- @skip_xp_backends('jax.numpy',
454
- reason='JAX arrays do not support item assignment',
455
- )
456
458
  def test_very_small_input_values(self, xp):
457
459
  # Very small values in an input. A naive implementation will
458
460
  # suffer from underflow.
@@ -468,9 +470,6 @@ class TestPearsonr:
468
470
  xp_assert_close(r, xp.asarray(0.7272930540750450, dtype=xp.float64))
469
471
  xp_assert_close(p, xp.asarray(0.1637805429533202, dtype=xp.float64))
470
472
 
471
- @skip_xp_backends('jax.numpy',
472
- reason='JAX arrays do not support item assignment',
473
- )
474
473
  def test_very_large_input_values(self, xp):
475
474
  # Very large values in an input. A naive implementation will
476
475
  # suffer from overflow.
@@ -485,8 +484,6 @@ class TestPearsonr:
485
484
  xp_assert_close(r, xp.asarray(0.8660254037844386, dtype=xp.float64))
486
485
  xp_assert_close(p, xp.asarray(0.011724811003954638, dtype=xp.float64))
487
486
 
488
- @skip_xp_backends('jax.numpy',
489
- reason='JAX arrays do not support item assignment')
490
487
  def test_extremely_large_input_values(self, xp):
491
488
  # Extremely large values in x and y. These values would cause the
492
489
  # product sigma_x * sigma_y to overflow if the two factors were
@@ -500,7 +497,6 @@ class TestPearsonr:
500
497
  xp_assert_close(r, xp.asarray(0.351312332103289, dtype=xp.float64))
501
498
  xp_assert_close(p, xp.asarray(0.648687667896711, dtype=xp.float64))
502
499
 
503
- @skip_xp_backends('jax.numpy', reason="JAX doesn't allow item assignment.")
504
500
  def test_length_two_pos1(self, xp):
505
501
  # Inputs with length 2.
506
502
  # See https://github.com/scipy/scipy/issues/7730
@@ -515,7 +511,6 @@ class TestPearsonr:
515
511
  xp_assert_equal(low, -one)
516
512
  xp_assert_equal(high, one)
517
513
 
518
- @skip_xp_backends('jax.numpy', reason="JAX doesn't allow item assignment.")
519
514
  def test_length_two_neg1(self, xp):
520
515
  # Inputs with length 2.
521
516
  # See https://github.com/scipy/scipy/issues/7730
@@ -530,7 +525,7 @@ class TestPearsonr:
530
525
  xp_assert_equal(low, -one)
531
526
  xp_assert_equal(high, one)
532
527
 
533
- @skip_xp_backends('jax.numpy', reason="JAX doesn't allow item assignment.")
528
+ @pytest.mark.filterwarnings("ignore:invalid value encountered in divide")
534
529
  def test_length_two_constant_input(self, xp):
535
530
  # Zero variance input
536
531
  # See https://github.com/scipy/scipy/issues/3728
@@ -550,7 +545,7 @@ class TestPearsonr:
550
545
  # cor.test(x, y, method = "pearson", alternative = "g")
551
546
  # correlation coefficient and p-value for alternative='two-sided'
552
547
  # calculated with mpmath agree to 16 digits.
553
- @pytest.mark.skip_xp_backends(np_only=True)
548
+ @skip_xp_backends(np_only=True)
554
549
  @pytest.mark.parametrize('alternative, pval, rlow, rhigh, sign',
555
550
  [('two-sided', 0.325800137536, -0.814938968841, 0.99230697523, 1),
556
551
  ('less', 0.8370999312316, -1, 0.985600937290653, 1),
@@ -558,7 +553,7 @@ class TestPearsonr:
558
553
  ('two-sided', 0.325800137536, -0.992306975236, 0.81493896884, -1),
559
554
  ('less', 0.1629000687684, -1.0, 0.6785654158217636, -1),
560
555
  ('greater', 0.8370999312316, -0.985600937290653, 1.0, -1)])
561
- def test_basic_example(self, alternative, pval, rlow, rhigh, sign):
556
+ def test_basic_example(self, alternative, pval, rlow, rhigh, sign, xp):
562
557
  x = [1, 2, 3, 4]
563
558
  y = np.array([0, 1, 0.5, 1]) * sign
564
559
  result = stats.pearsonr(x, y, alternative=alternative)
@@ -567,8 +562,6 @@ class TestPearsonr:
567
562
  ci = result.confidence_interval()
568
563
  assert_allclose(ci, (rlow, rhigh), rtol=1e-6)
569
564
 
570
- @skip_xp_backends('jax.numpy',
571
- reason='JAX arrays do not support item assignment')
572
565
  def test_negative_correlation_pvalue_gh17795(self, xp):
573
566
  x = xp.arange(10.)
574
567
  y = -x
@@ -577,8 +570,6 @@ class TestPearsonr:
577
570
  xp_assert_close(test_greater.pvalue, xp.asarray(1.))
578
571
  xp_assert_close(test_less.pvalue, xp.asarray(0.), atol=1e-20)
579
572
 
580
- @skip_xp_backends('jax.numpy',
581
- reason='JAX arrays do not support item assignment')
582
573
  def test_length3_r_exactly_negative_one(self, xp):
583
574
  x = xp.asarray([1., 2., 3.])
584
575
  y = xp.asarray([5., -4., -13.])
@@ -593,14 +584,20 @@ class TestPearsonr:
593
584
  xp_assert_equal(low, -one)
594
585
  xp_assert_equal(high, one)
595
586
 
596
- @pytest.mark.skip_xp_backends(np_only=True)
597
587
  def test_input_validation(self):
588
+ # Arraylike is np only
598
589
  x = [1, 2, 3]
599
- y = [4, 5]
590
+ y = [4]
600
591
  message = '`x` and `y` must have the same length along `axis`.'
601
592
  with pytest.raises(ValueError, match=message):
602
593
  stats.pearsonr(x, y)
603
594
 
595
+ x = [1, 2, 3]
596
+ y = [4, 5]
597
+ message = '`x` and `y` must be broadcastable.'
598
+ with pytest.raises(ValueError, match=message):
599
+ stats.pearsonr(x, y)
600
+
604
601
  x = [1]
605
602
  y = [2]
606
603
  message = '`x` and `y` must have length at least 2.'
@@ -622,7 +619,6 @@ class TestPearsonr:
622
619
  res.confidence_interval(method="exact")
623
620
 
624
621
  @pytest.mark.fail_slow(10)
625
- @pytest.mark.skip_xp_backends(np_only=True)
626
622
  @pytest.mark.xfail_on_32bit("Monte Carlo method needs > a few kB of memory")
627
623
  @pytest.mark.parametrize('alternative', ('less', 'greater', 'two-sided'))
628
624
  @pytest.mark.parametrize('method_name',
@@ -647,7 +643,7 @@ class TestPearsonr:
647
643
  assert_equal(res2.statistic, res.statistic)
648
644
  assert_equal(res2.pvalue, res.pvalue)
649
645
 
650
- @pytest.mark.skip_xp_backends(np_only=True)
646
+ @pytest.mark.slow
651
647
  @pytest.mark.parametrize('alternative', ('less', 'greater', 'two-sided'))
652
648
  def test_bootstrap_ci(self, alternative):
653
649
  rng = np.random.default_rng(2462935790378923)
@@ -668,9 +664,8 @@ class TestPearsonr:
668
664
  res_ci2 = res.confidence_interval(method=method)
669
665
  assert_allclose(res_ci2, res_ci)
670
666
 
671
- @pytest.mark.skip_xp_backends(np_only=True)
672
667
  @pytest.mark.parametrize('axis', [0, 1])
673
- def test_axis01(self, axis, xp):
668
+ def test_axis01(self, axis):
674
669
  rng = np.random.default_rng(38572345825)
675
670
  shape = (9, 10)
676
671
  x, y = rng.normal(size=(2,) + shape)
@@ -686,8 +681,7 @@ class TestPearsonr:
686
681
  assert_allclose(ci.low[i], ci_i.low)
687
682
  assert_allclose(ci.high[i], ci_i.high)
688
683
 
689
- @pytest.mark.skip_xp_backends(np_only=True)
690
- def test_axis_None(self, xp):
684
+ def test_axis_None(self):
691
685
  rng = np.random.default_rng(38572345825)
692
686
  shape = (9, 10)
693
687
  x, y = rng.normal(size=(2,) + shape)
@@ -723,33 +717,32 @@ class TestPearsonr:
723
717
  with pytest.raises(ValueError, match=message):
724
718
  stats.pearsonr(x, x, method=stats.PermutationMethod())
725
719
 
726
- @skip_xp_backends('jax.numpy',
727
- reason='JAX arrays do not support item assignment')
720
+ @pytest.mark.filterwarnings("ignore:invalid value encountered in divide")
728
721
  def test_nd_special_cases(self, xp):
729
722
  rng = np.random.default_rng(34989235492245)
730
- x0 = xp.asarray(rng.random((3, 5)))
731
- y0 = xp.asarray(rng.random((3, 5)))
723
+ x0, y0 = rng.random((4, 5)), rng.random((4, 5))
732
724
 
733
725
  message = 'An input array is constant'
734
726
  with pytest.warns(stats.ConstantInputWarning, match=message):
735
- x = xp_copy(x0)
736
- x[0, ...] = 1
737
- res = stats.pearsonr(x, y0, axis=1)
727
+ x0[0, ...], y0[1, ...] = 1, 2
728
+ x, y = xp.asarray(x0), xp.asarray(y0)
729
+ res = stats.pearsonr(x, y, axis=1)
738
730
  ci = res.confidence_interval()
739
- nan = xp.asarray(xp.nan, dtype=xp.float64)
740
- xp_assert_equal(res.statistic[0], nan)
741
- xp_assert_equal(res.pvalue[0], nan)
742
- xp_assert_equal(ci.low[0], nan)
743
- xp_assert_equal(ci.high[0], nan)
744
- assert not xp.any(xp.isnan(res.statistic[1:]))
745
- assert not xp.any(xp.isnan(res.pvalue[1:]))
746
- assert not xp.any(xp.isnan(ci.low[1:]))
747
- assert not xp.any(xp.isnan(ci.high[1:]))
731
+ nans = xp.asarray([xp.nan, xp.nan], dtype=xp.float64)
732
+ xp_assert_equal(res.statistic[0:2], nans)
733
+ xp_assert_equal(res.pvalue[0:2], nans)
734
+ xp_assert_equal(ci.low[0:2], nans)
735
+ xp_assert_equal(ci.high[0:2], nans)
736
+ assert xp.all(xp.isfinite(res.statistic[2:]))
737
+ assert xp.all(xp.isfinite(res.pvalue[2:]))
738
+ assert xp.all(xp.isfinite(ci.low[2:]))
739
+ assert xp.all(xp.isfinite(ci.high[2:]))
748
740
 
749
741
  message = 'An input array is nearly constant'
750
742
  with pytest.warns(stats.NearConstantInputWarning, match=message):
751
- x[0, 0] = 1 + 1e-15
752
- stats.pearsonr(x, y0, axis=1)
743
+ x0[0, 0], y0[1, 1] = 1 + 1e-15, 2 + 1e-15
744
+ x, y = xp.asarray(x0), xp.asarray(y0)
745
+ stats.pearsonr(x, y, axis=1)
753
746
 
754
747
  # length 2 along axis
755
748
  x = xp.asarray([[1, 2], [1, 2], [2, 1], [2, 1.]])
@@ -762,8 +755,24 @@ class TestPearsonr:
762
755
  xp_assert_close(ci.low, -ones)
763
756
  xp_assert_close(ci.high, ones)
764
757
 
765
- @skip_xp_backends('jax.numpy',
766
- reason='JAX arrays do not support item assignment')
758
+ def test_different_dimensionality(self, xp):
759
+ # For better or for worse, there is one difference between the broadcasting
760
+ # behavior of most stats functions and NumPy gufuncs / NEP 5: gufuncs `axis`
761
+ # refers to the core dimension *before* prepending `1`s to the array shapes
762
+ # to match dimensionality; SciPy's prepends `1`s first. For instance, in
763
+ # SciPy, `vecdot` would work just like `xp.sum(x * y, axis=axis)`, but this
764
+ # is NOT true of NumPy. The discrepancy only arises when there are multiple
765
+ # arguments with different dimensionality and positive indices are used,
766
+ # which is probably why it hasn't been a problem. There are pros and cons of
767
+ # each convention, and we might want to consider changing our behavior in
768
+ # SciPy 2.0. For now, preserve consistency / backward compatibility.
769
+ rng = np.random.default_rng(45834598265019344)
770
+ x = rng.random((3, 10))
771
+ y = rng.random(10)
772
+ res = stats.pearsonr(x, y, axis=1)
773
+ ref = stats.pearsonr(x, y, axis=-1)
774
+ assert_equal(res.statistic, ref.statistic)
775
+
767
776
  @pytest.mark.parametrize('axis', [0, 1, None])
768
777
  @pytest.mark.parametrize('alternative', ['less', 'greater', 'two-sided'])
769
778
  def test_array_api(self, xp, axis, alternative):
@@ -1276,9 +1285,9 @@ class TestCorrSpearmanr:
1276
1285
  def test_gh_8111(self):
1277
1286
  # Regression test for gh-8111 (different result for float/int/bool).
1278
1287
  n = 100
1279
- np.random.seed(234568)
1280
- x = np.random.rand(n)
1281
- m = np.random.rand(n) > 0.7
1288
+ rng = np.random.RandomState(234568)
1289
+ x = rng.rand(n)
1290
+ m = rng.rand(n) > 0.7
1282
1291
 
1283
1292
  # bool against float, no nans
1284
1293
  a = (x > .5)
@@ -1314,9 +1323,9 @@ class TestCorrSpearmanr2:
1314
1323
  assert_equal(stats.spearmanr([], []), (np.nan, np.nan))
1315
1324
 
1316
1325
  def test_normal_draws(self):
1317
- np.random.seed(7546)
1318
- x = np.array([np.random.normal(loc=1, scale=1, size=500),
1319
- np.random.normal(loc=1, scale=1, size=500)])
1326
+ rng = np.random.RandomState(7546)
1327
+ x = np.array([rng.normal(loc=1, scale=1, size=500),
1328
+ rng.normal(loc=1, scale=1, size=500)])
1320
1329
  corr = [[1.0, 0.3],
1321
1330
  [0.3, 1.0]]
1322
1331
  x = np.dot(np.linalg.cholesky(corr), x)
@@ -1666,12 +1675,13 @@ def test_kendalltau():
1666
1675
  (np.nan, np.nan))
1667
1676
 
1668
1677
  # empty arrays provided as input
1669
- assert_equal(stats.kendalltau([], []), (np.nan, np.nan))
1678
+ with pytest.warns(SmallSampleWarning, match="One or more sample..."):
1679
+ assert_equal(stats.kendalltau([], []), (np.nan, np.nan))
1670
1680
 
1671
1681
  # check with larger arrays
1672
- np.random.seed(7546)
1673
- x = np.array([np.random.normal(loc=1, scale=1, size=500),
1674
- np.random.normal(loc=1, scale=1, size=500)])
1682
+ rng = np.random.RandomState(7546)
1683
+ x = np.array([rng.normal(loc=1, scale=1, size=500),
1684
+ rng.normal(loc=1, scale=1, size=500)])
1675
1685
  corr = [[1.0, 0.3],
1676
1686
  [0.3, 1.0]]
1677
1687
  x = np.dot(np.linalg.cholesky(corr), x)
@@ -1703,10 +1713,8 @@ def test_kendalltau():
1703
1713
  assert_raises(ValueError, stats.kendalltau, x, y)
1704
1714
 
1705
1715
  # test all ties
1706
- tau, p_value = stats.kendalltau([], [])
1707
- assert_equal(np.nan, tau)
1708
- assert_equal(np.nan, p_value)
1709
- tau, p_value = stats.kendalltau([0], [0])
1716
+ with pytest.warns(SmallSampleWarning, match="One or more sample..."):
1717
+ tau, p_value = stats.kendalltau([0], [0])
1710
1718
  assert_equal(np.nan, tau)
1711
1719
  assert_equal(np.nan, p_value)
1712
1720
 
@@ -1715,19 +1723,19 @@ def test_kendalltau():
1715
1723
  x = np.ma.masked_greater(x, 1995)
1716
1724
  y = np.arange(2000, dtype=float)
1717
1725
  y = np.concatenate((y[1000:], y[:1000]))
1718
- assert_(np.isfinite(stats.kendalltau(x,y)[1]))
1726
+ assert_(np.isfinite(stats.mstats.kendalltau(x,y)[1]))
1719
1727
 
1720
1728
 
1721
1729
  def test_kendalltau_vs_mstats_basic():
1722
- np.random.seed(42)
1723
- for s in range(2,10):
1730
+ rng = np.random.RandomState(42)
1731
+ for s in range(3, 10):
1724
1732
  a = []
1725
1733
  # Generate rankings with ties
1726
1734
  for i in range(s):
1727
1735
  a += [i]*i
1728
1736
  b = list(a)
1729
- np.random.shuffle(a)
1730
- np.random.shuffle(b)
1737
+ rng.shuffle(a)
1738
+ rng.shuffle(b)
1731
1739
  expected = mstats_basic.kendalltau(a, b)
1732
1740
  actual = stats.kendalltau(a, b)
1733
1741
  assert_approx_equal(actual[0], expected[0])
@@ -1744,6 +1752,7 @@ def test_kendalltau_nan_2nd_arg():
1744
1752
  assert_allclose(r1.statistic, r2.statistic, atol=1e-15)
1745
1753
 
1746
1754
 
1755
+ @pytest.mark.thread_unsafe
1747
1756
  def test_kendalltau_gh18139_overflow():
1748
1757
  # gh-18139 reported an overflow in `kendalltau` that appeared after
1749
1758
  # SciPy 0.15.1. Check that this particular overflow does not occur.
@@ -1841,7 +1850,8 @@ class TestKendallTauAlternative:
1841
1850
  def test_against_R_n1(self, alternative, p_expected, rev):
1842
1851
  x, y = [1], [2]
1843
1852
  stat_expected = np.nan
1844
- self.exact_test(x, y, alternative, rev, stat_expected, p_expected)
1853
+ with pytest.warns(SmallSampleWarning, match="One or more sample..."):
1854
+ self.exact_test(x, y, alternative, rev, stat_expected, p_expected)
1845
1855
 
1846
1856
  case_R_n2 = (list(zip(alternatives, p_n2, [False]*3))
1847
1857
  + list(zip(alternatives, reversed(p_n2), [True]*3)))
@@ -1908,9 +1918,9 @@ class TestKendallTauAlternative:
1908
1918
 
1909
1919
  @pytest.mark.parametrize("alternative, p_expected, rev", case_R_lt_171b)
1910
1920
  def test_against_R_lt_171b(self, alternative, p_expected, rev):
1911
- np.random.seed(0)
1912
- x = np.random.rand(100)
1913
- y = np.random.rand(100)
1921
+ rng = np.random.RandomState(0)
1922
+ x = rng.rand(100)
1923
+ y = rng.rand(100)
1914
1924
  stat_expected = -0.04686868686868687
1915
1925
  self.exact_test(x, y, alternative, rev, stat_expected, p_expected)
1916
1926
 
@@ -1920,9 +1930,9 @@ class TestKendallTauAlternative:
1920
1930
 
1921
1931
  @pytest.mark.parametrize("alternative, p_expected, rev", case_R_lt_171c)
1922
1932
  def test_against_R_lt_171c(self, alternative, p_expected, rev):
1923
- np.random.seed(0)
1924
- x = np.random.rand(170)
1925
- y = np.random.rand(170)
1933
+ rng = np.random.RandomState(0)
1934
+ x = rng.rand(170)
1935
+ y = rng.rand(170)
1926
1936
  stat_expected = 0.1115906717716673
1927
1937
  self.exact_test(x, y, alternative, rev, stat_expected, p_expected)
1928
1938
 
@@ -1931,9 +1941,9 @@ class TestKendallTauAlternative:
1931
1941
 
1932
1942
  @pytest.mark.parametrize("alternative, rev", case_gt_171)
1933
1943
  def test_gt_171(self, alternative, rev):
1934
- np.random.seed(0)
1935
- x = np.random.rand(400)
1936
- y = np.random.rand(400)
1944
+ rng = np.random.RandomState(0)
1945
+ x = rng.rand(400)
1946
+ y = rng.rand(400)
1937
1947
  res0 = stats.kendalltau(x, y, method='exact',
1938
1948
  alternative=alternative)
1939
1949
  res1 = stats.kendalltau(x, y, method='asymptotic',
@@ -2028,15 +2038,17 @@ def test_weightedtau():
2028
2038
  np.asarray(y, dtype=np.float64))
2029
2039
  assert_approx_equal(tau, -0.56694968153682723)
2030
2040
  # All ties
2031
- tau, p_value = stats.weightedtau([], [])
2041
+ with pytest.warns(SmallSampleWarning, match="One or more sample..."):
2042
+ tau, p_value = stats.weightedtau([], [])
2032
2043
  assert_equal(np.nan, tau)
2033
2044
  assert_equal(np.nan, p_value)
2034
- tau, p_value = stats.weightedtau([0], [0])
2045
+ with pytest.warns(SmallSampleWarning, match="One or more sample..."):
2046
+ tau, p_value = stats.weightedtau([0], [0])
2035
2047
  assert_equal(np.nan, tau)
2036
2048
  assert_equal(np.nan, p_value)
2037
2049
  # Size mismatches
2038
2050
  assert_raises(ValueError, stats.weightedtau, [0, 1], [0, 1, 2])
2039
- assert_raises(ValueError, stats.weightedtau, [0, 1], [0, 1], [0])
2051
+ assert_raises(ValueError, stats.weightedtau, [0, 1], [0, 1], [0, 1, 2])
2040
2052
  # NaNs
2041
2053
  x = [12, 2, 1, 12, 2]
2042
2054
  y = [1, 4, 7, 1, np.nan]
@@ -2070,10 +2082,12 @@ def test_segfault_issue_9710():
2070
2082
  # https://github.com/scipy/scipy/issues/9710
2071
2083
  # This test was created to check segfault
2072
2084
  # In issue SEGFAULT only repros in optimized builds after calling the function twice
2073
- stats.weightedtau([1], [1.0])
2074
- stats.weightedtau([1], [1.0])
2075
- # The code below also caused SEGFAULT
2076
- stats.weightedtau([np.nan], [52])
2085
+ message = "One or more sample arguments is too small"
2086
+ with pytest.warns(SmallSampleWarning, match=message):
2087
+ stats.weightedtau([1], [1.0])
2088
+ stats.weightedtau([1], [1.0])
2089
+ # The code below also caused SEGFAULT
2090
+ stats.weightedtau([np.nan], [52])
2077
2091
 
2078
2092
 
2079
2093
  def test_kendall_tau_large():
@@ -2107,15 +2121,15 @@ def test_weightedtau_vs_quadratic():
2107
2121
  def weigher(x):
2108
2122
  return 1. / (x + 1)
2109
2123
 
2110
- np.random.seed(42)
2124
+ rng = np.random.default_rng(42)
2111
2125
  for s in range(3,10):
2112
2126
  a = []
2113
2127
  # Generate rankings with ties
2114
2128
  for i in range(s):
2115
2129
  a += [i]*i
2116
2130
  b = list(a)
2117
- np.random.shuffle(a)
2118
- np.random.shuffle(b)
2131
+ rng.shuffle(a)
2132
+ rng.shuffle(b)
2119
2133
  # First pass: use element indices as ranks
2120
2134
  rank = np.arange(len(a), dtype=np.intp)
2121
2135
  for _ in range(2):
@@ -2124,7 +2138,7 @@ def test_weightedtau_vs_quadratic():
2124
2138
  actual = stats.weightedtau(a, b, rank, weigher, add).statistic
2125
2139
  assert_approx_equal(expected, actual)
2126
2140
  # Second pass: use a random rank
2127
- np.random.shuffle(rank)
2141
+ rng.shuffle(rank)
2128
2142
 
2129
2143
 
2130
2144
  class TestFindRepeats:
@@ -2148,14 +2162,6 @@ class TestFindRepeats:
2148
2162
 
2149
2163
 
2150
2164
  class TestRegression:
2151
-
2152
- def test_one_arg_deprecation(self):
2153
- x = np.arange(20).reshape((2, 10))
2154
- message = "Inference of the two sets..."
2155
- with pytest.deprecated_call(match=message):
2156
- stats.linregress(x)
2157
- stats.linregress(x[0], x[1])
2158
-
2159
2165
  def test_linregressBIGX(self):
2160
2166
  # W.II.F. Regress BIG on X.
2161
2167
  result = stats.linregress(X, BIG)
@@ -2194,7 +2200,9 @@ class TestRegression:
2194
2200
  # total sum of squares of exactly 0.
2195
2201
  result = stats.linregress(X, ZERO)
2196
2202
  assert_almost_equal(result.intercept, 0.0)
2197
- assert_almost_equal(result.rvalue, 0.0)
2203
+ with pytest.warns(stats.ConstantInputWarning, match="An input array..."):
2204
+ ref_rvalue = stats.pearsonr(X, ZERO).statistic
2205
+ assert_almost_equal(result.rvalue, ref_rvalue)
2198
2206
 
2199
2207
  def test_regress_simple(self):
2200
2208
  # Regress a line with sinusoidal noise.
@@ -2247,42 +2255,6 @@ class TestRegression:
2247
2255
  assert_allclose(res.stderr, 0.0519051424731)
2248
2256
  assert_allclose(res.intercept_stderr, 8.0490133029927)
2249
2257
 
2250
- # TODO: remove this test once single-arg support is dropped;
2251
- # deprecation warning tested in `test_one_arg_deprecation`
2252
- @pytest.mark.filterwarnings('ignore::DeprecationWarning')
2253
- def test_regress_simple_onearg_rows(self):
2254
- # Regress a line w sinusoidal noise,
2255
- # with a single input of shape (2, N)
2256
- x = np.linspace(0, 100, 100)
2257
- y = 0.2 * np.linspace(0, 100, 100) + 10
2258
- y += np.sin(np.linspace(0, 20, 100))
2259
- rows = np.vstack((x, y))
2260
-
2261
- result = stats.linregress(rows)
2262
- assert_almost_equal(result.stderr, 2.3957814497838803e-3)
2263
- assert_almost_equal(result.intercept_stderr, 1.3866936078570702e-1)
2264
-
2265
- # TODO: remove this test once single-arg support is dropped;
2266
- # deprecation warning tested in `test_one_arg_deprecation`
2267
- @pytest.mark.filterwarnings('ignore::DeprecationWarning')
2268
- def test_regress_simple_onearg_cols(self):
2269
- x = np.linspace(0, 100, 100)
2270
- y = 0.2 * np.linspace(0, 100, 100) + 10
2271
- y += np.sin(np.linspace(0, 20, 100))
2272
- columns = np.hstack((np.expand_dims(x, 1), np.expand_dims(y, 1)))
2273
-
2274
- result = stats.linregress(columns)
2275
- assert_almost_equal(result.stderr, 2.3957814497838803e-3)
2276
- assert_almost_equal(result.intercept_stderr, 1.3866936078570702e-1)
2277
-
2278
- # TODO: remove this test once single-arg support is dropped;
2279
- # deprecation warning tested in `test_one_arg_deprecation`
2280
- @pytest.mark.filterwarnings('ignore::DeprecationWarning')
2281
- def test_regress_shape_error(self):
2282
- # Check that a single input argument to linregress with wrong shape
2283
- # results in a ValueError.
2284
- assert_raises(ValueError, stats.linregress, np.ones((3, 3)))
2285
-
2286
2258
  def test_linregress(self):
2287
2259
  # compared with multivariate ols with pinv
2288
2260
  x = np.arange(11)
@@ -2368,7 +2340,7 @@ class TestRegression:
2368
2340
  def test_nist_norris(self):
2369
2341
  # If this causes a lint failure in the future, please note the history of
2370
2342
  # requests to allow extra whitespace in table formatting (e.g. gh-12367).
2371
- # Also see https://github.com/scipy/scipy/wiki/Why-do-we-not-use-an-auto%E2%80%90formatter%3F # noqa: E501
2343
+ # Also see https://github.com/scipy/scipy/wiki/Why-do-we-not-use-an-auto%E2%80%90formatter%3F # noqa: E501
2372
2344
  x = [ 0.2, 337.4, 118.2, 884.6, 10.1, 226.5,
2373
2345
  666.3, 996.3, 448.6, 777.0, 558.2, 0.4,
2374
2346
  0.6, 775.5, 666.9, 338.0, 447.5, 11.6,
@@ -2405,7 +2377,9 @@ class TestRegression:
2405
2377
  assert_almost_equal(result.intercept, poly[1])
2406
2378
 
2407
2379
  def test_empty_input(self):
2408
- assert_raises(ValueError, stats.linregress, [], [])
2380
+ with pytest.warns(SmallSampleWarning, match="One or more sample..."):
2381
+ res = stats.linregress([], [])
2382
+ assert np.all(np.isnan(res))
2409
2383
 
2410
2384
  def test_nan_input(self):
2411
2385
  x = np.arange(10.)
@@ -2782,7 +2756,7 @@ class TestMode:
2782
2756
  # was deprecated, so check for the appropriate error.
2783
2757
  my_dtype = np.dtype([('asdf', np.uint8), ('qwer', np.float64, (3,))])
2784
2758
  test = np.zeros(10, dtype=my_dtype)
2785
- message = "Argument `a` is not....|An argument has dtype..."
2759
+ message = "Argument `a` is not....|An argument has dtype...|The DType..."
2786
2760
  with pytest.raises(TypeError, match=message):
2787
2761
  stats.mode(test, nan_policy=nan_policy)
2788
2762
 
@@ -2843,12 +2817,13 @@ class TestMode:
2843
2817
  stats.mode(np.arange(3, dtype=object))
2844
2818
 
2845
2819
 
2846
- @array_api_compatible
2820
+ @make_xp_test_case(stats.sem)
2847
2821
  class TestSEM:
2848
2822
 
2849
2823
  testcase = [1., 2., 3., 4.]
2850
2824
  scalar_testcase = 4.
2851
2825
 
2826
+ @pytest.mark.filterwarnings("ignore:invalid value encountered in divide")
2852
2827
  def test_sem_scalar(self, xp):
2853
2828
  # This is not in R, so used:
2854
2829
  # sqrt(var(testcase)*3/4)/sqrt(3)
@@ -2876,7 +2851,7 @@ class TestSEM:
2876
2851
  stats.sem(testcase, ddof=2))
2877
2852
 
2878
2853
  x = xp.arange(10.)
2879
- x = xp.where(x == 9, xp.asarray(xp.nan), x)
2854
+ x = xp.where(x == 9, xp.nan, x)
2880
2855
  xp_assert_equal(stats.sem(x), xp.asarray(xp.nan))
2881
2856
 
2882
2857
  @skip_xp_backends(np_only=True,
@@ -2889,10 +2864,8 @@ class TestSEM:
2889
2864
  assert_raises(ValueError, stats.sem, x, nan_policy='foobar')
2890
2865
 
2891
2866
 
2892
- @array_api_compatible
2893
- @pytest.mark.usefixtures("skip_xp_backends")
2894
- @skip_xp_backends('jax.numpy', reason="JAX can't do item assignment")
2895
- class TestZmapZscore:
2867
+ @make_xp_test_case(stats.zmap)
2868
+ class TestZmap:
2896
2869
 
2897
2870
  @pytest.mark.parametrize(
2898
2871
  'x, y',
@@ -2903,16 +2876,15 @@ class TestZmapZscore:
2903
2876
  # For these simple cases, calculate the expected result directly
2904
2877
  # by using the formula for the z-score.
2905
2878
  x, y = xp.asarray(x), xp.asarray(y)
2906
- xp_test = array_namespace(x, y) # std needs correction
2907
- expected = (x - xp.mean(y)) / xp_test.std(y, correction=0)
2879
+ expected = (x - xp.mean(y)) / xp.std(y, correction=0)
2908
2880
  z = stats.zmap(x, y)
2909
2881
  xp_assert_close(z, expected)
2910
2882
 
2911
2883
  def test_zmap_axis(self, xp):
2912
2884
  # Test use of 'axis' keyword in zmap.
2913
- x = np.array([[0.0, 0.0, 1.0, 1.0],
2914
- [1.0, 1.0, 1.0, 2.0],
2915
- [2.0, 0.0, 2.0, 0.0]])
2885
+ x = xp.asarray([[0.0, 0.0, 1.0, 1.0],
2886
+ [1.0, 1.0, 1.0, 2.0],
2887
+ [2.0, 0.0, 2.0, 0.0]])
2916
2888
 
2917
2889
  t1 = 1.0/(2.0/3)**0.5
2918
2890
  t2 = 3.**0.5/3
@@ -2927,6 +2899,8 @@ class TestZmapZscore:
2927
2899
  z1_expected = [[-1.0, -1.0, 1.0, 1.0],
2928
2900
  [-t2, -t2, -t2, 3.**0.5],
2929
2901
  [1.0, -1.0, 1.0, -1.0]]
2902
+ z0_expected = xp.asarray(z0_expected)
2903
+ z1_expected = xp.asarray(z1_expected)
2930
2904
 
2931
2905
  xp_assert_close(z0, z0_expected)
2932
2906
  xp_assert_close(z1, z1_expected)
@@ -2950,30 +2924,58 @@ class TestZmapZscore:
2950
2924
  scores = xp.asarray([-3, -1, 2, np.nan])
2951
2925
  compare = xp.asarray([-8, -3, 2, 7, 12, np.nan])
2952
2926
  z = stats.zmap(scores, compare, ddof=ddof, nan_policy='omit')
2953
- ref = stats.zmap(scores, compare[~xp.isnan(compare)], ddof=ddof)
2927
+ # exclude nans from compare, don't use isnan + mask since that messes up
2928
+ # dask
2929
+ ref = stats.zmap(scores, compare[:5], ddof=ddof)
2954
2930
  xp_assert_close(z, ref)
2955
2931
 
2956
2932
  @pytest.mark.parametrize('ddof', [0, 2])
2957
2933
  def test_zmap_nan_policy_omit_with_axis(self, ddof, xp):
2958
2934
  scores = xp.reshape(xp.arange(-5.0, 9.0), (2, -1))
2959
- compare = xp.reshape(xp.linspace(-8, 6, 24), (2, -1))
2960
- compare[0, 4] = xp.nan
2961
- compare[0, 6] = xp.nan
2962
- compare[1, 1] = xp.nan
2935
+ compare = np.reshape(np.linspace(-8, 6, 24), (2, -1))
2936
+ compare[0, 4] = np.nan
2937
+ compare[0, 6] = np.nan
2938
+ compare[1, 1] = np.nan
2939
+ # convert from numpy since some libraries like dask
2940
+ # can't handle the data-dependent shapes from the isnan masking
2941
+ compare_0_notna = xp.asarray(compare[0, :][~np.isnan(compare[0, :])])
2942
+ compare_1_notna = xp.asarray(compare[1, :][~np.isnan(compare[1, :])])
2943
+ compare = xp.asarray(compare)
2944
+
2963
2945
  z = stats.zmap(scores, compare, nan_policy='omit', axis=1, ddof=ddof)
2964
- res0 = stats.zmap(scores[0, :], compare[0, :][~xp.isnan(compare[0, :])],
2946
+ res0 = stats.zmap(scores[0, :], compare_0_notna,
2965
2947
  ddof=ddof)
2966
- res1 = stats.zmap(scores[1, :], compare[1, :][~xp.isnan(compare[1, :])],
2948
+ res1 = stats.zmap(scores[1, :], compare_1_notna,
2967
2949
  ddof=ddof)
2968
2950
  expected = xp.stack((res0, res1))
2969
2951
  xp_assert_close(z, expected)
2970
2952
 
2953
+ @skip_xp_backends(eager_only=True, reason="lazy arrays don't do 'raise'.")
2971
2954
  def test_zmap_nan_policy_raise(self, xp):
2972
2955
  scores = xp.asarray([1, 2, 3])
2973
2956
  compare = xp.asarray([-8, -3, 2, 7, 12, xp.nan])
2974
2957
  with pytest.raises(ValueError, match='input contains nan'):
2975
2958
  stats.zmap(scores, compare, nan_policy='raise')
2976
2959
 
2960
+ @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask")
2961
+ @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask")
2962
+ def test_degenerate_input(self, xp):
2963
+ scores = xp.arange(3)
2964
+ compare = xp.ones(3)
2965
+ ref = xp.asarray([-xp.inf, xp.nan, xp.inf])
2966
+ with eager_warns(scores, RuntimeWarning, match="Precision loss occurred..."):
2967
+ res = stats.zmap(scores, compare)
2968
+ xp_assert_equal(res, ref)
2969
+
2970
+ @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask")
2971
+ def test_complex_gh22404(self, xp):
2972
+ res = stats.zmap(xp.asarray([1, 2, 3, 4]), xp.asarray([1, 1j, -1, -1j]))
2973
+ ref = xp.asarray([1.+0.j, 2.+0.j, 3.+0.j, 4.+0.j])
2974
+ xp_assert_close(res, ref)
2975
+
2976
+
2977
+ @make_xp_test_case(stats.zscore)
2978
+ class TestZscore:
2977
2979
  def test_zscore(self, xp):
2978
2980
  # not in R, so tested by using:
2979
2981
  # (testcase[i] - mean(testcase, axis=0)) / sqrt(var(testcase) * 3/4)
@@ -3017,7 +3019,6 @@ class TestZmapZscore:
3017
3019
  xp_assert_close(z[0, :], z0_expected)
3018
3020
  xp_assert_close(z[1, :], z1_expected)
3019
3021
 
3020
- @skip_xp_backends('cupy', reason="cupy/cupy#8391")
3021
3022
  def test_zscore_nan_propagate(self, xp):
3022
3023
  x = xp.asarray([1, 2, np.nan, 4, 5])
3023
3024
  z = stats.zscore(x, nan_policy='propagate')
@@ -3038,32 +3039,32 @@ class TestZmapZscore:
3038
3039
 
3039
3040
  def test_zscore_nan_omit_with_ddof(self, xp):
3040
3041
  x = xp.asarray([xp.nan, 1.0, 3.0, 5.0, 7.0, 9.0])
3041
- xp_test = array_namespace(x) # numpy needs concat
3042
3042
  z = stats.zscore(x, ddof=1, nan_policy='omit')
3043
- expected = xp_test.concat([xp.asarray([xp.nan]), stats.zscore(x[1:], ddof=1)])
3043
+ expected = xp.concat([xp.asarray([xp.nan]), stats.zscore(x[1:], ddof=1)])
3044
3044
  xp_assert_close(z, expected)
3045
3045
 
3046
+ @skip_xp_backends(eager_only=True, reason="lazy arrays don't do 'raise'.")
3046
3047
  def test_zscore_nan_raise(self, xp):
3047
3048
  x = xp.asarray([1, 2, xp.nan, 4, 5])
3048
3049
  with pytest.raises(ValueError, match="The input contains nan..."):
3049
3050
  stats.zscore(x, nan_policy='raise')
3050
3051
 
3051
- @skip_xp_backends('cupy', reason="cupy/cupy#8391")
3052
3052
  def test_zscore_constant_input_1d(self, xp):
3053
3053
  x = xp.asarray([-0.087] * 3)
3054
- with pytest.warns(RuntimeWarning, match="Precision loss occurred..."):
3054
+ with eager_warns(x, RuntimeWarning, match="Precision loss occurred..."):
3055
3055
  z = stats.zscore(x)
3056
3056
  xp_assert_equal(z, xp.full(x.shape, xp.nan))
3057
3057
 
3058
+ @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask")
3058
3059
  def test_zscore_constant_input_2d(self, xp):
3059
3060
  x = xp.asarray([[10.0, 10.0, 10.0, 10.0],
3060
3061
  [10.0, 11.0, 12.0, 13.0]])
3061
- with pytest.warns(RuntimeWarning, match="Precision loss occurred..."):
3062
+ with eager_warns(x, RuntimeWarning, match="Precision loss occurred..."):
3062
3063
  z0 = stats.zscore(x, axis=0)
3063
3064
  xp_assert_close(z0, xp.asarray([[xp.nan, -1.0, -1.0, -1.0],
3064
3065
  [xp.nan, 1.0, 1.0, 1.0]]))
3065
3066
 
3066
- with pytest.warns(RuntimeWarning, match="Precision loss occurred..."):
3067
+ with eager_warns(x, RuntimeWarning, match="Precision loss occurred..."):
3067
3068
  z1 = stats.zscore(x, axis=1)
3068
3069
  xp_assert_equal(z1, xp.stack([xp.asarray([xp.nan, xp.nan, xp.nan, xp.nan]),
3069
3070
  stats.zscore(x[1, :])]))
@@ -3072,10 +3073,11 @@ class TestZmapZscore:
3072
3073
  xp_assert_equal(z, xp.reshape(stats.zscore(xp.reshape(x, (-1,))), x.shape))
3073
3074
 
3074
3075
  y = xp.ones((3, 6))
3075
- with pytest.warns(RuntimeWarning, match="Precision loss occurred..."):
3076
+ with eager_warns(y, RuntimeWarning, match="Precision loss occurred..."):
3076
3077
  z = stats.zscore(y, axis=None)
3077
- xp_assert_equal(z, xp.full(y.shape, xp.asarray(xp.nan)))
3078
+ xp_assert_equal(z, xp.full(y.shape, xp.nan))
3078
3079
 
3080
+ @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask")
3079
3081
  def test_zscore_constant_input_2d_nan_policy_omit(self, xp):
3080
3082
  x = xp.asarray([[10.0, 10.0, 10.0, 10.0],
3081
3083
  [10.0, 11.0, 12.0, xp.nan],
@@ -3083,18 +3085,19 @@ class TestZmapZscore:
3083
3085
  s = (3/2)**0.5
3084
3086
  s2 = 2**0.5
3085
3087
 
3086
- with pytest.warns(RuntimeWarning, match="Precision loss occurred..."):
3088
+ with eager_warns(x, RuntimeWarning, match="Precision loss occurred..."):
3087
3089
  z0 = stats.zscore(x, nan_policy='omit', axis=0)
3088
3090
  xp_assert_close(z0, xp.asarray([[xp.nan, -s, -1.0, xp.nan],
3089
3091
  [xp.nan, 0, 1.0, xp.nan],
3090
3092
  [xp.nan, s, xp.nan, xp.nan]]))
3091
3093
 
3092
- with pytest.warns(RuntimeWarning, match="Precision loss occurred..."):
3094
+ with eager_warns(x, RuntimeWarning, match="Precision loss occurred..."):
3093
3095
  z1 = stats.zscore(x, nan_policy='omit', axis=1)
3094
3096
  xp_assert_close(z1, xp.asarray([[xp.nan, xp.nan, xp.nan, xp.nan],
3095
3097
  [-s, 0, s, xp.nan],
3096
3098
  [-s2/2, s2, xp.nan, -s2/2]]))
3097
3099
 
3100
+ @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask")
3098
3101
  def test_zscore_2d_all_nan_row(self, xp):
3099
3102
  # A row is all nan, and we use axis=1.
3100
3103
  x = xp.asarray([[np.nan, np.nan, np.nan, np.nan],
@@ -3103,7 +3106,7 @@ class TestZmapZscore:
3103
3106
  xp_assert_close(z, xp.asarray([[np.nan, np.nan, np.nan, np.nan],
3104
3107
  [-1.0, -1.0, 1.0, 1.0]]))
3105
3108
 
3106
- @skip_xp_backends('cupy', reason="cupy/cupy#8391")
3109
+ @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask")
3107
3110
  def test_zscore_2d_all_nan(self, xp):
3108
3111
  # The entire 2d array is nan, and we use axis=None.
3109
3112
  y = xp.full((2, 3), xp.nan)
@@ -3116,25 +3119,6 @@ class TestZmapZscore:
3116
3119
  z = stats.zscore(x)
3117
3120
  xp_assert_equal(z, x)
3118
3121
 
3119
- def test_gzscore_normal_array(self, xp):
3120
- x = np.asarray([1, 2, 3, 4])
3121
- z = stats.gzscore(xp.asarray(x))
3122
- desired = np.log(x / stats.gmean(x)) / np.log(stats.gstd(x, ddof=0))
3123
- xp_assert_close(z, xp.asarray(desired, dtype=xp.asarray(1.).dtype))
3124
-
3125
- @skip_xp_invalid_arg
3126
- def test_gzscore_masked_array(self, xp):
3127
- x = np.array([1, 2, -1, 3, 4])
3128
- mask = [0, 0, 1, 0, 0]
3129
- mx = np.ma.masked_array(x, mask=mask)
3130
- z = stats.gzscore(mx)
3131
- desired = ([-1.526072095151, -0.194700599824, np.inf, 0.584101799472,
3132
- 1.136670895503])
3133
- desired = np.ma.masked_array(desired, mask=mask)
3134
- assert_allclose(z.compressed(), desired.compressed())
3135
- assert_allclose(z.mask, desired.mask)
3136
- assert isinstance(z, np.ma.MaskedArray)
3137
-
3138
3122
  @skip_xp_invalid_arg
3139
3123
  def test_zscore_masked_element_0_gh19039(self, xp):
3140
3124
  # zscore returned all NaNs when 0th element was masked. See gh-19039.
@@ -3159,19 +3143,27 @@ class TestZmapZscore:
3159
3143
  res = stats.zscore(y, axis=None)
3160
3144
  assert_equal(res[1:], np.nan)
3161
3145
 
3162
- def test_degenerate_input(self, xp):
3163
- scores = xp.arange(3)
3164
- compare = xp.ones(3)
3165
- ref = xp.asarray([-xp.inf, xp.nan, xp.inf])
3166
- with pytest.warns(RuntimeWarning, match="Precision loss occurred..."):
3167
- res = stats.zmap(scores, compare)
3168
- xp_assert_equal(res, ref)
3169
3146
 
3170
- @pytest.mark.skip_xp_backends('array_api_strict', reason='needs array-api#850')
3171
- def test_complex_gh22404(self, xp):
3172
- res = stats.zmap(xp.asarray([1, 2, 3, 4]), xp.asarray([1, 1j, -1, -1j]))
3173
- ref = xp.asarray([1.+0.j, 2.+0.j, 3.+0.j, 4.+0.j])
3174
- xp_assert_close(res, ref)
3147
+ @make_xp_test_case(stats.gzscore)
3148
+ class TestGZscore:
3149
+ def test_gzscore_normal_array(self, xp):
3150
+ x = np.asarray([1, 2, 3, 4])
3151
+ z = stats.gzscore(xp.asarray(x))
3152
+ desired = np.log(x / stats.gmean(x)) / np.log(stats.gstd(x, ddof=0))
3153
+ xp_assert_close(z, xp.asarray(desired, dtype=xp.asarray(1.).dtype))
3154
+
3155
+ @skip_xp_invalid_arg
3156
+ def test_gzscore_masked_array(self):
3157
+ x = np.array([1, 2, -1, 3, 4])
3158
+ mask = [0, 0, 1, 0, 0]
3159
+ mx = np.ma.masked_array(x, mask=mask)
3160
+ z = stats.gzscore(mx)
3161
+ desired = ([-1.526072095151, -0.194700599824, np.inf, 0.584101799472,
3162
+ 1.136670895503])
3163
+ desired = np.ma.masked_array(desired, mask=mask)
3164
+ assert_allclose(z.compressed(), desired.compressed())
3165
+ assert_allclose(z.mask, desired.mask)
3166
+ assert isinstance(z, np.ma.MaskedArray)
3175
3167
 
3176
3168
 
3177
3169
  class TestMedianAbsDeviation:
@@ -3481,6 +3473,7 @@ class TestIQR:
3481
3473
  assert_raises(ValueError, stats.iqr, x, scale='foobar')
3482
3474
 
3483
3475
 
3476
+ @make_xp_test_case(stats.moment)
3484
3477
  class TestMoments:
3485
3478
  """
3486
3479
  Comparison numbers are found using R v.1.5.1
@@ -3496,18 +3489,11 @@ class TestMoments:
3496
3489
  np.random.seed(1234)
3497
3490
  testcase_moment_accuracy = np.random.rand(42)
3498
3491
 
3499
- def _assert_equal(self, actual, expect, *, shape=None, dtype=None):
3500
- expect = np.asarray(expect)
3501
- if shape is not None:
3502
- expect = np.broadcast_to(expect, shape)
3503
- assert_array_equal(actual, expect)
3504
- if dtype is None:
3505
- dtype = expect.dtype
3506
- assert actual.dtype == dtype
3507
-
3508
- @array_api_compatible
3509
3492
  @pytest.mark.parametrize('size', [10, (10, 2)])
3510
3493
  @pytest.mark.parametrize('m, c', product((0, 1, 2, 3), (None, 0, 1)))
3494
+ @pytest.mark.filterwarnings(
3495
+ "ignore:divide by zero encountered in divide:RuntimeWarning:dask"
3496
+ )
3511
3497
  def test_moment_center_scalar_moment(self, size, m, c, xp):
3512
3498
  rng = np.random.default_rng(6581432544381372042)
3513
3499
  x = xp.asarray(rng.random(size=size))
@@ -3516,20 +3502,19 @@ class TestMoments:
3516
3502
  ref = xp.sum((x - c)**m, axis=0)/x.shape[0]
3517
3503
  xp_assert_close(res, ref, atol=1e-16)
3518
3504
 
3519
- @array_api_compatible
3520
3505
  @pytest.mark.parametrize('size', [10, (10, 2)])
3521
3506
  @pytest.mark.parametrize('c', (None, 0, 1))
3507
+ @pytest.mark.filterwarnings(
3508
+ "ignore:divide by zero encountered in divide:RuntimeWarning:dask"
3509
+ )
3522
3510
  def test_moment_center_array_moment(self, size, c, xp):
3523
3511
  rng = np.random.default_rng(1706828300224046506)
3524
3512
  x = xp.asarray(rng.random(size=size))
3525
3513
  m = [0, 1, 2, 3]
3526
3514
  res = stats.moment(x, m, center=c)
3527
- xp_test = array_namespace(x) # no `concat` in np < 2.0; no `newaxis` in torch
3528
- ref = xp_test.concat([stats.moment(x, i, center=c)[xp_test.newaxis, ...]
3529
- for i in m])
3515
+ ref = xp.concat([stats.moment(x, i, center=c)[xp.newaxis, ...] for i in m])
3530
3516
  xp_assert_equal(res, ref)
3531
3517
 
3532
- @array_api_compatible
3533
3518
  def test_moment(self, xp):
3534
3519
  # mean((testcase-mean(testcase))**power,axis=0),axis=0))**power))
3535
3520
  testcase = xp.asarray(self.testcase)
@@ -3591,9 +3576,6 @@ class TestMoments:
3591
3576
  assert_raises(ValueError, stats.moment, x, nan_policy='raise')
3592
3577
  assert_raises(ValueError, stats.moment, x, nan_policy='foobar')
3593
3578
 
3594
- @skip_xp_backends('cupy', reason='cupy/cupy#8391')
3595
- @pytest.mark.usefixtures("skip_xp_backends")
3596
- @array_api_compatible
3597
3579
  @pytest.mark.parametrize('dtype', ['float32', 'float64', 'complex128'])
3598
3580
  @pytest.mark.parametrize('expect, order', [(0, 1), (1, 0)])
3599
3581
  def test_constant_moments(self, dtype, expect, order, xp):
@@ -3615,26 +3597,21 @@ class TestMoments:
3615
3597
  order=order)
3616
3598
  xp_assert_equal(y, xp.full((), expect, dtype=dtype))
3617
3599
 
3618
- @skip_xp_backends('jax.numpy',
3619
- reason="JAX arrays do not support item assignment")
3620
- @pytest.mark.usefixtures("skip_xp_backends")
3621
- @array_api_compatible
3622
3600
  def test_moment_propagate_nan(self, xp):
3623
3601
  # Check that the shape of the result is the same for inputs
3624
3602
  # with and without nans, cf gh-5817
3625
3603
  a = xp.reshape(xp.arange(8.), (2, -1))
3626
- a[1, 0] = np.nan
3627
- mm = stats.moment(a, 2, axis=1)
3628
- xp_assert_close(mm, xp.asarray([1.25, np.nan]), atol=1e-15)
3604
+ a = xpx.at(a)[1, 0].set(xp.nan)
3605
+
3606
+ mm = stats.moment(xp.asarray(a), 2, axis=1)
3607
+ xp_assert_close(mm, xp.asarray([1.25, xp.nan]), atol=1e-15)
3629
3608
 
3630
- @array_api_compatible
3631
3609
  def test_moment_empty_order(self, xp):
3632
3610
  # tests moment with empty `order` list
3633
3611
  with pytest.raises(ValueError, match=r"`order` must be a scalar or a"
3634
3612
  r" non-empty 1D array."):
3635
3613
  stats.moment(xp.asarray([1, 2, 3, 4]), order=[])
3636
3614
 
3637
- @array_api_compatible
3638
3615
  def test_rename_moment_order(self, xp):
3639
3616
  # Parameter 'order' was formerly known as 'moment'. The old name
3640
3617
  # has not been deprecated, so it must continue to work.
@@ -3651,10 +3628,12 @@ class TestMoments:
3651
3628
  assert_allclose(np.power(tc_no_mean, 42).mean(),
3652
3629
  stats.moment(self.testcase_moment_accuracy, 42))
3653
3630
 
3654
- @array_api_compatible
3655
3631
  @pytest.mark.parametrize('order', [0, 1, 2, 3])
3656
3632
  @pytest.mark.parametrize('axis', [-1, 0, 1])
3657
3633
  @pytest.mark.parametrize('center', [None, 0])
3634
+ @pytest.mark.filterwarnings(
3635
+ "ignore:divide by zero encountered in divide:RuntimeWarning:dask"
3636
+ )
3658
3637
  def test_moment_array_api(self, xp, order, axis, center):
3659
3638
  rng = np.random.default_rng(34823589259425)
3660
3639
  x = rng.random(size=(5, 6, 7))
@@ -3668,27 +3647,28 @@ class SkewKurtosisTest:
3668
3647
  testcase = [1., 2., 3., 4.]
3669
3648
  testmathworks = [1.165, 0.6268, 0.0751, 0.3516, -0.6965]
3670
3649
 
3671
-
3672
- class TestSkew(SkewKurtosisTest):
3673
- @array_api_compatible
3674
- @pytest.mark.parametrize('stat_fun', [stats.skew, stats.kurtosis])
3675
- def test_empty_1d(self, stat_fun, xp):
3650
+ def test_empty_1d(self, xp):
3676
3651
  x = xp.asarray([])
3677
3652
  if is_numpy(xp):
3678
3653
  with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
3679
- res = stat_fun(x)
3654
+ res = self.stat_fun(x)
3680
3655
  else:
3681
3656
  with np.testing.suppress_warnings() as sup:
3682
3657
  # array_api_strict produces these
3683
3658
  sup.filter(RuntimeWarning, "Mean of empty slice")
3684
3659
  sup.filter(RuntimeWarning, "invalid value encountered")
3685
- res = stat_fun(x)
3660
+ res = self.stat_fun(x)
3686
3661
  xp_assert_equal(res, xp.asarray(xp.nan))
3687
3662
 
3688
- @skip_xp_backends('jax.numpy',
3689
- reason="JAX arrays do not support item assignment")
3690
- @pytest.mark.usefixtures("skip_xp_backends")
3691
- @array_api_compatible
3663
+
3664
+ @make_xp_test_case(stats.skew)
3665
+ class TestSkew(SkewKurtosisTest):
3666
+ def stat_fun(self, x):
3667
+ return stats.skew(x)
3668
+
3669
+ @pytest.mark.filterwarnings(
3670
+ "ignore:invalid value encountered in scalar divide:RuntimeWarning:dask"
3671
+ )
3692
3672
  def test_skewness(self, xp):
3693
3673
  # Scalar test case
3694
3674
  y = stats.skew(xp.asarray(self.scalar_testcase))
@@ -3716,25 +3696,21 @@ class TestSkew(SkewKurtosisTest):
3716
3696
  # `skew` must return a scalar for 1-dim input (only for NumPy arrays)
3717
3697
  assert_equal(stats.skew(arange(10)), 0.0)
3718
3698
 
3719
- @skip_xp_backends('jax.numpy',
3720
- reason="JAX arrays do not support item assignment")
3721
- @pytest.mark.usefixtures("skip_xp_backends")
3722
- @array_api_compatible
3723
3699
  def test_skew_propagate_nan(self, xp):
3724
3700
  # Check that the shape of the result is the same for inputs
3725
3701
  # with and without nans, cf gh-5817
3726
3702
  a = xp.arange(8.)
3727
3703
  a = xp.reshape(a, (2, -1))
3728
- a[1, 0] = xp.nan
3704
+ a = xpx.at(a)[1, 0].set(xp.nan)
3729
3705
  with np.errstate(invalid='ignore'):
3730
- s = stats.skew(a, axis=1)
3706
+ s = stats.skew(xp.asarray(a), axis=1)
3731
3707
  xp_assert_equal(s, xp.asarray([0, xp.nan]))
3732
3708
 
3733
- @array_api_compatible
3734
3709
  def test_skew_constant_value(self, xp):
3735
3710
  # Skewness of a constant input should be NaN (gh-16061)
3736
- with pytest.warns(RuntimeWarning, match="Precision loss occurred"):
3737
- a = xp.asarray([-0.27829495]*10) # xp.repeat not currently available
3711
+ a = xp.repeat(xp.asarray([-0.27829495]), 10)
3712
+
3713
+ with eager_warns(a, RuntimeWarning, match="Precision loss occurred"):
3738
3714
  xp_assert_equal(stats.skew(a), xp.asarray(xp.nan))
3739
3715
  xp_assert_equal(stats.skew(a*2.**50), xp.asarray(xp.nan))
3740
3716
  xp_assert_equal(stats.skew(a/2.**50), xp.asarray(xp.nan))
@@ -3746,24 +3722,17 @@ class TestSkew(SkewKurtosisTest):
3746
3722
  a = 1. + xp.arange(-3., 4)*1e-16
3747
3723
  xp_assert_equal(stats.skew(a), xp.asarray(xp.nan))
3748
3724
 
3749
- @skip_xp_backends('jax.numpy',
3750
- reason="JAX arrays do not support item assignment")
3751
- @pytest.mark.usefixtures("skip_xp_backends")
3752
- @array_api_compatible
3725
+ @skip_xp_backends(eager_only=True)
3753
3726
  def test_precision_loss_gh15554(self, xp):
3754
3727
  # gh-15554 was one of several issues that have reported problems with
3755
3728
  # constant or near-constant input. We can't always fix these, but
3756
3729
  # make sure there's a warning.
3757
3730
  with pytest.warns(RuntimeWarning, match="Precision loss occurred"):
3758
3731
  rng = np.random.default_rng(34095309370)
3759
- a = xp.asarray(rng.random(size=(100, 10)))
3732
+ a = rng.random(size=(100, 10))
3760
3733
  a[:, 0] = 1.01
3761
- stats.skew(a)
3734
+ stats.skew(xp.asarray(a))
3762
3735
 
3763
- @skip_xp_backends('jax.numpy',
3764
- reason="JAX arrays do not support item assignment")
3765
- @pytest.mark.usefixtures("skip_xp_backends")
3766
- @array_api_compatible
3767
3736
  @pytest.mark.parametrize('axis', [-1, 0, 2, None])
3768
3737
  @pytest.mark.parametrize('bias', [False, True])
3769
3738
  def test_vectorization(self, xp, axis, bias):
@@ -3777,10 +3746,9 @@ class TestSkew(SkewKurtosisTest):
3777
3746
  if axis is None:
3778
3747
  a = xp.reshape(a, (-1,))
3779
3748
  axis = 0
3780
- xp_test = array_namespace(a) # plain torch ddof=1 by default
3781
- mean = xp_test.mean(a, axis=axis, keepdims=True)
3782
- mu3 = xp_test.mean((a - mean)**3, axis=axis)
3783
- std = xp_test.std(a, axis=axis)
3749
+ mean = xp.mean(a, axis=axis, keepdims=True)
3750
+ mu3 = xp.mean((a - mean)**3, axis=axis)
3751
+ std = xp.std(a, axis=axis)
3784
3752
  res = mu3 / std ** 3
3785
3753
  if not bias:
3786
3754
  n = a.shape[axis]
@@ -3792,12 +3760,12 @@ class TestSkew(SkewKurtosisTest):
3792
3760
  xp_assert_close(res, ref)
3793
3761
 
3794
3762
 
3763
+ @make_xp_test_case(stats.kurtosis)
3795
3764
  class TestKurtosis(SkewKurtosisTest):
3765
+ def stat_fun(self, x):
3766
+ return stats.kurtosis(x)
3796
3767
 
3797
- @skip_xp_backends('jax.numpy',
3798
- reason='JAX arrays do not support item assignment')
3799
- @pytest.mark.usefixtures("skip_xp_backends")
3800
- @array_api_compatible
3768
+ @pytest.mark.filterwarnings("ignore:invalid value encountered in scalar divide")
3801
3769
  def test_kurtosis(self, xp):
3802
3770
  # Scalar test case
3803
3771
  y = stats.kurtosis(xp.asarray(self.scalar_testcase))
@@ -3826,7 +3794,7 @@ class TestKurtosis(SkewKurtosisTest):
3826
3794
  xp_assert_close(y, xp.asarray(1.64))
3827
3795
 
3828
3796
  x = xp.arange(10.)
3829
- x = xp.where(x == 8, xp.asarray(xp.nan), x)
3797
+ x = xp.where(x == 8, xp.nan, x)
3830
3798
  xp_assert_equal(stats.kurtosis(x), xp.asarray(xp.nan))
3831
3799
 
3832
3800
  def test_kurtosis_nan_policy(self):
@@ -3850,20 +3818,15 @@ class TestKurtosis(SkewKurtosisTest):
3850
3818
  k = stats.kurtosis(a, axis=1, nan_policy="propagate")
3851
3819
  np.testing.assert_allclose(k, [-1.36, np.nan], atol=1e-15)
3852
3820
 
3853
- @array_api_compatible
3854
3821
  def test_kurtosis_constant_value(self, xp):
3855
3822
  # Kurtosis of a constant input should be NaN (gh-16061)
3856
3823
  a = xp.asarray([-0.27829495]*10)
3857
- with pytest.warns(RuntimeWarning, match="Precision loss occurred"):
3824
+ with eager_warns(a, RuntimeWarning, match="Precision loss occurred"):
3858
3825
  assert xp.isnan(stats.kurtosis(a, fisher=False))
3859
3826
  assert xp.isnan(stats.kurtosis(a * float(2**50), fisher=False))
3860
3827
  assert xp.isnan(stats.kurtosis(a / float(2**50), fisher=False))
3861
3828
  assert xp.isnan(stats.kurtosis(a, fisher=False, bias=False))
3862
3829
 
3863
- @skip_xp_backends('jax.numpy',
3864
- reason='JAX arrays do not support item assignment')
3865
- @pytest.mark.usefixtures("skip_xp_backends")
3866
- @array_api_compatible
3867
3830
  @pytest.mark.parametrize('axis', [-1, 0, 2, None])
3868
3831
  @pytest.mark.parametrize('bias', [False, True])
3869
3832
  @pytest.mark.parametrize('fisher', [False, True])
@@ -3878,10 +3841,9 @@ class TestKurtosis(SkewKurtosisTest):
3878
3841
  if axis is None:
3879
3842
  a = xp.reshape(a, (-1,))
3880
3843
  axis = 0
3881
- xp_test = array_namespace(a) # plain torch ddof=1 by default
3882
- mean = xp_test.mean(a, axis=axis, keepdims=True)
3883
- mu4 = xp_test.mean((a - mean)**4, axis=axis)
3884
- mu2 = xp_test.var(a, axis=axis, correction=0)
3844
+ mean = xp.mean(a, axis=axis, keepdims=True)
3845
+ mu4 = xp.mean((a - mean)**4, axis=axis)
3846
+ mu2 = xp.var(a, axis=axis, correction=0)
3885
3847
  if bias:
3886
3848
  res = mu4 / mu2**2 - 3
3887
3849
  else:
@@ -3929,10 +3891,7 @@ def ttest_data_axis_strategy(draw):
3929
3891
  return data, axis
3930
3892
 
3931
3893
 
3932
- @pytest.mark.skip_xp_backends(cpu_only=True,
3933
- reason='Uses NumPy for pvalue, CI')
3934
- @pytest.mark.usefixtures("skip_xp_backends")
3935
- @array_api_compatible
3894
+ @make_xp_test_case(stats.ttest_1samp)
3936
3895
  class TestStudentTest:
3937
3896
  # Preserving original test cases.
3938
3897
  # Recomputed statistics and p-values with R t.test, e.g.
@@ -3951,6 +3910,8 @@ class TestStudentTest:
3951
3910
  P1_1_l = P1_1 / 2
3952
3911
  P1_1_g = 1 - (P1_1 / 2)
3953
3912
 
3913
+ @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask")
3914
+ @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask")
3954
3915
  def test_onesample(self, xp):
3955
3916
  with suppress_warnings() as sup, \
3956
3917
  np.errstate(invalid="ignore", divide="ignore"):
@@ -4003,6 +3964,7 @@ class TestStudentTest:
4003
3964
  assert_raises(ValueError, stats.ttest_1samp, x, 5.0,
4004
3965
  nan_policy='foobar')
4005
3966
 
3967
+ @pytest.mark.filterwarnings("ignore:divide by zero encountered in divide")
4006
3968
  def test_1samp_alternative(self, xp):
4007
3969
  message = "`alternative` must be 'less', 'greater', or 'two-sided'."
4008
3970
  with pytest.raises(ValueError, match=message):
@@ -4016,6 +3978,7 @@ class TestStudentTest:
4016
3978
  xp_assert_close(p, xp.asarray(self.P1_1_g))
4017
3979
  xp_assert_close(t, xp.asarray(self.T1_1))
4018
3980
 
3981
+ @skip_xp_backends('jax.numpy', reason='Generic stdtrit mutates array.')
4019
3982
  @pytest.mark.parametrize("alternative", ['two-sided', 'less', 'greater'])
4020
3983
  def test_1samp_ci_1d(self, xp, alternative):
4021
3984
  # test confidence interval method against reference values
@@ -4028,7 +3991,7 @@ class TestStudentTest:
4028
3991
  # x = c(2.75532884, 0.93892217, 0.94835861, 1.49489446, -0.62396595,
4029
3992
  # -1.88019867, -1.55684465, 4.88777104, 5.15310979, 4.34656348)
4030
3993
  # t.test(x, conf.level=0.85, alternative='l')
4031
- dtype = xp.float32 if is_torch(xp) else xp.float64 # use default dtype
3994
+ dtype = xp.asarray(1.0).dtype
4032
3995
  x = xp.asarray(x, dtype=dtype)
4033
3996
  popmean = xp.asarray(popmean, dtype=dtype)
4034
3997
 
@@ -4048,6 +4011,7 @@ class TestStudentTest:
4048
4011
  with pytest.raises(ValueError, match=message):
4049
4012
  res.confidence_interval(confidence_level=10)
4050
4013
 
4014
+ @skip_xp_backends(np_only=True, reason='Too slow.')
4051
4015
  @pytest.mark.xslow
4052
4016
  @hypothesis.given(alpha=hypothesis.strategies.floats(1e-15, 1-1e-15),
4053
4017
  data_axis=ttest_data_axis_strategy())
@@ -4060,8 +4024,7 @@ class TestStudentTest:
4060
4024
  alternative=alternative, axis=axis)
4061
4025
  l, u = res.confidence_interval(confidence_level=alpha)
4062
4026
  popmean = l if alternative == 'greater' else u
4063
- xp_test = array_namespace(l) # torch needs `expand_dims`
4064
- popmean = xp_test.expand_dims(popmean, axis=axis)
4027
+ popmean = xp.expand_dims(popmean, axis=axis)
4065
4028
  res = stats.ttest_1samp(data, popmean, alternative=alternative, axis=axis)
4066
4029
  shape = list(data.shape)
4067
4030
  shape.pop(axis)
@@ -4248,7 +4211,7 @@ power_div_empty_cases = [
4248
4211
  ]
4249
4212
 
4250
4213
 
4251
- @array_api_compatible
4214
+ @make_xp_test_case(stats.power_divergence)
4252
4215
  class TestPowerDivergence:
4253
4216
 
4254
4217
  def check_power_divergence(self, f_obs, f_exp, ddof, axis, lambda_,
@@ -4261,21 +4224,20 @@ class TestPowerDivergence:
4261
4224
  if axis is None:
4262
4225
  num_obs = xp_size(f_obs)
4263
4226
  else:
4264
- xp_test = array_namespace(f_obs) # torch needs broadcast_arrays
4265
- arrays = (xp_test.broadcast_arrays(f_obs, f_exp) if f_exp is not None
4227
+ arrays = (xp.broadcast_arrays(f_obs, f_exp) if f_exp is not None
4266
4228
  else (f_obs,))
4267
4229
  num_obs = arrays[0].shape[axis]
4268
4230
 
4269
4231
  with suppress_warnings() as sup:
4270
4232
  sup.filter(RuntimeWarning, "Mean of empty slice")
4271
4233
  stat, p = stats.power_divergence(
4272
- f_obs=f_obs, f_exp=f_exp, ddof=ddof,
4234
+ f_obs, f_exp=f_exp, ddof=ddof,
4273
4235
  axis=axis, lambda_=lambda_)
4274
4236
  xp_assert_close(stat, xp.asarray(expected_stat, dtype=dtype))
4275
4237
 
4276
4238
  if lambda_ == 1 or lambda_ == "pearson":
4277
4239
  # Also test stats.chisquare.
4278
- stat, p = stats.chisquare(f_obs=f_obs, f_exp=f_exp, ddof=ddof,
4240
+ stat, p = stats.chisquare(f_obs, f_exp=f_exp, ddof=ddof,
4279
4241
  axis=axis)
4280
4242
  xp_assert_close(stat, xp.asarray(expected_stat, dtype=dtype))
4281
4243
 
@@ -4352,13 +4314,10 @@ class TestPowerDivergence:
4352
4314
  xp_assert_close(stat, expected_chi2)
4353
4315
 
4354
4316
  # Compute the p values separately, passing in scalars for ddof.
4355
- stat0, p0 = stats.power_divergence(f_obs, f_exp, ddof=ddof[0, 0])
4356
- stat1, p1 = stats.power_divergence(f_obs, f_exp, ddof=ddof[1, 0])
4317
+ _, p0 = stats.power_divergence(f_obs, f_exp, ddof=ddof[0, 0])
4318
+ _, p1 = stats.power_divergence(f_obs, f_exp, ddof=ddof[1, 0])
4357
4319
 
4358
- xp_test = array_namespace(f_obs) # needs `concat`, `newaxis`
4359
- expected_p = xp_test.concat((p0[xp_test.newaxis, :],
4360
- p1[xp_test.newaxis, :]),
4361
- axis=0)
4320
+ expected_p = xp.concat((p0[xp.newaxis, :], p1[xp.newaxis, :]), axis=0)
4362
4321
  xp_assert_close(p, expected_p)
4363
4322
 
4364
4323
  @pytest.mark.parametrize('case', power_div_empty_cases)
@@ -4383,7 +4342,7 @@ class TestPowerDivergence:
4383
4342
  f_obs = xp.asarray(f_obs, dtype=dtype)
4384
4343
  # f_exp is None
4385
4344
 
4386
- res = stats.power_divergence(f_obs=f_obs, f_exp=f_exp, ddof=ddof,
4345
+ res = stats.power_divergence(f_obs, f_exp=f_exp, ddof=ddof,
4387
4346
  axis=axis, lambda_="pearson")
4388
4347
  attributes = ('statistic', 'pvalue')
4389
4348
  check_named_results(res, attributes, xp=xp)
@@ -4394,10 +4353,10 @@ class TestPowerDivergence:
4394
4353
  f_exp = xp.asarray([[5., 15.], [35., 25.]])
4395
4354
  message = 'For each axis slice...'
4396
4355
  with pytest.raises(ValueError, match=message):
4397
- stats.power_divergence(f_obs=f_obs, f_exp=xp.asarray([30., 60.]))
4356
+ stats.power_divergence(f_obs, f_exp=xp.asarray([30., 60.]))
4398
4357
  with pytest.raises(ValueError, match=message):
4399
- stats.power_divergence(f_obs=f_obs, f_exp=f_exp, axis=1)
4400
- stat, pval = stats.power_divergence(f_obs=f_obs, f_exp=f_exp)
4358
+ stats.power_divergence(f_obs, f_exp=f_exp, axis=1)
4359
+ stat, pval = stats.power_divergence(f_obs, f_exp=f_exp)
4401
4360
  xp_assert_close(stat, xp.asarray([5.71428571, 2.66666667]))
4402
4361
  xp_assert_close(pval, xp.asarray([0.01682741, 0.10247043]))
4403
4362
 
@@ -4418,9 +4377,8 @@ class TestPowerDivergence:
4418
4377
  expected_counts = xp.exp(alpha + beta*i)
4419
4378
 
4420
4379
  # `table4` holds just the second and third columns from Table 4.
4421
- xp_test = array_namespace(obs) # NumPy needs concat, torch needs newaxis
4422
- table4 = xp_test.concat((obs[xp_test.newaxis, :],
4423
- expected_counts[xp_test.newaxis, :])).T
4380
+ table4 = xp.concat((obs[xp.newaxis, :],
4381
+ expected_counts[xp.newaxis, :])).T
4424
4382
 
4425
4383
  table5 = xp.asarray([
4426
4384
  # lambda, statistic
@@ -4450,7 +4408,7 @@ class TestPowerDivergence:
4450
4408
  xp_assert_close(stat, expected_stat, rtol=5e-3)
4451
4409
 
4452
4410
 
4453
- @array_api_compatible
4411
+ @make_xp_test_case(stats.chisquare)
4454
4412
  class TestChisquare:
4455
4413
  def test_chisquare_12282a(self, xp):
4456
4414
  # Currently `chisquare` is implemented via power_divergence
@@ -4459,7 +4417,7 @@ class TestChisquare:
4459
4417
  with assert_raises(ValueError, match='For each axis slice...'):
4460
4418
  f_obs = xp.asarray([10., 20.])
4461
4419
  f_exp = xp.asarray([30., 60.])
4462
- stats.chisquare(f_obs=f_obs, f_exp=f_exp)
4420
+ stats.chisquare(f_obs, f_exp=f_exp)
4463
4421
 
4464
4422
  def test_chisquare_12282b(self, xp):
4465
4423
  # Check that users can now disable the sum check tested in
@@ -4471,7 +4429,7 @@ class TestChisquare:
4471
4429
  x = rng.poisson(lam)
4472
4430
  lam = xp.asarray(lam)
4473
4431
  x = xp.asarray(x, dtype=lam.dtype)
4474
- res = stats.chisquare(f_obs=x, f_exp=lam, ddof=-1, sum_check=False)
4432
+ res = stats.chisquare(x, f_exp=lam, ddof=-1, sum_check=False)
4475
4433
  # Poisson is approximately normal with mean and variance lam
4476
4434
  z = (x - lam) / xp.sqrt(lam)
4477
4435
  statistic = xp.sum(z**2)
@@ -4494,93 +4452,6 @@ class TestChisquare:
4494
4452
  xp_assert_equal(res.pvalue, p)
4495
4453
 
4496
4454
 
4497
- @skip_xp_invalid_arg
4498
- class TestChisquareMA:
4499
- @pytest.mark.filterwarnings('ignore::DeprecationWarning')
4500
- def test_chisquare_masked_arrays(self):
4501
- # Test masked arrays.
4502
- obs = np.array([[8, 8, 16, 32, -1], [-1, -1, 3, 4, 5]]).T
4503
- mask = np.array([[0, 0, 0, 0, 1], [1, 1, 0, 0, 0]]).T
4504
- mobs = np.ma.masked_array(obs, mask)
4505
- expected_chisq = np.array([24.0, 0.5])
4506
- expected_g = np.array([2*(2*8*np.log(0.5) + 32*np.log(2.0)),
4507
- 2*(3*np.log(0.75) + 5*np.log(1.25))])
4508
-
4509
- chi2 = stats.distributions.chi2
4510
-
4511
- chisq, p = stats.chisquare(mobs)
4512
- mat.assert_array_equal(chisq, expected_chisq)
4513
- mat.assert_array_almost_equal(p, chi2.sf(expected_chisq,
4514
- mobs.count(axis=0) - 1))
4515
-
4516
- g, p = stats.power_divergence(mobs, lambda_='log-likelihood')
4517
- mat.assert_array_almost_equal(g, expected_g, decimal=15)
4518
- mat.assert_array_almost_equal(p, chi2.sf(expected_g,
4519
- mobs.count(axis=0) - 1))
4520
-
4521
- chisq, p = stats.chisquare(mobs.T, axis=1)
4522
- mat.assert_array_equal(chisq, expected_chisq)
4523
- mat.assert_array_almost_equal(p, chi2.sf(expected_chisq,
4524
- mobs.T.count(axis=1) - 1))
4525
- g, p = stats.power_divergence(mobs.T, axis=1, lambda_="log-likelihood")
4526
- mat.assert_array_almost_equal(g, expected_g, decimal=15)
4527
- mat.assert_array_almost_equal(p, chi2.sf(expected_g,
4528
- mobs.count(axis=0) - 1))
4529
-
4530
- obs1 = np.ma.array([3, 5, 6, 99, 10], mask=[0, 0, 0, 1, 0])
4531
- exp1 = np.ma.array([2, 4, 8, 10, 99], mask=[0, 0, 0, 0, 1])
4532
- chi2, p = stats.chisquare(obs1, f_exp=exp1)
4533
- # Because of the mask at index 3 of obs1 and at index 4 of exp1,
4534
- # only the first three elements are included in the calculation
4535
- # of the statistic.
4536
- mat.assert_array_equal(chi2, 1/2 + 1/4 + 4/8)
4537
-
4538
- # When axis=None, the two values should have type np.float64.
4539
- chisq, p = stats.chisquare(np.ma.array([1,2,3]), axis=None)
4540
- assert_(isinstance(chisq, np.float64))
4541
- assert_(isinstance(p, np.float64))
4542
- assert_equal(chisq, 1.0)
4543
- assert_almost_equal(p, stats.distributions.chi2.sf(1.0, 2))
4544
-
4545
- # Empty arrays:
4546
- # A data set with length 0 returns a masked scalar.
4547
- with np.errstate(invalid='ignore'):
4548
- with suppress_warnings() as sup:
4549
- sup.filter(RuntimeWarning, "Mean of empty slice")
4550
- chisq, p = stats.chisquare(np.ma.array([]))
4551
- assert_(isinstance(chisq, np.ma.MaskedArray))
4552
- assert_equal(chisq.shape, ())
4553
- assert_(chisq.mask)
4554
-
4555
- empty3 = np.ma.array([[],[],[]])
4556
-
4557
- # empty3 is a collection of 0 data sets (whose lengths would be 3, if
4558
- # there were any), so the return value is an array with length 0.
4559
- chisq, p = stats.chisquare(empty3)
4560
- assert_(isinstance(chisq, np.ma.MaskedArray))
4561
- mat.assert_array_equal(chisq, [])
4562
-
4563
- # empty3.T is an array containing 3 data sets, each with length 0,
4564
- # so an array of size (3,) is returned, with all values masked.
4565
- with np.errstate(invalid='ignore'):
4566
- with suppress_warnings() as sup:
4567
- sup.filter(RuntimeWarning, "Mean of empty slice")
4568
- chisq, p = stats.chisquare(empty3.T)
4569
-
4570
- assert_(isinstance(chisq, np.ma.MaskedArray))
4571
- assert_equal(chisq.shape, (3,))
4572
- assert_(np.all(chisq.mask))
4573
-
4574
- def test_deprecation_warning(self):
4575
- a = np.asarray([1., 2., 3.])
4576
- ma = np.ma.masked_array(a)
4577
- message = "`power_divergence` and `chisquare` support for masked..."
4578
- with pytest.warns(DeprecationWarning, match=message):
4579
- stats.chisquare(ma)
4580
- with pytest.warns(DeprecationWarning, match=message):
4581
- stats.chisquare(a, ma)
4582
-
4583
-
4584
4455
  def test_friedmanchisquare():
4585
4456
  # see ticket:113
4586
4457
  # verified with matlab and R
@@ -4939,9 +4810,9 @@ class TestKSTwoSamples:
4939
4810
 
4940
4811
  def test_gh11184(self):
4941
4812
  # 3000, 3001, exact two-sided
4942
- np.random.seed(123456)
4943
- x = np.random.normal(size=3000)
4944
- y = np.random.normal(size=3001) * 1.5
4813
+ rng = np.random.RandomState(123456)
4814
+ x = rng.normal(size=3000)
4815
+ y = rng.normal(size=3001) * 1.5
4945
4816
  self._testOne(x, y, 'two-sided', 0.11292880151060758, 2.7755575615628914e-15,
4946
4817
  mode='asymp')
4947
4818
  self._testOne(x, y, 'two-sided', 0.11292880151060758, 2.7755575615628914e-15,
@@ -4950,9 +4821,9 @@ class TestKSTwoSamples:
4950
4821
  @pytest.mark.xslow
4951
4822
  def test_gh11184_bigger(self):
4952
4823
  # 10000, 10001, exact two-sided
4953
- np.random.seed(123456)
4954
- x = np.random.normal(size=10000)
4955
- y = np.random.normal(size=10001) * 1.5
4824
+ rng = np.random.RandomState(123456)
4825
+ x = rng.normal(size=10000)
4826
+ y = rng.normal(size=10001) * 1.5
4956
4827
  self._testOne(x, y, 'two-sided', 0.10597913208679133, 3.3149311398483503e-49,
4957
4828
  mode='asymp')
4958
4829
  self._testOne(x, y, 'two-sided', 0.10597913208679133, 2.7755575615628914e-15,
@@ -4964,10 +4835,10 @@ class TestKSTwoSamples:
4964
4835
 
4965
4836
  @pytest.mark.xslow
4966
4837
  def test_gh12999(self):
4967
- np.random.seed(123456)
4838
+ rng = np.random.RandomState(123456)
4968
4839
  for x in range(1000, 12000, 1000):
4969
- vals1 = np.random.normal(size=(x))
4970
- vals2 = np.random.normal(size=(x + 10), loc=0.5)
4840
+ vals1 = rng.normal(size=(x))
4841
+ vals2 = rng.normal(size=(x + 10), loc=0.5)
4971
4842
  exact = stats.ks_2samp(vals1, vals2, mode='exact').pvalue
4972
4843
  asymp = stats.ks_2samp(vals1, vals2, mode='asymp').pvalue
4973
4844
  # these two p-values should be in line with each other
@@ -5299,16 +5170,13 @@ def _desc_stats(x1, x2, axis=0, *, xp=None):
5299
5170
  return _stats(x1, axis) + _stats(x2, axis)
5300
5171
 
5301
5172
 
5302
- @array_api_compatible
5303
- @pytest.mark.skip_xp_backends(cpu_only=True,
5304
- reason='Uses NumPy for pvalue, CI')
5305
- @pytest.mark.usefixtures("skip_xp_backends")
5173
+ @make_xp_test_case(stats.ttest_ind, stats.ttest_ind_from_stats)
5306
5174
  def test_ttest_ind(xp):
5307
5175
  # regression test
5308
5176
  tr = xp.asarray(1.0912746897927283)
5309
5177
  pr = xp.asarray(0.27647818616351882)
5310
- tr_2D = xp.asarray([tr, -tr])
5311
- pr_2D = xp.asarray([pr, pr])
5178
+ tr_2D = xp.stack([tr, -tr])
5179
+ pr_2D = xp.stack([pr, pr])
5312
5180
 
5313
5181
  rvs1 = xp.linspace(5, 105, 100)
5314
5182
  rvs2 = xp.linspace(1, 100, 100)
@@ -5496,6 +5364,7 @@ class Test_ttest_ind_permutations:
5496
5364
  (a, b, {'random_state': np.random.default_rng(0), "axis": 1}, p_d_gen),
5497
5365
  ]
5498
5366
 
5367
+ @pytest.mark.thread_unsafe
5499
5368
  @pytest.mark.parametrize("a,b,update,p_d", params)
5500
5369
  def test_ttest_ind_permutations(self, a, b, update, p_d):
5501
5370
  options_a = {'axis': None, 'equal_var': False}
@@ -5510,10 +5379,10 @@ class Test_ttest_ind_permutations:
5510
5379
  assert_array_almost_equal(pvalue, p_d)
5511
5380
 
5512
5381
  def test_ttest_ind_exact_alternative(self):
5513
- np.random.seed(0)
5382
+ rng = np.random.RandomState(0)
5514
5383
  N = 3
5515
- a = np.random.rand(2, N, 2)
5516
- b = np.random.rand(2, N, 2)
5384
+ a = rng.rand(2, N, 2)
5385
+ b = rng.rand(2, N, 2)
5517
5386
 
5518
5387
  options_p = {'axis': 1, 'permutations': 1000}
5519
5388
 
@@ -5553,10 +5422,10 @@ class Test_ttest_ind_permutations:
5553
5422
 
5554
5423
  def test_ttest_ind_exact_selection(self):
5555
5424
  # test the various ways of activating the exact test
5556
- np.random.seed(0)
5425
+ rng = np.random.RandomState(0)
5557
5426
  N = 3
5558
- a = np.random.rand(N)
5559
- b = np.random.rand(N)
5427
+ a = rng.rand(N)
5428
+ b = rng.rand(N)
5560
5429
  res0 = stats.ttest_ind(a, b)
5561
5430
  res1 = stats.ttest_ind(a, b, permutations=1000)
5562
5431
  res2 = stats.ttest_ind(a, b, permutations=0)
@@ -5569,9 +5438,9 @@ class Test_ttest_ind_permutations:
5569
5438
  # the exact distribution of the test statistic should have
5570
5439
  # binom(na + nb, na) elements, all unique. This was not always true
5571
5440
  # in gh-4824; fixed by gh-13661.
5572
- np.random.seed(0)
5573
- a = np.random.rand(3)
5574
- b = np.random.rand(4)
5441
+ rng = np.random.RandomState(0)
5442
+ a = rng.rand(3)
5443
+ b = rng.rand(4)
5575
5444
 
5576
5445
  data = np.concatenate((a, b))
5577
5446
  na, nb = len(a), len(b)
@@ -5585,10 +5454,10 @@ class Test_ttest_ind_permutations:
5585
5454
  assert len(t_stat) == n_unique
5586
5455
 
5587
5456
  def test_ttest_ind_randperm_alternative(self):
5588
- np.random.seed(0)
5457
+ rng = np.random.RandomState(0)
5589
5458
  N = 50
5590
- a = np.random.rand(2, 3, N)
5591
- b = np.random.rand(3, N)
5459
+ a = rng.rand(2, 3, N)
5460
+ b = rng.rand(3, N)
5592
5461
  options_p = {'axis': -1, 'permutations': 1000, "random_state": 0}
5593
5462
 
5594
5463
  options_p.update(alternative="greater")
@@ -5615,10 +5484,10 @@ class Test_ttest_ind_permutations:
5615
5484
 
5616
5485
  @pytest.mark.slow()
5617
5486
  def test_ttest_ind_randperm_alternative2(self):
5618
- np.random.seed(0)
5487
+ rng = np.random.RandomState(0)
5619
5488
  N = 50
5620
- a = np.random.rand(N, 4)
5621
- b = np.random.rand(N, 4)
5489
+ a = rng.rand(N, 4)
5490
+ b = rng.rand(N, 4)
5622
5491
  options_p = {'permutations': 20000, "random_state": 0}
5623
5492
 
5624
5493
  options_p.update(alternative="greater")
@@ -5648,10 +5517,10 @@ class Test_ttest_ind_permutations:
5648
5517
  res_2_ab.pvalue[mask], atol=2e-2)
5649
5518
 
5650
5519
  def test_ttest_ind_permutation_nanpolicy(self):
5651
- np.random.seed(0)
5520
+ rng = np.random.RandomState(0)
5652
5521
  N = 50
5653
- a = np.random.rand(N, 5)
5654
- b = np.random.rand(N, 5)
5522
+ a = rng.rand(N, 5)
5523
+ b = rng.rand(N, 5)
5655
5524
  a[5, 1] = np.nan
5656
5525
  b[8, 2] = np.nan
5657
5526
  a[9, 3] = np.nan
@@ -5769,10 +5638,7 @@ class Test_ttest_ind_permutations:
5769
5638
  with pytest.raises(ValueError, match=message):
5770
5639
  stats.ttest_ind([1, 2, 3], [4, 5, 6], method='migratory')
5771
5640
 
5772
- @array_api_compatible
5773
- @pytest.mark.skip_xp_backends(cpu_only=True,
5774
- reason='Uses NumPy for pvalue, CI')
5775
- @pytest.mark.usefixtures("skip_xp_backends")
5641
+ @skip_xp_backends(cpu_only=True, reason='Uses NumPy for pvalue, CI')
5776
5642
  def test_permutation_not_implement_for_xp(self, xp):
5777
5643
  a2, b2 = xp.asarray(self.a2), xp.asarray(self.b2)
5778
5644
 
@@ -5845,8 +5711,9 @@ class Test_ttest_ind_common:
5845
5711
  def test_nans_on_axis(self, kwds, axis):
5846
5712
  # confirm that with `nan_policy='propagate'`, NaN results are returned
5847
5713
  # on the correct location
5848
- a = np.random.randint(10, size=(5, 3, 10)).astype('float')
5849
- b = np.random.randint(10, size=(5, 3, 10)).astype('float')
5714
+ rng = np.random.default_rng(363836384995579937222)
5715
+ a = rng.integers(10, size=(5, 3, 10)).astype('float')
5716
+ b = rng.integers(10, size=(5, 3, 10)).astype('float')
5850
5717
  # set some indices in `a` and `b` to be `np.nan`.
5851
5718
  a[0][2][3] = np.nan
5852
5719
  b[2][0][6] = np.nan
@@ -5998,10 +5865,7 @@ class Test_ttest_trim:
5998
5865
  stats.ttest_ind([1, 2], [2, 3], trim=.2, permutations=2,
5999
5866
  random_state=2)
6000
5867
 
6001
- @array_api_compatible
6002
- @pytest.mark.skip_xp_backends(cpu_only=True,
6003
- reason='Uses NumPy for pvalue, CI')
6004
- @pytest.mark.usefixtures("skip_xp_backends")
5868
+ @skip_xp_backends(cpu_only=True, reason='Uses NumPy for pvalue, CI')
6005
5869
  def test_permutation_not_implement_for_xp(self, xp):
6006
5870
  message = "Use of `trim` is compatible only with NumPy arrays."
6007
5871
  a, b = xp.arange(10), xp.arange(10)+1
@@ -6018,10 +5882,7 @@ class Test_ttest_trim:
6018
5882
  stats.ttest_ind([1, 2], [2, 1], trim=trim)
6019
5883
 
6020
5884
 
6021
- @array_api_compatible
6022
- @pytest.mark.skip_xp_backends(cpu_only=True,
6023
- reason='Uses NumPy for pvalue, CI')
6024
- @pytest.mark.usefixtures("skip_xp_backends")
5885
+ @make_xp_test_case(stats.ttest_ind)
6025
5886
  class Test_ttest_CI:
6026
5887
  # indices in order [alternative={two-sided, less, greater},
6027
5888
  # equal_var={False, True}, trim={0, 0.2}]
@@ -6068,6 +5929,7 @@ class Test_ttest_CI:
6068
5929
  @pytest.mark.parametrize('alternative', ['two-sided', 'less', 'greater'])
6069
5930
  @pytest.mark.parametrize('equal_var', [False, True])
6070
5931
  @pytest.mark.parametrize('trim', [0, 0.2])
5932
+ @skip_xp_backends('jax.numpy', reason='Generic stdtrit mutates array.')
6071
5933
  def test_confidence_interval(self, alternative, equal_var, trim, xp):
6072
5934
  if equal_var and trim:
6073
5935
  pytest.xfail('Discrepancy in `main`; needs further investigation.')
@@ -6115,234 +5977,220 @@ def test__broadcast_concatenate():
6115
5977
  assert b[i, j, k, l - a.shape[-3], m, n] == c[i, j, k, l, m, n]
6116
5978
 
6117
5979
 
6118
- @array_api_compatible
6119
- @pytest.mark.skip_xp_backends(cpu_only=True,
6120
- reason='Uses NumPy for pvalue, CI')
6121
- @pytest.mark.usefixtures("skip_xp_backends")
6122
- def test_ttest_ind_with_uneq_var(xp):
6123
- # check vs. R `t.test`, e.g.
6124
- # options(digits=20)
6125
- # a = c(1., 2., 3.)
6126
- # b = c(1.1, 2.9, 4.2)
6127
- # t.test(a, b, equal.var=FALSE)
6128
-
6129
- a = xp.asarray([1., 2., 3.])
6130
- b = xp.asarray([1.1, 2.9, 4.2])
6131
- pr = xp.asarray(0.53619490753126686)
6132
- tr = xp.asarray(-0.686495127355726265)
6133
-
6134
- t, p = stats.ttest_ind(a, b, equal_var=False)
6135
- xp_assert_close(t, tr)
6136
- xp_assert_close(p, pr)
6137
-
6138
- t, p = stats.ttest_ind_from_stats(*_desc_stats(a, b), equal_var=False)
6139
- xp_assert_close(t, tr)
6140
- xp_assert_close(p, pr)
6141
-
6142
- a = xp.asarray([1., 2., 3., 4.])
6143
- pr = xp.asarray(0.84354139131608252)
6144
- tr = xp.asarray(-0.210866331595072315)
6145
-
6146
- t, p = stats.ttest_ind(a, b, equal_var=False)
6147
- xp_assert_close(t, tr)
6148
- xp_assert_close(p, pr)
6149
-
6150
- t, p = stats.ttest_ind_from_stats(*_desc_stats(a, b), equal_var=False)
6151
- xp_assert_close(t, tr)
6152
- xp_assert_close(p, pr)
6153
-
6154
- # regression test
6155
- tr = xp.asarray(1.0912746897927283)
6156
- tr_uneq_n = xp.asarray(0.66745638708050492)
6157
- pr = xp.asarray(0.27647831993021388)
6158
- pr_uneq_n = xp.asarray(0.50873585065616544)
6159
- tr_2D = xp.asarray([tr, -tr])
6160
- pr_2D = xp.asarray([pr, pr])
6161
-
6162
- rvs3 = xp.linspace(1, 100, 25)
6163
- rvs2 = xp.linspace(1, 100, 100)
6164
- rvs1 = xp.linspace(5, 105, 100)
6165
- rvs1_2D = xp.stack([rvs1, rvs2])
6166
- rvs2_2D = xp.stack([rvs2, rvs1])
6167
-
6168
- t, p = stats.ttest_ind(rvs1, rvs2, axis=0, equal_var=False)
6169
- xp_assert_close(t, tr)
6170
- xp_assert_close(p, pr)
6171
-
6172
- t, p = stats.ttest_ind_from_stats(*_desc_stats(rvs1, rvs2), equal_var=False)
6173
- xp_assert_close(t, tr)
6174
- xp_assert_close(p, pr)
6175
-
6176
- t, p = stats.ttest_ind(rvs1, rvs3, axis=0, equal_var=False)
6177
- xp_assert_close(t, tr_uneq_n)
6178
- xp_assert_close(p, pr_uneq_n)
6179
-
6180
- t, p = stats.ttest_ind_from_stats(*_desc_stats(rvs1, rvs3), equal_var=False)
6181
- xp_assert_close(t, tr_uneq_n)
6182
- xp_assert_close(p, pr_uneq_n)
6183
-
6184
- res = stats.ttest_ind(rvs1_2D.T, rvs2_2D.T, axis=0, equal_var=False)
6185
- xp_assert_close(res.statistic, tr_2D)
6186
- xp_assert_close(res.pvalue, pr_2D)
6187
-
6188
- args = _desc_stats(rvs1_2D.T, rvs2_2D.T)
6189
- res = stats.ttest_ind_from_stats(*args, equal_var=False)
6190
- xp_assert_close(res.statistic, tr_2D)
6191
- xp_assert_close(res.pvalue, pr_2D)
6192
-
6193
- res = stats.ttest_ind(rvs1_2D, rvs2_2D, axis=1, equal_var=False)
6194
- xp_assert_close(res.statistic, tr_2D)
6195
- xp_assert_close(res.pvalue, pr_2D)
5980
+ @make_xp_test_case(stats.ttest_ind)
5981
+ class TestTTestInd:
5982
+ @make_xp_test_case(stats.ttest_ind_from_stats)
5983
+ def test_ttest_ind_with_uneq_var(self, xp):
5984
+ # check vs. R `t.test`, e.g.
5985
+ # options(digits=20)
5986
+ # a = c(1., 2., 3.)
5987
+ # b = c(1.1, 2.9, 4.2)
5988
+ # t.test(a, b, equal.var=FALSE)
5989
+
5990
+ a = xp.asarray([1., 2., 3.])
5991
+ b = xp.asarray([1.1, 2.9, 4.2])
5992
+ pr = xp.asarray(0.53619490753126686)
5993
+ tr = xp.asarray(-0.686495127355726265)
5994
+
5995
+ t, p = stats.ttest_ind(a, b, equal_var=False)
5996
+ xp_assert_close(t, tr)
5997
+ xp_assert_close(p, pr)
5998
+
5999
+ t, p = stats.ttest_ind_from_stats(*_desc_stats(a, b), equal_var=False)
6000
+ xp_assert_close(t, tr)
6001
+ xp_assert_close(p, pr)
6002
+
6003
+ a = xp.asarray([1., 2., 3., 4.])
6004
+ pr = xp.asarray(0.84354139131608252)
6005
+ tr = xp.asarray(-0.210866331595072315)
6006
+
6007
+ t, p = stats.ttest_ind(a, b, equal_var=False)
6008
+ xp_assert_close(t, tr)
6009
+ xp_assert_close(p, pr)
6010
+
6011
+ t, p = stats.ttest_ind_from_stats(*_desc_stats(a, b), equal_var=False)
6012
+ xp_assert_close(t, tr)
6013
+ xp_assert_close(p, pr)
6014
+
6015
+ # regression test
6016
+ tr = xp.asarray(1.0912746897927283)
6017
+ tr_uneq_n = xp.asarray(0.66745638708050492)
6018
+ pr = xp.asarray(0.27647831993021388)
6019
+ pr_uneq_n = xp.asarray(0.50873585065616544)
6020
+ tr_2D = xp.stack([tr, -tr])
6021
+ pr_2D = xp.stack([pr, pr])
6022
+
6023
+ rvs3 = xp.linspace(1, 100, 25)
6024
+ rvs2 = xp.linspace(1, 100, 100)
6025
+ rvs1 = xp.linspace(5, 105, 100)
6026
+ rvs1_2D = xp.stack([rvs1, rvs2])
6027
+ rvs2_2D = xp.stack([rvs2, rvs1])
6028
+
6029
+ t, p = stats.ttest_ind(rvs1, rvs2, axis=0, equal_var=False)
6030
+ xp_assert_close(t, tr)
6031
+ xp_assert_close(p, pr)
6032
+
6033
+ t, p = stats.ttest_ind_from_stats(*_desc_stats(rvs1, rvs2), equal_var=False)
6034
+ xp_assert_close(t, tr)
6035
+ xp_assert_close(p, pr)
6036
+
6037
+ t, p = stats.ttest_ind(rvs1, rvs3, axis=0, equal_var=False)
6038
+ xp_assert_close(t, tr_uneq_n)
6039
+ xp_assert_close(p, pr_uneq_n)
6040
+
6041
+ t, p = stats.ttest_ind_from_stats(*_desc_stats(rvs1, rvs3), equal_var=False)
6042
+ xp_assert_close(t, tr_uneq_n)
6043
+ xp_assert_close(p, pr_uneq_n)
6044
+
6045
+ res = stats.ttest_ind(rvs1_2D.T, rvs2_2D.T, axis=0, equal_var=False)
6046
+ xp_assert_close(res.statistic, tr_2D)
6047
+ xp_assert_close(res.pvalue, pr_2D)
6048
+
6049
+ args = _desc_stats(rvs1_2D.T, rvs2_2D.T)
6050
+ res = stats.ttest_ind_from_stats(*args, equal_var=False)
6051
+ xp_assert_close(res.statistic, tr_2D)
6052
+ xp_assert_close(res.pvalue, pr_2D)
6053
+
6054
+ res = stats.ttest_ind(rvs1_2D, rvs2_2D, axis=1, equal_var=False)
6055
+ xp_assert_close(res.statistic, tr_2D)
6056
+ xp_assert_close(res.pvalue, pr_2D)
6057
+
6058
+ args = _desc_stats(rvs1_2D, rvs2_2D, axis=1)
6059
+ res = stats.ttest_ind_from_stats(*args, equal_var=False)
6060
+ xp_assert_close(res.statistic, tr_2D)
6061
+ xp_assert_close(res.pvalue, pr_2D)
6062
+
6063
+ @pytest.mark.filterwarnings(
6064
+ "ignore:divide by zero encountered:RuntimeWarning"
6065
+ ) # for dask
6066
+ @pytest.mark.filterwarnings(
6067
+ "ignore:invalid value encountered:RuntimeWarning"
6068
+ ) # for dask
6069
+ def test_ttest_ind_zero_division(self, xp):
6070
+ # test zero division problem
6071
+ x = xp.zeros(3)
6072
+ y = xp.ones(3)
6073
+
6074
+ with eager_warns(x, RuntimeWarning, match="Precision loss occurred"):
6075
+ t, p = stats.ttest_ind(x, y, equal_var=False)
6076
+
6077
+ xp_assert_equal(t, xp.asarray(-xp.inf))
6078
+ xp_assert_equal(p, xp.asarray(0.))
6196
6079
 
6197
- args = _desc_stats(rvs1_2D, rvs2_2D, axis=1)
6198
- res = stats.ttest_ind_from_stats(*args, equal_var=False)
6199
- xp_assert_close(res.statistic, tr_2D)
6200
- xp_assert_close(res.pvalue, pr_2D)
6201
-
6202
-
6203
- @array_api_compatible
6204
- @pytest.mark.skip_xp_backends(cpu_only=True,
6205
- reason='Uses NumPy for pvalue, CI')
6206
- @pytest.mark.usefixtures("skip_xp_backends")
6207
- def test_ttest_ind_zero_division(xp):
6208
- # test zero division problem
6209
- x = xp.zeros(3)
6210
- y = xp.ones(3)
6211
- with pytest.warns(RuntimeWarning, match="Precision loss occurred"):
6212
- t, p = stats.ttest_ind(x, y, equal_var=False)
6213
- xp_assert_equal(t, xp.asarray(-xp.inf))
6214
- xp_assert_equal(p, xp.asarray(0.))
6215
-
6216
- with np.errstate(all='ignore'):
6217
- t, p = stats.ttest_ind(x, x, equal_var=False)
6218
- xp_assert_equal(t, xp.asarray(xp.nan))
6219
- xp_assert_equal(p, xp.asarray(xp.nan))
6220
-
6221
- # check that nan in input array result in nan output
6222
- anan = xp.asarray([[1, xp.nan], [-1, 1]])
6223
- t, p = stats.ttest_ind(anan, xp.zeros((2, 2)), equal_var=False)
6224
- xp_assert_equal(t, xp.asarray([0., np.nan]))
6225
- xp_assert_equal(p, xp.asarray([1., np.nan]))
6226
-
6227
-
6228
- def test_ttest_ind_nan_2nd_arg():
6229
- # regression test for gh-6134: nans in the second arg were not handled
6230
- x = [np.nan, 2.0, 3.0, 4.0]
6231
- y = [1.0, 2.0, 1.0, 2.0]
6232
-
6233
- r1 = stats.ttest_ind(x, y, nan_policy='omit')
6234
- r2 = stats.ttest_ind(y, x, nan_policy='omit')
6235
- assert_allclose(r2.statistic, -r1.statistic, atol=1e-15)
6236
- assert_allclose(r2.pvalue, r1.pvalue, atol=1e-15)
6237
-
6238
- # NB: arguments are not paired when NaNs are dropped
6239
- r3 = stats.ttest_ind(y, x[1:])
6240
- assert_allclose(r2, r3, atol=1e-15)
6241
-
6242
- # .. and this is consistent with R. R code:
6243
- # x = c(NA, 2.0, 3.0, 4.0)
6244
- # y = c(1.0, 2.0, 1.0, 2.0)
6245
- # t.test(x, y, var.equal=TRUE)
6246
- assert_allclose(r2, (-2.5354627641855498, 0.052181400457057901),
6247
- atol=1e-15)
6248
-
6249
-
6250
- @array_api_compatible
6251
- def test_ttest_ind_empty_1d_returns_nan(xp):
6252
- # Two empty inputs should return a TtestResult containing nan
6253
- # for both values.
6254
- if is_numpy(xp):
6255
- with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
6080
+ with np.errstate(all='ignore'):
6081
+ t, p = stats.ttest_ind(x, x, equal_var=False)
6082
+ xp_assert_equal(t, xp.asarray(xp.nan))
6083
+ xp_assert_equal(p, xp.asarray(xp.nan))
6084
+
6085
+ # check that nan in input array result in nan output
6086
+ anan = xp.asarray([[1, xp.nan], [-1, 1]])
6087
+ t, p = stats.ttest_ind(anan, xp.zeros((2, 2)), equal_var=False)
6088
+ xp_assert_equal(t, xp.asarray([0., np.nan]))
6089
+ xp_assert_equal(p, xp.asarray([1., np.nan]))
6090
+
6091
+ def test_ttest_ind_nan_2nd_arg(self):
6092
+ # regression test for gh-6134: nans in the second arg were not handled
6093
+ x = [np.nan, 2.0, 3.0, 4.0]
6094
+ y = [1.0, 2.0, 1.0, 2.0]
6095
+
6096
+ r1 = stats.ttest_ind(x, y, nan_policy='omit')
6097
+ r2 = stats.ttest_ind(y, x, nan_policy='omit')
6098
+ assert_allclose(r2.statistic, -r1.statistic, atol=1e-15)
6099
+ assert_allclose(r2.pvalue, r1.pvalue, atol=1e-15)
6100
+
6101
+ # NB: arguments are not paired when NaNs are dropped
6102
+ r3 = stats.ttest_ind(y, x[1:])
6103
+ assert_allclose(r2, r3, atol=1e-15)
6104
+
6105
+ # .. and this is consistent with R. R code:
6106
+ # x = c(NA, 2.0, 3.0, 4.0)
6107
+ # y = c(1.0, 2.0, 1.0, 2.0)
6108
+ # t.test(x, y, var.equal=TRUE)
6109
+ assert_allclose(r2, (-2.5354627641855498, 0.052181400457057901),
6110
+ atol=1e-15)
6111
+
6112
+ def test_ttest_ind_empty_1d_returns_nan(self, xp):
6113
+ # Two empty inputs should return a TtestResult containing nan
6114
+ # for both values.
6115
+ if is_numpy(xp):
6116
+ with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
6117
+ res = stats.ttest_ind(xp.asarray([]), xp.asarray([]))
6118
+ else:
6256
6119
  res = stats.ttest_ind(xp.asarray([]), xp.asarray([]))
6257
- else:
6258
- res = stats.ttest_ind(xp.asarray([]), xp.asarray([]))
6259
- assert isinstance(res, stats._stats_py.TtestResult)
6260
- NaN = xp.asarray(xp.nan)[()]
6261
- xp_assert_equal(res.statistic, NaN)
6262
- xp_assert_equal(res.pvalue, NaN)
6263
-
6264
-
6265
- @skip_xp_backends('cupy', reason='cupy/cupy#8391')
6266
- @pytest.mark.usefixtures("skip_xp_backends")
6267
- @array_api_compatible
6268
- @pytest.mark.parametrize('b, expected_shape',
6269
- [(np.empty((1, 5, 0)), (3, 5)),
6270
- (np.empty((1, 0, 0)), (3, 0))])
6271
- def test_ttest_ind_axis_size_zero(b, expected_shape, xp):
6272
- # In this test, the length of the axis dimension is zero.
6273
- # The results should be arrays containing nan with shape
6274
- # given by the broadcast nonaxis dimensions.
6275
- a = xp.empty((3, 1, 0))
6276
- b = xp.asarray(b)
6277
- with np.testing.suppress_warnings() as sup:
6278
- # first case should warn, second shouldn't?
6279
- sup.filter(SmallSampleWarning, too_small_nd_not_omit)
6280
- res = stats.ttest_ind(a, b, axis=-1)
6281
- assert isinstance(res, stats._stats_py.TtestResult)
6282
- expected_value = xp.full(expected_shape, fill_value=xp.nan)
6283
- xp_assert_equal(res.statistic, expected_value)
6284
- xp_assert_equal(res.pvalue, expected_value)
6285
-
6286
-
6287
- @array_api_compatible
6288
- def test_ttest_ind_nonaxis_size_zero(xp):
6289
- # In this test, the length of the axis dimension is nonzero,
6290
- # but one of the nonaxis dimensions has length 0. Check that
6291
- # we still get the correctly broadcast shape, which is (5, 0)
6292
- # in this case.
6293
- a = xp.empty((1, 8, 0))
6294
- b = xp.empty((5, 8, 1))
6295
- res = stats.ttest_ind(a, b, axis=1)
6296
- assert isinstance(res, stats._stats_py.TtestResult)
6297
- assert res.statistic.shape ==(5, 0)
6298
- assert res.pvalue.shape == (5, 0)
6299
-
6300
-
6301
- @array_api_compatible
6302
- def test_ttest_ind_nonaxis_size_zero_different_lengths(xp):
6303
- # In this test, the length of the axis dimension is nonzero,
6304
- # and that size is different in the two inputs,
6305
- # and one of the nonaxis dimensions has length 0. Check that
6306
- # we still get the correctly broadcast shape, which is (5, 0)
6307
- # in this case.
6308
- a = xp.empty((1, 7, 0))
6309
- b = xp.empty((5, 8, 1))
6310
- res = stats.ttest_ind(a, b, axis=1)
6311
- assert isinstance(res, stats._stats_py.TtestResult)
6312
- assert res.statistic.shape ==(5, 0)
6313
- assert res.pvalue.shape == (5, 0)
6314
-
6315
-
6316
- @array_api_compatible
6317
- @pytest.mark.skip_xp_backends(np_only=True,
6318
- reason="Other backends don't like integers")
6319
- @pytest.mark.usefixtures("skip_xp_backends")
6320
- def test_gh5686(xp):
6321
- mean1, mean2 = xp.asarray([1, 2]), xp.asarray([3, 4])
6322
- std1, std2 = xp.asarray([5, 3]), xp.asarray([4, 5])
6323
- nobs1, nobs2 = xp.asarray([130, 140]), xp.asarray([100, 150])
6324
- # This will raise a TypeError unless gh-5686 is fixed.
6325
- stats.ttest_ind_from_stats(mean1, std1, nobs1, mean2, std2, nobs2)
6326
-
6327
-
6328
- @array_api_compatible
6329
- @pytest.mark.skip_xp_backends(cpu_only=True,
6330
- reason='Uses NumPy for pvalue, CI')
6331
- @pytest.mark.usefixtures("skip_xp_backends")
6332
- def test_ttest_ind_from_stats_inputs_zero(xp):
6333
- # Regression test for gh-6409.
6334
- zero = xp.asarray(0.)
6335
- six = xp.asarray(6.)
6336
- NaN = xp.asarray(xp.nan)
6337
- res = stats.ttest_ind_from_stats(zero, zero, six, zero, zero, six, equal_var=False)
6338
- xp_assert_equal(res.statistic, NaN)
6339
- xp_assert_equal(res.pvalue, NaN)
6340
-
6341
-
6342
- @array_api_compatible
6343
- @pytest.mark.skip_xp_backends(cpu_only=True,
6344
- reason='Uses NumPy for pvalue, CI')
6345
- @pytest.mark.usefixtures("skip_xp_backends")
6120
+ assert isinstance(res, stats._stats_py.TtestResult)
6121
+ NaN = xp.asarray(xp.nan)[()]
6122
+ xp_assert_equal(res.statistic, NaN)
6123
+ xp_assert_equal(res.pvalue, NaN)
6124
+
6125
+ @pytest.mark.parametrize('b, expected_shape',
6126
+ [(np.empty((1, 5, 0)), (3, 5)),
6127
+ (np.empty((1, 0, 0)), (3, 0))])
6128
+ def test_ttest_ind_axis_size_zero(self, b, expected_shape, xp):
6129
+ # In this test, the length of the axis dimension is zero.
6130
+ # The results should be arrays containing nan with shape
6131
+ # given by the broadcast nonaxis dimensions.
6132
+ a = xp.empty((3, 1, 0))
6133
+ b = xp.asarray(b, dtype=a.dtype)
6134
+ with np.testing.suppress_warnings() as sup:
6135
+ # first case should warn, second shouldn't?
6136
+ sup.filter(SmallSampleWarning, too_small_nd_not_omit)
6137
+ res = stats.ttest_ind(a, b, axis=-1)
6138
+ assert isinstance(res, stats._stats_py.TtestResult)
6139
+ expected_value = xp.full(expected_shape, fill_value=xp.nan)
6140
+ xp_assert_equal(res.statistic, expected_value)
6141
+ xp_assert_equal(res.pvalue, expected_value)
6142
+
6143
+ def test_ttest_ind_nonaxis_size_zero(self, xp):
6144
+ # In this test, the length of the axis dimension is nonzero,
6145
+ # but one of the nonaxis dimensions has length 0. Check that
6146
+ # we still get the correctly broadcast shape, which is (5, 0)
6147
+ # in this case.
6148
+ a = xp.empty((1, 8, 0))
6149
+ b = xp.empty((5, 8, 1))
6150
+ res = stats.ttest_ind(a, b, axis=1)
6151
+ assert isinstance(res, stats._stats_py.TtestResult)
6152
+ assert res.statistic.shape ==(5, 0)
6153
+ assert res.pvalue.shape == (5, 0)
6154
+
6155
+ def test_ttest_ind_nonaxis_size_zero_different_lengths(self, xp):
6156
+ # In this test, the length of the axis dimension is nonzero,
6157
+ # and that size is different in the two inputs,
6158
+ # and one of the nonaxis dimensions has length 0. Check that
6159
+ # we still get the correctly broadcast shape, which is (5, 0)
6160
+ # in this case.
6161
+ a = xp.empty((1, 7, 0))
6162
+ b = xp.empty((5, 8, 1))
6163
+ res = stats.ttest_ind(a, b, axis=1)
6164
+ assert isinstance(res, stats._stats_py.TtestResult)
6165
+ assert res.statistic.shape ==(5, 0)
6166
+ assert res.pvalue.shape == (5, 0)
6167
+
6168
+
6169
+ @make_xp_test_case(stats.ttest_ind_from_stats)
6170
+ class TestTTestIndFromStats:
6171
+ @pytest.mark.skip_xp_backends(np_only=True,
6172
+ reason="Other backends don't like integers")
6173
+ def test_gh5686(self, xp):
6174
+ mean1, mean2 = xp.asarray([1, 2]), xp.asarray([3, 4])
6175
+ std1, std2 = xp.asarray([5, 3]), xp.asarray([4, 5])
6176
+ nobs1, nobs2 = xp.asarray([130, 140]), xp.asarray([100, 150])
6177
+ # This will raise a TypeError unless gh-5686 is fixed.
6178
+ stats.ttest_ind_from_stats(mean1, std1, nobs1, mean2, std2, nobs2)
6179
+
6180
+ @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning")
6181
+ def test_ttest_ind_from_stats_inputs_zero(self, xp):
6182
+ # Regression test for gh-6409.
6183
+ zero = xp.asarray(0.)
6184
+ six = xp.asarray(6.)
6185
+ NaN = xp.asarray(xp.nan)
6186
+ res = stats.ttest_ind_from_stats(zero, zero, six, zero, zero, six,
6187
+ equal_var=False)
6188
+ xp_assert_equal(res.statistic, NaN)
6189
+ xp_assert_equal(res.pvalue, NaN)
6190
+
6191
+
6192
+ @pytest.mark.skip_xp_backends(cpu_only=True, reason='Test uses ks_1samp')
6193
+ @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning")
6346
6194
  def test_ttest_uniform_pvalues(xp):
6347
6195
  # test that p-values are uniformly distributed under the null hypothesis
6348
6196
  rng = np.random.default_rng(246834602926842)
@@ -6366,7 +6214,7 @@ def test_ttest_uniform_pvalues(xp):
6366
6214
  x, y = xp.asarray([2, 3, 5]), xp.asarray([1.5])
6367
6215
 
6368
6216
  res = stats.ttest_ind(x, y, equal_var=True)
6369
- rtol = 1e-6 if is_torch(xp) else 1e-10
6217
+ rtol = 1e-6 if xp_default_dtype(xp) == xp.float32 else 1e-10
6370
6218
  xp_assert_close(res.statistic, xp.asarray(1.0394023007754), rtol=rtol)
6371
6219
  xp_assert_close(res.pvalue, xp.asarray(0.407779907736), rtol=rtol)
6372
6220
 
@@ -6381,10 +6229,9 @@ def _convert_pvalue_alternative(t, p, alt, xp):
6381
6229
 
6382
6230
 
6383
6231
  @pytest.mark.slow
6384
- @pytest.mark.skip_xp_backends(cpu_only=True,
6385
- reason='Uses NumPy for pvalue, CI')
6386
- @pytest.mark.usefixtures("skip_xp_backends")
6387
- @array_api_compatible
6232
+ @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask")
6233
+ @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask")
6234
+ @make_xp_test_case(stats.ttest_1samp)
6388
6235
  def test_ttest_1samp_new(xp):
6389
6236
  n1, n2, n3 = (10, 15, 20)
6390
6237
  rvn1 = stats.norm.rvs(loc=5, scale=10, size=(n1, n2, n3))
@@ -6445,10 +6292,7 @@ def test_ttest_1samp_new(xp):
6445
6292
  xp_assert_equal(res.pvalue, xp.asarray([1., xp.nan]))
6446
6293
 
6447
6294
 
6448
- @pytest.mark.skip_xp_backends(np_only=True,
6449
- reason="Only NumPy has nan_policy='omit' for now")
6450
- @pytest.mark.usefixtures("skip_xp_backends")
6451
- @array_api_compatible
6295
+ @skip_xp_backends(np_only=True, reason="Only NumPy has nan_policy='omit' for now")
6452
6296
  def test_ttest_1samp_new_omit(xp):
6453
6297
  n1, n2, n3 = (5, 10, 15)
6454
6298
  rvn1 = stats.norm.rvs(loc=5, scale=10, size=(n1, n2, n3))
@@ -6471,10 +6315,8 @@ def test_ttest_1samp_new_omit(xp):
6471
6315
  xp_assert_close(t, tr)
6472
6316
 
6473
6317
 
6474
- @pytest.mark.skip_xp_backends(cpu_only=True,
6475
- reason='Uses NumPy for pvalue, CI')
6476
- @pytest.mark.usefixtures("skip_xp_backends")
6477
- @array_api_compatible
6318
+ @make_xp_test_case(stats.ttest_1samp)
6319
+ @pytest.mark.skip_xp_backends('jax.numpy', reason='Generic stdtrit mutates array.')
6478
6320
  def test_ttest_1samp_popmean_array(xp):
6479
6321
  # when popmean.shape[axis] != 1, raise an error
6480
6322
  # if the user wants to test multiple null hypotheses simultaneously,
@@ -6492,10 +6334,9 @@ def test_ttest_1samp_popmean_array(xp):
6492
6334
  res = stats.ttest_1samp(x, popmean=popmean, axis=-2)
6493
6335
  assert res.statistic.shape == (5, 20)
6494
6336
 
6495
- xp_test = array_namespace(x) # torch needs expand_dims
6496
6337
  l, u = res.confidence_interval()
6497
- l = xp_test.expand_dims(l, axis=-2)
6498
- u = xp_test.expand_dims(u, axis=-2)
6338
+ l = xp.expand_dims(l, axis=-2)
6339
+ u = xp.expand_dims(u, axis=-2)
6499
6340
 
6500
6341
  res = stats.ttest_1samp(x, popmean=l, axis=-2)
6501
6342
  ref = xp.broadcast_to(xp.asarray(0.05, dtype=xp.float64), res.pvalue.shape)
@@ -6505,8 +6346,9 @@ def test_ttest_1samp_popmean_array(xp):
6505
6346
  xp_assert_close(res.pvalue, ref)
6506
6347
 
6507
6348
 
6349
+ @make_xp_test_case(stats.describe)
6508
6350
  class TestDescribe:
6509
- @array_api_compatible
6351
+ @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask")
6510
6352
  def test_describe_scalar(self, xp):
6511
6353
  with suppress_warnings() as sup, \
6512
6354
  np.errstate(invalid="ignore", divide="ignore"):
@@ -6520,12 +6362,8 @@ class TestDescribe:
6520
6362
  xp_assert_equal(sk, xp.asarray(xp.nan))
6521
6363
  xp_assert_equal(kurt, xp.asarray(xp.nan))
6522
6364
 
6523
- @skip_xp_backends('cupy', reason='cupy/cupy#8391')
6524
- @pytest.mark.usefixtures("skip_xp_backends")
6525
- @array_api_compatible
6526
6365
  def test_describe_numbers(self, xp):
6527
- xp_test = array_namespace(xp.asarray(1.)) # numpy needs `concat`
6528
- x = xp_test.concat((xp.ones((3, 4)), xp.full((2, 4), 2.)))
6366
+ x = xp.concat((xp.ones((3, 4)), xp.full((2, 4), 2.)))
6529
6367
  nc = 5
6530
6368
  mmc = (xp.asarray([1., 1., 1., 1.]), xp.asarray([2., 2., 2., 2.]))
6531
6369
  mc = xp.asarray([1.4, 1.4, 1.4, 1.4])
@@ -6567,14 +6405,16 @@ class TestDescribe:
6567
6405
  assert_array_almost_equal(sk, skc)
6568
6406
  assert_array_almost_equal(kurt, kurtc, decimal=13)
6569
6407
 
6570
- @array_api_compatible
6571
6408
  def test_describe_nan_policy_other(self, xp):
6572
6409
  x = xp.arange(10.)
6573
- x = xp.where(x==9, xp.asarray(xp.nan), x)
6410
+ x = xp.where(x==9, xp.nan, x)
6574
6411
 
6575
- message = 'The input contains nan values'
6576
- with pytest.raises(ValueError, match=message):
6577
- stats.describe(x, nan_policy='raise')
6412
+ if is_lazy_array(x):
6413
+ with pytest.raises(TypeError, match='not supported for lazy arrays'):
6414
+ stats.describe(x, nan_policy='raise')
6415
+ else:
6416
+ with pytest.raises(ValueError, match='The input contains nan values'):
6417
+ stats.describe(x, nan_policy='raise')
6578
6418
 
6579
6419
  n, mm, m, v, sk, kurt = stats.describe(x, nan_policy='propagate')
6580
6420
  ref = xp.asarray(xp.nan)[()]
@@ -6588,8 +6428,11 @@ class TestDescribe:
6588
6428
 
6589
6429
  if is_numpy(xp):
6590
6430
  self.describe_nan_policy_omit_test()
6431
+ elif is_lazy_array(x):
6432
+ with pytest.raises(TypeError, match='not supported for lazy arrays'):
6433
+ stats.describe(x, nan_policy='omit')
6591
6434
  else:
6592
- message = "`nan_policy='omit' is incompatible with non-NumPy arrays."
6435
+ message = "nan_policy='omit' is incompatible with non-NumPy arrays."
6593
6436
  with pytest.raises(ValueError, match=message):
6594
6437
  stats.describe(x, nan_policy='omit')
6595
6438
 
@@ -6604,12 +6447,8 @@ class TestDescribe:
6604
6447
  attributes = ('nobs', 'minmax', 'mean', 'variance', 'skewness', 'kurtosis')
6605
6448
  check_named_results(actual, attributes)
6606
6449
 
6607
- @skip_xp_backends('cupy', reason='cupy/cupy#8391')
6608
- @pytest.mark.usefixtures("skip_xp_backends")
6609
- @array_api_compatible
6610
6450
  def test_describe_ddof(self, xp):
6611
- xp_test = array_namespace(xp.asarray(1.)) # numpy needs `concat`
6612
- x = xp_test.concat((xp.ones((3, 4)), xp.full((2, 4), 2.)))
6451
+ x = xp.concat((xp.ones((3, 4)), xp.full((2, 4), 2.)))
6613
6452
  nc = 5
6614
6453
  mmc = (xp.asarray([1., 1., 1., 1.]), xp.asarray([2., 2., 2., 2.]))
6615
6454
  mc = xp.asarray([1.4, 1.4, 1.4, 1.4])
@@ -6625,12 +6464,8 @@ class TestDescribe:
6625
6464
  xp_assert_close(sk, skc)
6626
6465
  xp_assert_close(kurt, kurtc)
6627
6466
 
6628
- @skip_xp_backends('cupy', reason='cupy/cupy#8391')
6629
- @pytest.mark.usefixtures("skip_xp_backends")
6630
- @array_api_compatible
6631
6467
  def test_describe_axis_none(self, xp):
6632
- xp_test = array_namespace(xp.asarray(1.)) # numpy needs `concat`
6633
- x = xp_test.concat((xp.ones((3, 4)), xp.full((2, 4), 2.)))
6468
+ x = xp.concat((xp.ones((3, 4)), xp.full((2, 4), 2.)))
6634
6469
 
6635
6470
  # expected values
6636
6471
  nc = 20
@@ -6651,32 +6486,14 @@ class TestDescribe:
6651
6486
  xp_assert_close(sk, skc)
6652
6487
  xp_assert_close(kurt, kurtc)
6653
6488
 
6654
- @array_api_compatible
6655
6489
  def test_describe_empty(self, xp):
6656
6490
  message = "The input must not be empty."
6657
6491
  with pytest.raises(ValueError, match=message):
6658
6492
  stats.describe(xp.asarray([]))
6659
6493
 
6660
6494
 
6661
- @array_api_compatible
6662
6495
  class NormalityTests:
6663
- def test_too_small(self, xp):
6664
- # 1D sample has too few observations -> warning/error
6665
- test_fun = getattr(stats, self.test_name)
6666
- x = xp.asarray(4.)
6667
- if is_numpy(xp):
6668
- with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
6669
- res = test_fun(x)
6670
- NaN = xp.asarray(xp.nan)
6671
- xp_assert_equal(res.statistic, NaN)
6672
- xp_assert_equal(res.pvalue, NaN)
6673
- else:
6674
- message = "...requires at least..."
6675
- with pytest.raises(ValueError, match=message):
6676
- test_fun(x)
6677
6496
 
6678
- @skip_xp_backends('cupy', reason='cupy/cupy#8391')
6679
- @pytest.mark.usefixtures("skip_xp_backends")
6680
6497
  @pytest.mark.parametrize("alternative", ['two-sided', 'less', 'greater'])
6681
6498
  def test_against_R(self, alternative, xp):
6682
6499
  # testa against R `dagoTest` from package `fBasics`
@@ -6708,8 +6525,6 @@ class NormalityTests:
6708
6525
  xp_assert_close(res_pvalue, ref_pvalue)
6709
6526
  check_named_results(res, ('statistic', 'pvalue'), xp=xp)
6710
6527
 
6711
- @skip_xp_backends('cupy', reason='cupy/cupy#8391')
6712
- @pytest.mark.usefixtures("skip_xp_backends")
6713
6528
  def test_nan(self, xp):
6714
6529
  # nan in input -> nan output (default nan_policy='propagate')
6715
6530
  test_fun = getattr(stats, self.test_name)
@@ -6721,6 +6536,8 @@ class NormalityTests:
6721
6536
  xp_assert_equal(res.statistic, NaN)
6722
6537
  xp_assert_equal(res.pvalue, NaN)
6723
6538
 
6539
+
6540
+ @make_xp_test_case(stats.skewtest)
6724
6541
  class TestSkewTest(NormalityTests):
6725
6542
  test_name = 'skewtest'
6726
6543
  case_ref = (1.98078826090875881, 0.04761502382843208) # statistic, pvalue
@@ -6734,28 +6551,25 @@ class TestSkewTest(NormalityTests):
6734
6551
 
6735
6552
  def test_skewtest_too_few_observations(self, xp):
6736
6553
  # Regression test for ticket #1492.
6737
- # skewtest requires at least 8 observations; 7 should raise a ValueError.
6554
+ # skewtest requires at least 8 observations; 7 should warn and return NaN.
6738
6555
  stats.skewtest(xp.arange(8.0))
6739
6556
 
6740
6557
  x = xp.arange(7.0)
6741
- if is_numpy(xp):
6742
- with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
6743
- res = stats.skewtest(x)
6744
- NaN = xp.asarray(xp.nan)
6745
- xp_assert_equal(res.statistic, NaN)
6746
- xp_assert_equal(res.pvalue, NaN)
6747
- else:
6748
- message = "`skewtest` requires at least 8 observations"
6749
- with pytest.raises(ValueError, match=message):
6750
- stats.skewtest(x)
6751
6558
 
6559
+ message = (too_small_1d_not_omit if is_numpy(xp)
6560
+ else "`skewtest` requires at least 8 valid observations")
6561
+ with pytest.warns(SmallSampleWarning, match=message):
6562
+ res = stats.skewtest(x)
6563
+ NaN = xp.asarray(xp.nan)
6564
+ xp_assert_equal(res.statistic, NaN)
6565
+ xp_assert_equal(res.pvalue, NaN)
6752
6566
 
6567
+
6568
+ @make_xp_test_case(stats.kurtosistest)
6753
6569
  class TestKurtosisTest(NormalityTests):
6754
6570
  test_name = 'kurtosistest'
6755
6571
  case_ref = (-0.01403734404759738, 0.98880018772590561) # statistic, pvalue
6756
6572
 
6757
- @skip_xp_backends('cupy', reason='cupy/cupy#8391')
6758
- @pytest.mark.usefixtures("skip_xp_backends")
6759
6573
  def test_intuitive(self, xp):
6760
6574
  # intuitive tests; see gh-13549. excess kurtosis of laplace is 3 > 0
6761
6575
  a2 = stats.laplace.rvs(size=10000, random_state=123)
@@ -6763,8 +6577,6 @@ class TestKurtosisTest(NormalityTests):
6763
6577
  pval = stats.kurtosistest(a2_xp, alternative='greater').pvalue
6764
6578
  xp_assert_close(pval, xp.asarray(0.0, dtype=a2_xp.dtype), atol=1e-15)
6765
6579
 
6766
- @skip_xp_backends('cupy', reason='cupy/cupy#8391')
6767
- @pytest.mark.usefixtures("skip_xp_backends")
6768
6580
  def test_gh9033_regression(self, xp):
6769
6581
  # regression test for issue gh-9033: x clearly non-normal but power of
6770
6582
  # negative denom needs to be handled correctly to reject normality
@@ -6773,37 +6585,36 @@ class TestKurtosisTest(NormalityTests):
6773
6585
  x = xp.asarray(x, dtype=xp.float64)
6774
6586
  assert stats.kurtosistest(x)[1] < 0.01
6775
6587
 
6776
- @skip_xp_backends('cupy', reason='cupy/cupy#8391')
6777
- @pytest.mark.usefixtures("skip_xp_backends")
6778
6588
  def test_kurtosistest_too_few_observations(self, xp):
6779
- # kurtosistest requires at least 5 observations; 4 should raise a ValueError.
6780
- # At least 20 are needed to avoid warning
6589
+ # kurtosistest requires at least 5 observations; 4 should warn and return NaN.
6781
6590
  # Regression test for ticket #1425.
6782
- stats.kurtosistest(xp.arange(20.0))
6783
-
6784
- message = "`kurtosistest` p-value may be inaccurate..."
6785
- with pytest.warns(UserWarning, match=message):
6786
- stats.kurtosistest(xp.arange(5.0))
6787
- with pytest.warns(UserWarning, match=message):
6788
- stats.kurtosistest(xp.arange(19.0))
6591
+ stats.kurtosistest(xp.arange(5.0))
6789
6592
 
6790
- x = xp.arange(4.0)
6791
- if is_numpy(xp):
6792
- with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
6793
- res = stats.skewtest(x)
6794
- NaN = xp.asarray(xp.nan)
6795
- xp_assert_equal(res.statistic, NaN)
6796
- xp_assert_equal(res.pvalue, NaN)
6797
- else:
6798
- message = "`kurtosistest` requires at least 5 observations"
6799
- with pytest.raises(ValueError, match=message):
6800
- stats.kurtosistest(x)
6593
+ message = (too_small_1d_not_omit if is_numpy(xp)
6594
+ else "`kurtosistest` requires at least 5 valid")
6595
+ with pytest.warns(SmallSampleWarning, match=message):
6596
+ res = stats.kurtosistest(xp.arange(4.))
6597
+ NaN = xp.asarray(xp.nan)
6598
+ xp_assert_equal(res.statistic, NaN)
6599
+ xp_assert_equal(res.pvalue, NaN)
6801
6600
 
6802
6601
 
6602
+ @make_xp_test_case(stats.normaltest)
6803
6603
  class TestNormalTest(NormalityTests):
6804
6604
  test_name = 'normaltest'
6805
6605
  case_ref = (3.92371918158185551, 0.14059672529747502) # statistic, pvalue
6806
6606
 
6607
+ def test_too_few_observations(self, xp):
6608
+ stats.normaltest(xp.arange(8.))
6609
+
6610
+ # 1D sample has too few observations -> warning / NaN output
6611
+ # specific warning messages tested for `skewtest`/`kurtosistest`
6612
+ with pytest.warns(SmallSampleWarning):
6613
+ res = stats.normaltest(xp.arange(7.))
6614
+ NaN = xp.asarray(xp.nan)
6615
+ xp_assert_equal(res.statistic, NaN)
6616
+ xp_assert_equal(res.pvalue, NaN)
6617
+
6807
6618
 
6808
6619
  class TestRankSums:
6809
6620
 
@@ -6828,7 +6639,7 @@ class TestRankSums:
6828
6639
  stats.ranksums(self.x, self.y, alternative='foobar')
6829
6640
 
6830
6641
 
6831
- @array_api_compatible
6642
+ @make_xp_test_case(stats.jarque_bera)
6832
6643
  class TestJarqueBera:
6833
6644
  def test_jarque_bera_against_R(self, xp):
6834
6645
  # library(tseries)
@@ -6844,8 +6655,7 @@ class TestJarqueBera:
6844
6655
  xp_assert_close(res.pvalue, ref[1])
6845
6656
 
6846
6657
  @skip_xp_backends(np_only=True)
6847
- @pytest.mark.usefixtures("skip_xp_backends")
6848
- def test_jarque_bera_array_like(self):
6658
+ def test_jarque_bera_array_like(self, xp):
6849
6659
  # array-like only relevant for NumPy
6850
6660
  np.random.seed(987654321)
6851
6661
  x = np.random.normal(0, 1, 100000)
@@ -6857,18 +6667,15 @@ class TestJarqueBera:
6857
6667
  assert JB1 == JB2 == JB3 == jb_test1.statistic == jb_test2.statistic == jb_test3.statistic # noqa: E501
6858
6668
  assert p1 == p2 == p3 == jb_test1.pvalue == jb_test2.pvalue == jb_test3.pvalue
6859
6669
 
6860
- def test_jarque_bera_size(self, xp):
6670
+ @skip_xp_backends('array_api_strict', reason='Noisy; see TestSkew')
6671
+ def test_jarque_bera_too_few_observations(self, xp):
6861
6672
  x = xp.asarray([])
6862
- if is_numpy(xp):
6863
- with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
6864
- res = stats.jarque_bera(x)
6673
+
6674
+ with pytest.warns(SmallSampleWarning, match=too_small_1d_not_omit):
6675
+ res = stats.jarque_bera(x)
6865
6676
  NaN = xp.asarray(xp.nan)
6866
6677
  xp_assert_equal(res.statistic, NaN)
6867
6678
  xp_assert_equal(res.pvalue, NaN)
6868
- else:
6869
- message = "At least one observation is required."
6870
- with pytest.raises(ValueError, match=message):
6871
- res = stats.jarque_bera(x)
6872
6679
 
6873
6680
  def test_axis(self, xp):
6874
6681
  rng = np.random.RandomState(seed=122398129)
@@ -6882,8 +6689,8 @@ class TestJarqueBera:
6882
6689
  res = stats.jarque_bera(x, axis=1)
6883
6690
  s0, p0 = stats.jarque_bera(x[0, :])
6884
6691
  s1, p1 = stats.jarque_bera(x[1, :])
6885
- xp_assert_close(res.statistic, xp.asarray([s0, s1]))
6886
- xp_assert_close(res.pvalue, xp.asarray([p0, p1]))
6692
+ xp_assert_close(res.statistic, xp.stack([s0, s1]))
6693
+ xp_assert_close(res.pvalue, xp.stack([p0, p1]))
6887
6694
 
6888
6695
  resT = stats.jarque_bera(x.T, axis=0)
6889
6696
  xp_assert_close(res.statistic, resT.statistic)
@@ -7104,8 +6911,9 @@ def check_equal_pmean(*args, **kwargs):
7104
6911
  return check_equal_xmean(*args, mean_fun=stats.pmean, **kwargs)
7105
6912
 
7106
6913
 
7107
- @array_api_compatible
6914
+ @make_xp_test_case(stats.hmean)
7108
6915
  class TestHMean:
6916
+ @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask")
7109
6917
  def test_0(self, xp):
7110
6918
  a = [1, 0, 2]
7111
6919
  desired = 0
@@ -7121,17 +6929,17 @@ class TestHMean:
7121
6929
  desired = 4. / (1. / 1 + 1. / 2 + 1. / 3 + 1. / 4)
7122
6930
  check_equal_hmean(a, desired, xp=xp)
7123
6931
 
6932
+ @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask")
6933
+ @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask")
7124
6934
  def test_1d_with_zero(self, xp):
7125
6935
  a = np.array([1, 0])
7126
6936
  desired = 0.0
7127
6937
  check_equal_hmean(a, desired, xp=xp, rtol=0.0)
7128
6938
 
7129
- @skip_xp_backends('array_api_strict',
7130
- reason=("`array_api_strict.where` `fillvalue` doesn't "
7131
- "accept Python scalars. See data-apis/array-api#807."))
7132
- @pytest.mark.usefixtures("skip_xp_backends")
6939
+ @pytest.mark.filterwarnings(
6940
+ "ignore:divide by zero encountered:RuntimeWarning"
6941
+ ) # for dask
7133
6942
  def test_1d_with_negative_value(self, xp):
7134
- # Won't work for array_api_strict for now, but see data-apis/array-api#807
7135
6943
  a = np.array([1, 0, -1])
7136
6944
  message = "The harmonic mean is only defined..."
7137
6945
  with pytest.warns(RuntimeWarning, match=message):
@@ -7150,6 +6958,7 @@ class TestHMean:
7150
6958
  desired = np.array([22.88135593, 39.13043478, 52.90076336, 65.45454545])
7151
6959
  check_equal_hmean(a, desired, axis=0, xp=xp)
7152
6960
 
6961
+ @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask")
7153
6962
  def test_2d_axis0_with_zero(self, xp):
7154
6963
  a = [[10, 0, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
7155
6964
  desired = np.array([22.88135593, 0.0, 52.90076336, 65.45454545])
@@ -7161,16 +6970,16 @@ class TestHMean:
7161
6970
  desired = np.array([19.2, 63.03939962, 103.80078637])
7162
6971
  check_equal_hmean(a, desired, axis=1, xp=xp)
7163
6972
 
6973
+ @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask")
7164
6974
  def test_2d_axis1_with_zero(self, xp):
7165
6975
  a = [[10, 0, 30, 40], [50, 60, 70, 80], [90, 100, 110, 120]]
7166
6976
  desired = np.array([0.0, 63.03939962, 103.80078637])
7167
6977
  check_equal_hmean(a, desired, axis=1, xp=xp)
7168
6978
 
7169
- @pytest.mark.skip_xp_backends(
6979
+ @skip_xp_backends(
7170
6980
  np_only=True,
7171
6981
  reason='array-likes only supported for NumPy backend',
7172
6982
  )
7173
- @pytest.mark.usefixtures("skip_xp_backends")
7174
6983
  def test_weights_1d_list(self, xp):
7175
6984
  # Desired result from:
7176
6985
  # https://www.hackmath.net/en/math-problem/35871
@@ -7219,8 +7028,11 @@ class TestHMean:
7219
7028
  dtype=np.float64, xp=xp)
7220
7029
 
7221
7030
 
7222
- @array_api_compatible
7031
+ @make_xp_test_case(stats.gmean)
7223
7032
  class TestGMean:
7033
+ @pytest.mark.filterwarnings(
7034
+ "ignore:divide by zero encountered in log:RuntimeWarning:dask"
7035
+ )
7224
7036
  def test_0(self, xp):
7225
7037
  a = [1, 0, 2]
7226
7038
  desired = 0
@@ -7273,6 +7085,9 @@ class TestGMean:
7273
7085
  desired = 1e200
7274
7086
  check_equal_gmean(a, desired, rtol=1e-13, xp=xp)
7275
7087
 
7088
+ @pytest.mark.filterwarnings(
7089
+ "ignore:divide by zero encountered in log:RuntimeWarning:dask"
7090
+ )
7276
7091
  def test_1d_with_0(self, xp):
7277
7092
  # Test a 1d case with zero element
7278
7093
  a = [10, 20, 30, 40, 50, 60, 70, 80, 90, 0]
@@ -7280,6 +7095,9 @@ class TestGMean:
7280
7095
  with np.errstate(all='ignore'):
7281
7096
  check_equal_gmean(a, desired, xp=xp)
7282
7097
 
7098
+ @pytest.mark.filterwarnings(
7099
+ "ignore:invalid value encountered in log:RuntimeWarning:dask"
7100
+ )
7283
7101
  def test_1d_neg(self, xp):
7284
7102
  # Test a 1d case with negative element
7285
7103
  a = [10, 20, 30, 40, 50, 60, 70, 80, 90, -1]
@@ -7287,11 +7105,10 @@ class TestGMean:
7287
7105
  with np.errstate(invalid='ignore'):
7288
7106
  check_equal_gmean(a, desired, xp=xp)
7289
7107
 
7290
- @pytest.mark.skip_xp_backends(
7108
+ @skip_xp_backends(
7291
7109
  np_only=True,
7292
7110
  reason='array-likes only supported for NumPy backend',
7293
7111
  )
7294
- @pytest.mark.usefixtures("skip_xp_backends")
7295
7112
  def test_weights_1d_list(self, xp):
7296
7113
  # Desired result from:
7297
7114
  # https://www.dummies.com/education/math/business-statistics/how-to-find-the-weighted-geometric-mean-of-a-data-set/
@@ -7325,7 +7142,7 @@ class TestGMean:
7325
7142
  dtype=np.float64, xp=xp)
7326
7143
 
7327
7144
 
7328
- @array_api_compatible
7145
+ @make_xp_test_case(stats.pmean)
7329
7146
  class TestPMean:
7330
7147
 
7331
7148
  def pmean_reference(a, p):
@@ -7353,15 +7170,13 @@ class TestPMean:
7353
7170
  desired = np.sqrt((1**2 + 2**2 + 3**2 + 4**2) / 4)
7354
7171
  check_equal_pmean(a, p, desired, xp=xp)
7355
7172
 
7173
+ @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask")
7174
+ @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask")
7356
7175
  def test_1d_with_zero(self, xp):
7357
7176
  a, p = np.array([1, 0]), -1
7358
7177
  desired = 0.0
7359
7178
  check_equal_pmean(a, p, desired, rtol=0.0, xp=xp)
7360
7179
 
7361
- @skip_xp_backends('array_api_strict',
7362
- reason=("`array_api_strict.where` `fillvalue` doesn't "
7363
- "accept Python scalars. See data-apis/array-api#807."))
7364
- @pytest.mark.usefixtures("skip_xp_backends")
7365
7180
  def test_1d_with_negative_value(self, xp):
7366
7181
  a, p = np.array([1, 0, -1]), 1.23
7367
7182
  message = "The power mean is only defined..."
@@ -7406,11 +7221,10 @@ class TestPMean:
7406
7221
  desired = TestPMean.wpmean_reference(np.array(a), p, weights)
7407
7222
  check_equal_pmean(a, p, desired, weights=weights, rtol=1e-5, xp=xp)
7408
7223
 
7409
- @pytest.mark.skip_xp_backends(
7224
+ @skip_xp_backends(
7410
7225
  np_only=True,
7411
7226
  reason='array-likes only supported for NumPy backend',
7412
7227
  )
7413
- @pytest.mark.usefixtures("skip_xp_backends")
7414
7228
  def test_weights_1d_list(self, xp):
7415
7229
  a, p = [2, 10, 6], -1.23456789
7416
7230
  weights = [10, 5, 3]
@@ -7448,87 +7262,90 @@ class TestPMean:
7448
7262
  check_equal_pmean(a, p, desired, axis=axis, weights=weights, rtol=1e-5, xp=xp)
7449
7263
 
7450
7264
 
7265
+ @make_xp_test_case(stats.gstd)
7451
7266
  class TestGSTD:
7452
7267
  # must add 1 as `gstd` is only defined for positive values
7453
- array_1d = np.arange(2 * 3 * 4) + 1
7268
+ array_1d = (np.arange(2 * 3 * 4) + 1).tolist()
7454
7269
  gstd_array_1d = 2.294407613602
7455
- array_3d = array_1d.reshape(2, 3, 4)
7270
+ array_3d = np.reshape(array_1d, (2, 3, 4)).tolist()
7456
7271
 
7457
- def test_1d_array(self):
7458
- gstd_actual = stats.gstd(self.array_1d)
7459
- assert_allclose(gstd_actual, self.gstd_array_1d)
7272
+ def test_1d_array(self, xp):
7273
+ gstd_actual = stats.gstd(xp.asarray(self.array_1d))
7274
+ xp_assert_close(gstd_actual, xp.asarray(self.gstd_array_1d))
7460
7275
 
7461
- def test_1d_numeric_array_like_input(self):
7276
+ @skip_xp_backends(np_only=True, reason="Only NumPy supports array-like input")
7277
+ def test_1d_numeric_array_like_input(self, xp):
7462
7278
  gstd_actual = stats.gstd(tuple(self.array_1d))
7463
7279
  assert_allclose(gstd_actual, self.gstd_array_1d)
7464
7280
 
7465
- def test_raises_value_error_non_numeric_input(self):
7466
- # this is raised by NumPy, but it's quite interpretable
7467
- with pytest.raises(TypeError, match="ufunc 'log' not supported"):
7281
+ @skip_xp_invalid_arg
7282
+ def test_raises_error_non_numeric_input(self, xp):
7283
+ message = "could not convert string to float|The DType..."
7284
+ with pytest.raises((ValueError, TypeError), match=message):
7468
7285
  stats.gstd('You cannot take the logarithm of a string.')
7469
7286
 
7287
+ @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask")
7288
+ @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask")
7470
7289
  @pytest.mark.parametrize('bad_value', (0, -1, np.inf, np.nan))
7471
- def test_returns_nan_invalid_value(self, bad_value):
7472
- x = np.append(self.array_1d, [bad_value])
7473
- if np.isfinite(bad_value):
7290
+ def test_returns_nan_invalid_value(self, bad_value, xp):
7291
+ x = xp.asarray(self.array_1d + [bad_value])
7292
+ if np.isfinite(bad_value) and not is_lazy_array(x):
7474
7293
  message = "The geometric standard deviation is only defined..."
7475
7294
  with pytest.warns(RuntimeWarning, match=message):
7476
7295
  res = stats.gstd(x)
7477
7296
  else:
7478
7297
  res = stats.gstd(x)
7479
- assert_equal(res, np.nan)
7298
+ xp_assert_equal(res, xp.asarray(np.nan))
7480
7299
 
7481
- def test_propagates_nan_values(self):
7482
- a = array([[1, 1, 1, 16], [np.nan, 1, 2, 3]])
7300
+ def test_propagates_nan_values(self, xp):
7301
+ a = xp.asarray([[1, 1, 1, 16], [xp.nan, 1, 2, 3]])
7483
7302
  gstd_actual = stats.gstd(a, axis=1)
7484
- assert_allclose(gstd_actual, np.array([4, np.nan]))
7303
+ xp_assert_close(gstd_actual, xp.asarray([4, np.nan]))
7485
7304
 
7486
- def test_ddof_equal_to_number_of_observations(self):
7487
- with pytest.warns(RuntimeWarning, match='Degrees of freedom <= 0'):
7488
- assert_equal(stats.gstd(self.array_1d, ddof=self.array_1d.size), np.inf)
7489
-
7490
- def test_3d_array(self):
7491
- gstd_actual = stats.gstd(self.array_3d, axis=None)
7492
- assert_allclose(gstd_actual, self.gstd_array_1d)
7493
-
7494
- def test_3d_array_axis_type_tuple(self):
7495
- gstd_actual = stats.gstd(self.array_3d, axis=(1,2))
7496
- assert_allclose(gstd_actual, [2.12939215, 1.22120169])
7305
+ def test_ddof_equal_to_number_of_observations(self, xp):
7306
+ x = xp.asarray(self.array_1d)
7307
+ res = stats.gstd(x, ddof=x.shape[0])
7308
+ xp_assert_equal(res, xp.asarray(xp.nan))
7497
7309
 
7498
- def test_3d_array_axis_0(self):
7499
- gstd_actual = stats.gstd(self.array_3d, axis=0)
7500
- gstd_desired = np.array([
7310
+ def test_3d_array(self, xp):
7311
+ x = xp.asarray(self.array_3d)
7312
+ gstd_actual = stats.gstd(x, axis=None)
7313
+ ref = xp.asarray(self.gstd_array_1d)
7314
+ xp_assert_close(gstd_actual, ref)
7315
+
7316
+ def test_3d_array_axis_type_tuple(self, xp):
7317
+ x = xp.asarray(self.array_3d)
7318
+ gstd_actual = stats.gstd(x, axis=(1, 2))
7319
+ ref = xp.asarray([2.12939215, 1.22120169])
7320
+ xp_assert_close(gstd_actual, ref)
7321
+
7322
+ def test_3d_array_axis_0(self, xp):
7323
+ x = xp.asarray(self.array_3d)
7324
+ gstd_actual = stats.gstd(x, axis=0)
7325
+ gstd_desired = xp.asarray([
7501
7326
  [6.1330555493918, 3.958900210120, 3.1206598248344, 2.6651441426902],
7502
7327
  [2.3758135028411, 2.174581428192, 2.0260062829505, 1.9115518327308],
7503
7328
  [1.8205343606803, 1.746342404566, 1.6846557065742, 1.6325269194382]
7504
7329
  ])
7505
- assert_allclose(gstd_actual, gstd_desired)
7330
+ xp_assert_close(gstd_actual, gstd_desired)
7506
7331
 
7507
- def test_3d_array_axis_1(self):
7508
- gstd_actual = stats.gstd(self.array_3d, axis=1)
7509
- gstd_desired = np.array([
7332
+ def test_3d_array_axis_1(self, xp):
7333
+ x = xp.asarray(self.array_3d)
7334
+ gstd_actual = stats.gstd(x, axis=1)
7335
+ gstd_desired = xp.asarray([
7510
7336
  [3.118993630946, 2.275985934063, 1.933995977619, 1.742896469724],
7511
7337
  [1.271693593916, 1.254158641801, 1.238774141609, 1.225164057869]
7512
7338
  ])
7513
- assert_allclose(gstd_actual, gstd_desired)
7339
+ xp_assert_close(gstd_actual, gstd_desired)
7514
7340
 
7515
- def test_3d_array_axis_2(self):
7516
- gstd_actual = stats.gstd(self.array_3d, axis=2)
7517
- gstd_desired = np.array([
7341
+ def test_3d_array_axis_2(self, xp):
7342
+ x = xp.asarray(self.array_3d)
7343
+ gstd_actual = stats.gstd(x, axis=2)
7344
+ gstd_desired = xp.asarray([
7518
7345
  [1.8242475707664, 1.2243686572447, 1.1318311657788],
7519
7346
  [1.0934830582351, 1.0724479791887, 1.0591498540749]
7520
7347
  ])
7521
- assert_allclose(gstd_actual, gstd_desired)
7522
-
7523
- def test_masked_3d_array(self):
7524
- ma = np.ma.masked_where(self.array_3d > 16, self.array_3d)
7525
- message = "`gstd` support for masked array input was deprecated in..."
7526
- with pytest.warns(DeprecationWarning, match=message):
7527
- gstd_actual = stats.gstd(ma, axis=2)
7528
- gstd_desired = stats.gstd(self.array_3d, axis=2)
7529
- mask = [[0, 0, 0], [0, 1, 1]]
7530
- assert_allclose(gstd_actual, gstd_desired)
7531
- assert_equal(gstd_actual.mask, mask)
7348
+ xp_assert_close(gstd_actual, gstd_desired)
7532
7349
 
7533
7350
 
7534
7351
  def test_binomtest():
@@ -8057,15 +7874,49 @@ class TestFOneWay:
8057
7874
  assert_equal(F, 2.0)
8058
7875
  assert_allclose(p, 1 - np.sqrt(0.5), rtol=1e-14)
8059
7876
 
7877
+ def test_unequal_var(self):
7878
+ # toy samples with unequal variances and different observations
7879
+ samples = [[-50.42, 40.31, -18.09, 35.58, -6.8, 0.22],
7880
+ [23.44, 4.5, 15.1, 9.66],
7881
+ [11.94, 11.1 , 9.87, 9.09, 3.33]]
7882
+
7883
+ F, p = stats.f_oneway(*samples, equal_var=False)
7884
+
7885
+ # R language as benchmark
7886
+ # group1 <- c(-50.42, 40.31, -18.09, 35.58, -6.8, 0.22)
7887
+ # group2 <- c(23.44, 4.5, 15.1, 9.66)
7888
+ # group3 <- c(11.94, 11.1 , 9.87, 9.09, 3.33)
7889
+ #
7890
+ # data <- data.frame(
7891
+ # value = c(group1, group2, group3),
7892
+ # group = factor(c(rep("G1", length(group1)),
7893
+ # rep("G2", length(group2)),
7894
+ # rep("G3", length(group3))))
7895
+ # )
7896
+ # welch_anova <- oneway.test(value ~ group, data = data, var.equal = FALSE)
7897
+ # welch_anova$statistic
7898
+ ## F: 0.609740409019517
7899
+ # welch_anova$p.value
7900
+ ## 0.574838941286302
7901
+
7902
+ assert_allclose(F, 0.609740409019517, rtol=1e-14)
7903
+ assert_allclose(p, 0.574838941286302, rtol=1e-14)
7904
+
7905
+ def test_equal_var_input_validation(self):
7906
+ samples = [[-50.42, 40.31, -18.09, 35.58, -6.8, 0.22],
7907
+ [23.44, 4.5, 15.1, 9.66],
7908
+ [11.94, 11.1 , 9.87, 9.09, 3.33]]
7909
+
7910
+ message = "Expected a boolean value for 'equal_var'"
7911
+ with pytest.raises(TypeError, match=message):
7912
+ stats.f_oneway(*samples, equal_var="False")
7913
+
8060
7914
  def test_known_exact(self):
8061
7915
  # Another trivial dataset for which the exact F and p can be
8062
- # calculated.
7916
+ # calculated on most platforms
8063
7917
  F, p = stats.f_oneway([2], [2], [2, 3, 4])
8064
- # The use of assert_equal might be too optimistic, but the calculation
8065
- # in this case is trivial enough that it is likely to go through with
8066
- # no loss of precision.
8067
- assert_equal(F, 3/5)
8068
- assert_equal(p, 5/8)
7918
+ assert_allclose(F, 3/5, rtol=1e-15) # assert_equal fails on some CI platforms
7919
+ assert_allclose(p, 5/8, rtol=1e-15)
8069
7920
 
8070
7921
  def test_large_integer_array(self):
8071
7922
  a = np.array([655, 788], dtype=np.uint16)
@@ -8322,12 +8173,8 @@ class TestKruskal:
8322
8173
  stats.kruskal()
8323
8174
 
8324
8175
 
8325
- @skip_xp_backends(cpu_only=True,
8326
- exceptions=['cupy', 'jax.numpy'],
8327
- reason=('Delegation for `special.stdtr` only implemented '
8328
- 'for CuPy and JAX.'))
8329
- @pytest.mark.usefixtures("skip_xp_backends")
8330
- @array_api_compatible
8176
+
8177
+ @make_xp_test_case(stats.combine_pvalues)
8331
8178
  class TestCombinePvalues:
8332
8179
  # Reference values computed using the following R code:
8333
8180
  # options(digits=16)
@@ -8372,7 +8219,6 @@ class TestCombinePvalues:
8372
8219
  @pytest.mark.parametrize("variant", ["single", "all", "random"])
8373
8220
  @pytest.mark.parametrize("method", methods)
8374
8221
  def test_monotonicity(self, variant, method, xp):
8375
- xp_test = array_namespace(xp.asarray(1))
8376
8222
  # Test that result increases monotonically with respect to input.
8377
8223
  m, n = 10, 7
8378
8224
  rng = np.random.default_rng(278448169958891062669391462690811630763)
@@ -8383,15 +8229,15 @@ class TestCombinePvalues:
8383
8229
  # column (all), or independently down each column (random).
8384
8230
  if variant == "single":
8385
8231
  pvaluess = xp.broadcast_to(xp.asarray(rng.random(n)), (m, n))
8386
- pvaluess = xp_test.concat([xp.reshape(xp.linspace(0.1, 0.9, m), (-1, 1)),
8387
- pvaluess[:, 1:]], axis=1)
8232
+ pvaluess = xp.concat([xp.reshape(xp.linspace(0.1, 0.9, m), (-1, 1)),
8233
+ pvaluess[:, 1:]], axis=1)
8388
8234
  elif variant == "all":
8389
8235
  pvaluess = xp.broadcast_to(xp.linspace(0.1, 0.9, m), (n, m)).T
8390
8236
  elif variant == "random":
8391
- pvaluess = xp_test.sort(xp.asarray(rng.uniform(0, 1, size=(m, n))), axis=0)
8237
+ pvaluess = xp.sort(xp.asarray(rng.uniform(0, 1, size=(m, n))), axis=0)
8392
8238
 
8393
8239
  combined_pvalues = xp.asarray([
8394
- stats.combine_pvalues(pvaluess[i, :], method=method)[1]
8240
+ float(stats.combine_pvalues(pvaluess[i, :], method=method)[1])
8395
8241
  for i in range(pvaluess.shape[0])
8396
8242
  ])
8397
8243
  assert xp.all(combined_pvalues[1:] - combined_pvalues[:-1] >= 0)
@@ -9569,7 +9415,6 @@ class TestLMoment:
9569
9415
  assert_allclose(res, ref, rtol=1e-15)
9570
9416
 
9571
9417
 
9572
- @array_api_compatible
9573
9418
  class TestXP_Mean:
9574
9419
  @pytest.mark.parametrize('axis', [None, 1, -1, (-2, 2)])
9575
9420
  @pytest.mark.parametrize('weights', [None, True])
@@ -9597,6 +9442,8 @@ class TestXP_Mean:
9597
9442
  with pytest.raises(ValueError, match=message):
9598
9443
  _xp_mean(x, weights=w)
9599
9444
 
9445
+ @pytest.mark.filterwarnings("ignore:divide by zero encountered:RuntimeWarning:dask")
9446
+ @pytest.mark.filterwarnings("ignore:invalid value encountered:RuntimeWarning:dask")
9600
9447
  def test_special_cases(self, xp):
9601
9448
  # weights sum to zero
9602
9449
  weights = xp.asarray([-1., 0., 1.])
@@ -9610,15 +9457,21 @@ class TestXP_Mean:
9610
9457
  res = _xp_mean(xp.asarray([1., 1., 2.]), weights=weights)
9611
9458
  xp_assert_close(res, xp.asarray(np.inf))
9612
9459
 
9460
+ @pytest.mark.filterwarnings(
9461
+ "ignore:invalid value encountered:RuntimeWarning"
9462
+ ) # for dask
9613
9463
  def test_nan_policy(self, xp):
9614
9464
  x = xp.arange(10.)
9615
9465
  mask = (x == 3)
9616
- x = xp.where(mask, xp.asarray(xp.nan), x)
9466
+ x = xp.where(mask, xp.nan, x)
9617
9467
 
9618
9468
  # nan_policy='raise' raises an error
9619
- message = 'The input contains nan values'
9620
- with pytest.raises(ValueError, match=message):
9621
- _xp_mean(x, nan_policy='raise')
9469
+ if is_lazy_array(x):
9470
+ with pytest.raises(TypeError, match='not supported for lazy arrays'):
9471
+ _xp_mean(x, nan_policy='raise')
9472
+ else:
9473
+ with pytest.raises(ValueError, match='The input contains nan values'):
9474
+ _xp_mean(x, nan_policy='raise')
9622
9475
 
9623
9476
  # `nan_policy='propagate'` is the default, and the result is NaN
9624
9477
  res1 = _xp_mean(x)
@@ -9634,11 +9487,16 @@ class TestXP_Mean:
9634
9487
 
9635
9488
  # `nan_policy='omit'` omits NaNs in `weights`, too
9636
9489
  weights = xp.ones(10)
9637
- weights = xp.where(mask, xp.asarray(xp.nan), weights)
9490
+ weights = xp.where(mask, xp.nan, weights)
9638
9491
  res = _xp_mean(xp.arange(10.), weights=weights, nan_policy='omit')
9639
9492
  ref = xp.mean(x[~mask])
9640
9493
  xp_assert_close(res, ref)
9641
9494
 
9495
+ @skip_xp_backends(eager_only=True)
9496
+ def test_nan_policy_warns(self, xp):
9497
+ x = xp.arange(10.)
9498
+ x = xp.where(x == 3, xp.nan, x)
9499
+
9642
9500
  # Check for warning if omitting NaNs causes empty slice
9643
9501
  message = 'After omitting NaNs...'
9644
9502
  with pytest.warns(RuntimeWarning, match=message):
@@ -9663,6 +9521,9 @@ class TestXP_Mean:
9663
9521
  ref = xp.asarray([])
9664
9522
  xp_assert_equal(res, ref)
9665
9523
 
9524
+ @pytest.mark.filterwarnings(
9525
+ "ignore:overflow encountered in reduce:RuntimeWarning"
9526
+ ) # for dask
9666
9527
  def test_dtype(self, xp):
9667
9528
  max = xp.finfo(xp.float32).max
9668
9529
  x_np = np.asarray([max, max], dtype=np.float32)
@@ -9695,9 +9556,6 @@ class TestXP_Mean:
9695
9556
  xp_assert_close(res, xp.asarray(ref))
9696
9557
 
9697
9558
 
9698
- @array_api_compatible
9699
- @pytest.mark.usefixtures("skip_xp_backends")
9700
- @skip_xp_backends('jax.numpy', reason='JAX arrays do not support item assignment')
9701
9559
  class TestXP_Var:
9702
9560
  @pytest.mark.parametrize('axis', [None, 1, -1, (-2, 2)])
9703
9561
  @pytest.mark.parametrize('keepdims', [False, True])
@@ -9732,12 +9590,7 @@ class TestXP_Var:
9732
9590
  def test_nan_policy(self, xp):
9733
9591
  x = xp.arange(10.)
9734
9592
  mask = (x == 3)
9735
- x = xp.where(mask, xp.asarray(xp.nan), x)
9736
-
9737
- # nan_policy='raise' raises an error
9738
- message = 'The input contains nan values'
9739
- with pytest.raises(ValueError, match=message):
9740
- _xp_var(x, nan_policy='raise')
9593
+ x = xp.where(mask, xp.nan, x)
9741
9594
 
9742
9595
  # `nan_policy='propagate'` is the default, and the result is NaN
9743
9596
  res1 = _xp_var(x)
@@ -9748,10 +9601,14 @@ class TestXP_Var:
9748
9601
 
9749
9602
  # `nan_policy='omit'` omits NaNs in `x`
9750
9603
  res = _xp_var(x, nan_policy='omit')
9751
- xp_test = array_namespace(x) # torch has different default correction
9752
- ref = xp_test.var(x[~mask])
9604
+ ref = xp.var(x[~mask])
9753
9605
  xp_assert_close(res, ref)
9754
9606
 
9607
+ @skip_xp_backends(eager_only=True)
9608
+ def test_nan_policy_warns(self, xp):
9609
+ x = xp.arange(10.)
9610
+ x = xp.where(x == 3, xp.nan, x)
9611
+
9755
9612
  # Check for warning if omitting NaNs causes empty slice
9756
9613
  message = 'After omitting NaNs...'
9757
9614
  with pytest.warns(RuntimeWarning, match=message):
@@ -9759,6 +9616,13 @@ class TestXP_Var:
9759
9616
  ref = xp.asarray(xp.nan)
9760
9617
  xp_assert_equal(res, ref)
9761
9618
 
9619
+ @skip_xp_backends(eager_only=True)
9620
+ def test_nan_policy_raise(self, xp):
9621
+ # nan_policy='raise' raises an error when NaNs are present
9622
+ message = 'The input contains nan values'
9623
+ with pytest.raises(ValueError, match=message):
9624
+ _xp_var(xp.asarray([1, 2, xp.nan]), nan_policy='raise')
9625
+
9762
9626
  def test_empty(self, xp):
9763
9627
  message = 'One or more sample arguments is too small...'
9764
9628
  with pytest.warns(SmallSampleWarning, match=message):
@@ -9776,17 +9640,18 @@ class TestXP_Var:
9776
9640
  ref = xp.asarray([])
9777
9641
  xp_assert_equal(res, ref)
9778
9642
 
9643
+ @pytest.mark.filterwarnings(
9644
+ "ignore:overflow encountered in reduce:RuntimeWarning"
9645
+ ) # Overflow occurs for float32 input
9779
9646
  def test_dtype(self, xp):
9780
9647
  max = xp.finfo(xp.float32).max
9781
9648
  x_np = np.asarray([max, max/2], dtype=np.float32)
9782
9649
  x_xp = xp.asarray(x_np)
9783
9650
 
9784
- # Overflow occurs for float32 input
9785
- with np.errstate(over='ignore'):
9786
- res = _xp_var(x_xp)
9787
- ref = np.var(x_np)
9788
- np.testing.assert_equal(ref, np.inf)
9789
- xp_assert_close(res, xp.asarray(ref))
9651
+ res = _xp_var(x_xp)
9652
+ ref = np.var(x_np)
9653
+ np.testing.assert_equal(ref, np.inf)
9654
+ xp_assert_close(res, xp.asarray(ref))
9790
9655
 
9791
9656
  # correct result is returned if `float64` is used
9792
9657
  res = _xp_var(x_xp, dtype=xp.float64)
@@ -9799,7 +9664,6 @@ class TestXP_Var:
9799
9664
  y = xp.arange(10.)
9800
9665
  xp_assert_equal(_xp_var(x), _xp_var(y))
9801
9666
 
9802
- @pytest.mark.skip_xp_backends('array_api_strict', reason='needs array-api#850')
9803
9667
  def test_complex_gh22404(self, xp):
9804
9668
  rng = np.random.default_rng(90359458245906)
9805
9669
  x, y = rng.random((2, 20))
@@ -9808,7 +9672,6 @@ class TestXP_Var:
9808
9672
  xp_assert_close(res, xp.asarray(ref), check_dtype=False)
9809
9673
 
9810
9674
 
9811
- @array_api_compatible
9812
9675
  def test_chk_asarray(xp):
9813
9676
  rng = np.random.default_rng(2348923425434)
9814
9677
  x0 = rng.random(size=(2, 3, 4))
@@ -9830,10 +9693,7 @@ def test_chk_asarray(xp):
9830
9693
  assert_equal(axis_out, axis)
9831
9694
 
9832
9695
 
9833
- @pytest.mark.skip_xp_backends('numpy',
9834
- reason='These parameters *are* compatible with NumPy')
9835
- @pytest.mark.usefixtures("skip_xp_backends")
9836
- @array_api_compatible
9696
+ @skip_xp_backends('numpy', reason='These parameters *are* compatible with NumPy')
9837
9697
  def test_axis_nan_policy_keepdims_nanpolicy(xp):
9838
9698
  # this test does not need to be repeated for every function
9839
9699
  # using the _axis_nan_policy decorator. The test is here