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