Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
test_nanfunctions.py1439 linesDownload Raw Back to tests
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())

Showing the first 1,200 of 1439 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai