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

721 statements  

1from __future__ import annotations 

2 

3from collections.abc import ( 

4 Callable, 

5 Hashable, 

6 Iterator, 

7 Sequence, 

8) 

9from datetime import timedelta 

10import operator 

11from sys import getsizeof 

12from typing import ( 

13 TYPE_CHECKING, 

14 Any, 

15 Literal, 

16 Self, 

17 cast, 

18 overload, 

19) 

20import warnings 

21 

22import numpy as np 

23 

24from pandas._libs import ( 

25 index as libindex, 

26 lib, 

27) 

28from pandas._libs.internals import BlockValuesRefs 

29from pandas._libs.lib import no_default 

30from pandas.compat.numpy import function as nv 

31from pandas.errors import Pandas4Warning 

32from pandas.util._decorators import ( 

33 cache_readonly, 

34 set_module, 

35) 

36from pandas.util._exceptions import find_stack_level 

37 

38from pandas.core.dtypes.base import ExtensionDtype 

39from pandas.core.dtypes.cast import maybe_unbox_numpy_scalar 

40from pandas.core.dtypes.common import ( 

41 ensure_platform_int, 

42 ensure_python_int, 

43 is_float, 

44 is_integer, 

45 is_scalar, 

46 is_signed_integer_dtype, 

47) 

48from pandas.core.dtypes.generic import ABCTimedeltaIndex 

49 

50from pandas.core import ops 

51import pandas.core.common as com 

52from pandas.core.construction import extract_array 

53from pandas.core.indexers import check_array_indexer 

54import pandas.core.indexes.base as ibase 

55from pandas.core.indexes.base import ( 

56 Index, 

57 maybe_extract_name, 

58) 

59from pandas.core.ops.common import unpack_zerodim_and_defer 

60 

61if TYPE_CHECKING: 

62 from pandas._typing import ( 

63 ArrayLike, 

64 Axes, 

65 Axis, 

66 AxisInt, 

67 Dtype, 

68 JoinHow, 

69 NaPosition, 

70 NumpySorter, 

71 NumpyValueArrayLike, 

72 ScalarLike_co, 

73 npt, 

74 ) 

75 

76 from pandas import Series 

77 from pandas.core.arrays import ExtensionArray 

78 

79_empty_range = range(0) 

80_dtype_int64 = np.dtype(np.int64) 

81 

82 

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

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

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

86 return start + abs(step) * no_steps 

87 

88 

89@set_module("pandas") 

90class RangeIndex(Index): 

91 """ 

92 Immutable Index implementing a monotonic integer range. 

93 

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

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

96 improve computing speed. 

97 

98 This is the default index type used 

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

100 

101 Parameters 

102 ---------- 

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

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

105 stop : int, default None 

106 The end value of the range (exclusive). 

107 step : int, default None 

108 The step size of the range. 

109 dtype : np.int64, default None 

110 Unused, accepted for homogeneity with other index types. 

111 copy : bool, default False 

112 Unused, accepted for homogeneity with other index types. 

113 name : object, optional 

114 Name to be stored in the index. 

115 

116 Attributes 

117 ---------- 

118 start 

119 stop 

120 step 

121 

122 Methods 

123 ------- 

124 from_range 

125 

126 See Also 

127 -------- 

128 Index : The base pandas Index type. 

129 

130 Examples 

131 -------- 

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

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

134 

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

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

137 

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

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

140 

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

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

143 

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

145 [] 

146 

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

148 [] 

149 """ 

150 

151 _typ = "rangeindex" 

152 _dtype_validation_metadata = (is_signed_integer_dtype, "signed integer") 

153 _range: range 

154 _values: np.ndarray 

155 

156 @property 

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

158 return libindex.Int64Engine 

159 

160 # -------------------------------------------------------------------- 

161 # Constructors 

162 

163 def __new__( 

164 cls, 

165 start: int | np.integer | range | RangeIndex | None = None, 

166 stop: int | np.integer | None = None, 

167 step: int | np.integer | None = None, 

168 dtype: Dtype | None = None, 

169 copy: bool = False, 

170 name: Hashable | None = None, 

171 ) -> Self: 

172 cls._validate_dtype(dtype) 

173 name = maybe_extract_name(name, start, cls) 

174 

175 # RangeIndex 

176 if isinstance(start, cls): 

177 return start.copy(name=name) 

178 elif isinstance(start, range): 

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

180 

181 # validate the arguments 

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

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

184 

185 start = ensure_python_int(start) if start is not None else 0 # type: ignore[arg-type] 

186 

187 if stop is None: 

188 start, stop = 0, start 

189 else: 

190 stop = ensure_python_int(stop) 

191 

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

193 if step == 0: 

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

195 

196 rng = range(start, stop, step) 

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

198 

199 @classmethod 

200 def from_range( 

201 cls, data: range, name: Hashable | None = None, dtype: Dtype | None = None 

202 ) -> Self: 

