Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/core/indexes/range.py: 19%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

694 statements  

1from __future__ import annotations 

2 

3from collections.abc import ( 

4 Callable, 

5 Hashable, 

6 Iterator, 

7) 

8from datetime import timedelta 

9import operator 

10from sys import getsizeof 

11from typing import ( 

12 TYPE_CHECKING, 

13 Any, 

14 Literal, 

15 Self, 

16 cast, 

17 overload, 

18) 

19 

20import numpy as np 

21 

22from pandas._libs import ( 

23 index as libindex, 

24 lib, 

25) 

26from pandas._libs.internals import BlockValuesRefs 

27from pandas._libs.lib import no_default 

28from pandas.compat.numpy import function as nv 

29from pandas.util._decorators import ( 

30 cache_readonly, 

31 set_module, 

32) 

33 

34from pandas.core.dtypes.base import ExtensionDtype 

35from pandas.core.dtypes.common import ( 

36 ensure_platform_int, 

37 ensure_python_int, 

38 is_float, 

39 is_integer, 

40 is_scalar, 

41 is_signed_integer_dtype, 

42) 

43from pandas.core.dtypes.generic import ABCTimedeltaIndex 

44 

45from pandas.core import ops 

46import pandas.core.common as com 

47from pandas.core.construction import extract_array 

48from pandas.core.indexers import check_array_indexer 

49import pandas.core.indexes.base as ibase 

50from pandas.core.indexes.base import ( 

51 Index, 

52 maybe_extract_name, 

53) 

54from pandas.core.ops.common import unpack_zerodim_and_defer 

55 

56if TYPE_CHECKING: 

57 from pandas._typing import ( 

58 Axis, 

59 Dtype, 

60 JoinHow, 

61 NaPosition, 

62 NumpySorter, 

63 NumpyValueArrayLike, 

64 ScalarLike_co, 

65 npt, 

66 ) 

67 

68 from pandas import Series 

69 from pandas.core.arrays import ExtensionArray 

70 

71_empty_range = range(0) 

72_dtype_int64 = np.dtype(np.int64) 

73 

74 

75def min_fitting_element(start: int, step: int, lower_limit: int) -> int: 

76 """Returns the smallest element greater than or equal to the limit""" 

