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

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

683 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.lib import no_default 

27from pandas.compat.numpy import function as nv 

28from pandas.util._decorators import ( 

29 cache_readonly, 

30 set_module, 

31) 

32 

33from pandas.core.dtypes.base import ExtensionDtype 

34from pandas.core.dtypes.common import ( 

35 ensure_platform_int, 

36 ensure_python_int, 

37 is_float, 

38 is_integer, 

39 is_scalar, 

40 is_signed_integer_dtype, 

41) 

42from pandas.core.dtypes.generic import ABCTimedeltaIndex 

43 

44from pandas.core import ops 

45import pandas.core.common as com 

46from pandas.core.construction import extract_array 

47from pandas.core.indexers import check_array_indexer 

48import pandas.core.indexes.base as ibase 

49from pandas.core.indexes.base import ( 

50 Index, 

51 maybe_extract_name, 

52) 

53from pandas.core.ops.common import unpack_zerodim_and_defer 

54 

55if TYPE_CHECKING: 

56 from pandas._typing import ( 

57 Axis, 

58 Dtype, 

59 JoinHow, 

60 NaPosition, 

61 NumpySorter, 

62 npt, 

63 ) 

64 

65 from pandas import Series 

66 

67_empty_range = range(0) 

68_dtype_int64 = np.dtype(np.int64) 

69 

70 

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

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

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

74 return start + abs(step) * no_steps 

75 

76 

77@set_module("pandas") 

78class RangeIndex(Index): 

79 """ 

80 Immutable Index implementing a monotonic integer range. 

81 

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

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

84 improve computing speed. 

85 

86 This is the default index type used 

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

88 

89 Parameters 

90 ---------- 

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

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

93 stop : int, default None 

94 The end value of the range (exclusive). 

95 step : int, default None 

96 The step size of the range. 

97 dtype : np.int64, default None 

98 Unused, accepted for homogeneity with other index types. 

99 copy : bool, default False 

100 Unused, accepted for homogeneity with other index types. 

101 name : object, optional 

102 Name to be stored in the index. 

103 

104 Attributes 

105 ---------- 

106 start 

107 stop 

108 step 

109 

110 Methods 

111 ------- 

112 from_range 

113 

114 See Also 

115 -------- 

116 Index : The base pandas Index type. 

117 

118 Examples 

119 -------- 

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

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

122 

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

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

125 

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

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

128 

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

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

131 

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

133 [] 

134 

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

136 [] 

137 """ 

138 

139 _typ = "rangeindex" 

140 _dtype_validation_metadata = (is_signed_integer_dtype, "signed integer") 

141 _range: range 

142 _values: np.ndarray 

143 

144 @property 

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

146 return libindex.Int64Engine 

147 

148 # -------------------------------------------------------------------- 

149 # Constructors 

150 

151 def __new__( 

152 cls, 

153 start=None, 

154 stop=None, 

155 step=None, 

156 dtype: Dtype | None = None, 

157 copy: bool = False, 

158 name: Hashable | None = None, 

159 ) -> Self: 

160 cls._validate_dtype(dtype) 

161 name = maybe_extract_name(name, start, cls) 

162 

163 # RangeIndex 

164 if isinstance(start, cls): 

165 return start.copy(name=name) 

166 elif isinstance(start, range): 

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

168 

169 # validate the arguments 

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

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

172 

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

174 

175 if stop is None: 

176 start, stop = 0, start 

177 else: 

178 stop = ensure_python_int(stop) 

179 

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

181 if step == 0: 

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

183 

184 rng = range(start, stop, step) 

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

186 

187 @classmethod 

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

189 """ 

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

191 

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

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

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

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

196 memory-friendly manner. 

197 

198 Parameters 

199 ---------- 

200 data : range 

201 The range object to be converted into a RangeIndex. 

202 name : str, default None 

203 Name to be stored in the index. 

204 dtype : Dtype or None 

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

206 be used. 

207 

208 Returns 

209 ------- 

210 RangeIndex 

211 

212 See Also 

213 -------- 

214 RangeIndex : Immutable Index implementing a monotonic integer range. 

215 Index : Immutable sequence used for indexing and alignment. 

216 

217 Examples 

218 -------- 

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

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

221 

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

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

224 """ 

