test_nanfunctions.py 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438
  1. import inspect
  2. import warnings
  3. from functools import partial
  4. import pytest
  5. import numpy as np
  6. from numpy._core.numeric import normalize_axis_tuple
  7. from numpy.exceptions import AxisError, ComplexWarning
  8. from numpy.lib._nanfunctions_impl import _nan_mask, _replace_nan
  9. from numpy.testing import (
  10. assert_,
  11. assert_almost_equal,
  12. assert_array_equal,
  13. assert_equal,
  14. assert_raises,
  15. assert_raises_regex,
  16. )
  17. # Test data
  18. _ndat = np.array([[0.6244, np.nan, 0.2692, 0.0116, np.nan, 0.1170],
  19. [0.5351, -0.9403, np.nan, 0.2100, 0.4759, 0.2833],
  20. [np.nan, np.nan, np.nan, 0.1042, np.nan, -0.5954],
  21. [0.1610, np.nan, np.nan, 0.1859, 0.3146, np.nan]])
  22. # Rows of _ndat with nans removed
  23. _rdat = [np.array([0.6244, 0.2692, 0.0116, 0.1170]),
  24. np.array([0.5351, -0.9403, 0.2100, 0.4759, 0.2833]),
  25. np.array([0.1042, -0.5954]),
  26. np.array([0.1610, 0.1859, 0.3146])]
  27. # Rows of _ndat with nans converted to ones
  28. _ndat_ones = np.array([[0.6244, 1.0, 0.2692, 0.0116, 1.0, 0.1170],
  29. [0.5351, -0.9403, 1.0, 0.2100, 0.4759, 0.2833],
  30. [1.0, 1.0, 1.0, 0.1042, 1.0, -0.5954],
  31. [0.1610, 1.0, 1.0, 0.1859, 0.3146, 1.0]])
  32. # Rows of _ndat with nans converted to zeros
  33. _ndat_zeros = np.array([[0.6244, 0.0, 0.2692, 0.0116, 0.0, 0.1170],
  34. [0.5351, -0.9403, 0.0, 0.2100, 0.4759, 0.2833],
  35. [0.0, 0.0, 0.0, 0.1042, 0.0, -0.5954],
  36. [0.1610, 0.0, 0.0, 0.1859, 0.3146, 0.0]])
  37. class TestSignatureMatch:
  38. NANFUNCS = {
  39. np.nanmin: np.amin,
  40. np.nanmax: np.amax,
  41. np.nanargmin: np.argmin,
  42. np.nanargmax: np.argmax,
  43. np.nansum: np.sum,
  44. np.nanprod: np.prod,
  45. np.nancumsum: np.cumsum,
  46. np.nancumprod: np.cumprod,
  47. np.nanmean: np.mean,
  48. np.nanmedian: np.median,
  49. np.nanpercentile: np.percentile,
  50. np.nanquantile: np.quantile,
  51. np.nanvar: np.var,
  52. np.nanstd: np.std,
  53. }
  54. IDS = [k.__name__ for k in NANFUNCS]
  55. @staticmethod
  56. def get_signature(func, default="..."):
  57. """Construct a signature and replace all default parameter-values."""
  58. prm_list = []
  59. signature = inspect.signature(func)
  60. for prm in signature.parameters.values():
  61. if prm.default is inspect.Parameter.empty:
  62. prm_list.append(prm)
  63. else:
  64. prm_list.append(prm.replace(default=default))
  65. return inspect.Signature(prm_list)
  66. @pytest.mark.parametrize("nan_func,func", NANFUNCS.items(), ids=IDS)
  67. def test_signature_match(self, nan_func, func):
  68. # Ignore the default parameter-values as they can sometimes differ
  69. # between the two functions (*e.g.* one has `False` while the other
  70. # has `np._NoValue`)
  71. signature = self.get_signature(func)
  72. nan_signature = self.get_signature(nan_func)
  73. np.testing.assert_equal(signature, nan_signature)
  74. def test_exhaustiveness(self):
  75. """Validate that all nan functions are actually tested."""
  76. np.testing.assert_equal(
  77. set(self.IDS), set(np.lib._nanfunctions_impl.__all__)
  78. )
  79. class TestNanFunctions_MinMax:
  80. nanfuncs = [np.nanmin, np.nanmax]
  81. stdfuncs = [np.min, np.max]
  82. def test_mutation(self):
  83. # Check that passed array is not modified.
  84. ndat = _ndat.copy()
  85. for f in self.nanfuncs:
  86. f(ndat)
  87. assert_equal(ndat, _ndat)
  88. def test_keepdims(self):
  89. mat = np.eye(3)
  90. for nf, rf in zip(self.nanfuncs, self.stdfuncs):
  91. for axis in [None, 0, 1]:
  92. tgt = rf(mat, axis=axis, keepdims=True)
  93. res = nf(mat, axis=axis, keepdims=True)
  94. assert_(res.ndim == tgt.ndim)
  95. def test_out(self):
  96. mat = np.eye(3)
  97. for nf, rf in zip(self.nanfuncs, self.stdfuncs):
  98. resout = np.zeros(3)
  99. tgt = rf(mat, axis=1)
  100. res = nf(mat, axis=1, out=resout)
  101. assert_almost_equal(res, resout)
  102. assert_almost_equal(res, tgt)
  103. def test_dtype_from_input(self):
  104. codes = 'efdgFDG'
  105. for nf, rf in zip(self.nanfuncs, self.stdfuncs):
  106. for c in codes:
  107. mat = np.eye(3, dtype=c)
  108. tgt = rf(mat, axis=1).dtype.type
  109. res = nf(mat, axis=1).dtype.type
  110. assert_(res is tgt)
  111. # scalar case
  112. tgt = rf(mat, axis=None).dtype.type
  113. res = nf(mat, axis=None).dtype.type
  114. assert_(res is tgt)
  115. def test_result_values(self):
  116. for nf, rf in zip(self.nanfuncs, self.stdfuncs):
  117. tgt = [rf(d) for d in _rdat]
  118. res = nf(_ndat, axis=1)
  119. assert_almost_equal(res, tgt)
  120. @pytest.mark.parametrize("axis", [None, 0, 1])
  121. @pytest.mark.parametrize("dtype", np.typecodes["AllFloat"])
  122. @pytest.mark.parametrize("array", [
  123. np.array(np.nan),
  124. np.full((3, 3), np.nan),
  125. ], ids=["0d", "2d"])
  126. def test_allnans(self, axis, dtype, array):
  127. if axis is not None and array.ndim == 0:
  128. pytest.skip("`axis != None` not supported for 0d arrays")
  129. array = array.astype(dtype)
  130. match = "All-NaN slice encountered"
  131. for func in self.nanfuncs:
  132. with pytest.warns(RuntimeWarning, match=match):
  133. out = func(array, axis=axis)
  134. assert np.isnan(out).all()
  135. assert out.dtype == array.dtype
  136. def test_masked(self):
  137. mat = np.ma.fix_invalid(_ndat)
  138. msk = mat._mask.copy()
  139. for f in [np.nanmin]:
  140. res = f(mat, axis=1)
  141. tgt = f(_ndat, axis=1)
  142. assert_equal(res, tgt)
  143. assert_equal(mat._mask, msk)
  144. assert_(not np.isinf(mat).any())
  145. def test_scalar(self):
  146. for f in self.nanfuncs:
  147. assert_(f(0.) == 0.)
  148. def test_subclass(self):
  149. class MyNDArray(np.ndarray):
  150. pass
  151. # Check that it works and that type and
  152. # shape are preserved
  153. mine = np.eye(3).view(MyNDArray)
  154. for f in self.nanfuncs:
  155. res = f(mine, axis=0)
  156. assert_(isinstance(res, MyNDArray))
  157. assert_(res.shape == (3,))
  158. res = f(mine, axis=1)
  159. assert_(isinstance(res, MyNDArray))
  160. assert_(res.shape == (3,))
  161. res = f(mine)
  162. assert_(res.shape == ())
  163. # check that rows of nan are dealt with for subclasses (#4628)
  164. mine[1] = np.nan
  165. for f in self.nanfuncs:
  166. with warnings.catch_warnings(record=True) as w:
  167. warnings.simplefilter('always')
  168. res = f(mine, axis=0)
  169. assert_(isinstance(res, MyNDArray))
  170. assert_(not np.any(np.isnan(res)))
  171. assert_(len(w) == 0)
  172. with warnings.catch_warnings(record=True) as w:
  173. warnings.simplefilter('always')
  174. res = f(mine, axis=1)
  175. assert_(isinstance(res, MyNDArray))
  176. assert_(np.isnan(res[1]) and not np.isnan(res[0])
  177. and not np.isnan(res[2]))
  178. assert_(len(w) == 1, 'no warning raised')
  179. assert_(issubclass(w[0].category, RuntimeWarning))
  180. with warnings.catch_warnings(record=True) as w:
  181. warnings.simplefilter('always')
  182. res = f(mine)
  183. assert_(res.shape == ())
  184. assert_(res != np.nan)
  185. assert_(len(w) == 0)
  186. def test_object_array(self):
  187. arr = np.array([[1.0, 2.0], [np.nan, 4.0], [np.nan, np.nan]], dtype=object)
  188. assert_equal(np.nanmin(arr), 1.0)
  189. assert_equal(np.nanmin(arr, axis=0), [1.0, 2.0])
  190. with warnings.catch_warnings(record=True) as w:
  191. warnings.simplefilter('always')
  192. # assert_equal does not work on object arrays of nan
  193. assert_equal(list(np.nanmin(arr, axis=1)), [1.0, 4.0, np.nan])
  194. assert_(len(w) == 1, 'no warning raised')
  195. assert_(issubclass(w[0].category, RuntimeWarning))
  196. @pytest.mark.parametrize("dtype", np.typecodes["AllFloat"])
  197. def test_initial(self, dtype):
  198. class MyNDArray(np.ndarray):
  199. pass
  200. ar = np.arange(9).astype(dtype)
  201. ar[:5] = np.nan
  202. for f in self.nanfuncs:
  203. initial = 100 if f is np.nanmax else 0
  204. ret1 = f(ar, initial=initial)
  205. assert ret1.dtype == dtype
  206. assert ret1 == initial
  207. ret2 = f(ar.view(MyNDArray), initial=initial)
  208. assert ret2.dtype == dtype
  209. assert ret2 == initial
  210. @pytest.mark.parametrize("dtype", np.typecodes["AllFloat"])
  211. def test_where(self, dtype):
  212. class MyNDArray(np.ndarray):
  213. pass
  214. ar = np.arange(9).reshape(3, 3).astype(dtype)
  215. ar[0, :] = np.nan
  216. where = np.ones_like(ar, dtype=np.bool)
  217. where[:, 0] = False
  218. for f in self.nanfuncs:
  219. reference = 4 if f is np.nanmin else 8
  220. ret1 = f(ar, where=where, initial=5)
  221. assert ret1.dtype == dtype
  222. assert ret1 == reference
  223. ret2 = f(ar.view(MyNDArray), where=where, initial=5)
  224. assert ret2.dtype == dtype
  225. assert ret2 == reference
  226. class TestNanFunctions_ArgminArgmax:
  227. nanfuncs = [np.nanargmin, np.nanargmax]
  228. def test_mutation(self):
  229. # Check that passed array is not modified.
  230. ndat = _ndat.copy()
  231. for f in self.nanfuncs:
  232. f(ndat)
  233. assert_equal(ndat, _ndat)
  234. def test_result_values(self):
  235. for f, fcmp in zip(self.nanfuncs, [np.greater, np.less]):
  236. for row in _ndat:
  237. with warnings.catch_warnings():
  238. warnings.filterwarnings(
  239. 'ignore', "invalid value encountered in", RuntimeWarning)
  240. ind = f(row)
  241. val = row[ind]
  242. # comparing with NaN is tricky as the result
  243. # is always false except for NaN != NaN
  244. assert_(not np.isnan(val))
  245. assert_(not fcmp(val, row).any())
  246. assert_(not np.equal(val, row[:ind]).any())
  247. @pytest.mark.parametrize("axis", [None, 0, 1])
  248. @pytest.mark.parametrize("dtype", np.typecodes["AllFloat"])
  249. @pytest.mark.parametrize("array", [
  250. np.array(np.nan),
  251. np.full((3, 3), np.nan),
  252. ], ids=["0d", "2d"])
  253. def test_allnans(self, axis, dtype, array):
  254. if axis is not None and array.ndim == 0:
  255. pytest.skip("`axis != None` not supported for 0d arrays")
  256. array = array.astype(dtype)
  257. for func in self.nanfuncs:
  258. with pytest.raises(ValueError, match="All-NaN slice encountered"):
  259. func(array, axis=axis)
  260. def test_empty(self):
  261. mat = np.zeros((0, 3))
  262. for f in self.nanfuncs:
  263. for axis in [0, None]:
  264. assert_raises_regex(
  265. ValueError,
  266. "attempt to get argm.. of an empty sequence",
  267. f, mat, axis=axis)
  268. for axis in [1]:
  269. res = f(mat, axis=axis)
  270. assert_equal(res, np.zeros(0))
  271. def test_scalar(self):
  272. for f in self.nanfuncs:
  273. assert_(f(0.) == 0.)
  274. def test_subclass(self):
  275. class MyNDArray(np.ndarray):
  276. pass
  277. # Check that it works and that type and
  278. # shape are preserved
  279. mine = np.eye(3).view(MyNDArray)
  280. for f in self.nanfuncs:
  281. res = f(mine, axis=0)
  282. assert_(isinstance(res, MyNDArray))
  283. assert_(res.shape == (3,))
  284. res = f(mine, axis=1)
  285. assert_(isinstance(res, MyNDArray))
  286. assert_(res.shape == (3,))
  287. res = f(mine)
  288. assert_(res.shape == ())
  289. @pytest.mark.parametrize("dtype", np.typecodes["AllFloat"])
  290. def test_keepdims(self, dtype):
  291. ar = np.arange(9).astype(dtype)
  292. ar[:5] = np.nan
  293. for f in self.nanfuncs:
  294. reference = 5 if f is np.nanargmin else 8
  295. ret = f(ar, keepdims=True)
  296. assert ret.ndim == ar.ndim
  297. assert ret == reference
  298. @pytest.mark.parametrize("dtype", np.typecodes["AllFloat"])
  299. def test_out(self, dtype):
  300. ar = np.arange(9).astype(dtype)
  301. ar[:5] = np.nan
  302. for f in self.nanfuncs:
  303. out = np.zeros((), dtype=np.intp)
  304. reference = 5 if f is np.nanargmin else 8
  305. ret = f(ar, out=out)
  306. assert ret is out
  307. assert ret == reference
  308. _TEST_ARRAYS = {
  309. "0d": np.array(5),
  310. "1d": np.array([127, 39, 93, 87, 46])
  311. }
  312. for _v in _TEST_ARRAYS.values():
  313. _v.setflags(write=False)
  314. @pytest.mark.parametrize(
  315. "dtype",
  316. np.typecodes["AllInteger"] + np.typecodes["AllFloat"] + "O",
  317. )
  318. @pytest.mark.parametrize("mat", _TEST_ARRAYS.values(), ids=_TEST_ARRAYS.keys())
  319. class TestNanFunctions_NumberTypes:
  320. nanfuncs = {
  321. np.nanmin: np.min,
  322. np.nanmax: np.max,
  323. np.nanargmin: np.argmin,
  324. np.nanargmax: np.argmax,
  325. np.nansum: np.sum,
  326. np.nanprod: np.prod,
  327. np.nancumsum: np.cumsum,
  328. np.nancumprod: np.cumprod,
  329. np.nanmean: np.mean,
  330. np.nanmedian: np.median,
  331. np.nanvar: np.var,
  332. np.nanstd: np.std,
  333. }
  334. nanfunc_ids = [i.__name__ for i in nanfuncs]
  335. @pytest.mark.parametrize("nanfunc,func", nanfuncs.items(), ids=nanfunc_ids)
  336. @np.errstate(over="ignore")
  337. def test_nanfunc(self, mat, dtype, nanfunc, func):
  338. mat = mat.astype(dtype)
  339. tgt = func(mat)
  340. out = nanfunc(mat)
  341. assert_almost_equal(out, tgt)
  342. if dtype == "O":
  343. assert type(out) is type(tgt)
  344. else:
  345. assert out.dtype == tgt.dtype
  346. @pytest.mark.parametrize(
  347. "nanfunc,func",
  348. [(np.nanquantile, np.quantile), (np.nanpercentile, np.percentile)],
  349. ids=["nanquantile", "nanpercentile"],
  350. )
  351. def test_nanfunc_q(self, mat, dtype, nanfunc, func):
  352. mat = mat.astype(dtype)
  353. if mat.dtype.kind == "c":
  354. assert_raises(TypeError, func, mat, q=1)
  355. assert_raises(TypeError, nanfunc, mat, q=1)
  356. else:
  357. tgt = func(mat, q=1)
  358. out = nanfunc(mat, q=1)
  359. assert_almost_equal(out, tgt)
  360. if dtype == "O":
  361. assert type(out) is type(tgt)
  362. else:
  363. assert out.dtype == tgt.dtype
  364. @pytest.mark.parametrize(
  365. "nanfunc,func",
  366. [(np.nanvar, np.var), (np.nanstd, np.std)],
  367. ids=["nanvar", "nanstd"],
  368. )
  369. def test_nanfunc_ddof(self, mat, dtype, nanfunc, func):
  370. mat = mat.astype(dtype)
  371. tgt = func(mat, ddof=0.5)
  372. out = nanfunc(mat, ddof=0.5)
  373. assert_almost_equal(out, tgt)
  374. if dtype == "O":
  375. assert type(out) is type(tgt)
  376. else:
  377. assert out.dtype == tgt.dtype
  378. @pytest.mark.parametrize(
  379. "nanfunc", [np.nanvar, np.nanstd]
  380. )
  381. def test_nanfunc_correction(self, mat, dtype, nanfunc):
  382. mat = mat.astype(dtype)
  383. assert_almost_equal(
  384. nanfunc(mat, correction=0.5), nanfunc(mat, ddof=0.5)
  385. )
  386. err_msg = "ddof and correction can't be provided simultaneously."
  387. with assert_raises_regex(ValueError, err_msg):
  388. nanfunc(mat, ddof=0.5, correction=0.5)
  389. with assert_raises_regex(ValueError, err_msg):
  390. nanfunc(mat, ddof=1, correction=0)
  391. class SharedNanFunctionsTestsMixin:
  392. def test_mutation(self):
  393. # Check that passed array is not modified.
  394. ndat = _ndat.copy()
  395. for f in self.nanfuncs:
  396. f(ndat)
  397. assert_equal(ndat, _ndat)
  398. def test_keepdims(self):
  399. mat = np.eye(3)
  400. for nf, rf in zip(self.nanfuncs, self.stdfuncs):
  401. for axis in [None, 0, 1]:
  402. tgt = rf(mat, axis=axis, keepdims=True)
  403. res = nf(mat, axis=axis, keepdims=True)
  404. assert_(res.ndim == tgt.ndim)
  405. def test_out(self):
  406. mat = np.eye(3)
  407. for nf, rf in zip(self.nanfuncs, self.stdfuncs):
  408. resout = np.zeros(3)
  409. tgt = rf(mat, axis=1)
  410. res = nf(mat, axis=1, out=resout)
  411. assert_almost_equal(res, resout)
  412. assert_almost_equal(res, tgt)
  413. def test_dtype_from_dtype(self):
  414. mat = np.eye(3)
  415. codes = 'efdgFDG'
  416. for nf, rf in zip(self.nanfuncs, self.stdfuncs):
  417. for c in codes:
  418. with warnings.catch_warnings():
  419. if nf in {np.nanstd, np.nanvar} and c in 'FDG':
  420. # Giving the warning is a small bug, see gh-8000
  421. warnings.simplefilter('ignore', ComplexWarning)
  422. tgt = rf(mat, dtype=np.dtype(c), axis=1).dtype.type
  423. res = nf(mat, dtype=np.dtype(c), axis=1).dtype.type
  424. assert_(res is tgt)
  425. # scalar case
  426. tgt = rf(mat, dtype=np.dtype(c), axis=None).dtype.type
  427. res = nf(mat, dtype=np.dtype(c), axis=None).dtype.type
  428. assert_(res is tgt)
  429. def test_dtype_from_char(self):
  430. mat = np.eye(3)
  431. codes = 'efdgFDG'
  432. for nf, rf in zip(self.nanfuncs, self.stdfuncs):
  433. for c in codes:
  434. with warnings.catch_warnings():
  435. if nf in {np.nanstd, np.nanvar} and c in 'FDG':
  436. # Giving the warning is a small bug, see gh-8000
  437. warnings.simplefilter('ignore', ComplexWarning)
  438. tgt = rf(mat, dtype=c, axis=1).dtype.type
  439. res = nf(mat, dtype=c, axis=1).dtype.type
  440. assert_(res is tgt)
  441. # scalar case
  442. tgt = rf(mat, dtype=c, axis=None).dtype.type
  443. res = nf(mat, dtype=c, axis=None).dtype.type
  444. assert_(res is tgt)
  445. def test_dtype_from_input(self):
  446. codes = 'efdgFDG'
  447. for nf, rf in zip(self.nanfuncs, self.stdfuncs):
  448. for c in codes:
  449. mat = np.eye(3, dtype=c)
  450. tgt = rf(mat, axis=1).dtype.type
  451. res = nf(mat, axis=1).dtype.type
  452. assert_(res is tgt, f"res {res}, tgt {tgt}")
  453. # scalar case
  454. tgt = rf(mat, axis=None).dtype.type
  455. res = nf(mat, axis=None).dtype.type
  456. assert_(res is tgt)
  457. def test_result_values(self):
  458. for nf, rf in zip(self.nanfuncs, self.stdfuncs):
  459. tgt = [rf(d) for d in _rdat]
  460. res = nf(_ndat, axis=1)
  461. assert_almost_equal(res, tgt)
  462. def test_scalar(self):
  463. for f in self.nanfuncs:
  464. assert_(f(0.) == 0.)
  465. def test_subclass(self):
  466. class MyNDArray(np.ndarray):
  467. pass
  468. # Check that it works and that type and
  469. # shape are preserved
  470. array = np.eye(3)
  471. mine = array.view(MyNDArray)
  472. for f in self.nanfuncs:
  473. expected_shape = f(array, axis=0).shape
  474. res = f(mine, axis=0)
  475. assert_(isinstance(res, MyNDArray))
  476. assert_(res.shape == expected_shape)
  477. expected_shape = f(array, axis=1).shape
  478. res = f(mine, axis=1)
  479. assert_(isinstance(res, MyNDArray))
  480. assert_(res.shape == expected_shape)
  481. expected_shape = f(array).shape
  482. res = f(mine)
  483. assert_(isinstance(res, MyNDArray))
  484. assert_(res.shape == expected_shape)
  485. class TestNanFunctions_SumProd(SharedNanFunctionsTestsMixin):
  486. nanfuncs = [np.nansum, np.nanprod]
  487. stdfuncs = [np.sum, np.prod]
  488. @pytest.mark.parametrize("axis", [None, 0, 1])
  489. @pytest.mark.parametrize("dtype", np.typecodes["AllFloat"])
  490. @pytest.mark.parametrize("array", [
  491. np.array(np.nan),
  492. np.full((3, 3), np.nan),
  493. ], ids=["0d", "2d"])
  494. def test_allnans(self, axis, dtype, array):
  495. if axis is not None and array.ndim == 0:
  496. pytest.skip("`axis != None` not supported for 0d arrays")
  497. array = array.astype(dtype)
  498. for func, identity in zip(self.nanfuncs, [0, 1]):
  499. out = func(array, axis=axis)
  500. assert np.all(out == identity)
  501. assert out.dtype == array.dtype
  502. def test_empty(self):
  503. for f, tgt_value in zip([np.nansum, np.nanprod], [0, 1]):
  504. mat = np.zeros((0, 3))
  505. tgt = [tgt_value] * 3
  506. res = f(mat, axis=0)
  507. assert_equal(res, tgt)
  508. tgt = []
  509. res = f(mat, axis=1)
  510. assert_equal(res, tgt)
  511. tgt = tgt_value
  512. res = f(mat, axis=None)
  513. assert_equal(res, tgt)
  514. @pytest.mark.parametrize("dtype", np.typecodes["AllFloat"])
  515. def test_initial(self, dtype):
  516. ar = np.arange(9).astype(dtype)
  517. ar[:5] = np.nan
  518. for f in self.nanfuncs:
  519. reference = 28 if f is np.nansum else 3360
  520. ret = f(ar, initial=2)
  521. assert ret.dtype == dtype
  522. assert ret == reference
  523. @pytest.mark.parametrize("dtype", np.typecodes["AllFloat"])
  524. def test_where(self, dtype):
  525. ar = np.arange(9).reshape(3, 3).astype(dtype)
  526. ar[0, :] = np.nan
  527. where = np.ones_like(ar, dtype=np.bool)
  528. where[:, 0] = False
  529. for f in self.nanfuncs:
  530. reference = 26 if f is np.nansum else 2240
  531. ret = f(ar, where=where, initial=2)
  532. assert ret.dtype == dtype
  533. assert ret == reference
  534. class TestNanFunctions_CumSumProd(SharedNanFunctionsTestsMixin):
  535. nanfuncs = [np.nancumsum, np.nancumprod]
  536. stdfuncs = [np.cumsum, np.cumprod]
  537. @pytest.mark.parametrize("axis", [None, 0, 1])
  538. @pytest.mark.parametrize("dtype", np.typecodes["AllFloat"])
  539. @pytest.mark.parametrize("array", [
  540. np.array(np.nan),
  541. np.full((3, 3), np.nan)
  542. ], ids=["0d", "2d"])
  543. def test_allnans(self, axis, dtype, array):
  544. if axis is not None and array.ndim == 0:
  545. pytest.skip("`axis != None` not supported for 0d arrays")
  546. array = array.astype(dtype)
  547. for func, identity in zip(self.nanfuncs, [0, 1]):
  548. out = func(array)
  549. assert np.all(out == identity)
  550. assert out.dtype == array.dtype
  551. def test_empty(self):
  552. for f, tgt_value in zip(self.nanfuncs, [0, 1]):
  553. mat = np.zeros((0, 3))
  554. tgt = tgt_value * np.ones((0, 3))
  555. res = f(mat, axis=0)
  556. assert_equal(res, tgt)
  557. tgt = mat
  558. res = f(mat, axis=1)
  559. assert_equal(res, tgt)
  560. tgt = np.zeros(0)
  561. res = f(mat, axis=None)
  562. assert_equal(res, tgt)
  563. def test_keepdims(self):
  564. for f, g in zip(self.nanfuncs, self.stdfuncs):
  565. mat = np.eye(3)
  566. for axis in [None, 0, 1]:
  567. tgt = f(mat, axis=axis, out=None)
  568. res = g(mat, axis=axis, out=None)
  569. assert_(res.ndim == tgt.ndim)
  570. for f in self.nanfuncs:
  571. d = np.ones((3, 5, 7, 11))
  572. # Randomly set some elements to NaN:
  573. rs = np.random.RandomState(0)
  574. d[rs.rand(*d.shape) < 0.5] = np.nan
  575. res = f(d, axis=None)
  576. assert_equal(res.shape, (1155,))
  577. for axis in np.arange(4):
  578. res = f(d, axis=axis)
  579. assert_equal(res.shape, (3, 5, 7, 11))
  580. def test_result_values(self):
  581. for axis in (-2, -1, 0, 1, None):
  582. tgt = np.cumprod(_ndat_ones, axis=axis)
  583. res = np.nancumprod(_ndat, axis=axis)
  584. assert_almost_equal(res, tgt)
  585. tgt = np.cumsum(_ndat_zeros, axis=axis)
  586. res = np.nancumsum(_ndat, axis=axis)
  587. assert_almost_equal(res, tgt)
  588. def test_out(self):
  589. mat = np.eye(3)
  590. for nf, rf in zip(self.nanfuncs, self.stdfuncs):
  591. resout = np.eye(3)
  592. for axis in (-2, -1, 0, 1):
  593. tgt = rf(mat, axis=axis)
  594. res = nf(mat, axis=axis, out=resout)
  595. assert_almost_equal(res, resout)
  596. assert_almost_equal(res, tgt)
  597. class TestNanFunctions_MeanVarStd(SharedNanFunctionsTestsMixin):
  598. nanfuncs = [np.nanmean, np.nanvar, np.nanstd]
  599. stdfuncs = [np.mean, np.var, np.std]
  600. def test_dtype_error(self):
  601. for f in self.nanfuncs:
  602. for dtype in [np.bool, np.int_, np.object_]:
  603. assert_raises(TypeError, f, _ndat, axis=1, dtype=dtype)
  604. def test_out_dtype_error(self):
  605. for f in self.nanfuncs:
  606. for dtype in [np.bool, np.int_, np.object_]:
  607. out = np.empty(_ndat.shape[0], dtype=dtype)
  608. assert_raises(TypeError, f, _ndat, axis=1, out=out)
  609. def test_ddof(self):
  610. nanfuncs = [np.nanvar, np.nanstd]
  611. stdfuncs = [np.var, np.std]
  612. for nf, rf in zip(nanfuncs, stdfuncs):
  613. for ddof in [0, 1]:
  614. tgt = [rf(d, ddof=ddof) for d in _rdat]
  615. res = nf(_ndat, axis=1, ddof=ddof)
  616. assert_almost_equal(res, tgt)
  617. def test_ddof_too_big(self):
  618. nanfuncs = [np.nanvar, np.nanstd]
  619. stdfuncs = [np.var, np.std]
  620. dsize = [len(d) for d in _rdat]
  621. for nf, rf in zip(nanfuncs, stdfuncs):
  622. for ddof in range(5):
  623. with warnings.catch_warnings(record=True) as w:
  624. warnings.simplefilter('always')
  625. warnings.simplefilter('ignore', ComplexWarning)
  626. tgt = [ddof >= d for d in dsize]
  627. res = nf(_ndat, axis=1, ddof=ddof)
  628. assert_equal(np.isnan(res), tgt)
  629. if any(tgt):
  630. assert_(len(w) == 1)
  631. else:
  632. assert_(len(w) == 0)
  633. @pytest.mark.parametrize("axis", [None, 0, 1])
  634. @pytest.mark.parametrize("dtype", np.typecodes["AllFloat"])
  635. @pytest.mark.parametrize("array", [
  636. np.array(np.nan),
  637. np.full((3, 3), np.nan),
  638. ], ids=["0d", "2d"])
  639. def test_allnans(self, axis, dtype, array):
  640. if axis is not None and array.ndim == 0:
  641. pytest.skip("`axis != None` not supported for 0d arrays")
  642. array = array.astype(dtype)
  643. match = "(Degrees of freedom <= 0 for slice.)|(Mean of empty slice)"
  644. for func in self.nanfuncs:
  645. with pytest.warns(RuntimeWarning, match=match):
  646. out = func(array, axis=axis)
  647. assert np.isnan(out).all()
  648. # `nanvar` and `nanstd` convert complex inputs to their
  649. # corresponding floating dtype
  650. if func is np.nanmean:
  651. assert out.dtype == array.dtype
  652. else:
  653. assert out.dtype == np.abs(array).dtype
  654. def test_empty(self):
  655. mat = np.zeros((0, 3))
  656. for f in self.nanfuncs:
  657. for axis in [0, None]:
  658. with warnings.catch_warnings(record=True) as w:
  659. warnings.simplefilter('always')
  660. assert_(np.isnan(f(mat, axis=axis)).all())
  661. assert_(len(w) == 1)
  662. assert_(issubclass(w[0].category, RuntimeWarning))
  663. for axis in [1]:
  664. with warnings.catch_warnings(record=True) as w:
  665. warnings.simplefilter('always')
  666. assert_equal(f(mat, axis=axis), np.zeros([]))
  667. assert_(len(w) == 0)
  668. @pytest.mark.parametrize("dtype", np.typecodes["AllFloat"])
  669. def test_where(self, dtype):
  670. ar = np.arange(9).reshape(3, 3).astype(dtype)
  671. ar[0, :] = np.nan
  672. where = np.ones_like(ar, dtype=np.bool)
  673. where[:, 0] = False
  674. for f, f_std in zip(self.nanfuncs, self.stdfuncs):
  675. reference = f_std(ar[where][2:])
  676. dtype_reference = dtype if f is np.nanmean else ar.real.dtype
  677. ret = f(ar, where=where)
  678. assert ret.dtype == dtype_reference
  679. np.testing.assert_allclose(ret, reference)
  680. def test_nanstd_with_mean_keyword(self):
  681. # Setting the seed to make the test reproducible
  682. rng = np.random.RandomState(1234)
  683. A = rng.randn(10, 20, 5) + 0.5
  684. A[:, 5, :] = np.nan
  685. mean_out = np.zeros((10, 1, 5))
  686. std_out = np.zeros((10, 1, 5))
  687. mean = np.nanmean(A,
  688. out=mean_out,
  689. axis=1,
  690. keepdims=True)
  691. # The returned object should be the object specified during calling
  692. assert mean_out is mean
  693. std = np.nanstd(A,
  694. out=std_out,
  695. axis=1,
  696. keepdims=True,
  697. mean=mean)
  698. # The returned object should be the object specified during calling
  699. assert std_out is std
  700. # Shape of returned mean and std should be same
  701. assert std.shape == mean.shape
  702. assert std.shape == (10, 1, 5)
  703. # Output should be the same as from the individual algorithms
  704. std_old = np.nanstd(A, axis=1, keepdims=True)
  705. assert std_old.shape == mean.shape
  706. assert_almost_equal(std, std_old)
  707. _TIME_UNITS = (
  708. "Y", "M", "W", "D", "h", "m", "s", "ms", "us", "ns", "ps", "fs", "as"
  709. )
  710. # All `inexact` + `timdelta64` type codes
  711. _TYPE_CODES = list(np.typecodes["AllFloat"])
  712. _TYPE_CODES += [f"m8[{unit}]" for unit in _TIME_UNITS]
  713. class TestNanFunctions_Median:
  714. def test_mutation(self):
  715. # Check that passed array is not modified.
  716. ndat = _ndat.copy()
  717. np.nanmedian(ndat)
  718. assert_equal(ndat, _ndat)
  719. def test_keepdims(self):
  720. mat = np.eye(3)
  721. for axis in [None, 0, 1]:
  722. tgt = np.median(mat, axis=axis, out=None, overwrite_input=False)
  723. res = np.nanmedian(mat, axis=axis, out=None, overwrite_input=False)
  724. assert_(res.ndim == tgt.ndim)
  725. d = np.ones((3, 5, 7, 11))
  726. # Randomly set some elements to NaN:
  727. w = np.random.random((4, 200)) * np.array(d.shape)[:, None]
  728. w = w.astype(np.intp)
  729. d[tuple(w)] = np.nan
  730. with warnings.catch_warnings():
  731. warnings.simplefilter('ignore', RuntimeWarning)
  732. res = np.nanmedian(d, axis=None, keepdims=True)
  733. assert_equal(res.shape, (1, 1, 1, 1))
  734. res = np.nanmedian(d, axis=(0, 1), keepdims=True)
  735. assert_equal(res.shape, (1, 1, 7, 11))
  736. res = np.nanmedian(d, axis=(0, 3), keepdims=True)
  737. assert_equal(res.shape, (1, 5, 7, 1))
  738. res = np.nanmedian(d, axis=(1,), keepdims=True)
  739. assert_equal(res.shape, (3, 1, 7, 11))
  740. res = np.nanmedian(d, axis=(0, 1, 2, 3), keepdims=True)
  741. assert_equal(res.shape, (1, 1, 1, 1))
  742. res = np.nanmedian(d, axis=(0, 1, 3), keepdims=True)
  743. assert_equal(res.shape, (1, 1, 7, 1))
  744. @pytest.mark.parametrize(
  745. argnames='axis',
  746. argvalues=[
  747. None,
  748. 1,
  749. (1, ),
  750. (0, 1),
  751. (-3, -1),
  752. ]
  753. )
  754. @pytest.mark.filterwarnings("ignore:All-NaN slice:RuntimeWarning")
  755. def test_keepdims_out(self, axis):
  756. d = np.ones((3, 5, 7, 11))
  757. # Randomly set some elements to NaN:
  758. w = np.random.random((4, 200)) * np.array(d.shape)[:, None]
  759. w = w.astype(np.intp)
  760. d[tuple(w)] = np.nan
  761. if axis is None:
  762. shape_out = (1,) * d.ndim
  763. else:
  764. axis_norm = normalize_axis_tuple(axis, d.ndim)
  765. shape_out = tuple(
  766. 1 if i in axis_norm else d.shape[i] for i in range(d.ndim))
  767. out = np.empty(shape_out)
  768. result = np.nanmedian(d, axis=axis, keepdims=True, out=out)
  769. assert result is out
  770. assert_equal(result.shape, shape_out)
  771. def test_out(self):
  772. mat = np.random.rand(3, 3)
  773. nan_mat = np.insert(mat, [0, 2], np.nan, axis=1)
  774. resout = np.zeros(3)
  775. tgt = np.median(mat, axis=1)
  776. res = np.nanmedian(nan_mat, axis=1, out=resout)
  777. assert_almost_equal(res, resout)
  778. assert_almost_equal(res, tgt)
  779. # 0-d output:
  780. resout = np.zeros(())
  781. tgt = np.median(mat, axis=None)
  782. res = np.nanmedian(nan_mat, axis=None, out=resout)
  783. assert_almost_equal(res, resout)
  784. assert_almost_equal(res, tgt)
  785. res = np.nanmedian(nan_mat, axis=(0, 1), out=resout)
  786. assert_almost_equal(res, resout)
  787. assert_almost_equal(res, tgt)
  788. def test_small_large(self):
  789. # test the small and large code paths, current cutoff 400 elements
  790. for s in [5, 20, 51, 200, 1000]:
  791. d = np.random.randn(4, s)
  792. # Randomly set some elements to NaN:
  793. w = np.random.randint(0, d.size, size=d.size // 5)
  794. d.ravel()[w] = np.nan
  795. d[:, 0] = 1. # ensure at least one good value
  796. # use normal median without nans to compare
  797. tgt = []
  798. for x in d:
  799. nonan = np.compress(~np.isnan(x), x)
  800. tgt.append(np.median(nonan, overwrite_input=True))
  801. assert_array_equal(np.nanmedian(d, axis=-1), tgt)
  802. def test_result_values(self):
  803. tgt = [np.median(d) for d in _rdat]
  804. res = np.nanmedian(_ndat, axis=1)
  805. assert_almost_equal(res, tgt)
  806. @pytest.mark.parametrize("axis", [None, 0, 1])
  807. @pytest.mark.parametrize("dtype", _TYPE_CODES)
  808. def test_allnans(self, dtype, axis):
  809. mat = np.full((3, 3), np.nan).astype(dtype)
  810. with pytest.warns(RuntimeWarning) as r:
  811. output = np.nanmedian(mat, axis=axis)
  812. assert output.dtype == mat.dtype
  813. assert np.isnan(output).all()
  814. if axis is None:
  815. assert_(len(r) == 1)
  816. else:
  817. assert_(len(r) == 3)
  818. # Check scalar
  819. scalar = np.array(np.nan).astype(dtype)[()]
  820. output_scalar = np.nanmedian(scalar)
  821. assert output_scalar.dtype == scalar.dtype
  822. assert np.isnan(output_scalar)
  823. if axis is None:
  824. assert_(len(r) == 2)
  825. else:
  826. assert_(len(r) == 4)
  827. def test_empty(self):
  828. mat = np.zeros((0, 3))
  829. for axis in [0, None]:
  830. with warnings.catch_warnings(record=True) as w:
  831. warnings.simplefilter('always')
  832. assert_(np.isnan(np.nanmedian(mat, axis=axis)).all())
  833. assert_(len(w) == 1)
  834. assert_(issubclass(w[0].category, RuntimeWarning))
  835. for axis in [1]:
  836. with warnings.catch_warnings(record=True) as w:
  837. warnings.simplefilter('always')
  838. assert_equal(np.nanmedian(mat, axis=axis), np.zeros([]))
  839. assert_(len(w) == 0)
  840. def test_scalar(self):
  841. assert_(np.nanmedian(0.) == 0.)
  842. def test_extended_axis_invalid(self):
  843. d = np.ones((3, 5, 7, 11))
  844. assert_raises(AxisError, np.nanmedian, d, axis=-5)
  845. assert_raises(AxisError, np.nanmedian, d, axis=(0, -5))
  846. assert_raises(AxisError, np.nanmedian, d, axis=4)
  847. assert_raises(AxisError, np.nanmedian, d, axis=(0, 4))
  848. assert_raises(ValueError, np.nanmedian, d, axis=(1, 1))
  849. def test_float_special(self):
  850. with warnings.catch_warnings():
  851. warnings.simplefilter('ignore', RuntimeWarning)
  852. for inf in [np.inf, -np.inf]:
  853. a = np.array([[inf, np.nan], [np.nan, np.nan]])
  854. assert_equal(np.nanmedian(a, axis=0), [inf, np.nan])
  855. assert_equal(np.nanmedian(a, axis=1), [inf, np.nan])
  856. assert_equal(np.nanmedian(a), inf)
  857. # minimum fill value check
  858. a = np.array([[np.nan, np.nan, inf],
  859. [np.nan, np.nan, inf]])
  860. assert_equal(np.nanmedian(a), inf)
  861. assert_equal(np.nanmedian(a, axis=0), [np.nan, np.nan, inf])
  862. assert_equal(np.nanmedian(a, axis=1), inf)
  863. # no mask path
  864. a = np.array([[inf, inf], [inf, inf]])
  865. assert_equal(np.nanmedian(a, axis=1), inf)
  866. a = np.array([[inf, 7, -inf, -9],
  867. [-10, np.nan, np.nan, 5],
  868. [4, np.nan, np.nan, inf]],
  869. dtype=np.float32)
  870. if inf > 0:
  871. assert_equal(np.nanmedian(a, axis=0), [4., 7., -inf, 5.])
  872. assert_equal(np.nanmedian(a), 4.5)
  873. else:
  874. assert_equal(np.nanmedian(a, axis=0), [-10., 7., -inf, -9.])
  875. assert_equal(np.nanmedian(a), -2.5)
  876. assert_equal(np.nanmedian(a, axis=-1), [-1., -2.5, inf])
  877. for i in range(10):
  878. for j in range(1, 10):
  879. a = np.array([([np.nan] * i) + ([inf] * j)] * 2)
  880. assert_equal(np.nanmedian(a), inf)
  881. assert_equal(np.nanmedian(a, axis=1), inf)
  882. assert_equal(np.nanmedian(a, axis=0),
  883. ([np.nan] * i) + [inf] * j)
  884. a = np.array([([np.nan] * i) + ([-inf] * j)] * 2)
  885. assert_equal(np.nanmedian(a), -inf)
  886. assert_equal(np.nanmedian(a, axis=1), -inf)
  887. assert_equal(np.nanmedian(a, axis=0),
  888. ([np.nan] * i) + [-inf] * j)
  889. class TestNanFunctions_Percentile:
  890. def test_mutation(self):
  891. # Check that passed array is not modified.
  892. ndat = _ndat.copy()
  893. np.nanpercentile(ndat, 30)
  894. assert_equal(ndat, _ndat)
  895. def test_keepdims(self):
  896. mat = np.eye(3)
  897. for axis in [None, 0, 1]:
  898. tgt = np.percentile(mat, 70, axis=axis, out=None,
  899. overwrite_input=False)
  900. res = np.nanpercentile(mat, 70, axis=axis, out=None,
  901. overwrite_input=False)
  902. assert_(res.ndim == tgt.ndim)
  903. d = np.ones((3, 5, 7, 11))
  904. # Randomly set some elements to NaN:
  905. w = np.random.random((4, 200)) * np.array(d.shape)[:, None]
  906. w = w.astype(np.intp)
  907. d[tuple(w)] = np.nan
  908. with warnings.catch_warnings():
  909. warnings.simplefilter('ignore', RuntimeWarning)
  910. res = np.nanpercentile(d, 90, axis=None, keepdims=True)
  911. assert_equal(res.shape, (1, 1, 1, 1))
  912. res = np.nanpercentile(d, 90, axis=(0, 1), keepdims=True)
  913. assert_equal(res.shape, (1, 1, 7, 11))
  914. res = np.nanpercentile(d, 90, axis=(0, 3), keepdims=True)
  915. assert_equal(res.shape, (1, 5, 7, 1))
  916. res = np.nanpercentile(d, 90, axis=(1,), keepdims=True)
  917. assert_equal(res.shape, (3, 1, 7, 11))
  918. res = np.nanpercentile(d, 90, axis=(0, 1, 2, 3), keepdims=True)
  919. assert_equal(res.shape, (1, 1, 1, 1))
  920. res = np.nanpercentile(d, 90, axis=(0, 1, 3), keepdims=True)
  921. assert_equal(res.shape, (1, 1, 7, 1))
  922. @pytest.mark.parametrize('q', [7, [1, 7]])
  923. @pytest.mark.parametrize(
  924. argnames='axis',
  925. argvalues=[
  926. None,
  927. 1,
  928. (1,),
  929. (0, 1),
  930. (-3, -1),
  931. ]
  932. )
  933. @pytest.mark.filterwarnings("ignore:All-NaN slice:RuntimeWarning")
  934. def test_keepdims_out(self, q, axis):
  935. d = np.ones((3, 5, 7, 11))
  936. # Randomly set some elements to NaN:
  937. w = np.random.random((4, 200)) * np.array(d.shape)[:, None]
  938. w = w.astype(np.intp)
  939. d[tuple(w)] = np.nan
  940. if axis is None:
  941. shape_out = (1,) * d.ndim
  942. else:
  943. axis_norm = normalize_axis_tuple(axis, d.ndim)
  944. shape_out = tuple(
  945. 1 if i in axis_norm else d.shape[i] for i in range(d.ndim))
  946. shape_out = np.shape(q) + shape_out
  947. out = np.empty(shape_out)
  948. result = np.nanpercentile(d, q, axis=axis, keepdims=True, out=out)
  949. assert result is out
  950. assert_equal(result.shape, shape_out)
  951. @pytest.mark.parametrize("weighted", [False, True])
  952. def test_out(self, weighted):
  953. mat = np.random.rand(3, 3)
  954. nan_mat = np.insert(mat, [0, 2], np.nan, axis=1)
  955. resout = np.zeros(3)
  956. if weighted:
  957. w_args = {"weights": np.ones_like(mat), "method": "inverted_cdf"}
  958. nan_w_args = {
  959. "weights": np.ones_like(nan_mat), "method": "inverted_cdf"
  960. }
  961. else:
  962. w_args = {}
  963. nan_w_args = {}
  964. tgt = np.percentile(mat, 42, axis=1, **w_args)
  965. res = np.nanpercentile(nan_mat, 42, axis=1, out=resout, **nan_w_args)
  966. assert_almost_equal(res, resout)
  967. assert_almost_equal(res, tgt)
  968. # 0-d output:
  969. resout = np.zeros(())
  970. tgt = np.percentile(mat, 42, axis=None, **w_args)
  971. res = np.nanpercentile(
  972. nan_mat, 42, axis=None, out=resout, **nan_w_args
  973. )
  974. assert_almost_equal(res, resout)
  975. assert_almost_equal(res, tgt)
  976. res = np.nanpercentile(
  977. nan_mat, 42, axis=(0, 1), out=resout, **nan_w_args
  978. )
  979. assert_almost_equal(res, resout)
  980. assert_almost_equal(res, tgt)
  981. def test_complex(self):
  982. arr_c = np.array([0.5 + 3.0j, 2.1 + 0.5j, 1.6 + 2.3j], dtype='G')
  983. assert_raises(TypeError, np.nanpercentile, arr_c, 0.5)
  984. arr_c = np.array([0.5 + 3.0j, 2.1 + 0.5j, 1.6 + 2.3j], dtype='D')
  985. assert_raises(TypeError, np.nanpercentile, arr_c, 0.5)
  986. arr_c = np.array([0.5 + 3.0j, 2.1 + 0.5j, 1.6 + 2.3j], dtype='F')
  987. assert_raises(TypeError, np.nanpercentile, arr_c, 0.5)
  988. @pytest.mark.parametrize("weighted", [False, True])
  989. @pytest.mark.parametrize("use_out", [False, True])
  990. def test_result_values(self, weighted, use_out):
  991. if weighted:
  992. percentile = partial(np.percentile, method="inverted_cdf")
  993. nanpercentile = partial(np.nanpercentile, method="inverted_cdf")
  994. def gen_weights(d):
  995. return np.ones_like(d)
  996. else:
  997. percentile = np.percentile
  998. nanpercentile = np.nanpercentile
  999. def gen_weights(d):
  1000. return None
  1001. tgt = [percentile(d, 28, weights=gen_weights(d)) for d in _rdat]
  1002. out = np.empty_like(tgt) if use_out else None
  1003. res = nanpercentile(_ndat, 28, axis=1,
  1004. weights=gen_weights(_ndat), out=out)
  1005. assert_almost_equal(res, tgt)
  1006. # Transpose the array to fit the output convention of numpy.percentile
  1007. tgt = np.transpose([percentile(d, (28, 98), weights=gen_weights(d))
  1008. for d in _rdat])
  1009. out = np.empty_like(tgt) if use_out else None
  1010. res = nanpercentile(_ndat, (28, 98), axis=1,
  1011. weights=gen_weights(_ndat), out=out)
  1012. assert_almost_equal(res, tgt)
  1013. @pytest.mark.parametrize("axis", [None, 0, 1])
  1014. @pytest.mark.parametrize("dtype", np.typecodes["Float"])
  1015. @pytest.mark.parametrize("array", [
  1016. np.array(np.nan),
  1017. np.full((3, 3), np.nan),
  1018. ], ids=["0d", "2d"])
  1019. def test_allnans(self, axis, dtype, array):
  1020. if axis is not None and array.ndim == 0:
  1021. pytest.skip("`axis != None` not supported for 0d arrays")
  1022. array = array.astype(dtype)
  1023. with pytest.warns(RuntimeWarning, match="All-NaN slice encountered"):
  1024. out = np.nanpercentile(array, 60, axis=axis)
  1025. assert np.isnan(out).all()
  1026. assert out.dtype == array.dtype
  1027. def test_empty(self):
  1028. mat = np.zeros((0, 3))
  1029. for axis in [0, None]:
  1030. with warnings.catch_warnings(record=True) as w:
  1031. warnings.simplefilter('always')
  1032. assert_(np.isnan(np.nanpercentile(mat, 40, axis=axis)).all())
  1033. assert_(len(w) == 1)
  1034. assert_(issubclass(w[0].category, RuntimeWarning))
  1035. for axis in [1]:
  1036. with warnings.catch_warnings(record=True) as w:
  1037. warnings.simplefilter('always')
  1038. assert_equal(np.nanpercentile(mat, 40, axis=axis), np.zeros([]))
  1039. assert_(len(w) == 0)
  1040. def test_scalar(self):
  1041. assert_equal(np.nanpercentile(0., 100), 0.)
  1042. a = np.arange(6)
  1043. r = np.nanpercentile(a, 50, axis=0)
  1044. assert_equal(r, 2.5)
  1045. assert_(np.isscalar(r))
  1046. def test_extended_axis_invalid(self):
  1047. d = np.ones((3, 5, 7, 11))
  1048. assert_raises(AxisError, np.nanpercentile, d, q=5, axis=-5)
  1049. assert_raises(AxisError, np.nanpercentile, d, q=5, axis=(0, -5))
  1050. assert_raises(AxisError, np.nanpercentile, d, q=5, axis=4)
  1051. assert_raises(AxisError, np.nanpercentile, d, q=5, axis=(0, 4))
  1052. assert_raises(ValueError, np.nanpercentile, d, q=5, axis=(1, 1))
  1053. def test_multiple_percentiles(self):
  1054. perc = [50, 100]
  1055. mat = np.ones((4, 3))
  1056. nan_mat = np.nan * mat
  1057. # For checking consistency in higher dimensional case
  1058. large_mat = np.ones((3, 4, 5))
  1059. large_mat[:, 0:2:4, :] = 0
  1060. large_mat[:, :, 3:] *= 2
  1061. for axis in [None, 0, 1]:
  1062. for keepdim in [False, True]:
  1063. with warnings.catch_warnings():
  1064. warnings.filterwarnings(
  1065. 'ignore', "All-NaN slice encountered", RuntimeWarning)
  1066. val = np.percentile(mat, perc, axis=axis, keepdims=keepdim)
  1067. nan_val = np.nanpercentile(nan_mat, perc, axis=axis,
  1068. keepdims=keepdim)
  1069. assert_equal(nan_val.shape, val.shape)
  1070. val = np.percentile(large_mat, perc, axis=axis,
  1071. keepdims=keepdim)
  1072. nan_val = np.nanpercentile(large_mat, perc, axis=axis,
  1073. keepdims=keepdim)
  1074. assert_equal(nan_val, val)
  1075. megamat = np.ones((3, 4, 5, 6))
  1076. assert_equal(
  1077. np.nanpercentile(megamat, perc, axis=(1, 2)).shape, (2, 3, 6)
  1078. )
  1079. @pytest.mark.parametrize("nan_weight", [0, 1, 2, 3, 1e200])
  1080. def test_nan_value_with_weight(self, nan_weight):
  1081. x = [1, np.nan, 2, 3]
  1082. result = np.float64(2.0)
  1083. q_unweighted = np.nanpercentile(x, 50, method="inverted_cdf")
  1084. assert_equal(q_unweighted, result)
  1085. # The weight value at the nan position should not matter.
  1086. w = [1.0, nan_weight, 1.0, 1.0]
  1087. q_weighted = np.nanpercentile(x, 50, weights=w, method="inverted_cdf")
  1088. assert_equal(q_weighted, result)
  1089. @pytest.mark.parametrize("axis", [0, 1, 2])
  1090. def test_nan_value_with_weight_ndim(self, axis):
  1091. # Create a multi-dimensional array to test
  1092. np.random.seed(1)
  1093. x_no_nan = np.random.random(size=(100, 99, 2))
  1094. # Set some places to NaN (not particularly smart) so there is always
  1095. # some non-Nan.
  1096. x = x_no_nan.copy()
  1097. x[np.arange(99), np.arange(99), 0] = np.nan
  1098. p = np.array([[20., 50., 30], [70, 33, 80]])
  1099. # We just use ones as weights, but replace it with 0 or 1e200 at the
  1100. # NaN positions below.
  1101. weights = np.ones_like(x)
  1102. # For comparison use weighted normal percentile with nan weights at
  1103. # 0 (and no NaNs); not sure this is strictly identical but should be
  1104. # sufficiently so (if a percentile lies exactly on a 0 value).
  1105. weights[np.isnan(x)] = 0
  1106. p_expected = np.percentile(
  1107. x_no_nan, p, axis=axis, weights=weights, method="inverted_cdf")
  1108. p_unweighted = np.nanpercentile(
  1109. x, p, axis=axis, method="inverted_cdf")
  1110. # The normal and unweighted versions should be identical:
  1111. assert_equal(p_unweighted, p_expected)
  1112. weights[np.isnan(x)] = 1e200 # huge value, shouldn't matter
  1113. p_weighted = np.nanpercentile(
  1114. x, p, axis=axis, weights=weights, method="inverted_cdf")
  1115. assert_equal(p_weighted, p_expected)
  1116. # Also check with out passed:
  1117. out = np.empty_like(p_weighted)
  1118. res = np.nanpercentile(
  1119. x, p, axis=axis, weights=weights, out=out, method="inverted_cdf")
  1120. assert res is out
  1121. assert_equal(out, p_expected)
  1122. class TestNanFunctions_Quantile:
  1123. # most of this is already tested by TestPercentile
  1124. @pytest.mark.parametrize("weighted", [False, True])
  1125. def test_regression(self, weighted):
  1126. ar = np.arange(24).reshape(2, 3, 4).astype(float)
  1127. ar[0][1] = np.nan
  1128. if weighted:
  1129. w_args = {"weights": np.ones_like(ar), "method": "inverted_cdf"}
  1130. else:
  1131. w_args = {}
  1132. assert_equal(np.nanquantile(ar, q=0.5, **w_args),
  1133. np.nanpercentile(ar, q=50, **w_args))
  1134. assert_equal(np.nanquantile(ar, q=0.5, axis=0, **w_args),
  1135. np.nanpercentile(ar, q=50, axis=0, **w_args))
  1136. assert_equal(np.nanquantile(ar, q=0.5, axis=1, **w_args),
  1137. np.nanpercentile(ar, q=50, axis=1, **w_args))
  1138. assert_equal(np.nanquantile(ar, q=[0.5], axis=1, **w_args),
  1139. np.nanpercentile(ar, q=[50], axis=1, **w_args))
  1140. assert_equal(np.nanquantile(ar, q=[0.25, 0.5, 0.75], axis=1, **w_args),
  1141. np.nanpercentile(ar, q=[25, 50, 75], axis=1, **w_args))
  1142. def test_basic(self):
  1143. x = np.arange(8) * 0.5
  1144. assert_equal(np.nanquantile(x, 0), 0.)
  1145. assert_equal(np.nanquantile(x, 1), 3.5)
  1146. assert_equal(np.nanquantile(x, 0.5), 1.75)
  1147. def test_complex(self):
  1148. arr_c = np.array([0.5 + 3.0j, 2.1 + 0.5j, 1.6 + 2.3j], dtype='G')
  1149. assert_raises(TypeError, np.nanquantile, arr_c, 0.5)
  1150. arr_c = np.array([0.5 + 3.0j, 2.1 + 0.5j, 1.6 + 2.3j], dtype='D')
  1151. assert_raises(TypeError, np.nanquantile, arr_c, 0.5)
  1152. arr_c = np.array([0.5 + 3.0j, 2.1 + 0.5j, 1.6 + 2.3j], dtype='F')
  1153. assert_raises(TypeError, np.nanquantile, arr_c, 0.5)
  1154. def test_no_p_overwrite(self):
  1155. # this is worth retesting, because quantile does not make a copy
  1156. p0 = np.array([0, 0.75, 0.25, 0.5, 1.0])
  1157. p = p0.copy()
  1158. np.nanquantile(np.arange(100.), p, method="midpoint")
  1159. assert_array_equal(p, p0)
  1160. p0 = p0.tolist()
  1161. p = p.tolist()
  1162. np.nanquantile(np.arange(100.), p, method="midpoint")
  1163. assert_array_equal(p, p0)
  1164. @pytest.mark.parametrize("axis", [None, 0, 1])
  1165. @pytest.mark.parametrize("dtype", np.typecodes["Float"])
  1166. @pytest.mark.parametrize("array", [
  1167. np.array(np.nan),
  1168. np.full((3, 3), np.nan),
  1169. ], ids=["0d", "2d"])
  1170. def test_allnans(self, axis, dtype, array):
  1171. if axis is not None and array.ndim == 0:
  1172. pytest.skip("`axis != None` not supported for 0d arrays")
  1173. array = array.astype(dtype)
  1174. with pytest.warns(RuntimeWarning, match="All-NaN slice encountered"):
  1175. out = np.nanquantile(array, 1, axis=axis)
  1176. assert np.isnan(out).all()
  1177. assert out.dtype == array.dtype
  1178. @pytest.mark.parametrize("arr, expected", [
  1179. # array of floats with some nans
  1180. (np.array([np.nan, 5.0, np.nan, np.inf]),
  1181. np.array([False, True, False, True])),
  1182. # int64 array that can't possibly have nans
  1183. (np.array([1, 5, 7, 9], dtype=np.int64),
  1184. True),
  1185. # bool array that can't possibly have nans
  1186. (np.array([False, True, False, True]),
  1187. True),
  1188. # 2-D complex array with nans
  1189. (np.array([[np.nan, 5.0],
  1190. [np.nan, np.inf]], dtype=np.complex64),
  1191. np.array([[False, True],
  1192. [False, True]])),
  1193. ])
  1194. def test__nan_mask(arr, expected):
  1195. for out in [None, np.empty(arr.shape, dtype=np.bool)]:
  1196. actual = _nan_mask(arr, out=out)
  1197. assert_equal(actual, expected)
  1198. # the above won't distinguish between True proper
  1199. # and an array of True values; we want True proper
  1200. # for types that can't possibly contain NaN
  1201. if type(expected) is not np.ndarray:
  1202. assert actual is True
  1203. def test__replace_nan():
  1204. """ Test that _replace_nan returns the original array if there are no
  1205. NaNs, not a copy.
  1206. """
  1207. for dtype in [np.bool, np.int32, np.int64]:
  1208. arr = np.array([0, 1], dtype=dtype)
  1209. result, mask = _replace_nan(arr, 0)
  1210. assert mask is None
  1211. # do not make a copy if there are no nans
  1212. assert result is arr
  1213. for dtype in [np.float32, np.float64]:
  1214. arr = np.array([0, 1], dtype=dtype)
  1215. result, mask = _replace_nan(arr, 2)
  1216. assert (mask == False).all()
  1217. # mask is not None, so we make a copy
  1218. assert result is not arr
  1219. assert_equal(result, arr)
  1220. arr_nan = np.array([0, 1, np.nan], dtype=dtype)
  1221. result_nan, mask_nan = _replace_nan(arr_nan, 2)
  1222. assert_equal(mask_nan, np.array([False, False, True]))
  1223. assert result_nan is not arr_nan
  1224. assert_equal(result_nan, np.array([0, 1, 2]))
  1225. assert np.isnan(arr_nan[-1])
  1226. @pytest.mark.thread_unsafe(reason="memmap is thread-unsafe (gh-29126)")
  1227. def test_memmap_takes_fast_route(tmpdir):
  1228. # We want memory mapped arrays to take the fast route through nanmax,
  1229. # which avoids creating a mask by using fmax.reduce (see gh-28721). So we
  1230. # check that on bad input, the error is from fmax (rather than maximum).
  1231. a = np.arange(10., dtype=float)
  1232. with open(tmpdir.join("data.bin"), "w+b") as fh:
  1233. fh.write(a.tobytes())
  1234. mm = np.memmap(fh, dtype=a.dtype, shape=a.shape)
  1235. with pytest.raises(ValueError, match="reduction operation fmax"):
  1236. np.nanmax(mm, out=np.zeros(2))
  1237. # For completeness, same for nanmin.
  1238. with pytest.raises(ValueError, match="reduction operation fmin"):
  1239. np.nanmin(mm, out=np.zeros(2))