77 no_steps = -(-(lower_limit - start) // abs(step)) 

78 return start + abs(step) * no_steps 

79 

80 

81@set_module("pandas") 

82class RangeIndex(Index): 

83 """ 

84 Immutable Index implementing a monotonic integer range. 

85 

86 RangeIndex is a memory-saving special case of an Index limited to representing 

87 monotonic ranges with a 64-bit dtype. Using RangeIndex may in some instances 

88 improve computing speed. 

89 

90 This is the default index type used 

91 by DataFrame and Series when no explicit index is provided by the user. 

92 

93 Parameters 

94 ---------- 

95 start : int, range, or other RangeIndex instance, default None 

96 If int and "stop" is not given, interpreted as "stop" instead. 

97 stop : int, default None 

98 The end value of the range (exclusive). 

99 step : int, default None 

100 The step size of the range. 

101 dtype : np.int64, default None 

102 Unused, accepted for homogeneity with other index types. 

103 copy : bool, default False 

104 Unused, accepted for homogeneity with other index types. 

105 name : object, optional 

106 Name to be stored in the index. 

107 

108 Attributes 

109 ---------- 

110 start 

111 stop 

112 step 

113 

114 Methods 

115 ------- 

116 from_range 

117 

118 See Also 

119 -------- 

120 Index : The base pandas Index type. 

121 

122 Examples 

123 -------- 

124 >>> list(pd.RangeIndex(5)) 

125 [0, 1, 2, 3, 4] 

126 

127 >>> list(pd.RangeIndex(-2, 4)) 

128 [-2, -1, 0, 1, 2, 3] 

129 

130 >>> list(pd.RangeIndex(0, 10, 2)) 

131 [0, 2, 4, 6, 8] 

132 

133 >>> list(pd.RangeIndex(2, -10, -3)) 

134 [2, -1, -4, -7] 

135 

136 >>> list(pd.RangeIndex(0)) 

137 [] 

138 

139 >>> list(pd.RangeIndex(1, 0)) 

140 [] 

141 """ 

142 

143 _typ = "rangeindex" 

144 _dtype_validation_metadata = (is_signed_integer_dtype, "signed integer") 

145 _range: range 

146 _values: np.ndarray 

147 

148 @property 

149 def _engine_type(self) -> type[libindex.Int64Engine]: 

150 return libindex.Int64Engine 

151 

152 # -------------------------------------------------------------------- 

153 # Constructors 

154 

155 def __new__( 

156 cls, 

157 start=None, 

158 stop=None, 

159 step=None, 

160 dtype: Dtype | None = None, 

161 copy: bool = False, 

162 name: Hashable | None = None, 

163 ) -> Self: 

164 cls._validate_dtype(dtype) 

165 name = maybe_extract_name(name, start, cls) 

166 

167 # RangeIndex 

168 if isinstance(start, cls): 

169 return start.copy(name=name) 

170 elif isinstance(start, range): 

171 return cls._simple_new(start, name=name) 

172 

173 # validate the arguments 

174 if com.all_none(start, stop, step): 

175 raise TypeError("RangeIndex(...) must be called with integers") 

176 

177 start = ensure_python_int(start) if start is not None else 0 

178 

179 if stop is None: 

180 start, stop = 0, start 

181 else: 

182 stop = ensure_python_int(stop) 

183 

184 step = ensure_python_int(step) if step is not None else 1 

185 if step == 0: 

186 raise ValueError("Step must not be zero") 

187 

188 rng = range(start, stop, step) 

189 return cls._simple_new(rng, name=name) 

190 

191 @classmethod 

192 def from_range(cls, data: range, name=None, dtype: Dtype | None = None) -> Self: 

193 """ 

194 Create :class:`pandas.RangeIndex` from a ``range`` object. 

195 

196 This method provides a way to create a :class:`pandas.RangeIndex` directly 

197 from a Python ``range`` object. The resulting :class:`RangeIndex` will have 

198 the same start, stop, and step values as the input ``range`` object. 

199 It is particularly useful for constructing indices in an efficient and 

200 memory-friendly manner. 

201 

202 Parameters 

203 ---------- 

204 data : range 

205 The range object to be converted into a RangeIndex. 

206 name : str, default None 

207 Name to be stored in the index. 

208 dtype : Dtype or None 

209 Data type for the RangeIndex. If None, the default integer type will 

210 be used. 

211 

212 Returns 

213 ------- 

214 RangeIndex 

215 

216 See Also 

217 -------- 

218 RangeIndex : Immutable Index implementing a monotonic integer range. 

219 Index : Immutable sequence used for indexing and alignment. 

220 

221 Examples 

222 -------- 

223 >>> pd.RangeIndex.from_range(range(5)) 

224 RangeIndex(start=0, stop=5, step=1) 

225 

226 >>> pd.RangeIndex.from_range(range(2, -10, -3)) 

227 RangeIndex(start=2, stop=-10, step=-3) 

228 """ 

229 if not isinstance(data, range): 

230 raise TypeError( 

231 f"{cls.__name__}(...) must be called with object coercible to a " 

232 f"range, {data!r} was passed" 

233 ) 

234 cls._validate_dtype(dtype) 

235 return cls._simple_new(data, name=name) 

236 

237 # error: Argument 1 of "_simple_new" is incompatible with supertype "Index"; 

238 # supertype defines the argument type as 

239 # "Union[ExtensionArray, ndarray[Any, Any]]" [override] 

240 @classmethod 

241 def _simple_new( # type: ignore[override] 

242 cls, values: range, name: Hashable | None = None 

243 ) -> Self: 

244 result = object.__new__(cls) 

245 

246 assert isinstance(values, range) 

247 

248 result._range = values 

249 result._name = name 

250 result._cache = {} 

251 result._reset_identity() 

252 # result._references populated lazily 

253 return result 

254 

255 @cache_readonly 

256 def _references(self) -> BlockValuesRefs: # type: ignore[override] 

257 result = BlockValuesRefs() 

258 result.add_index_reference(self) 

259 return result 

260 

261 @classmethod 

262 def _validate_dtype(cls, dtype: Dtype | None) -> None: 

263 if dtype is None: 

264 return 

265 

266 validation_func, expected = cls._dtype_validation_metadata 

267 if not validation_func(dtype): 

268 raise ValueError( 

269 f"Incorrect `dtype` passed: expected {expected}, received {dtype}" 

270 ) 

271 

272 # -------------------------------------------------------------------- 

273 

274 # error: Return type "Type[Index]" of "_constructor" incompatible with return 

275 # type "Type[RangeIndex]" in supertype "Index" 

276 @cache_readonly 

277 def _constructor(self) -> type[Index]: # type: ignore[override] 

278 """return the class to use for construction""" 

279 return Index 

280 

281 # error: Signature of "_data" incompatible with supertype "Index" 

282 @cache_readonly 

283 def _data(self) -> np.ndarray: # type: ignore[override] 

284 """ 

285 An int array that for performance reasons is created only when needed. 

286 

287 The constructed array is saved in ``_cache``. 

288 """ 

289 return np.arange(self.start, self.stop, self.step, dtype=np.int64) 

290 

291 def _get_data_as_items(self) -> list[tuple[str, int]]: 

292 """return a list of tuples of start, stop, step""" 

293 rng = self._range 

294 return [("start", rng.start), ("stop", rng.stop), ("step", rng.step)] 

295 

296 def __reduce__(self): 

297 d = {"name": self._name} 

298 d.update(dict(self._get_data_as_items())) 

299 return ibase._new_Index, (type(self), d), None 

300 

301 # -------------------------------------------------------------------- 

302 # Rendering Methods 

303 

304 def _format_attrs(self): 

305 """ 

306 Return a list of tuples of the (attr, formatted_value) 

307 """ 

308 attrs = cast("list[tuple[str, str | int]]", self._get_data_as_items()) 

309 if self._name is not None: 

310 attrs.append(("name", ibase.default_pprint(self._name))) 

311 return attrs 

312 

313 def _format_with_header(self, *, header: list[str], na_rep: str) -> list[str]: 

314 # Equivalent to Index implementation, but faster 

315 if not len(self._range): 

316 return header 

317 first_val_str = str(self._range[0]) 

318 last_val_str = str(self._range[-1]) 

319 max_length = max(len(first_val_str), len(last_val_str)) 

320 

321 return header + [f"{x:<{max_length}}" for x in self._range] 

322 

323 # -------------------------------------------------------------------- 

324 

325 @property 

326 def start(self) -> int: 

327 """ 

328 The value of the `start` parameter (``0`` if this was not supplied). 

329 

330 This property returns the starting value of the `RangeIndex`. If the `start` 

331 value is not explicitly provided during the creation of the `RangeIndex`, 

332 it defaults to 0. 

333 

334 See Also 

335 -------- 

336 RangeIndex : Immutable index implementing a range-based index. 

337 RangeIndex.stop : Returns the stop value of the `RangeIndex`. 

338 RangeIndex.step : Returns the step value of the `RangeIndex`. 

339 

340 Examples 

341 -------- 

342 >>> idx = pd.RangeIndex(5) 

343 >>> idx.start 

344 0 

345 

346 >>> idx = pd.RangeIndex(2, -10, -3) 

347 >>> idx.start 

348 2 

349 """ 

350 # GH 25710 

351 return self._range.start 

352 

353 @property 

354 def stop(self) -> int: 

355 """ 

356 The value of the `stop` parameter. 

357 

358 This property returns the `stop` value of the RangeIndex, which defines the 

359 upper (or lower, in case of negative steps) bound of the index range. The 

360 `stop` value is exclusive, meaning the RangeIndex includes values up to but 

361 not including this value. 

362 

363 See Also 

364 -------- 

365 RangeIndex : Immutable index representing a range of integers. 

366 RangeIndex.start : The start value of the RangeIndex. 

367 RangeIndex.step : The step size between elements in the RangeIndex. 

368 

369 Examples 

370 -------- 

371 >>> idx = pd.RangeIndex(5) 

372 >>> idx.stop 

373 5 

374 

375 >>> idx = pd.RangeIndex(2, -10, -3) 

376 >>> idx.stop 

377 -10 

378 """ 

379 return self._range.stop 

380 

381 @property 

382 def step(self) -> int: 

383 """ 

384 The value of the `step` parameter (``1`` if this was not supplied). 

385 

386 The ``step`` parameter determines the increment (or decrement in the case 

387 of negative values) between consecutive elements in the ``RangeIndex``. 

388 

389 See Also 

390 -------- 

391 RangeIndex : Immutable index implementing a range-based index. 

392 RangeIndex.stop : Returns the stop value of the RangeIndex. 

393 RangeIndex.start : Returns the start value of the RangeIndex. 

394 

395 Examples 

396 -------- 

397 >>> idx = pd.RangeIndex(5) 

398 >>> idx.step 

399 1 

400 

401 >>> idx = pd.RangeIndex(2, -10, -3) 

402 >>> idx.step 

403 -3 

404 

405 Even if :class:`pandas.RangeIndex` is empty, ``step`` is still ``1`` if 

406 not supplied. 

407 

408 >>> idx = pd.RangeIndex(1, 0) 

409 >>> idx.step 

410 1 

411 """ 

412 # GH 25710 

413 return self._range.step 

414 

415 @cache_readonly 

416 def nbytes(self) -> int: 

417 """ 

418 Return the number of bytes in the underlying data. 

419 """ 

420 rng = self._range 

421 return getsizeof(rng) + sum( 

422 getsizeof(getattr(rng, attr_name)) 

423 for attr_name in ["start", "stop", "step"] 

424 ) 

425 

426 def memory_usage(self, deep: bool = False) -> int: 

427 """ 

428 Memory usage of my values 

429 

430 Parameters 

431 ---------- 

432 deep : bool 

433 Introspect the data deeply, interrogate 

434 `object` dtypes for system-level memory consumption 

435 

436 Returns 

437 ------- 

438 bytes used 

439 

440 Notes 

441 ----- 

442 Memory usage does not include memory consumed by elements that 

443 are not components of the array if deep=False 

444 

445 See Also 

446 -------- 

447 numpy.ndarray.nbytes 

448 """ 

449 return self.nbytes 

450 

451 @property 

452 def dtype(self) -> np.dtype: 

453 return _dtype_int64 

454 

455 @property 

456 def is_unique(self) -> bool: 

457 """return if the index has unique values""" 

458 return True 

459 

460 @cache_readonly 

461 def is_monotonic_increasing(self) -> bool: 

462 return self._range.step > 0 or len(self) <= 1 

463 

464 @cache_readonly 

465 def is_monotonic_decreasing(self) -> bool: 

466 return self._range.step < 0 or len(self) <= 1 

467 

468 def __contains__(self, key: Any) -> bool: 

469 hash(key) 

470 try: 

471 key = ensure_python_int(key) 

472 except (TypeError, OverflowError): 

473 return False 

474 return key in self._range 

475 

476 @property 

477 def inferred_type(self) -> str: 

478 return "integer" 

479 

480 # -------------------------------------------------------------------- 

481 # Indexing Methods 

482 

483 def get_loc(self, key) -> int: 

484 """ 

485 Get integer location for requested label. 

486 

487 Parameters 

488 ---------- 

489 key : int or float 

490 Label to locate. Integer-like floats (e.g. 3.0) are accepted and 

491 treated as the corresponding integer. Non-integer floats and other 

492 non-integer labels are not valid and will raise KeyError or 

493 InvalidIndexError. 

494 

495 Returns 

496 ------- 

497 int 

498 Integer location of the label within the RangeIndex. 

499 

500 Raises 

501 ------ 

502 KeyError 

503 If the label is not present in the RangeIndex or the label is a 

504 non-integer value. 

505 InvalidIndexError 

506 If the label is of an invalid type for the RangeIndex. 

507 

508 See Also 

509 -------- 

510 RangeIndex.get_slice_bound : Calculate slice bound that corresponds to 

511 given label. 

512 RangeIndex.get_indexer : Computes indexer and mask for new index given 

513 the current index. 

514 RangeIndex.get_non_unique : Returns indexer and masks for new index given 

515 the current index. 

516 RangeIndex.get_indexer_for : Returns an indexer even when non-unique. 

517 

518 Examples 

519 -------- 

520 >>> idx = pd.RangeIndex(5) 

521 >>> idx.get_loc(3) 

522 3 

523 

524 >>> idx = pd.RangeIndex(2, 10, 2) # values [2, 4, 6, 8] 

525 >>> idx.get_loc(6) 

526 2 

527 """ 

528 if is_integer(key) or (is_float(key) and key.is_integer()): 

529 new_key = int(key) 

530 try: 

531 return self._range.index(new_key) 

532 except ValueError as err: 

533 raise KeyError(key) from err 

534 if isinstance(key, Hashable): 

535 raise KeyError(key) 

536 self._check_indexing_error(key) 

537 raise KeyError(key) 

538 

539 def _get_indexer( 

540 self, 

541 target: Index, 

542 method: str | None = None, 

543 limit: int | None = None, 

544 tolerance=None, 

545 ) -> npt.NDArray[np.intp]: 

546 if com.any_not_none(method, tolerance, limit): 

547 return super()._get_indexer( 

548 target, method=method, tolerance=tolerance, limit=limit 

549 ) 

550 

551 if self.step > 0: 

552 start, stop, step = self.start, self.stop, self.step 

553 else: 

554 # GH 28678: work on reversed range for simplicity 

555 reverse = self._range[::-1] 

556 start, stop, step = reverse.start, reverse.stop, reverse.step 

557 

558 target_array = np.asarray(target) 

559 locs = target_array - start 

560 valid = (locs % step == 0) & (locs >= 0) & (target_array < stop) 

561 locs[~valid] = -1 

562 locs[valid] = locs[valid] / step 

563 

564 if step != self.step: 

565 # We reversed this range: transform to original locs 

566 locs[valid] = len(self) - 1 - locs[valid] 

567 return ensure_platform_int(locs) 

568 

569 @cache_readonly 

570 def _should_fallback_to_positional(self) -> bool: 

571 """ 

572 Should an integer key be treated as positional? 

573 """ 

574 return False 

575 

576 # -------------------------------------------------------------------- 

577 

578 def tolist(self) -> list[int]: 

579 return list(self._range) 

580 

581 def __iter__(self) -> Iterator[int]: 

582 """ 

583 Return an iterator of the values. 

584 

585 Returns 

586 ------- 

587 iterator 

588 An iterator yielding ints from the RangeIndex. 

589 

590 Examples 

591 -------- 

592 >>> idx = pd.RangeIndex(3) 

593 >>> for x in idx: 

594 ... print(x) 

595 0 

596 1 

597 2 

598 """ 

599 yield from self._range 

600 

601 def _shallow_copy(self, values, name: Hashable = no_default): 

602 """ 

603 Create a new RangeIndex with the same class as the caller, don't copy the 

604 data, use the same object attributes with passed in attributes taking 

605 precedence. 

606 

607 *this is an internal non-public method* 

608 

609 Parameters 

610 ---------- 

611 values : the values to create the new RangeIndex, optional 

612 name : Label, defaults to self.name 

613 """ 

614 name = self._name if name is no_default else name 

615 

616 if values.dtype.kind == "f": 

617 return Index(values, name=name, dtype=np.float64, copy=False) 

618 if values.dtype.kind == "i" and values.ndim == 1: 

619 # GH 46675 & 43885: If values is equally spaced, return a 

620 # more memory-compact RangeIndex instead of Index with 64-bit dtype 

621 if len(values) == 1: 

622 start = values[0] 

623 new_range = range(start, start + self.step, self.step) 

624 return type(self)._simple_new(new_range, name=name) 

625 maybe_range = ibase.maybe_sequence_to_range(values) 

626 if isinstance(maybe_range, range): 

627 return type(self)._simple_new(maybe_range, name=name) 

628 return self._constructor._simple_new(values, name=name) 

629 

630 def _view(self) -> Self: 

631 result = type(self)._simple_new(self._range, name=self._name) 

632 result._cache = self._cache 

633 self._references.add_index_reference(result) 

634 return result 

635 

636 def _wrap_reindex_result(self, target, indexer, preserve_names: bool): 

637 if not isinstance(target, type(self)) and target.dtype.kind == "i": 

638 target = self._shallow_copy(target._values, name=target.name) 

639 return super()._wrap_reindex_result(target, indexer, preserve_names) 

640 

641 def copy(self, name: Hashable | None = None, deep: bool = False) -> Self: 

642 """ 

643 Make a copy of this object. 

644 

645 Name is set on the new object. 

646 

647 Parameters 

648 ---------- 

649 name : Label, optional 

650 Set name for new object. 

651 deep : bool, default False 

652 If True attempts to make a deep copy of the RangeIndex. 

653 Else makes a shallow copy. 

654 

655 Returns 

656 ------- 

657 RangeIndex 

658 RangeIndex refer to new object which is a copy of this object. 

659 

660 See Also 

661 -------- 

662 RangeIndex.delete: Make new RangeIndex with passed location(-s) deleted. 

663 RangeIndex.drop: Make new RangeIndex with passed list of labels deleted. 

664 

665 Notes 

666 ----- 

667 In most cases, there should be no functional difference from using 

668 ``deep``, but if ``deep`` is passed it will attempt to deepcopy. 

669 

670 Examples 

671 -------- 

672 >>> idx = pd.RangeIndex(3) 

673 >>> new_idx = idx.copy() 

674 >>> idx is new_idx 

675 False 

676 """ 

677 name = self._validate_names(name=name, deep=deep)[0] 

678 new_index = self._rename(name=name) 

679 return new_index 

680 

681 def _minmax(self, meth: Literal["min", "max"]) -> int | float: 

682 no_steps = len(self) - 1 

683 if no_steps == -1: 

684 return np.nan 

685 elif (meth == "min" and self.step > 0) or (meth == "max" and self.step < 0): 

686 return self.start 

687 

688 return self.start + self.step * no_steps 

689 

690 def min(self, axis=None, skipna: bool = True, *args, **kwargs) -> int | float: 

691 """The minimum value of the RangeIndex""" 

692 nv.validate_minmax_axis(axis) 

693 nv.validate_min(args, kwargs) 

694 return self._minmax("min") 

695 

696 def max(self, axis=None, skipna: bool = True, *args, **kwargs) -> int | float: 

697 """The maximum value of the RangeIndex""" 

698 nv.validate_minmax_axis(axis) 

699 nv.validate_max(args, kwargs) 

700 return self._minmax("max") 

701 

702 def _argminmax( 

703 self, 

704 meth: Literal["min", "max"], 

705 axis=None, 

706 skipna: bool = True, 

707 ) -> int: 

708 nv.validate_minmax_axis(axis) 

709 if len(self) == 0: 

710 return getattr(super(), f"arg{meth}")( 

711 axis=axis, 

712 skipna=skipna, 

713 ) 

714 elif meth == "min": 

715 if self.step > 0: 

716 return 0 

717 else: 

718 return len(self) - 1 

719 elif meth == "max": 

720 if self.step > 0: 

721 return len(self) - 1 

722 else: 

723 return 0 

724 else: 

725 raise ValueError(f"{meth=} must be max or min") 

726 

727 def argmin(self, axis=None, skipna: bool = True, *args, **kwargs) -> int: 

728 nv.validate_argmin(args, kwargs) 

729 return self._argminmax("min", axis=axis, skipna=skipna) 

730 

731 def argmax(self, axis=None, skipna: bool = True, *args, **kwargs) -> int: 

732 nv.validate_argmax(args, kwargs) 

733 return self._argminmax("max", axis=axis, skipna=skipna) 

734 

735 def argsort(self, *args, **kwargs) -> npt.NDArray[np.intp]: 

736 """ 

737 Returns the indices that would sort the index and its 

738 underlying data. 

739 

740 Returns 

741 ------- 

742 np.ndarray[np.intp] 

743 

744 See Also 

745 -------- 

746 numpy.ndarray.argsort 

747 """ 

748 ascending = kwargs.pop("ascending", True) # EA compat 

749 kwargs.pop("kind", None) # e.g. "mergesort" is irrelevant 

750 nv.validate_argsort(args, kwargs) 

751 

752 start, stop, step = None, None, None 

753 if self._range.step > 0: 

754 if ascending: 

755 start = len(self) 

756 else: 

757 start, stop, step = len(self) - 1, -1, -1 

758 elif ascending: 

759 start, stop, step = len(self) - 1, -1, -1 

760 else: 

761 start = len(self) 

762 

763 return np.arange(start, stop, step, dtype=np.intp) 

764 

765 def factorize( 

766 self, 

767 sort: bool = False, 

768 use_na_sentinel: bool = True, 

769 ) -> tuple[npt.NDArray[np.intp], RangeIndex]: 

770 if sort and self.step < 0: 

771 codes = np.arange(len(self) - 1, -1, -1, dtype=np.intp) 

772 uniques = self[::-1] 

773 else: 

774 codes = np.arange(len(self), dtype=np.intp) 

775 uniques = self 

776 return codes, uniques 

777 

778 def equals(self, other: object) -> bool: 

779 """ 

780 Determines if two Index objects contain the same elements. 

781 """ 

782 if isinstance(other, RangeIndex): 

783 return self._range == other._range 

784 return super().equals(other) 

785 

786 @overload 

787 def sort_values( 

788 self, 

789 *, 

790 return_indexer: Literal[False] = ..., 

791 ascending: bool = ..., 

792 na_position: NaPosition = ..., 

793 key: Callable | None = ..., 

794 ) -> Self: ... 

795 

796 @overload 

797 def sort_values( 

798 self, 

799 *, 

800 return_indexer: Literal[True], 

801 ascending: bool = ..., 

802 na_position: NaPosition = ..., 

803 key: Callable | None = ..., 

804 ) -> tuple[Self, np.ndarray]: ... 

805 

806 @overload 

807 def sort_values( 

808 self, 

809 *, 

810 return_indexer: bool = ..., 

811 ascending: bool = ..., 

812 na_position: NaPosition = ..., 

813 key: Callable | None = ..., 

814 ) -> Self | tuple[Self, np.ndarray]: ... 

815 

816 def sort_values( 

817 self, 

818 *, 

819 return_indexer: bool = False, 

820 ascending: bool = True, 

821 na_position: NaPosition = "last", 

822 key: Callable | None = None, 

823 ) -> Self | tuple[Self, np.ndarray]: 

824 if key is not None: 

825 return super().sort_values( 

826 return_indexer=return_indexer, 

827 ascending=ascending, 

828 na_position=na_position, 

829 key=key, 

830 ) 

831 else: 

832 sorted_index = self 

833 inverse_indexer = False 

834 if ascending: 

835 if self.step < 0: 

836 sorted_index = self[::-1] 

837 inverse_indexer = True 

838 elif self.step > 0: 

839 sorted_index = self[::-1] 

840 inverse_indexer = True 

841 

842 if return_indexer: 

843 if inverse_indexer: 

844 indexer = np.arange(len(self) - 1, -1, -1, dtype=np.intp) 

845 else: 

846 indexer = np.arange(len(self), dtype=np.intp) 

847 return sorted_index, indexer 

848 else: 

849 return sorted_index 

850 

851 # -------------------------------------------------------------------- 

852 # Set Operations 

853 

854 def _intersection(self, other: Index, sort: bool = False): 

855 # caller is responsible for checking self and other are both non-empty 

856 

857 if not isinstance(other, RangeIndex): 

858 return super()._intersection(other, sort=sort) 

859 

860 first = self._range[::-1] if self.step < 0 else self._range 

861 second = other._range[::-1] if other.step < 0 else other._range 

862 

863 # check whether intervals intersect 

864 # deals with in- and decreasing ranges 

865 int_low = max(first.start, second.start) 

866 int_high = min(first.stop, second.stop) 

867 if int_high <= int_low: 

868 return self._simple_new(_empty_range) 

869 

870 # Method hint: linear Diophantine equation 

871 # solve intersection problem 

872 # performance hint: for identical step sizes, could use 

873 # cheaper alternative 

874 gcd, s, _ = self._extended_gcd(first.step, second.step) 

875 

876 # check whether element sets intersect 

877 if (first.start - second.start) % gcd: 

878 return self._simple_new(_empty_range) 

879 

880 # calculate parameters for the RangeIndex describing the 

881 # intersection disregarding the lower bounds 

882 tmp_start = first.start + (second.start - first.start) * first.step // gcd * s 

883 new_step = first.step * second.step // gcd 

884 

885 # adjust index to limiting interval 

886 new_start = min_fitting_element(tmp_start, new_step, int_low) 

887 new_range = range(new_start, int_high, new_step) 

888 

889 if (self.step < 0 and other.step < 0) is not (new_range.step < 0): 

890 new_range = new_range[::-1] 

891 

892 return self._simple_new(new_range) 

893 

894 def _extended_gcd(self, a: int, b: int) -> tuple[int, int, int]: 

895 """ 

896 Extended Euclidean algorithms to solve Bezout's identity: 

897 a*x + b*y = gcd(x, y) 

898 Finds one particular solution for x, y: s, t 

899 Returns: gcd, s, t 

900 """ 

901 s, old_s = 0, 1 

902 t, old_t = 1, 0 

903 r, old_r = b, a 

904 while r: 

905 quotient = old_r // r 

906 old_r, r = r, old_r - quotient * r 

907 old_s, s = s, old_s - quotient * s 

908 old_t, t = t, old_t - quotient * t 

909 return old_r, old_s, old_t 

910 

911 def _range_in_self(self, other: range) -> bool: 

912 """Check if other range is contained in self""" 

913 # https://stackoverflow.com/a/32481015 

914 if not other: 

915 return True 

916 if not self._range: 

917 return False 

918 if len(other) > 1 and other.step % self._range.step: 

919 return False 

920 return other.start in self._range and other[-1] in self._range 

921 

922 def _union(self, other: Index, sort: bool | None): 

923 """ 

924 Form the union of two Index objects and sorts if possible 

925 

926 Parameters 

927 ---------- 

928 other : Index or array-like 

929 

930 sort : bool or None, default None 

931 Whether to sort (monotonically increasing) the resulting index. 

932 ``sort=None|True`` returns a ``RangeIndex`` if possible or a sorted 

933 ``Index`` with an int64 dtype if not. 

934 ``sort=False`` can return a ``RangeIndex`` if self is monotonically 

935 increasing and other is fully contained in self. Otherwise, returns 

936 an unsorted ``Index`` with an int64 dtype. 

937 

938 Returns 

939 ------- 

940 union : Index 

941 """ 

942 if isinstance(other, RangeIndex): 

943 if sort in (None, True) or ( 

944 sort is False and self.step > 0 and self._range_in_self(other._range) 

945 ): 

946 # GH 47557: Can still return a RangeIndex 

947 # if other range in self and sort=False 

948 start_s, step_s = self.start, self.step 

949 end_s = self.start + self.step * (len(self) - 1) 

950 start_o, step_o = other.start, other.step 

951 end_o = other.start + other.step * (len(other) - 1) 

952 if self.step < 0: 

953 start_s, step_s, end_s = end_s, -step_s, start_s 

954 if other.step < 0: 

955 start_o, step_o, end_o = end_o, -step_o, start_o 

956 if len(self) == 1 and len(other) == 1: 

957 step_s = step_o = abs(self.start - other.start) 

958 elif len(self) == 1: 

959 step_s = step_o 

960 elif len(other) == 1: 

961 step_o = step_s 

962 start_r = min(start_s, start_o) 

963 end_r = max(end_s, end_o) 

964 if step_o == step_s: 

965 if ( 

966 (start_s - start_o) % step_s == 0 

967 and (start_s - end_o) <= step_s 

968 and (start_o - end_s) <= step_s 

969 ): 

970 return type(self)(start_r, end_r + step_s, step_s) 

971 if ( 

972 (step_s % 2 == 0) 

973 and (abs(start_s - start_o) == step_s / 2) 

974 and (abs(end_s - end_o) == step_s / 2) 

975 ): 

976 # e.g. range(0, 10, 2) and range(1, 11, 2) 

977 # but not range(0, 20, 4) and range(1, 21, 4) GH#44019 

978 return type(self)(start_r, end_r + step_s / 2, step_s / 2) 

979 

980 elif step_o % step_s == 0: 

981 if ( 

982 (start_o - start_s) % step_s == 0 

983 and (start_o + step_s >= start_s) 

984 and (end_o - step_s <= end_s) 

985 ): 

986 return type(self)(start_r, end_r + step_s, step_s) 

987 elif step_s % step_o == 0: 

988 if ( 

989 (start_s - start_o) % step_o == 0 

990 and (start_s + step_o >= start_o) 

991 and (end_s - step_o <= end_o) 

992 ): 

993 return type(self)(start_r, end_r + step_o, step_o) 

994 

995 return super()._union(other, sort=sort) 

996 

997 def _difference(self, other, sort=None): 

998 # optimized set operation if we have another RangeIndex 

999 self._validate_sort_keyword(sort) 

1000 self._assert_can_do_setop(other) 

1001 other, result_name = self._convert_can_do_setop(other) 

1002 

1003 if not isinstance(other, RangeIndex): 

1004 return super()._difference(other, sort=sort) 

1005 

1006 if sort is not False and self.step < 0: 

1007 return self[::-1]._difference(other) 

1008 

1009 res_name = ops.get_op_result_name(self, other) 

1010 

1011 first = self._range[::-1] if self.step < 0 else self._range 

1012 overlap = self.intersection(other) 

1013 if overlap.step < 0: 

1014 overlap = overlap[::-1] 

1015 

1016 if len(overlap) == 0: 

1017 return self.rename(name=res_name) 

1018 if len(overlap) == len(self): 

1019 return self[:0].rename(res_name) 

1020 

1021 # overlap.step will always be a multiple of self.step (see _intersection) 

1022 

1023 if len(overlap) == 1: 

1024 if overlap[0] == self[0]: 

1025 return self[1:] 

1026 

1027 elif overlap[0] == self[-1]: 

1028 return self[:-1] 

1029 

1030 elif len(self) == 3 and overlap[0] == self[1]: 

1031 return self[::2] 

1032 

1033 else: 

1034 return super()._difference(other, sort=sort) 

1035 

1036 elif len(overlap) == 2 and overlap[0] == first[0] and overlap[-1] == first[-1]: 

1037 # e.g. range(-8, 20, 7) and range(13, -9, -3) 

1038 return self[1:-1] 

1039 

1040 if overlap.step == first.step: 

1041 if overlap[0] == first.start: 

1042 # The difference is everything after the intersection 

1043 new_rng = range(overlap[-1] + first.step, first.stop, first.step) 

1044 elif overlap[-1] == first[-1]: 

1045 # The difference is everything before the intersection 

1046 new_rng = range(first.start, overlap[0], first.step) 

1047 elif overlap._range == first[1:-1]: 

1048 # e.g. range(4) and range(1, 3) 

1049 step = len(first) - 1 

1050 new_rng = first[::step] 

1051 else: 

1052 # The difference is not range-like 

1053 # e.g. range(1, 10, 1) and range(3, 7, 1) 

1054 return super()._difference(other, sort=sort) 

1055 

1056 else: 

1057 # We must have len(self) > 1, bc we ruled out above 

1058 # len(overlap) == 0 and len(overlap) == len(self) 

1059 assert len(self) > 1 

1060 

1061 if overlap.step == first.step * 2: 

1062 if overlap[0] == first[0] and overlap[-1] in (first[-1], first[-2]): 

1063 # e.g. range(1, 10, 1) and range(1, 10, 2) 

1064 new_rng = first[1::2] 

1065 

1066 elif overlap[0] == first[1] and overlap[-1] in (first[-1], first[-2]): 

1067 # e.g. range(1, 10, 1) and range(2, 10, 2) 

1068 new_rng = first[::2] 

1069 

1070 else: 

1071 # We can get here with e.g. range(20) and range(0, 10, 2) 

1072 return super()._difference(other, sort=sort) 

1073 

1074 else: 

1075 # e.g. range(10) and range(0, 10, 3) 

1076 return super()._difference(other, sort=sort) 

1077 

1078 if first is not self._range: 

1079 new_rng = new_rng[::-1] 

1080 new_index = type(self)._simple_new(new_rng, name=res_name) 

1081 

1082 return new_index 

1083 

1084 def symmetric_difference( 

1085 self, other, result_name: Hashable | None = None, sort=None 

1086 ) -> Index: 

1087 if not isinstance(other, RangeIndex) or sort is not None: 

1088 return super().symmetric_difference(other, result_name, sort) 

1089 

1090 left = self.difference(other) 

1091 right = other.difference(self) 

1092 result = left.union(right) 

1093 

1094 if result_name is not None: 

1095 result = result.rename(result_name) 

1096 return result 

1097 

1098 def _join_empty( 

1099 self, other: Index, how: JoinHow, sort: bool 

1100 ) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]: 

