1from datetime import datetime
2
3import numpy as np
4import pytest
5
6from pandas._libs.tslibs import Timestamp
7
8import pandas as pd
9from pandas import Float64Index, Index, Int64Index, RangeIndex, Series, UInt64Index
10import pandas._testing as tm
11from pandas.tests.indexes.common import Base
12
13
14class TestArithmetic:
15    @pytest.mark.parametrize(
16        "klass", [Float64Index, Int64Index, UInt64Index, RangeIndex]
17    )
18    def test_arithmetic_explicit_conversions(self, klass):
19
20        # GH 8608
21        # add/sub are overridden explicitly for Float/Int Index
22        if klass is RangeIndex:
23            idx = RangeIndex(5)
24        else:
25            idx = klass(np.arange(5, dtype="int64"))
26
27        # float conversions
28        arr = np.arange(5, dtype="int64") * 3.2
29        expected = Float64Index(arr)
30        fidx = idx * 3.2
31        tm.assert_index_equal(fidx, expected)
32        fidx = 3.2 * idx
33        tm.assert_index_equal(fidx, expected)
34
35        # interops with numpy arrays
36        expected = Float64Index(arr)
37        a = np.zeros(5, dtype="float64")
38        result = fidx - a
39        tm.assert_index_equal(result, expected)
40
41        expected = Float64Index(-arr)
42        a = np.zeros(5, dtype="float64")
43        result = a - fidx
44        tm.assert_index_equal(result, expected)
45
46
47class TestNumericIndex:
48    def test_index_groupby(self):
49        int_idx = Index(range(6))
50        float_idx = Index(np.arange(0, 0.6, 0.1))
51        obj_idx = Index("A B C D E F".split())
52        dt_idx = pd.date_range("2013-01-01", freq="M", periods=6)
53
54        for idx in [int_idx, float_idx, obj_idx, dt_idx]:
55            to_groupby = np.array([1, 2, np.nan, np.nan, 2, 1])
56            tm.assert_dict_equal(
57                idx.groupby(to_groupby), {1.0: idx[[0, 5]], 2.0: idx[[1, 4]]}
58            )
59
60            to_groupby = Index(
61                [
62                    datetime(2011, 11, 1),
63                    datetime(2011, 12, 1),
64                    pd.NaT,
65                    pd.NaT,
66                    datetime(2011, 12, 1),
67                    datetime(2011, 11, 1),
68                ],
69                tz="UTC",
70            ).values
71
72            ex_keys = [Timestamp("2011-11-01"), Timestamp("2011-12-01")]
73            expected = {ex_keys[0]: idx[[0, 5]], ex_keys[1]: idx[[1, 4]]}
74            tm.assert_dict_equal(idx.groupby(to_groupby), expected)
75
76
77class Numeric(Base):
78    def test_where(self):
79        # Tested in numeric.test_indexing
80        pass
81
82    def test_can_hold_identifiers(self):
83        idx = self.create_index()
84        key = idx[0]
85        assert idx._can_hold_identifiers_and_holds_name(key) is False
86
87    def test_format(self):
88        # GH35439
89        idx = self.create_index()
90        max_width = max(len(str(x)) for x in idx)
91        expected = [str(x).ljust(max_width) for x in idx]
92        assert idx.format() == expected
93
94    def test_numeric_compat(self):
95        pass  # override Base method
96
97    def test_insert_na(self, nulls_fixture):
98        # GH 18295 (test missing)
99        index = self.create_index()
100
101        if nulls_fixture is pd.NaT:
102            expected = Index([index[0], pd.NaT] + list(index[1:]), dtype=object)
103        else:
104            expected = Float64Index([index[0], np.nan] + list(index[1:]))
105        result = index.insert(1, nulls_fixture)
106        tm.assert_index_equal(result, expected)
107
108
109class TestFloat64Index(Numeric):
110    _holder = Float64Index
111
112    @pytest.fixture(
113        params=[
114            [1.5, 2, 3, 4, 5],
115            [0.0, 2.5, 5.0, 7.5, 10.0],
116            [5, 4, 3, 2, 1.5],
117            [10.0, 7.5, 5.0, 2.5, 0.0],
118        ],
119        ids=["mixed", "float", "mixed_dec", "float_dec"],
120    )
121    def index(self, request):
122        return Float64Index(request.param)
123
124    @pytest.fixture
125    def mixed_index(self):
126        return Float64Index([1.5, 2, 3, 4, 5])
127
128    @pytest.fixture
129    def float_index(self):
130        return Float64Index([0.0, 2.5, 5.0, 7.5, 10.0])
131
132    def create_index(self) -> Float64Index:
133        return Float64Index(np.arange(5, dtype="float64"))
134
135    def test_repr_roundtrip(self, index):
136        tm.assert_index_equal(eval(repr(index)), index)
137
138    def check_is_index(self, i):
139        assert isinstance(i, Index)
140        assert not isinstance(i, Float64Index)
141
142    def check_coerce(self, a, b, is_float_index=True):
143        assert a.equals(b)
144        tm.assert_index_equal(a, b, exact=False)
145        if is_float_index:
146            assert isinstance(b, Float64Index)
147        else:
148            self.check_is_index(b)
149
150    def test_constructor(self):
151
152        # explicit construction
153        index = Float64Index([1, 2, 3, 4, 5])
154        assert isinstance(index, Float64Index)
155        expected = np.array([1, 2, 3, 4, 5], dtype="float64")
156        tm.assert_numpy_array_equal(index.values, expected)
157        index = Float64Index(np.array([1, 2, 3, 4, 5]))
158        assert isinstance(index, Float64Index)
159        index = Float64Index([1.0, 2, 3, 4, 5])
160        assert isinstance(index, Float64Index)
161        index = Float64Index(np.array([1.0, 2, 3, 4, 5]))
162        assert isinstance(index, Float64Index)
163        assert index.dtype == float
164
165        index = Float64Index(np.array([1.0, 2, 3, 4, 5]), dtype=np.float32)
166        assert isinstance(index, Float64Index)
167        assert index.dtype == np.float64
168
169        index = Float64Index(np.array([1, 2, 3, 4, 5]), dtype=np.float32)
170        assert isinstance(index, Float64Index)
171        assert index.dtype == np.float64
172
173        # nan handling
174        result = Float64Index([np.nan, np.nan])
175        assert pd.isna(result.values).all()
176        result = Float64Index(np.array([np.nan]))
177        assert pd.isna(result.values).all()
178        result = Index(np.array([np.nan]))
179        assert pd.isna(result.values).all()
180
181    @pytest.mark.parametrize(
182        "index, dtype",
183        [
184            (Int64Index, "float64"),
185            (UInt64Index, "categorical"),
186            (Float64Index, "datetime64"),
187            (RangeIndex, "float64"),
188        ],
189    )
190    def test_invalid_dtype(self, index, dtype):
191        # GH 29539
192        with pytest.raises(
193            ValueError,
194            match=rf"Incorrect `dtype` passed: expected \w+(?: \w+)?, received {dtype}",
195        ):
196            index([1, 2, 3], dtype=dtype)
197
198    def test_constructor_invalid(self):
199
200        # invalid
201        msg = (
202            r"Float64Index\(\.\.\.\) must be called with a collection of "
203            r"some kind, 0\.0 was passed"
204        )
205        with pytest.raises(TypeError, match=msg):
206            Float64Index(0.0)
207
208        # 2021-02-1 we get ValueError in numpy 1.20, but not on all builds
209        msg = "|".join(
210            [
211                "String dtype not supported, you may need to explicitly cast ",
212                "could not convert string to float: 'a'",
213            ]
214        )
215        with pytest.raises((TypeError, ValueError), match=msg):
216            Float64Index(["a", "b", 0.0])
217
218        msg = r"float\(\) argument must be a string or a number, not 'Timestamp'"
219        with pytest.raises(TypeError, match=msg):
220            Float64Index([Timestamp("20130101")])
221
222    def test_constructor_coerce(self, mixed_index, float_index):
223
224        self.check_coerce(mixed_index, Index([1.5, 2, 3, 4, 5]))
225        self.check_coerce(float_index, Index(np.arange(5) * 2.5))
226        self.check_coerce(
227            float_index, Index(np.array(np.arange(5) * 2.5, dtype=object))
228        )
229
230    def test_constructor_explicit(self, mixed_index, float_index):
231
232        # these don't auto convert
233        self.check_coerce(
234            float_index, Index((np.arange(5) * 2.5), dtype=object), is_float_index=False
235        )
236        self.check_coerce(
237            mixed_index, Index([1.5, 2, 3, 4, 5], dtype=object), is_float_index=False
238        )
239
240    def test_type_coercion_fail(self, any_int_dtype):
241        # see gh-15832
242        msg = "Trying to coerce float values to integers"
243        with pytest.raises(ValueError, match=msg):
244            Index([1, 2, 3.5], dtype=any_int_dtype)
245
246    def test_type_coercion_valid(self, float_dtype):
247        # There is no Float32Index, so we always
248        # generate Float64Index.
249        i = Index([1, 2, 3.5], dtype=float_dtype)
250        tm.assert_index_equal(i, Index([1, 2, 3.5]))
251
252    def test_equals_numeric(self):
253
254        i = Float64Index([1.0, 2.0])
255        assert i.equals(i)
256        assert i.identical(i)
257
258        i2 = Float64Index([1.0, 2.0])
259        assert i.equals(i2)
260
261        i = Float64Index([1.0, np.nan])
262        assert i.equals(i)
263        assert i.identical(i)
264
265        i2 = Float64Index([1.0, np.nan])
266        assert i.equals(i2)
267
268    @pytest.mark.parametrize(
269        "other",
270        (
271            Int64Index([1, 2]),
272            Index([1.0, 2.0], dtype=object),
273            Index([1, 2], dtype=object),
274        ),
275    )
276    def test_equals_numeric_other_index_type(self, other):
277        i = Float64Index([1.0, 2.0])
278        assert i.equals(other)
279        assert other.equals(i)
280
281    @pytest.mark.parametrize(
282        "vals",
283        [
284            pd.date_range("2016-01-01", periods=3),
285            pd.timedelta_range("1 Day", periods=3),
286        ],
287    )
288    def test_lookups_datetimelike_values(self, vals):
289        # If we have datetime64 or timedelta64 values, make sure they are
290        #  wrappped correctly  GH#31163
291        ser = Series(vals, index=range(3, 6))
292        ser.index = ser.index.astype("float64")
293
294        expected = vals[1]
295
296        with tm.assert_produces_warning(FutureWarning):
297            result = ser.index.get_value(ser, 4.0)
298        assert isinstance(result, type(expected)) and result == expected
299        with tm.assert_produces_warning(FutureWarning):
300            result = ser.index.get_value(ser, 4)
301        assert isinstance(result, type(expected)) and result == expected
302
303        result = ser[4.0]
304        assert isinstance(result, type(expected)) and result == expected
305        result = ser[4]
306        assert isinstance(result, type(expected)) and result == expected
307
308        result = ser.loc[4.0]
309        assert isinstance(result, type(expected)) and result == expected
310        result = ser.loc[4]
311        assert isinstance(result, type(expected)) and result == expected
312
313        result = ser.at[4.0]
314        assert isinstance(result, type(expected)) and result == expected
315        # GH#31329 .at[4] should cast to 4.0, matching .loc behavior
316        result = ser.at[4]
317        assert isinstance(result, type(expected)) and result == expected
318
319        result = ser.iloc[1]
320        assert isinstance(result, type(expected)) and result == expected
321
322        result = ser.iat[1]
323        assert isinstance(result, type(expected)) and result == expected
324
325    def test_doesnt_contain_all_the_things(self):
326        i = Float64Index([np.nan])
327        assert not i.isin([0]).item()
328        assert not i.isin([1]).item()
329        assert i.isin([np.nan]).item()
330
331    def test_nan_multiple_containment(self):
332        i = Float64Index([1.0, np.nan])
333        tm.assert_numpy_array_equal(i.isin([1.0]), np.array([True, False]))
334        tm.assert_numpy_array_equal(i.isin([2.0, np.pi]), np.array([False, False]))
335        tm.assert_numpy_array_equal(i.isin([np.nan]), np.array([False, True]))
336        tm.assert_numpy_array_equal(i.isin([1.0, np.nan]), np.array([True, True]))
337        i = Float64Index([1.0, 2.0])
338        tm.assert_numpy_array_equal(i.isin([np.nan]), np.array([False, False]))
339
340    def test_fillna_float64(self):
341        # GH 11343
342        idx = Index([1.0, np.nan, 3.0], dtype=float, name="x")
343        # can't downcast
344        exp = Index([1.0, 0.1, 3.0], name="x")
345        tm.assert_index_equal(idx.fillna(0.1), exp)
346
347        # downcast
348        exp = Float64Index([1.0, 2.0, 3.0], name="x")
349        tm.assert_index_equal(idx.fillna(2), exp)
350
351        # object
352        exp = Index([1.0, "obj", 3.0], name="x")
353        tm.assert_index_equal(idx.fillna("obj"), exp)
354
355
356class NumericInt(Numeric):
357    def test_view(self):
358        i = self._holder([], name="Foo")
359        i_view = i.view()
360        assert i_view.name == "Foo"
361
362        i_view = i.view(self._dtype)
363        tm.assert_index_equal(i, self._holder(i_view, name="Foo"))
364
365        i_view = i.view(self._holder)
366        tm.assert_index_equal(i, self._holder(i_view, name="Foo"))
367
368    def test_is_monotonic(self):
369        index = self._holder([1, 2, 3, 4])
370        assert index.is_monotonic is True
371        assert index.is_monotonic_increasing is True
372        assert index._is_strictly_monotonic_increasing is True
373        assert index.is_monotonic_decreasing is False
374        assert index._is_strictly_monotonic_decreasing is False
375
376        index = self._holder([4, 3, 2, 1])
377        assert index.is_monotonic is False
378        assert index._is_strictly_monotonic_increasing is False
379        assert index._is_strictly_monotonic_decreasing is True
380
381        index = self._holder([1])
382        assert index.is_monotonic is True
383        assert index.is_monotonic_increasing is True
384        assert index.is_monotonic_decreasing is True
385        assert index._is_strictly_monotonic_increasing is True
386        assert index._is_strictly_monotonic_decreasing is True
387
388    def test_is_strictly_monotonic(self):
389        index = self._holder([1, 1, 2, 3])
390        assert index.is_monotonic_increasing is True
391        assert index._is_strictly_monotonic_increasing is False
392
393        index = self._holder([3, 2, 1, 1])
394        assert index.is_monotonic_decreasing is True
395        assert index._is_strictly_monotonic_decreasing is False
396
397        index = self._holder([1, 1])
398        assert index.is_monotonic_increasing
399        assert index.is_monotonic_decreasing
400        assert not index._is_strictly_monotonic_increasing
401        assert not index._is_strictly_monotonic_decreasing
402
403    def test_logical_compat(self):
404        idx = self.create_index()
405        assert idx.all() == idx.values.all()
406        assert idx.any() == idx.values.any()
407
408    def test_identical(self):
409        index = self.create_index()
410        i = Index(index.copy())
411        assert i.identical(index)
412
413        same_values_different_type = Index(i, dtype=object)
414        assert not i.identical(same_values_different_type)
415
416        i = index.astype(dtype=object)
417        i = i.rename("foo")
418        same_values = Index(i, dtype=object)
419        assert same_values.identical(i)
420
421        assert not i.identical(index)
422        assert Index(same_values, name="foo", dtype=object).identical(i)
423
424        assert not index.astype(dtype=object).identical(index.astype(dtype=self._dtype))
425
426    def test_cant_or_shouldnt_cast(self):
427        msg = (
428            "String dtype not supported, "
429            "you may need to explicitly cast to a numeric type"
430        )
431        # can't
432        data = ["foo", "bar", "baz"]
433        with pytest.raises(TypeError, match=msg):
434            self._holder(data)
435
436        # shouldn't
437        data = ["0", "1", "2"]
438        with pytest.raises(TypeError, match=msg):
439            self._holder(data)
440
441    def test_view_index(self):
442        index = self.create_index()
443        index.view(Index)
444
445    def test_prevent_casting(self):
446        index = self.create_index()
447        result = index.astype("O")
448        assert result.dtype == np.object_
449
450
451class TestInt64Index(NumericInt):
452    _dtype = "int64"
453    _holder = Int64Index
454
455    @pytest.fixture(
456        params=[range(0, 20, 2), range(19, -1, -1)], ids=["index_inc", "index_dec"]
457    )
458    def index(self, request):
459        return Int64Index(request.param)
460
461    def create_index(self) -> Int64Index:
462        # return Int64Index(np.arange(5, dtype="int64"))
463        return Int64Index(range(0, 20, 2))
464
465    def test_constructor(self):
466        # pass list, coerce fine
467        index = Int64Index([-5, 0, 1, 2])
468        expected = Index([-5, 0, 1, 2], dtype=np.int64)
469        tm.assert_index_equal(index, expected)
470
471        # from iterable
472        index = Int64Index(iter([-5, 0, 1, 2]))
473        tm.assert_index_equal(index, expected)
474
475        # scalar raise Exception
476        msg = (
477            r"Int64Index\(\.\.\.\) must be called with a collection of some "
478            "kind, 5 was passed"
479        )
480        with pytest.raises(TypeError, match=msg):
481            Int64Index(5)
482
483        # copy
484        arr = index.values
485        new_index = Int64Index(arr, copy=True)
486        tm.assert_index_equal(new_index, index)
487        val = arr[0] + 3000
488
489        # this should not change index
490        arr[0] = val
491        assert new_index[0] != val
492
493        # interpret list-like
494        expected = Int64Index([5, 0])
495        for cls in [Index, Int64Index]:
496            for idx in [
497                cls([5, 0], dtype="int64"),
498                cls(np.array([5, 0]), dtype="int64"),
499                cls(Series([5, 0]), dtype="int64"),
500            ]:
501                tm.assert_index_equal(idx, expected)
502
503    def test_constructor_corner(self):
504        arr = np.array([1, 2, 3, 4], dtype=object)
505        index = Int64Index(arr)
506        assert index.values.dtype == np.int64
507        tm.assert_index_equal(index, Index(arr))
508
509        # preventing casting
510        arr = np.array([1, "2", 3, "4"], dtype=object)
511        with pytest.raises(TypeError, match="casting"):
512            Int64Index(arr)
513
514        arr_with_floats = [0, 2, 3, 4, 5, 1.25, 3, -1]
515        with pytest.raises(TypeError, match="casting"):
516            Int64Index(arr_with_floats)
517
518    def test_constructor_coercion_signed_to_unsigned(self, uint_dtype):
519
520        # see gh-15832
521        msg = "Trying to coerce negative values to unsigned integers"
522
523        with pytest.raises(OverflowError, match=msg):
524            Index([-1], dtype=uint_dtype)
525
526    def test_constructor_unwraps_index(self):
527        idx = Index([1, 2])
528        result = Int64Index(idx)
529        expected = np.array([1, 2], dtype="int64")
530        tm.assert_numpy_array_equal(result._data, expected)
531
532    def test_coerce_list(self):
533        # coerce things
534        arr = Index([1, 2, 3, 4])
535        assert isinstance(arr, Int64Index)
536
537        # but not if explicit dtype passed
538        arr = Index([1, 2, 3, 4], dtype=object)
539        assert isinstance(arr, Index)
540
541
542class TestUInt64Index(NumericInt):
543
544    _dtype = "uint64"
545    _holder = UInt64Index
546
547    @pytest.fixture(
548        params=[
549            [2 ** 63, 2 ** 63 + 10, 2 ** 63 + 15, 2 ** 63 + 20, 2 ** 63 + 25],
550            [2 ** 63 + 25, 2 ** 63 + 20, 2 ** 63 + 15, 2 ** 63 + 10, 2 ** 63],
551        ],
552        ids=["index_inc", "index_dec"],
553    )
554    def index(self, request):
555        return UInt64Index(request.param)
556
557    def create_index(self) -> UInt64Index:
558        # compat with shared Int64/Float64 tests
559        return UInt64Index(np.arange(5, dtype="uint64"))
560
561    def test_constructor(self):
562        idx = UInt64Index([1, 2, 3])
563        res = Index([1, 2, 3], dtype=np.uint64)
564        tm.assert_index_equal(res, idx)
565
566        idx = UInt64Index([1, 2 ** 63])
567        res = Index([1, 2 ** 63], dtype=np.uint64)
568        tm.assert_index_equal(res, idx)
569
570        idx = UInt64Index([1, 2 ** 63])
571        res = Index([1, 2 ** 63])
572        tm.assert_index_equal(res, idx)
573
574        idx = Index([-1, 2 ** 63], dtype=object)
575        res = Index(np.array([-1, 2 ** 63], dtype=object))
576        tm.assert_index_equal(res, idx)
577
578        # https://github.com/pandas-dev/pandas/issues/29526
579        idx = UInt64Index([1, 2 ** 63 + 1], dtype=np.uint64)
580        res = Index([1, 2 ** 63 + 1], dtype=np.uint64)
581        tm.assert_index_equal(res, idx)
582
583
584@pytest.mark.parametrize(
585    "box",
586    [list, lambda x: np.array(x, dtype=object), lambda x: Index(x, dtype=object)],
587)
588def test_uint_index_does_not_convert_to_float64(box):
589    # https://github.com/pandas-dev/pandas/issues/28279
590    # https://github.com/pandas-dev/pandas/issues/28023
591    series = Series(
592        [0, 1, 2, 3, 4, 5],
593        index=[
594            7606741985629028552,
595            17876870360202815256,
596            17876870360202815256,
597            13106359306506049338,
598            8991270399732411471,
599            8991270399732411472,
600        ],
601    )
602
603    result = series.loc[box([7606741985629028552, 17876870360202815256])]
604
605    expected = UInt64Index(
606        [7606741985629028552, 17876870360202815256, 17876870360202815256],
607        dtype="uint64",
608    )
609    tm.assert_index_equal(result.index, expected)
610
611    tm.assert_equal(result, series[:3])
612
613
614def test_float64_index_equals():
615    # https://github.com/pandas-dev/pandas/issues/35217
616    float_index = Index([1.0, 2, 3])
617    string_index = Index(["1", "2", "3"])
618
619    result = float_index.equals(string_index)
620    assert result is False
621
622    result = string_index.equals(float_index)
623    assert result is False
624