225 if not isinstance(data, range): 

226 raise TypeError( 

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

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

229 ) 

230 cls._validate_dtype(dtype) 

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

232 

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

234 # supertype defines the argument type as 

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

236 @classmethod 

237 def _simple_new( # type: ignore[override] 

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

239 ) -> Self: 

240 result = object.__new__(cls) 

241 

242 assert isinstance(values, range) 

243 

244 result._range = values 

245 result._name = name 

246 result._cache = {} 

247 result._reset_identity() 

248 result._references = None 

249 return result 

250 

251 @classmethod 

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

253 if dtype is None: 

254 return 

255 

256 validation_func, expected = cls._dtype_validation_metadata 

257 if not validation_func(dtype): 

258 raise ValueError( 

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

260 ) 

261 

262 # -------------------------------------------------------------------- 

263 

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

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

266 @cache_readonly 

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

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

269 return Index 

270 

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

272 @cache_readonly 

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

274 """ 

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

276 

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

278 """ 

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

280 

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

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

283 rng = self._range 

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

285 

286 def __reduce__(self): 

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

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

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

290 

291 # -------------------------------------------------------------------- 

292 # Rendering Methods 

293 

294 def _format_attrs(self): 

295 """ 

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

297 """ 

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

299 if self._name is not None: 

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

301 return attrs 

302 

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

304 # Equivalent to Index implementation, but faster 

305 if not len(self._range): 

306 return header 

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

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

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

310 

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

312 

313 # -------------------------------------------------------------------- 

314 

315 @property 

316 def start(self) -> int: 

317 """ 

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

319 

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

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

322 it defaults to 0. 

323 

324 See Also 

325 -------- 

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

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

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

329 

330 Examples 

331 -------- 

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

333 >>> idx.start 

334 0 

335 

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

337 >>> idx.start 

338 2 

339 """ 

340 # GH 25710 

341 return self._range.start 

342 

343 @property 

344 def stop(self) -> int: 

345 """ 

346 The value of the `stop` parameter. 

347 

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

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

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

351 not including this value. 

352 

353 See Also 

354 -------- 

355 RangeIndex : Immutable index representing a range of integers. 

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

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

358 

359 Examples 

360 -------- 

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

362 >>> idx.stop 

363 5 

364 

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

366 >>> idx.stop 

367 -10 

368 """ 

369 return self._range.stop 

370 

371 @property 

372 def step(self) -> int: 

373 """ 

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

375 

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

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

378 

379 See Also 

380 -------- 

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

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

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

384 

385 Examples 

386 -------- 

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

388 >>> idx.step 

389 1 

390 

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

392 >>> idx.step 

393 -3 

394 

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

396 not supplied. 

397 

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

399 >>> idx.step 

400 1 

401 """ 

402 # GH 25710 

403 return self._range.step 

404 

405 @cache_readonly 

406 def nbytes(self) -> int: 

407 """ 

408 Return the number of bytes in the underlying data. 

409 """ 

410 rng = self._range 

411 return getsizeof(rng) + sum( 

412 getsizeof(getattr(rng, attr_name)) 

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

414 ) 

415 

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

417 """ 

418 Memory usage of my values 

419 

420 Parameters 

421 ---------- 

422 deep : bool 

423 Introspect the data deeply, interrogate 

424 `object` dtypes for system-level memory consumption 

425 

426 Returns 

427 ------- 

428 bytes used 

429 

430 Notes 

431 ----- 

432 Memory usage does not include memory consumed by elements that 

433 are not components of the array if deep=False 

434 

435 See Also 

436 -------- 

437 numpy.ndarray.nbytes 

438 """ 

439 return self.nbytes 

440 

441 @property 

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

443 return _dtype_int64 

444 

445 @property 

446 def is_unique(self) -> bool: 

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

448 return True 

449 

450 @cache_readonly 