1101 if not isinstance(other, RangeIndex) and other.dtype.kind == "i": 

1102 other = self._shallow_copy(other._values, name=other.name) 

1103 return super()._join_empty(other, how=how, sort=sort) 

1104 

1105 def _join_monotonic( 

1106 self, other: Index, how: JoinHow = "left" 

1107 ) -> tuple[Index, npt.NDArray[np.intp] | None, npt.NDArray[np.intp] | None]: 

1108 # This currently only gets called for the monotonic increasing case 

1109 if not isinstance(other, type(self)): 

1110 maybe_ri = self._shallow_copy(other._values, name=other.name) 

1111 if not isinstance(maybe_ri, type(self)): 

1112 return super()._join_monotonic(other, how=how) 

1113 other = maybe_ri 

1114 

1115 if self.equals(other): 

1116 ret_index = other if how == "right" else self 

1117 return ret_index, None, None 

1118 

1119 if how == "left": 

1120 join_index = self 

1121 lidx = None 

1122 ridx = other.get_indexer(join_index) 

1123 elif how == "right": 

1124 join_index = other 

1125 lidx = self.get_indexer(join_index) 

1126 ridx = None 

1127 elif how == "inner": 

1128 join_index = self.intersection(other) 

1129 lidx = self.get_indexer(join_index) 