203 """ 

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

205 

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

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

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

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

210 memory-friendly manner. 

211 

212 Parameters 

213 ---------- 

214 data : range 

215 The range object to be converted into a RangeIndex. 

216 name : str, default None 

217 Name to be stored in the index. 

218 dtype : Dtype or None 

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

220 be used. 

221 

222 Returns 

223 ------- 

224 RangeIndex 

225 

226 See Also 

227 -------- 

228 RangeIndex : Immutable Index implementing a monotonic integer range. 

229 Index : Immutable sequence used for indexing and alignment. 

230 

231 Examples 

232 -------- 

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

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

235 

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

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

238 """ 

239 if not isinstance(data, range): 

240 raise TypeError( 

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

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

243 ) 

244 cls._validate_dtype(dtype) 

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

246 

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

248 # supertype defines the argument type as 

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

250 @classmethod 

251 def _simple_new( # type: ignore[override] 

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

253 ) -> Self: 

254 result = object.__new__(cls) 

255 

256 assert isinstance(values, range) 

257 

258 result._range = values 

259 result._name = name 

260 result._cache = {} 

261 result._reset_identity() 

262 # result._references populated lazily 

263 return result 

264 

265 @cache_readonly 

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

267 result = BlockValuesRefs() 

268 result.add_index_reference(self) 

269 return result 

270 

271 @classmethod 

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

273 if dtype is None: 

274 return 

275 

276 validation_func, expected = cls._dtype_validation_metadata 

277 if not validation_func(dtype): 

278 raise ValueError( 

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

280 ) 

281 

282 # -------------------------------------------------------------------- 

283 

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

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

286 @cache_readonly 

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

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

289 return Index 

290 

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

292 @cache_readonly 

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

294 """ 

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

296 

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

298 """ 

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

300 

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

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

303 rng = self._range 

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

305 

306 def __reduce__(self) -> tuple: 

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

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

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

310 

311 # -------------------------------------------------------------------- 

312 # Rendering Methods 

313 

314 def _format_attrs(self) -> list[tuple[str, str | int]]: # type: ignore[override] 

315 """ 

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

317 """ 

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

319 if self._name is not None: 

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

321 return attrs 

322 

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

324 # Equivalent to Index implementation, but faster 

325 if not len(self._range): 

326 return header 

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

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

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

330 

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

332 

333 # -------------------------------------------------------------------- 

334 

335 @property 

336 def start(self) -> int: 

337 """ 

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

339 

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

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

342 it defaults to 0. 

343 

344 See Also 

345 -------- 

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

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

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

349 

350 Examples 

351 -------- 

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

353 >>> idx.start 

354 0 

355 

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

357 >>> idx.start 

358 2 

359 """ 

360 # GH 25710 

361 return self._range.start 

362 

363 @property 

364 def stop(self) -> int: 

365 """ 

366 The value of the `stop` parameter. 

367 

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

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

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

371 not including this value. 

372 

373 See Also 

374 -------- 

375 RangeIndex : Immutable index representing a range of integers. 

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

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

378 

379 Examples 

380 -------- 

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

382 >>> idx.stop 

383 5 

384 

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

386 >>> idx.stop 

387 -10 

388 """ 

389 return self._range.stop 

390 

391 @property 

392 def step(self) -> int: 

393 """ 

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

395 

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

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

398 

399 See Also 

400 -------- 

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

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

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

404 

405 Examples 

406 -------- 

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

408 >>> idx.step 

409 1 

410 

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

412 >>> idx.step 

413 -3 

414 

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

416 not supplied. 

417 

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

419 >>> idx.step 

420 1 

421 """ 

422 # GH 25710 

423 return self._range.step 

424 

425 @cache_readonly 

426 def nbytes(self) -> int: 

427 """ 

428 Return the number of bytes in the underlying data. 

429 """ 

430 rng = self._range 

431 # passing a default to getsizeof avoids a TypeError on PyPy, where 

432 # sys.getsizeof always raises TypeError unless a default is provided 

433 # (GH#46176) 

434 objsize = 24 

435 return getsizeof(rng, objsize) + sum( 

436 getsizeof(getattr(rng, attr_name), objsize) 

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

438 ) 

439 

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

441 """ 

442 Memory usage of my values 

443 

444 Parameters 

445 ---------- 

446 deep : bool 

447 Introspect the data deeply, interrogate 

448 `object` dtypes for system-level memory consumption 

449 

450 Returns 

451 ------- 

452 bytes used 

453 

454 Notes 

455 ----- 

456 Memory usage does not include memory consumed by elements that 

457 are not components of the array if deep=False 

458 

459 See Also 

460 -------- 

461 numpy.ndarray.nbytes 

462 """ 

463 return self.nbytes 

464 

465 @property 

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

467 return _dtype_int64 

468 

469 @property 

470 def is_unique(self) -> bool: 

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

472 return True 

473 

474 @cache_readonly 

475 def is_monotonic_increasing(self) -> bool: 

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

477 

478 @cache_readonly 

479 def is_monotonic_decreasing(self) -> bool: 

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