451 def is_monotonic_increasing(self) -> bool: 

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

453 

454 @cache_readonly 

455 def is_monotonic_decreasing(self) -> bool: 

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

457 

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

459 hash(key) 

460 try: 

461 key = ensure_python_int(key) 

462 except (TypeError, OverflowError): 

463 return False 

464 return key in self._range 

465 

466 @property 

467 def inferred_type(self) -> str: 

468 return "integer" 

469 

470 # -------------------------------------------------------------------- 

471 # Indexing Methods 

472 

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

474 """ 

475 Get integer location for requested label. 

476 

477 Parameters 

478 ---------- 

479 key : int or float 

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

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

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

483 InvalidIndexError. 

484 

485 Returns 

486 ------- 

487 int 

488 Integer location of the label within the RangeIndex. 

489 

490 Raises 

491 ------ 

492 KeyError 

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

494 non-integer value. 

495 InvalidIndexError 

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

497 

498 See Also 

499 -------- 

500 RangeIndex.get_slice_bound : Calculate slice bound that corresponds to 

501 given label. 

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

503 the current index. 

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

505 the current index. 

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

507 

508 Examples 

509 -------- 

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

511 >>> idx.get_loc(3) 

512 3 

513 

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

515 >>> idx.get_loc(6) 

516 2 

517 """ 

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

519 new_key = int(key) 

520 try: 

521 return self._range.index(new_key) 

522 except ValueError as err: 

523 raise KeyError(key) from err 

524 if isinstance(key, Hashable): 

525 raise KeyError(key) 

526 self._check_indexing_error(key) 

527 raise KeyError(key) 

528 

529 def _get_indexer( 

530 self, 

531 target: Index, 

532 method: str | None = None, 

533 limit: int | None = None, 

534 tolerance=None, 

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

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

537 return super()._get_indexer( 

538 target, method=method, tolerance=tolerance, limit=limit 

539 ) 

540 

541 if self.step > 0: 

542 start, stop, step = self.start, self.stop, self.step 

543 else: 

544 # GH 28678: work on reversed range for simplicity 

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

546 start, stop, step = reverse.start, reverse.stop, reverse.step 

547 

548 target_array = np.asarray(target) 

549 locs = target_array - start 

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

551 locs[~valid] = -1 

552 locs[valid] = locs[valid] / step 

553 

554 if step != self.step: 

555 # We reversed this range: transform to original locs 

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

557 return ensure_platform_int(locs) 

558 

559 @cache_readonly 

560 def _should_fallback_to_positional(self) -> bool: 

561 """ 

562 Should an integer key be treated as positional? 

563 """ 

564 return False 

565 

566 # -------------------------------------------------------------------- 

567 

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

569 return list(self._range) 

570 

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

572 """ 

573 Return an iterator of the values. 

574 

575 Returns 

576 ------- 

577 iterator 

578 An iterator yielding ints from the RangeIndex. 

579 

580 Examples 

581 -------- 

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

583 >>> for x in idx: 

584 ... print(x) 

585 0 

586 1 

587 2 

588 """ 

589 yield from self._range 

590 

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

592 """ 

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

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

595 precedence. 

596 

597 *this is an internal non-public method* 

598 

599 Parameters 

600 ---------- 

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

602 name : Label, defaults to self.name 

603 """ 

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

605 

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

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

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

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

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

611 if len(values) == 1: 

612 start = values[0] 

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

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

615 maybe_range = ibase.maybe_sequence_to_range(values) 

616 if isinstance(maybe_range, range): 

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

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

619 

620 def _view(self) -> Self: 

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

622 result._cache = self._cache 

623 return result 

624 

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

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

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

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

629 

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

631 """ 

632 Make a copy of this object. 

633 

634 Name is set on the new object. 

635 

636 Parameters 

637 ---------- 

638 name : Label, optional 

639 Set name for new object. 

640 deep : bool, default False 

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

642 Else makes a shallow copy. 

643 

644 Returns 

645 ------- 

646 RangeIndex 

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

648 

649 See Also 

650 -------- 

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

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

653 

654 Notes 

655 ----- 

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

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

658 

659 Examples 

660 -------- 

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

662 >>> new_idx = idx.copy() 

663 >>> idx is new_idx 

664 False 

665 """ 

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

667 new_index = self._rename(name=name) 

668 return new_index 

669 

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

671 no_steps = len(self) - 1 

672 if no_steps == -1: 

673 return np.nan 

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

675 return self.start 

676 

677 return self.start + self.step * no_steps 

678 

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

680 """The minimum value of the RangeIndex""" 

681 nv.validate_minmax_axis(axis) 

682 nv.validate_min(args, kwargs) 

683 return self._minmax("min") 

684 

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

686 """The maximum value of the RangeIndex""" 

687 nv.validate_minmax_axis(axis) 

688 nv.validate_max(args, kwargs) 

689 return self._minmax("max") 

690 

691 def _argminmax( 

692 self, 

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

694 axis=None, 

695 skipna: bool = True, 

696 ) -> int: 

697 nv.validate_minmax_axis(axis) 

698 if len(self) == 0: 

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

700 axis=axis, 

701 skipna=skipna, 

702 ) 