1130 ridx = other.get_indexer(join_index) 

1131 elif how == "outer": 

1132 join_index = self.union(other) 

1133 lidx = self.get_indexer(join_index) 

1134 ridx = other.get_indexer(join_index) 

1135 

1136 lidx = None if lidx is None else ensure_platform_int(lidx) 

1137 ridx = None if ridx is None else ensure_platform_int(ridx) 

1138 return join_index, lidx, ridx 

1139 

1140 # -------------------------------------------------------------------- 

1141 

1142 # error: Return type "Index" of "delete" incompatible with return type 

1143 # "RangeIndex" in supertype "Index" 

1144 def delete(self, loc) -> Index: # type: ignore[override] 

1145 # In some cases we can retain RangeIndex, see also 

1146 # DatetimeTimedeltaMixin._get_delete_Freq 

1147 if is_integer(loc): 

1148 if loc in (0, -len(self)): 

1149 return self[1:] 

1150 if loc in (-1, len(self) - 1): 

1151 return self[:-1] 

1152 if len(self) == 3 and loc in (1, -2): 

1153 return self[::2] 

1154 

1155 elif lib.is_list_like(loc): 

1156 slc = lib.maybe_indices_to_slice(np.asarray(loc, dtype=np.intp), len(self)) 

1157 