481 

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

483 hash(key) 

484 try: 

485 key = ensure_python_int(key) 

486 except (TypeError, OverflowError): 

487 return False 

488 return key in self._range 

489 

490 @property 

491 def inferred_type(self) -> str: 

492 return "integer" 

493 

494 # -------------------------------------------------------------------- 

495 # Indexing Methods 

496 

497 def get_loc(self, key: int) -> int: # type: ignore[override] 

498 """ 

499 Get integer location for requested label. 

500 

501 Parameters 

502 ---------- 

503 key : int or float 

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

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

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

507 InvalidIndexError. 

508 

509 Returns 

510 ------- 

511 int 

512 Integer location of the label within the RangeIndex. 

513 

514 Raises 

515 ------ 

516 KeyError 

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

518 non-integer value. 

519 InvalidIndexError 

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

521 

522 See Also 

523 -------- 

524 RangeIndex.get_slice_bound : Calculate slice bound that corresponds to 

525 given label. 

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

527 the current index. 

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

529 the current index. 

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

531 

532 Examples 

533 -------- 

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

535 >>> idx.get_loc(3) 

536 3 

537 

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

539 >>> idx.get_loc(6) 

540 2 

541 """ 

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

543 new_key = int(key) 

544 try: 

545 return self._range.index(new_key) 

546 except ValueError as err: 

547 raise KeyError(key) from err 

548 if isinstance(key, Hashable): 

549 raise KeyError(key) 

550 self._check_indexing_error(key) 

551 raise KeyError(key) 

552 

