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