1158 if isinstance(slc, slice): 

1159 # defer to RangeIndex._difference, which is optimized to return 

1160 # a RangeIndex whenever possible 

1161 other = self[slc] 

1162 return self.difference(other, sort=False) 

1163 

1164 return super().delete(loc) 

1165 

1166 def insert(self, loc: int, item) -> Index: 

1167 if is_integer(item) or is_float(item): 

1168 # We can retain RangeIndex is inserting at the beginning or end, 

1169 # or right in the middle. 

1170 if len(self) == 0 and loc == 0 and is_integer(item): 

1171 new_rng = range(item, item + self.step, self.step) 

1172 return type(self)._simple_new(new_rng, name=self._name) 

1173 elif len(self): 

1174 rng = self._range 

1175 if loc == 0 and item == self[0] - self.step: 

1176 new_rng = range(rng.start - rng.step, rng.stop, rng.step) 

1177 return type(self)._simple_new(new_rng, name=self._name) 

1178 

1179 elif loc == len(self) and item == self[-1] + self.step: 

1180 new_rng = range(rng.start, rng.stop + rng.step, rng.step) 

1181 return type(self)._simple_new(new_rng, name=self._name) 

1182 

1183 elif len(self) == 2 and item == self[0] + self.step / 2: 

1184 # e.g. inserting 1 into [0, 2] 