703 elif meth == "min": 

704 if self.step > 0: 

705 return 0 

706 else: 

707 return len(self) - 1 

708 elif meth == "max": 

709 if self.step > 0: 

710 return len(self) - 1 

711 else: 

712 return 0 

713 else: 

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

715 

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

717 nv.validate_argmin(args, kwargs) 

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

719 

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

721 nv.validate_argmax(args, kwargs) 

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

723 

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

725 """ 

726 Returns the indices that would sort the index and its 

727 underlying data. 

728 

729 Returns 

730 ------- 

731 np.ndarray[np.intp] 

732 

733 See Also 

734 -------- 

735 numpy.ndarray.argsort 

736 """ 

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

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

739 nv.validate_argsort(args, kwargs) 

740 

741 start, stop, step = None, None, None 

742 if self._range.step > 0: 

743 if ascending: 

744 start = len(self) 

745 else: 

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

747 elif ascending: 

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

749 else: 

750 start = len(self) 

751 

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

753 

754 def factorize( 

755 self, 

756 sort: bool = False, 

757 use_na_sentinel: bool = True, 

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

759 if sort and self.step < 0: 

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

761 uniques = self[::-1] 

762 else: 

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

764 uniques = self 

765 return codes, uniques 

766 

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

768 """ 

769 Determines if two Index objects contain the same elements. 

770 """ 

771 if isinstance(other, RangeIndex): 

772 return self._range == other._range 

773 return super().equals(other) 

774 

775 @overload 

776 def sort_values( 

777 self, 

778 *, 

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

780 ascending: bool = ..., 

781 na_position: NaPosition = ..., 

782 key: Callable | None = ..., 

783 ) -> Self: ... 

784 

785 @overload 

786 def sort_values( 

787 self, 

788 *, 

789 return_indexer: Literal[True], 

790 ascending: bool = ..., 

791 na_position: NaPosition = ..., 

792 key: Callable | None = ..., 

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

794 

795 @overload 

796 def sort_values( 

797 self, 

798 *, 

799 return_indexer: bool = ..., 

800 ascending: bool = ..., 

801 na_position: NaPosition = ..., 

802 key: Callable | None = ..., 

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

804 

805 def sort_values( 

806 self, 

807 *, 

808 return_indexer: bool = False, 

809 ascending: bool = True, 

810 na_position: NaPosition = "last", 

811 key: Callable | None = None, 

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

813 if key is not None: 

814 return super().sort_values( 

815 return_indexer=return_indexer, 

816 ascending=ascending, 

817 na_position=na_position, 

818 key=key, 

819 ) 

820 else: 

821 sorted_index = self 

822 inverse_indexer = False 

823 if ascending: 

824 if self.step < 0: 

825 sorted_index = self[::-1] 

826 inverse_indexer = True 

827 elif self.step > 0: 

828 sorted_index = self[::-1] 

829 inverse_indexer = True 

830 

831 if return_indexer: 

832 if inverse_indexer: 

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

834 else: 

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

836 return sorted_index, indexer 

837 else: 

838 return sorted_index 

839 

840 # -------------------------------------------------------------------- 

841 # Set Operations 

842 

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

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

845 

846 if not isinstance(other, RangeIndex): 

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

848 

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

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

851 

852 # check whether intervals intersect 

853 # deals with in- and decreasing ranges 

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

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

856 if int_high <= int_low: 

857 return self._simple_new(_empty_range) 

858 

859 # Method hint: linear Diophantine equation 

860 # solve intersection problem 

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

862 # cheaper alternative 

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

864 

865 # check whether element sets intersect 

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

867 return self._simple_new(_empty_range) 

868 

869 # calculate parameters for the RangeIndex describing the 

870 # intersection disregarding the lower bounds 

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

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

873 

874 # adjust index to limiting interval 

875 new_start = min_fitting_element(tmp_start, new_step, int_low) 

876 new_range = range(new_start, int_high, new_step) 

877 

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

879 new_range = new_range[::-1] 

880 

881 return self._simple_new(new_range) 

882 

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

884 """ 

885 Extended Euclidean algorithms to solve Bezout's identity: 

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

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

888 Returns: gcd, s, t 

889 """ 

890 s, old_s = 0, 1 

891 t, old_t = 1, 0 

892 r, old_r = b, a 

893 while r: 

894 quotient = old_r // r 

895 old_r, r = r, old_r - quotient * r 

896 old_s, s = s, old_s - quotient * s 

897 old_t, t = t, old_t - quotient * t 

898 return old_r, old_s, old_t 

899 

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

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

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

903 if not other: 

904 return True 

905 if not self._range: 

906 return False 

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

908 return False 

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

910 

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

912 """ 

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

914 

915 Parameters 

916 ---------- 

917 other : Index or array-like 

918 

919 sort : bool or None, default None 

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

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

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

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

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

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

926 

927 Returns 

928 ------- 

929 union : Index 

930 """ 

931 if isinstance(other, RangeIndex): 

932 if sort in (None, True) or ( 

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

934 ): 

935 # GH 47557: Can still return a RangeIndex 

936 # if other range in self and sort=False 

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

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

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

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

941 if self.step < 0: 

942 start_s, step_s, end_s = end_s, -step_s, start_s 

943 if other.step < 0: 

944 start_o, step_o, end_o = end_o, -step_o, start_o 

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

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

947 elif len(self) == 1: 

948 step_s = step_o 

949 elif len(other) == 1: 

950 step_o = step_s 

951 start_r = min(start_s, start_o) 

952 end_r = max(end_s, end_o) 

953 if step_o == step_s: 

954 if ( 

955 (start_s - start_o) % step_s == 0 

956 and (start_s - end_o) <= step_s 

957 and (start_o - end_s) <= step_s 

958 ): 

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

960 if ( 

961 (step_s % 2 == 0) 

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

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

964 ): 

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

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

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

968 

969 elif step_o % step_s == 0: 

970 if ( 

971 (start_o - start_s) % step_s == 0 

972 and (start_o + step_s >= start_s) 

973 and (end_o - step_s <= end_s) 

974 ): 

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

976 elif step_s % step_o == 0: 

977 if ( 

978 (start_s - start_o) % step_o == 0 

979 and (start_s + step_o >= start_o) 

980 and (end_s - step_o <= end_o) 

981 ): 

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

983 

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

985 

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

987 # optimized set operation if we have another RangeIndex 

988 self._validate_sort_keyword(sort) 

989 self._assert_can_do_setop(other) 

990 other, result_name = self._convert_can_do_setop(other) 

991 

992 if not isinstance(other, RangeIndex): 

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

994 

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

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

997 

998 res_name = ops.get_op_result_name(self, other) 

999 

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

1001 overlap = self.intersection(other) 

1002 if overlap.step < 0: 

1003 overlap = overlap[::-1] 

1004 

1005 if len(overlap) == 0: 

1006 return self.rename(name=res_name) 

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

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

1009 

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

1011 

1012 if len(overlap) == 1: 

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

1014 return self[1:] 

1015 

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

1017 return self[:-1] 

1018 

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

1020 return self[::2] 

1021 

1022 else: 

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

1024 

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

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

1027 return self[1:-1] 

1028 

1029 if overlap.step == first.step: 

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

1031 # The difference is everything after the intersection 

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

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

1034 # The difference is everything before the intersection 

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

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

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

1038 step = len(first) - 1 

1039 new_rng = first[::step] 

1040 else: 

1041 # The difference is not range-like 

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

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

1044 

1045 else: 

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

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

1048 assert len(self) > 1 

1049 

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

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

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

1053 new_rng = first[1::2] 

1054 

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

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

1057 new_rng = first[::2] 

1058 

1059 else: 

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

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

1062 

1063 else: 

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

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

1066 

1067 if first is not self._range: 

1068 new_rng = new_rng[::-1] 

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

1070 

1071 return new_index 

1072 

1073 def symmetric_difference( 

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

1075 ) -> Index: 

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

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

1078 

1079 left = self.difference(other) 

1080 right = other.difference(self) 

1081 result = left.union(right) 

1082 

1083 if result_name is not None: 

1084 result = result.rename(result_name) 

1085 return result 

1086 

1087 def _join_empty( 

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

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

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

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

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

1093 

1094 def _join_monotonic( 

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

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

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

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

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

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

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

1102 other = maybe_ri 

1103 

1104 if self.equals(other): 

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

1106 return ret_index, None, None 

1107 

1108 if how == "left": 

1109 join_index = self 

1110 lidx = None 

1111 ridx = other.get_indexer(join_index) 

1112 elif how == "right": 

1113 join_index = other 

1114 lidx = self.get_indexer(join_index) 

1115 ridx = None 

1116 elif how == "inner": 

1117 join_index = self.intersection(other) 

1118 lidx = self.get_indexer(join_index) 

1119 ridx = other.get_indexer(join_index) 

1120 elif how == "outer": 

1121 join_index = self.union(other) 

1122 lidx = self.get_indexer(join_index) 

1123 ridx = other.get_indexer(join_index) 

1124 

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

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

1127 return join_index, lidx, ridx 

1128 

1129 # -------------------------------------------------------------------- 

1130 

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

1132 # "RangeIndex" in supertype "Index" 

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

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

1135 # DatetimeTimedeltaMixin._get_delete_Freq 

1136 if is_integer(loc): 

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

1138 return self[1:] 

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

1140 return self[:-1] 

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

1142 return self[::2] 

1143 

1144 elif lib.is_list_like(loc): 

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

1146 

1147 if isinstance(slc, slice): 

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

1149 # a RangeIndex whenever possible 

1150 other = self[slc] 

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

1152 

1153 return super().delete(loc) 

1154 

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

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

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

1158 # or right in the middle. 

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

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

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

1162 elif len(self): 

1163 rng = self._range 

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

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

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

1167 

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

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

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

1171 

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

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

1174 step = int(self.step / 2) 

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

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

1177 

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

1179 

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

1181 """ 

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

1183 

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

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

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

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

1188 """ 

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

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

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

1192 return self._shallow_copy(result._values) 

1193 return result 

1194 

1195 elif len(indexes) == 1: 

1196 return indexes[0] 

1197 

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

1199 

1200 start = step = next_ = None 

1201 

1202 # Filter the empty indexes 

1203 non_empty_indexes = [] 

1204 all_same_index = True 

1205 prev: RangeIndex | None = None 

1206 for obj in rng_indexes: 

1207 if len(obj): 

1208 non_empty_indexes.append(obj) 

1209 if all_same_index: 

1210 if prev is not None: 

1211 all_same_index = prev.equals(obj) 

1212 else: 

1213 prev = obj 

1214 

1215 for obj in non_empty_indexes: 

1216 rng = obj._range 

1217 

1218 if start is None: 

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

1220 start = rng.start 

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

1222 step = rng.step 

1223 elif step is None: 

1224 # First non-empty index had only one element 

1225 if rng.start == start: 

1226 if all_same_index: 

1227 values = np.tile( 

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

1229 ) 

1230 else: 

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

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

1233 return result.rename(name) 

1234 

1235 step = rng.start - start 

1236 

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

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

1239 ) 

1240 if non_consecutive: 

1241 if all_same_index: 

1242 values = np.tile( 

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

1244 ) 

1245 else: 

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

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

1248 return result.rename(name) 

1249 

1250 if step is not None: 

1251 next_ = rng[-1] + step 

1252 

1253 if non_empty_indexes: 

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

1255 # from the last non-empty index 

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

1257 if len(non_empty_indexes) == 1: 

1258 step = non_empty_indexes[0].step 

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

1260 

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

1262 # In this case return an empty range index. 

1263 return RangeIndex(_empty_range, name=name) 

1264 

1265 def __len__(self) -> int: 

1266 """ 

1267 return the length of the RangeIndex 

1268 """ 

1269 return len(self._range) 

1270 

1271 @property 

1272 def size(self) -> int: 

1273 return len(self) 

1274 

1275 def __getitem__(self, key): 

1276 """ 

1277 Conserve RangeIndex type for scalar and slice keys. 

1278 """ 

1279 key = lib.item_from_zerodim(key) 

1280 if key is Ellipsis: 

1281 key = slice(None) 

1282 if isinstance(key, slice): 

1283 return self._getitem_slice(key) 

1284 elif is_integer(key): 

1285 new_key = int(key) 

1286 try: 

1287 return self._range[new_key] 

1288 except IndexError as err: 

1289 raise IndexError( 

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

1291 ) from err 

1292 elif is_scalar(key): 

1293 raise IndexError( 

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

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

1296 "and integer or boolean " 

1297 "arrays are valid indices" 

1298 ) 

1299 elif com.is_bool_indexer(key): 

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

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

1302 else: 

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

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

1305 key = np.flatnonzero(key) 

1306 try: 

1307 return self.take(key) 

1308 except (TypeError, ValueError): 

1309 return super().__getitem__(key) 

1310 

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

1312 """ 

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

1314 """ 

1315 res = self._range[slobj] 

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

1317 

1318 @unpack_zerodim_and_defer("__floordiv__") 

1319 def __floordiv__(self, other): 

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

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

1322 start = self.start // other 

1323 step = self.step // other 

1324 stop = start + len(self) * step 

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

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

1327 if len(self) == 1: 

1328 start = self.start // other 

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

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

1331 

1332 return super().__floordiv__(other) 

1333 

1334 # -------------------------------------------------------------------- 

1335 # Reductions 

1336 

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

1338 return 0 not in self._range 

1339 

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

1341 return any(self._range) 

1342 

1343 # -------------------------------------------------------------------- 

1344 

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

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

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

1348 """ 

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

1350 

1351 Parameters 

1352 ---------- 

1353 decimals : int, optional 

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

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

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

1357 

1358 Returns 

1359 ------- 

1360 Index or RangeIndex 

1361 A new Index with the rounded values. 

1362 

1363 Examples 

1364 -------- 

1365 >>> import pandas as pd 

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

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

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

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

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

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

1372 """ 

1373 if decimals >= 0: 

1374 return self.copy() 

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

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

1377 return self.copy() 

1378 else: 

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

1380 

1381 def _cmp_method(self, other, op): 

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

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

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

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

1386 

1387 def _arith_method(self, other, op): 

1388 """ 

1389 Parameters 

1390 ---------- 

1391 other : Any 

1392 op : callable that accepts 2 params 

1393 perform the binary op 

1394 """ 

1395 

1396 if isinstance(other, ABCTimedeltaIndex): 

1397 # Defer to TimedeltaIndex implementation 

1398 return NotImplemented 

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

1400 # GH#19333 is_integer evaluated True on timedelta64, 

1401 # so we need to catch these explicitly 

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

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

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

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

1406 

1407 if op in [ 

1408 operator.pow, 

1409 ops.rpow, 

1410 operator.mod, 

1411 ops.rmod, 

1412 operator.floordiv, 

1413 ops.rfloordiv, 

1414 divmod, 

1415 ops.rdivmod, 

1416 ]: 

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

1418 

1419 step: Callable | None = None 

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

1421 step = op 

1422 

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

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

1425 left = self 

1426 

1427 try: 

1428 # apply if we have an override 

1429 if step: 

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

1431 rstep = step(left.step, right) 

1432 

1433 # we don't have a representable op 

1434 # so return a base index 

1435 if not is_integer(rstep) or not rstep: 

1436 raise ValueError 

1437 

1438 # GH#53255 

1439 else: 

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

1441 

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

1443 rstart = op(left.start, right) 

1444 rstop = op(left.stop, right) 

1445 

1446 res_name = ops.get_op_result_name(self, other) 

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

1448 

1449 # for compat with numpy / Index with int64 dtype 

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

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

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

1453 result = result.astype("float64") 

1454 

1455 return result 

1456 

1457 except (ValueError, TypeError, ZeroDivisionError): 

1458 # test_arithmetic_explicit_conversions 

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

1460 

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

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

1463 return self.copy() 

1464 elif self.max() <= 0: 

1465 return -self 

1466 else: 

1467 return super().__abs__() 

1468 

1469 def __neg__(self) -> Self: 

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

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

1472 

1473 def __pos__(self) -> Self: 

1474 return self.copy() 

1475 

1476 def __invert__(self) -> Self: 

1477 if len(self) == 0: 

1478 return self.copy() 

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

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

1481 

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

1483 # "RangeIndex" in supertype "Index" 

1484 def take( # type: ignore[override] 

1485 self, 

1486 indices, 

1487 axis: Axis = 0, 

1488 allow_fill: bool = True, 

1489 fill_value=None, 

1490 **kwargs, 

1491 ) -> Self | Index: 

1492 if kwargs: 

1493 nv.validate_take((), kwargs) 

1494 if is_scalar(indices): 

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

1496 indices = ensure_platform_int(indices) 

1497 

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

1499 self._maybe_disallow_fill(allow_fill, fill_value, indices) 

1500 

1501 if len(indices) == 0: 

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

1503 else: 

1504 ind_max = indices.max() 

1505 if ind_max >= len(self): 

1506 raise IndexError( 

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

1508 ) 

1509 ind_min = indices.min() 

1510 if ind_min < -len(self): 

1511 raise IndexError( 

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

1513 ) 

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

1515 if ind_min < 0: 

1516 taken %= len(self) 

1517 if self.step != 1: 

1518 taken *= self.step 

1519 if self.start != 0: 

1520 taken += self.start 

1521 

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

1523 

1524 def value_counts( 

1525 self, 

1526 normalize: bool = False, 

1527 sort: bool = True, 

1528 ascending: bool = False, 

1529 bins=None, 

1530 dropna: bool = True, 

1531 ) -> Series: 

1532 from pandas import Series 

1533 

1534 if bins is not None: 

1535 return super().value_counts( 

1536 normalize=normalize, 

1537 sort=sort, 

1538 ascending=ascending, 

1539 bins=bins, 

1540 dropna=dropna, 

1541 ) 

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

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

1544 len(self), dtype=np.int64 

1545 ) 

1546 if normalize: 

1547 data = data / len(self) 

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

1549 

1550 def searchsorted( # type: ignore[override] 

1551 self, 

1552 value, 

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

1554 sorter: NumpySorter | None = None, 

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

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

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

1558 

1559 was_scalar = False 

1560 if is_scalar(value): 

1561 was_scalar = True 

1562 array_value = np.array([value]) 

1563 else: 

1564 array_value = np.asarray(value) 

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

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

1567 

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

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

1570 start = rng.start 

1571 step = rng.step 

1572 shift = side == "right" 

1573 else: 

1574 start = self.start 

1575 step = self.step 

1576 shift = side == "left" 

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

1578 if flip: 

1579 result = len(self) - result 

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

1581 if was_scalar: 

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

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