553 def _get_indexer( 

554 self, 

555 target: Index, 

556 method: str | None = None, 

557 limit: int | None = None, 

558 tolerance: Any = None, 

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

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

561 return super()._get_indexer( 

562 target, method=method, tolerance=tolerance, limit=limit 

563 ) 

564 

565 if self.step > 0: 

566 start, stop, step = self.start, self.stop, self.step 

567 else: 

568 # GH 28678: work on reversed range for simplicity 

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

570 start, stop, step = reverse.start, reverse.stop, reverse.step 

571 

572 target_array = np.asarray(target) 

573 if target_array.dtype.kind in "iu": 

574 # GH#64148: ``target_array - start`` overflows int64 for extreme 

575 # targets and for ranges spanning more than INT64_MAX. Establish 

576 # membership with exact comparisons first; an in-window offset then 

577 # fits in uint64, so the modular arithmetic below is exact. 

578 valid = (target_array >= start) & (target_array < stop) 

579 start_uint = np.uint64(start % 2**64) 

580 step_uint = np.uint64(step) 

581 locs_uint = target_array.astype(np.uint64, copy=False) - start_uint 

582 valid &= locs_uint % step_uint == 0 

583 locs = np.where(valid, (locs_uint // step_uint).astype(np.int64), -1) 

584 if step != self.step: 

585 # We reversed this range: transform to original locs 

586 locs = np.where(valid, len(self) - 1 - locs, -1) 

587 return ensure_platform_int(locs) 

588 

589 locs = target_array - start 

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

591 locs[~valid] = -1 

592 locs[valid] = locs[valid] / step 

593 

594 if step != self.step: 

595 # We reversed this range: transform to original locs 

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

597 return ensure_platform_int(locs) 

598 

599 @cache_readonly 

600 def _should_fallback_to_positional(self) -> bool: 

601 """ 

602 Should an integer key be treated as positional? 

603 """ 

604 return False 

605 

606 # -------------------------------------------------------------------- 

607 

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

609 return list(self._range) 

610 

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

612 """ 

613 Return an iterator of the values. 

614 

615 Returns 

616 ------- 

617 iterator 

618 An iterator yielding ints from the RangeIndex. 

619 

620 Examples 

621 -------- 

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

623 >>> for x in idx: 

624 ... print(x) 

625 0 

626 1 

627 2 

628 """ 

629 yield from self._range 

630 

631 def _shallow_copy( # type: ignore[override] 

632 self, values: ArrayLike, name: Hashable = no_default 

633 ) -> Index: 

634 """ 

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

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

637 precedence. 

638 

639 *this is an internal non-public method* 

640 

641 Parameters 

642 ---------- 

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

644 name : Label, defaults to self.name 

645 """ 

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

647 

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

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

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

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

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

653 if len(values) == 1: 

654 start = values[0] 

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

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

657 maybe_range = ibase.maybe_sequence_to_range(values) 

658 if isinstance(maybe_range, range): 

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

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

661 

662 def _view(self) -> Self: 

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

664 result._cache = self._cache 

665 self._references.add_index_reference(result) 

666 return result 

667 

668 def _wrap_reindex_result( 

669 self, target: Index, indexer: npt.NDArray[np.intp] | None, preserve_names: bool 

670 ) -> Index: 

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

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

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

674 

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

676 """ 

677 Make a copy of this object. 

678 

679 Name is set on the new object. 

680 

681 Parameters 

682 ---------- 

683 name : Label, optional 

684 Set name for new object. 

685 deep : bool, default False 

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

687 Else makes a shallow copy. 

688 

689 Returns 

690 ------- 

691 RangeIndex 

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

693 

694 See Also 

695 -------- 

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

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

698 

699 Notes 

700 ----- 

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

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

703 

704 Examples 

705 -------- 

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

707 >>> new_idx = idx.copy() 

708 >>> idx is new_idx 

709 False 

710 """ 

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

712 new_index = self._rename(name=name) 

713 return new_index 

714 

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

716 no_steps = len(self) - 1 

717 if no_steps == -1: 

718 return np.nan 

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

720 return self.start 

721 

722 return self.start + self.step * no_steps 

723 

724 def min( 

725 self, 

726 axis: AxisInt | None = None, 

727 skipna: bool = True, 

728 *args: Any, 

729 **kwargs: Any, 

730 ) -> int | float: 

731 """The minimum value of the RangeIndex""" 

732 nv.validate_minmax_axis(axis) 

733 nv.validate_min(args, kwargs) 

734 return self._minmax("min") 

735 

736 def max( 

737 self, 

738 axis: AxisInt | None = None, 

739 skipna: bool = True, 

740 *args: Any, 

741 **kwargs: Any, 

742 ) -> int | float: 

743 """The maximum value of the RangeIndex""" 

744 nv.validate_minmax_axis(axis) 

745 nv.validate_max(args, kwargs) 

746 return self._minmax("max") 

747 

748 def _argminmax( 

749 self, 

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

751 axis: AxisInt | None = None, 

752 skipna: bool = True, 

753 ) -> int: 

754 nv.validate_minmax_axis(axis) 

755 if len(self) == 0: 

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

757 axis=axis, 

758 skipna=skipna, 

759 ) 

760 elif meth == "min": 

761 if self.step > 0: 

762 return 0 

763 else: 

764 return len(self) - 1 

765 elif meth == "max": 

766 if self.step > 0: 

767 return len(self) - 1 

768 else: 

769 return 0 

770 else: 

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

772 

773 def argmin( 

774 self, 

775 axis: AxisInt | None = None, 

776 skipna: bool = True, 

777 *args: Any, 

778 **kwargs: Any, 

779 ) -> int: 

780 nv.validate_argmin(args, kwargs) 

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

782 

783 def argmax( 

784 self, 

785 axis: AxisInt | None = None, 

786 skipna: bool = True, 

787 *args: Any, 

788 **kwargs: Any, 

789 ) -> int: 

790 nv.validate_argmax(args, kwargs) 

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

792 

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

794 """ 

795 Returns the indices that would sort the index and its 

796 underlying data. 

797 

798 Returns 

799 ------- 

800 np.ndarray[np.intp] 

801 

802 See Also 

803 -------- 

804 numpy.ndarray.argsort 

805 """ 

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

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

808 nv.validate_argsort(args, kwargs) 

809 

810 start, stop, step = None, None, None 

811 if self._range.step > 0: 

812 if ascending: 

813 start = len(self) 

814 else: 

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

816 elif ascending: 

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

818 else: 

819 start = len(self) 

820 

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

822 

823 def factorize( 

824 self, 

825 sort: bool = False, 

826 use_na_sentinel: bool = True, 

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

828 if sort and self.step < 0: 

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

830 uniques = self[::-1] 

831 else: 

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

833 uniques = self 

834 return codes, uniques 

835 

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

837 """ 

838 Determines if two Index objects contain the same elements. 

839 """ 

840 if isinstance(other, RangeIndex): 

841 return self._range == other._range 

842 return super().equals(other) 

843 

844 @overload 

845 def sort_values( 

846 self, 

847 *, 

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

849 ascending: bool = ..., 

850 na_position: NaPosition = ..., 

851 key: Callable | None = ..., 

852 ) -> Self: ... 

853 

854 @overload 

855 def sort_values( 

856 self, 

857 *, 

858 return_indexer: Literal[True], 

859 ascending: bool = ..., 

860 na_position: NaPosition = ..., 

861 key: Callable | None = ..., 

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

863 

864 @overload 

865 def sort_values( 

866 self, 

867 *, 

868 return_indexer: bool = ..., 

869 ascending: bool = ..., 

870 na_position: NaPosition = ..., 

871 key: Callable | None = ..., 

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

873 

874 def sort_values( 

875 self, 

876 *, 

877 return_indexer: bool = False, 

878 ascending: bool = True, 

879 na_position: NaPosition = "last", 

880 key: Callable | None = None, 

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

882 if key is not None: 

883 return super().sort_values( 

884 return_indexer=return_indexer, 

885 ascending=ascending, 

886 na_position=na_position, 

887 key=key, 

888 ) 

889 else: 

890 sorted_index = self 

891 inverse_indexer = False 

892 if ascending: 

893 if self.step < 0: 

894 sorted_index = self[::-1] 

895 inverse_indexer = True 

896 elif self.step > 0: 

897 sorted_index = self[::-1] 

898 inverse_indexer = True 

899 

900 if return_indexer: 

901 if inverse_indexer: 

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

903 else: 

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

905 return sorted_index, indexer 

906 else: 

907 return sorted_index 

908 

909 # -------------------------------------------------------------------- 

910 # Set Operations 

911 

912 def _intersection(self, other: Index, sort: bool = False) -> Index: 

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

914 

915 if not isinstance(other, RangeIndex): 

916 return super()._intersection(other, sort=sort) # type: ignore[return-value] 

917 

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

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

920 

921 # check whether intervals intersect 

922 # deals with in- and decreasing ranges 

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

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

925 if int_high <= int_low: 

926 return self._simple_new(_empty_range) 

927 

928 # Method hint: linear Diophantine equation 

929 # solve intersection problem 

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

931 # cheaper alternative 

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

933 

934 # check whether element sets intersect 

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

936 return self._simple_new(_empty_range) 

937 

938 # calculate parameters for the RangeIndex describing the 

939 # intersection disregarding the lower bounds 

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

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

942 

943 # adjust index to limiting interval 

944 new_start = min_fitting_element(tmp_start, new_step, int_low) 

945 new_range = range(new_start, int_high, new_step) 

946 

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

948 new_range = new_range[::-1] 

949 

950 return self._simple_new(new_range) 

951 

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

953 """ 

954 Extended Euclidean algorithms to solve Bezout's identity: 

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

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

957 Returns: gcd, s, t 

958 """ 

959 s, old_s = 0, 1 

960 t, old_t = 1, 0 

961 r, old_r = b, a 

962 while r: 

963 quotient = old_r // r 

964 old_r, r = r, old_r - quotient * r 

965 old_s, s = s, old_s - quotient * s 

966 old_t, t = t, old_t - quotient * t 

967 return old_r, old_s, old_t 

968 

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

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

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

972 if not other: 

973 return True 

974 if not self._range: 

975 return False 

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

977 return False 

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

979 

980 def _union(self, other: Index, sort: bool | None) -> Index: 

981 """ 

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

983 

984 Parameters 

985 ---------- 

986 other : Index or array-like 

987 

988 sort : bool or None, default None 

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

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

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

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

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

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

995 

996 Returns 

997 ------- 

998 union : Index 

999 """ 

1000 if isinstance(other, RangeIndex): 

1001 if sort in (None, True) or ( 

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

1003 ): 

1004 # GH 47557: Can still return a RangeIndex 

1005 # if other range in self and sort=False 

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

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

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

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

1010 if self.step < 0: 

1011 start_s, step_s, end_s = end_s, -step_s, start_s 

1012 if other.step < 0: 

1013 start_o, step_o, end_o = end_o, -step_o, start_o 

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

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

1016 elif len(self) == 1: 

1017 step_s = step_o 

1018 elif len(other) == 1: 

1019 step_o = step_s 

1020 start_r = min(start_s, start_o) 

1021 end_r = max(end_s, end_o) 

1022 if step_o == step_s: 

1023 if ( 

1024 (start_s - start_o) % step_s == 0 

1025 and (start_s - end_o) <= step_s 

1026 and (start_o - end_s) <= step_s 

1027 ): 

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

1029 if ( 

1030 (step_s % 2 == 0) 

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

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

1033 ): 

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

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

1036 return type(self)(start_r, end_r + step_s / 2, step_s / 2) # type: ignore[arg-type] 

1037 

1038 elif step_o % step_s == 0: 

1039 if ( 

1040 (start_o - start_s) % step_s == 0 

1041 and (start_o + step_s >= start_s) 

1042 and (end_o - step_s <= end_s) 

1043 ): 

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

1045 elif step_s % step_o == 0: 

1046 if ( 

1047 (start_s - start_o) % step_o == 0 

1048 and (start_s + step_o >= start_o) 

1049 and (end_s - step_o <= end_o) 

1050 ): 

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

1052 

1053 return super()._union(other, sort=sort) # type: ignore[return-value] 

1054 

1055 def _difference(self, other: Index, sort: bool | None = None) -> Index: 

1056 # optimized set operation if we have another RangeIndex 

1057 self._validate_sort_keyword(sort) 

1058 self._assert_can_do_setop(other) 

1059 other, result_name = self._convert_can_do_setop(other) 

1060 

1061 if not isinstance(other, RangeIndex): 

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

1063 

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

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

1066 

1067 res_name = ops.get_op_result_name(self, other) # type: ignore[no-untyped-call] 

1068 

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

1070 overlap = self.intersection(other) 

1071 if overlap.step < 0: # type: ignore[attr-defined] 

1072 overlap = overlap[::-1] 

1073 

1074 if len(overlap) == 0: 

1075 return self.rename(name=res_name) 

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

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

1078 

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

1080 

1081 if len(overlap) == 1: 

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

1083 return self[1:] 

1084 

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

1086 return self[:-1] 

1087 

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

1089 return self[::2] 

1090 

1091 else: 

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

1093 

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

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

1096 return self[1:-1] 

1097 

1098 if overlap.step == first.step: # type: ignore[attr-defined] 

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

1100 # The difference is everything after the intersection 

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

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

1103 # The difference is everything before the intersection 

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

1105 elif overlap._range == first[1:-1]: # type: ignore[attr-defined] 

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

1107 step = len(first) - 1 

1108 new_rng = first[::step] 

1109 else: 

1110 # The difference is not range-like 

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

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

1113 

1114 else: 

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

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

1117 assert len(self) > 1 

1118 

1119 if overlap.step == first.step * 2: # type: ignore[attr-defined] 

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

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

1122 new_rng = first[1::2] 

1123 

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

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

1126 new_rng = first[::2] 

1127 

1128 else: 

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

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

1131 

1132 else: 

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

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

1135 

1136 if first is not self._range: 

1137 new_rng = new_rng[::-1] 

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

1139 

1140 return new_index 

1141 

1142 def symmetric_difference( 

1143 self, other: Axes, result_name: Hashable | None = None, sort: bool | None = None 

1144 ) -> Index: 

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

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

1147 

1148 left = self.difference(other) 

1149 right = other.difference(self) 

1150 result = left.union(right) 

1151 

1152 if isinstance(result, RangeIndex) and result.step < 0: 

1153 result = result[::-1] 

1154 

1155 if result_name is not None: 

1156 result = result.rename(result_name) 

1157 return result 

1158 

1159 def _join_empty( 

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

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

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

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

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

1165 

1166 def _join_monotonic( 

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

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

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

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

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

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

1173 # Cannot convert other to RangeIndex; fall back to 

1174 # _join_via_get_indexer since RangeIndex._can_use_libjoin 

1175 # is False. 

1176 raise NotImplementedError 

1177 other = maybe_ri 

1178 

1179 if self.equals(other): 

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

1181 return ret_index, None, None 

1182 

1183 if how == "left": 

1184 join_index = self 

1185 lidx = None 

1186 ridx = other.get_indexer(join_index) 

1187 elif how == "right": 

1188 join_index = other 

1189 lidx = self.get_indexer(join_index) 

1190 ridx = None 

1191 elif how == "inner": 

1192 join_index = self.intersection(other) # type: ignore[assignment] 

1193 lidx = self.get_indexer(join_index) 

1194 ridx = other.get_indexer(join_index) 

1195 elif how == "outer": 

1196 join_index = self.union(other) # type: ignore[assignment] 

1197 lidx = self.get_indexer(join_index) 

1198 ridx = other.get_indexer(join_index) 

1199 

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

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

1202 return join_index, lidx, ridx 

1203 

1204 # -------------------------------------------------------------------- 

1205 

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

1207 # "RangeIndex" in supertype "Index" 

1208 def delete( # type: ignore[override] 

1209 self, loc: int | np.integer | list[int] | npt.NDArray[np.integer] 

1210 ) -> Index: 

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

1212 # DatetimeTimedeltaMixin._get_delete_Freq 

1213 if is_integer(loc): 

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

1215 return self[1:] 

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

1217 return self[:-1] 

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

1219 return self[::2] 

1220 

1221 elif lib.is_list_like(loc): 

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

1223 

1224 if isinstance(slc, slice): 

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

1226 # a RangeIndex whenever possible 

1227 other = self[slc] 

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

1229 

1230 return super().delete(loc) 

1231 

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

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

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

1235 # or right in the middle. 

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

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

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

1239 elif len(self): 

1240 rng = self._range 

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

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

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

1244 

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

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

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

1248 

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

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

1251 step = int(self.step / 2) 

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

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

1254 

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

1256 

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

1258 """ 

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

1260 

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

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

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

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

1265 """ 

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

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

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

1269 return self._shallow_copy(result._values) 

1270 return result 

1271 

1272 elif len(indexes) == 1: 

1273 return indexes[0] 

1274 

1275 rng_indexes = cast("list[RangeIndex]", indexes) 

1276 

1277 start = step = next_ = None 

1278 

1279 # Filter the empty indexes 

1280 non_empty_indexes = [] 

1281 all_same_index = True 

1282 prev: RangeIndex | None = None 

1283 for obj in rng_indexes: 

1284 if len(obj): 

1285 non_empty_indexes.append(obj) 

1286 if all_same_index: 

1287 if prev is not None: 

1288 all_same_index = prev.equals(obj) 

1289 else: 

1290 prev = obj 

1291 

1292 for obj in non_empty_indexes: 

1293 rng = obj._range 

1294 

1295 if start is None: 

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

1297 start = rng.start 

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

1299 step = rng.step 

1300 elif step is None: 

1301 # First non-empty index had only one element 

1302 if rng.start == start: 

1303 if all_same_index: 

1304 values = np.tile( 

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

1306 ) 

1307 else: 

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

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

1310 return result.rename(name) 

1311 

1312 step = rng.start - start 

1313 

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

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

1316 ) 

1317 if non_consecutive: 

1318 if all_same_index: 

1319 values = np.tile( 

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

1321 ) 

1322 else: 

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

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

1325 return result.rename(name) 

1326 

1327 if step is not None: 

1328 next_ = rng[-1] + step 

1329 

1330 if non_empty_indexes: 

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

1332 # from the last non-empty index 

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

1334 if len(non_empty_indexes) == 1: 

1335 step = non_empty_indexes[0].step 

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

1337 

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

1339 # In this case return an empty range index. 

1340 return RangeIndex(_empty_range, name=name) 

1341 

1342 def __len__(self) -> int: 

1343 """ 

1344 return the length of the RangeIndex 

1345 """ 

1346 return len(self._range) 

1347 

1348 @property 

1349 def size(self) -> int: 

1350 return len(self) 

1351 

1352 @overload # type: ignore[override] 

1353 def __getitem__(self, key: int) -> int: ... 

1354 

1355 @overload 

1356 def __getitem__(self, key: slice | np.ndarray | list[int] | list[bool]) -> Self: ... 

1357 

1358 def __getitem__(self, key: Any) -> Any: 

1359 """ 

1360 Conserve RangeIndex type for scalar and slice keys. 

1361 """ 

1362 key = lib.item_from_zerodim(key) 

1363 if key is Ellipsis: 

1364 key = slice(None) 

1365 if isinstance(key, slice): 

1366 return self._getitem_slice(key) 

1367 elif is_integer(key): 

1368 new_key = int(key) 

1369 try: 

1370 return self._range[new_key] 

1371 except IndexError as err: 

1372 raise IndexError( 

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

1374 ) from err 

1375 elif is_scalar(key): 

1376 raise IndexError( 

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

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

1379 "and integer or boolean " 

1380 "arrays are valid indices" 

1381 ) 

1382 elif com.is_bool_indexer(key): 

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

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

1385 else: 

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

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

1388 key = np.flatnonzero(key) 

1389 try: 

1390 return self.take(key) 

1391 except (TypeError, ValueError): 

1392 return super().__getitem__(key) 

1393 

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

1395 """ 

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

1397 """ 

1398 res = self._range[slobj] 

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

1400 

1401 @unpack_zerodim_and_defer("__floordiv__") 

1402 def __floordiv__(self, other: object) -> Index: # type: ignore[override] 

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

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

1405 start = self.start // other 

1406 step = self.step // other 

1407 stop = start + len(self) * step 

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

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

1410 if len(self) == 1: 

1411 start = self.start // other 

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

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

1414 

1415 return super().__floordiv__(other) 

1416 

1417 # -------------------------------------------------------------------- 

1418 # Reductions 

1419 

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

1421 return 0 not in self._range 

1422 

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

1424 return any(self._range) 

1425 

1426 # -------------------------------------------------------------------- 

1427 

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

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

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

1431 """ 

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

1433 

1434 Parameters 

1435 ---------- 

1436 decimals : int, optional 

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

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

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

1440 

1441 Returns 

1442 ------- 

1443 Index or RangeIndex 

1444 A new Index with the rounded values. 

1445 

1446 Examples 

1447 -------- 

1448 >>> import pandas as pd 

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

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

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

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

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

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

1455 """ 

1456 if decimals >= 0: 

1457 return self.copy() 

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

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

1460 return self.copy() 

1461 else: 

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

1463 

1464 def _cmp_method(self, other: object, op: Callable) -> Any: 

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

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

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

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

1469 

1470 def _arith_method(self, other: object, op: Callable) -> Index: 

1471 """ 

1472 Parameters 

1473 ---------- 

1474 other : Any 

1475 op : callable that accepts 2 params 

1476 perform the binary op 

1477 """ 

1478 

1479 if isinstance(other, ABCTimedeltaIndex): 

1480 # Defer to TimedeltaIndex implementation 

1481 return NotImplemented 

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

1483 # GH#19333 is_integer evaluated True on timedelta64, 

1484 # so we need to catch these explicitly 

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

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

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

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

1489 

1490 if op in [ 

1491 operator.pow, 

1492 ops.rpow, 

1493 operator.mod, 

1494 ops.rmod, 

1495 operator.floordiv, 

1496 ops.rfloordiv, 

1497 divmod, 

1498 ops.rdivmod, 

1499 ]: 

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

1501 

1502 step: Callable | None = None 

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

1504 step = op 

1505 

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

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

1508 left = self 

1509 

1510 try: 

1511 # apply if we have an override 

1512 if step: 

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

1514 rstep = step(left.step, right) 

1515 

1516 # we don't have a representable op 

1517 # so return a base index 

1518 if not is_integer(rstep) or not rstep: 

1519 raise ValueError 

1520 

1521 # GH#53255 

1522 else: 

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

1524 

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

1526 rstart = op(left.start, right) 

1527 rstop = op(left.stop, right) 

1528 

1529 res_name = ops.get_op_result_name(self, other) # type: ignore[no-untyped-call] 

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

1531 

1532 # for compat with numpy / Index with int64 dtype 

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

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

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

1536 result = result.astype("float64") # type: ignore[assignment] 

1537 

1538 return result 

1539 

1540 except (ValueError, TypeError, ZeroDivisionError): 

1541 # test_arithmetic_explicit_conversions 

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

1543 

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

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

1546 return self.copy() 

1547 elif self.max() <= 0: 

1548 return -self 

1549 else: 

1550 return super().__abs__() 

1551 

1552 def __neg__(self) -> Self: 

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

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

1555 

1556 def __pos__(self) -> Self: 

1557 return self.copy() 

1558 

1559 def __invert__(self) -> Self: 

1560 if len(self) == 0: 

1561 return self.copy() 

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

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

1564 

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

1566 # "RangeIndex" in supertype "Index" 

1567 def take( # type: ignore[override] 

1568 self, 

1569 indices: Sequence[int] | np.ndarray, 

1570 axis: Axis = 0, 

1571 allow_fill: bool | lib.NoDefault = no_default, 

1572 fill_value: object = no_default, 

1573 **kwargs: Any, 

1574 ) -> Self | Index: 

1575 if kwargs: 

1576 nv.validate_take((), kwargs) 

1577 if is_scalar(indices): 

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

1579 indices = ensure_platform_int(indices) 

1580 

1581 if allow_fill is no_default: 

1582 if fill_value is None: 

1583 # GH#65210: preserve pre-3.1 wrap behavior for this case, but 

1584 # warn since the sentinel-based default would otherwise flip 

1585 # it into fill-with-NA semantics. 

1586 warnings.warn( 

1587 "Passing fill_value=None without allow_fill previously " 

1588 "used numpy-style wrapping of negative indices. In a " 

1589 "future version this will trigger fill semantics " 

1590 "(filling -1 entries with the dtype's NA value). Pass " 

1591 "allow_fill=False to keep wrapping behavior, or " 

1592 "allow_fill=True to opt into fill semantics.", 

1593 Pandas4Warning, 

1594 stacklevel=find_stack_level(), 

1595 ) 

1596 allow_fill = False 

1597 else: 

1598 # Default: opt into fill semantics only if fill_value is 

1599 # explicit. 

1600 allow_fill = fill_value is not no_default 

1601 

1602 if allow_fill: 

1603 # RangeIndex can't hold NA natively; delegate to Index.take which 

1604 # will promote dtype as needed. 

1605 return super().take( 

1606 indices, axis=axis, allow_fill=True, fill_value=fill_value 

1607 ) 

1608 

1609 if len(indices) == 0: 

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

1611 else: 

1612 ind_max = indices.max() 

1613 if ind_max >= len(self): 

1614 raise IndexError( 

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

1616 ) 

1617 ind_min = indices.min() 

1618 if ind_min < -len(self): 

1619 raise IndexError( 

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

1621 ) 

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

1623 if ind_min < 0: 

1624 taken %= len(self) 

1625 if self.step != 1: 

1626 taken *= self.step 

1627 if self.start != 0: 

1628 taken += self.start 

1629 

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

1631 

1632 def value_counts( 

1633 self, 

1634 normalize: bool = False, 

1635 sort: bool = True, 

1636 ascending: bool = False, 

1637 bins: int | None = None, 

1638 dropna: bool = True, 

1639 ) -> Series: 

1640 from pandas import Series 

1641 

1642 if bins is not None: 

1643 return super().value_counts( 

1644 normalize=normalize, 

1645 sort=sort, 

1646 ascending=ascending, 

1647 bins=bins, 

1648 dropna=dropna, 

1649 ) 

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

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

1652 len(self), dtype=np.int64 

1653 ) 

1654 if normalize: 

1655 data = data / len(self) 

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

1657 

1658 @overload 

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

1660 self, 

1661 value: ScalarLike_co, 

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

1663 sorter: NumpySorter = ..., 

1664 ) -> np.intp: ... 

1665 

1666 @overload 

1667 def searchsorted( 

1668 self, 

1669 value: npt.ArrayLike | ExtensionArray, 

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

1671 sorter: NumpySorter = ..., 

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

1673 

1674 def searchsorted( 

1675 self, 

1676 value: NumpyValueArrayLike | ExtensionArray, 

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

1678 sorter: NumpySorter | None = None, 

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

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

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

1682 

1683 was_scalar = False 

1684 if is_scalar(value): 

1685 was_scalar = True 

1686 array_value = np.array([value]) 

1687 else: 

1688 array_value = np.asarray(value) 

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

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

1691 

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

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

1694 start = rng.start 

1695 step = rng.step 

1696 shift = side == "right" 

1697 else: 

1698 start = self.start 

1699 step = self.step 

1700 shift = side == "left" 

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

1702 if flip: 

1703 result = len(self) - result 

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

1705 if was_scalar: 

1706 return maybe_unbox_numpy_scalar(np.intp(result.item())) 

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