1185 step = int(self.step / 2) 

1186 new_rng = range(self.start, self.stop, step) 

1187 return type(self)._simple_new(new_rng, name=self._name) 

1188 

1189 return super().insert(loc, item) 

1190 

1191 def _concat(self, indexes: list[Index], name: Hashable) -> Index: 

1192 """ 

1193 Overriding parent method for the case of all RangeIndex instances. 

1194 

1195 When all members of "indexes" are of type RangeIndex: result will be 

1196 RangeIndex if possible, Index with an int64 dtype otherwise. E.g.: 

1197 indexes = [RangeIndex(3), RangeIndex(3, 6)] -> RangeIndex(6) 

1198 indexes = [RangeIndex(3), RangeIndex(4, 6)] -> Index([0,1,2,4,5], dtype='int64') 

1199 """ 

1200 if not all(isinstance(x, RangeIndex) for x in indexes): 

1201 result = super()._concat(indexes, name) 

1202 if result.dtype.kind == "i": 

1203 return self._shallow_copy(result._values) 

1204 return result 

1205 

1206 elif len(indexes) == 1: 

1207 return indexes[0] 

1208 

1209 rng_indexes = cast(list[RangeIndex], indexes) 

1210 

1211 start = step = next_ = None 

1212 

1213 # Filter the empty indexes 

1214 non_empty_indexes = [] 

1215 all_same_index = True 

1216 prev: RangeIndex | None = None 

1217 for obj in rng_indexes: 

1218 if len(obj): 

1219 non_empty_indexes.append(obj) 

1220 if all_same_index: 

1221 if prev is not None: 

1222 all_same_index = prev.equals(obj) 

1223 else: 

1224 prev = obj 

1225 

1226 for obj in non_empty_indexes: 

1227 rng = obj._range 

1228 

1229 if start is None: 

1230 # This is set by the first non-empty index 

1231 start = rng.start 

1232 if step is None and len(rng) > 1: 

1233 step = rng.step 

1234 elif step is None: 

1235 # First non-empty index had only one element 

1236 if rng.start == start: 

1237 if all_same_index: 

1238 values = np.tile( 

1239 non_empty_indexes[0]._values, len(non_empty_indexes) 

1240 ) 

1241 else: 

1242 values = np.concatenate([x._values for x in rng_indexes]) 

1243 result = self._constructor(values, copy=False) 

1244 return result.rename(name) 

1245 

1246 step = rng.start - start 

1247 

1248 non_consecutive = (step != rng.step and len(rng) > 1) or ( 

1249 next_ is not None and rng.start != next_ 

1250 ) 

1251 if non_consecutive: 

1252 if all_same_index: 

1253 values = np.tile( 

1254 non_empty_indexes[0]._values, len(non_empty_indexes) 

1255 ) 

1256 else: 

1257 values = np.concatenate([x._values for x in rng_indexes]) 

1258 result = self._constructor(values, copy=False) 

1259 return result.rename(name) 

1260 

1261 if step is not None: 

1262 next_ = rng[-1] + step 

1263 

1264 if non_empty_indexes: 

1265 # Get the stop value from "next" or alternatively 

1266 # from the last non-empty index 

1267 stop = non_empty_indexes[-1].stop if next_ is None else next_ 

1268 if len(non_empty_indexes) == 1: 

1269 step = non_empty_indexes[0].step 

1270 return RangeIndex(start, stop, step, name=name) 

1271 

1272 # Here all "indexes" had 0 length, i.e. were empty. 

1273 # In this case return an empty range index. 

1274 return RangeIndex(_empty_range, name=name) 

1275 

1276 def __len__(self) -> int: 

1277 """ 

1278 return the length of the RangeIndex 

1279 """ 

1280 return len(self._range) 

1281 

1282 @property 

1283 def size(self) -> int: 

1284 return len(self) 

1285 

1286 def __getitem__(self, key): 

1287 """ 

1288 Conserve RangeIndex type for scalar and slice keys. 

1289 """ 

1290 key = lib.item_from_zerodim(key) 

1291 if key is Ellipsis: 

1292 key = slice(None) 

1293 if isinstance(key, slice): 

1294 return self._getitem_slice(key) 

1295 elif is_integer(key): 

1296 new_key = int(key) 

1297 try: 

1298 return self._range[new_key] 

1299 except IndexError as err: 

1300 raise IndexError( 

1301 f"index {key} is out of bounds for axis 0 with size {len(self)}" 

1302 ) from err 

1303 elif is_scalar(key): 

1304 raise IndexError( 

1305 "only integers, slices (`:`), " 

1306 "ellipsis (`...`), numpy.newaxis (`None`) " 

1307 "and integer or boolean " 

1308 "arrays are valid indices" 

1309 ) 

1310 elif com.is_bool_indexer(key): 

1311 if isinstance(getattr(key, "dtype", None), ExtensionDtype): 

1312 key = key.to_numpy(dtype=bool, na_value=False) 

1313 else: 

1314 key = np.asarray(key, dtype=bool) 

1315 check_array_indexer(self._range, key) # type: ignore[arg-type] 

1316 key = np.flatnonzero(key) 

1317 try: 

1318 return self.take(key) 

1319 except (TypeError, ValueError): 

1320 return super().__getitem__(key) 

1321 

1322 def _getitem_slice(self, slobj: slice) -> Self: 

1323 """ 

1324 Fastpath for __getitem__ when we know we have a slice. 

1325 """ 

1326 res = self._range[slobj] 

1327 return type(self)._simple_new(res, name=self._name) 

1328 

1329 @unpack_zerodim_and_defer("__floordiv__") 

1330 def __floordiv__(self, other): 

1331 if is_integer(other) and other != 0: 

1332 if len(self) == 0 or (self.start % other == 0 and self.step % other == 0): 

1333 start = self.start // other 

1334 step = self.step // other 

1335 stop = start + len(self) * step 

1336 new_range = range(start, stop, step or 1) 

1337 return self._simple_new(new_range, name=self._name) 

1338 if len(self) == 1: 

1339 start = self.start // other 

1340 new_range = range(start, start + 1, 1) 

1341 return self._simple_new(new_range, name=self._name) 

1342 

1343 return super().__floordiv__(other) 

1344 

1345 # -------------------------------------------------------------------- 

1346 # Reductions 

1347 

1348 def all(self, *args, **kwargs) -> bool: 

1349 return 0 not in self._range 

1350 

1351 def any(self, *args, **kwargs) -> bool: 

1352 return any(self._range) 

1353 

1354 # -------------------------------------------------------------------- 

1355 

1356 # error: Return type "RangeIndex | Index" of "round" incompatible with 

1357 # return type "RangeIndex" in supertype "Index" 

1358 def round(self, decimals: int = 0) -> Self | Index: # type: ignore[override] 

1359 """ 

1360 Round each value in the Index to the given number of decimals. 

1361 

1362 Parameters 

1363 ---------- 

1364 decimals : int, optional 

1365 Number of decimal places to round to. If decimals is negative, 

1366 it specifies the number of positions to the left of the decimal point 

1367 e.g. ``round(11.0, -1) == 10.0``. 

1368 

1369 Returns 

1370 ------- 

1371 Index or RangeIndex 

1372 A new Index with the rounded values. 

1373 

1374 Examples 

1375 -------- 

1376 >>> import pandas as pd 

1377 >>> idx = pd.RangeIndex(10, 30, 10) 

1378 >>> idx.round(decimals=-1) 

1379 RangeIndex(start=10, stop=30, step=10) 

1380 >>> idx = pd.RangeIndex(10, 15, 1) 

1381 >>> idx.round(decimals=-1) 

1382 Index([10, 10, 10, 10, 10], dtype='int64') 

1383 """ 

1384 if decimals >= 0: 

1385 return self.copy() 

1386 elif self.start % 10**-decimals == 0 and self.step % 10**-decimals == 0: 

1387 # e.g. RangeIndex(10, 30, 10).round(-1) doesn't need rounding 

1388 return self.copy() 

1389 else: 

1390 return super().round(decimals=decimals) 

1391 

1392 def _cmp_method(self, other, op): 

1393 if isinstance(other, RangeIndex) and self._range == other._range: 

1394 # Both are immutable so if ._range attr. are equal, shortcut is possible 

1395 return super()._cmp_method(self, op) 

1396 return super()._cmp_method(other, op) 

1397 

1398 def _arith_method(self, other, op): 

1399 """ 

1400 Parameters 

1401 ---------- 

1402 other : Any 

1403 op : callable that accepts 2 params 

1404 perform the binary op 

1405 """ 

1406 

1407 if isinstance(other, ABCTimedeltaIndex): 

1408 # Defer to TimedeltaIndex implementation 

1409 return NotImplemented 

1410 elif isinstance(other, (timedelta, np.timedelta64)): 

1411 # GH#19333 is_integer evaluated True on timedelta64, 

1412 # so we need to catch these explicitly 

1413 return super()._arith_method(other, op) 

1414 elif lib.is_np_dtype(getattr(other, "dtype", None), "m"): 

1415 # Must be an np.ndarray; GH#22390 

1416 return super()._arith_method(other, op) 

1417 

1418 if op in [ 

1419 operator.pow, 

1420 ops.rpow, 

1421 operator.mod, 

1422 ops.rmod, 

1423 operator.floordiv, 

1424 ops.rfloordiv, 

1425 divmod, 

1426 ops.rdivmod, 

1427 ]: 

1428 return super()._arith_method(other, op) 

1429 

1430 step: Callable | None = None 

1431 if op in [operator.mul, ops.rmul, operator.truediv, ops.rtruediv]: 

1432 step = op 

1433 

1434 # TODO: if other is a RangeIndex we may have more efficient options 

1435 right = extract_array(other, extract_numpy=True, extract_range=True) 

1436 left = self 

1437 

1438 try: 

1439 # apply if we have an override 

1440 if step: 

1441 with np.errstate(all="ignore"): 

1442 rstep = step(left.step, right) 

1443 

1444 # we don't have a representable op 

1445 # so return a base index 

1446 if not is_integer(rstep) or not rstep: 

1447 raise ValueError 

1448 

1449 # GH#53255 

1450 else: 

1451 rstep = -left.step if op == ops.rsub else left.step 

1452 

1453 with np.errstate(all="ignore"): 

1454 rstart = op(left.start, right) 

1455 rstop = op(left.stop, right) 

1456 

1457 res_name = ops.get_op_result_name(self, other) 

1458 result = type(self)(rstart, rstop, rstep, name=res_name) 

1459 

1460 # for compat with numpy / Index with int64 dtype 

1461 # even if we can represent as a RangeIndex, return 

1462 # as a float64 Index if we have float-like descriptors 

1463 if not all(is_integer(x) for x in [rstart, rstop, rstep]): 

1464 result = result.astype("float64") 

1465 

1466 return result 

1467 

1468 except (ValueError, TypeError, ZeroDivisionError): 

1469 # test_arithmetic_explicit_conversions 

1470 return super()._arith_method(other, op) 

1471 

1472 def __abs__(self) -> Self | Index: 

1473 if len(self) == 0 or self.min() >= 0: 

1474 return self.copy() 

1475 elif self.max() <= 0: 

1476 return -self 

1477 else: 

1478 return super().__abs__() 

1479 

1480 def __neg__(self) -> Self: 

1481 rng = range(-self.start, -self.stop, -self.step) 

1482 return self._simple_new(rng, name=self.name) 

1483 

1484 def __pos__(self) -> Self: 

1485 return self.copy() 

1486 

1487 def __invert__(self) -> Self: 

1488 if len(self) == 0: 

1489 return self.copy() 

1490 rng = range(~self.start, ~self.stop, -self.step) 

1491 return self._simple_new(rng, name=self.name) 

1492 

1493 # error: Return type "Index" of "take" incompatible with return type 

1494 # "RangeIndex" in supertype "Index" 

1495 def take( # type: ignore[override] 

1496 self, 

1497 indices, 

1498 axis: Axis = 0, 

1499 allow_fill: bool = True, 

1500 fill_value=None, 

1501 **kwargs, 

1502 ) -> Self | Index: 

1503 if kwargs: 

1504 nv.validate_take((), kwargs) 

1505 if is_scalar(indices): 

1506 raise TypeError("Expected indices to be array-like") 

1507 indices = ensure_platform_int(indices) 

1508 

1509 # raise an exception if allow_fill is True and fill_value is not None 

1510 self._maybe_disallow_fill(allow_fill, fill_value, indices) 

1511 

1512 if len(indices) == 0: 

1513 return type(self)(_empty_range, name=self.name) 

1514 else: 

1515 ind_max = indices.max() 

1516 if ind_max >= len(self): 

1517 raise IndexError( 

1518 f"index {ind_max} is out of bounds for axis 0 with size {len(self)}" 

1519 ) 

1520 ind_min = indices.min() 

1521 if ind_min < -len(self): 

1522 raise IndexError( 

1523 f"index {ind_min} is out of bounds for axis 0 with size {len(self)}" 

1524 ) 

1525 taken = indices.astype(self.dtype, casting="safe") 

1526 if ind_min < 0: 

1527 taken %= len(self) 

1528 if self.step != 1: 

1529 taken *= self.step 

1530 if self.start != 0: 

1531 taken += self.start 

1532 

1533 return self._shallow_copy(taken, name=self.name) 

1534 

1535 def value_counts( 

1536 self, 

1537 normalize: bool = False, 

1538 sort: bool = True, 

1539 ascending: bool = False, 

1540 bins=None, 

1541 dropna: bool = True, 

1542 ) -> Series: 

1543 from pandas import Series 

1544 

1545 if bins is not None: 

1546 return super().value_counts( 

1547 normalize=normalize, 

1548 sort=sort, 

1549 ascending=ascending, 

1550 bins=bins, 

1551 dropna=dropna, 

1552 ) 

1553 name = "proportion" if normalize else "count" 

1554 data: npt.NDArray[np.floating] | npt.NDArray[np.signedinteger] = np.ones( 

1555 len(self), dtype=np.int64 

1556 ) 

1557 if normalize: 

1558 data = data / len(self) 

1559 return Series(data, index=self.copy(), name=name) 

1560 

1561 @overload 

1562 def searchsorted( # type: ignore[overload-overlap] # pyright: ignore[reportOverlappingOverload] 

1563 self, 

1564 value: ScalarLike_co, 

1565 side: Literal["left", "right"] = ..., 

1566 sorter: NumpySorter = ..., 

1567 ) -> np.intp: ... 

1568 

1569 @overload 

1570 def searchsorted( 

1571 self, 

1572 value: npt.ArrayLike | ExtensionArray, 

1573 side: Literal["left", "right"] = ..., 

1574 sorter: NumpySorter = ..., 

1575 ) -> npt.NDArray[np.intp]: ... 

1576 

1577 def searchsorted( 

1578 self, 

1579 value: NumpyValueArrayLike | ExtensionArray, 

1580 side: Literal["left", "right"] = "left", 

1581 sorter: NumpySorter | None = None, 

1582 ) -> npt.NDArray[np.intp] | np.intp: 

1583 if side not in {"left", "right"} or sorter is not None: 

1584 return super().searchsorted(value=value, side=side, sorter=sorter) 

1585 

1586 was_scalar = False 

1587 if is_scalar(value): 

1588 was_scalar = True 

1589 array_value = np.array([value]) 

1590 else: 

1591 array_value = np.asarray(value) 

1592 if array_value.dtype.kind not in "iu": 

1593 return super().searchsorted(value=value, side=side, sorter=sorter) 

1594 

1595 if flip := (self.step < 0): 

1596 rng = self._range[::-1] 

1597 start = rng.start 

1598 step = rng.step 

1599 shift = side == "right" 

1600 else: 

1601 start = self.start 

1602 step = self.step 

1603 shift = side == "left" 

1604 result = (array_value - start - int(shift)) // step + 1 

1605 if flip: 

1606 result = len(self) - result 

1607 result = np.maximum(np.minimum(result, len(self)), 0) 

1608 if was_scalar: 

1609 return np.intp(result.item()) 

1610 return result.astype(np.intp, copy=False)