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

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

508 statements  

1from __future__ import annotations 

2 

3from datetime import timedelta 

4import operator 

5from sys import getsizeof 

6from typing import ( 

7 Any, 

8 Callable, 

9 Hashable, 

10 Iterator, 

11 List, 

12 cast, 

13) 

14 

15import numpy as np 

16 

17from pandas._libs import ( 

18 index as libindex, 

19 lib, 

20) 

21from pandas._libs.algos import unique_deltas 

22from pandas._libs.lib import no_default 

23from pandas._typing import ( 

24 Dtype, 

25 npt, 

26) 

27from pandas.compat.numpy import function as nv 

28from pandas.util._decorators import ( 

29 cache_readonly, 

30 doc, 

31) 

32 

33from pandas.core.dtypes.common import ( 

34 ensure_platform_int, 

35 ensure_python_int, 

36 is_float, 

37 is_integer, 

38 is_scalar, 

39 is_signed_integer_dtype, 

40 is_timedelta64_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 

47import pandas.core.indexes.base as ibase 

48from pandas.core.indexes.base import ( 

49 Index, 

50 maybe_extract_name, 

51) 

52from pandas.core.ops.common import unpack_zerodim_and_defer 

53 

54_empty_range = range(0) 

55 

56 

57class RangeIndex(Index): 

58 """ 

59 Immutable Index implementing a monotonic integer range. 

60 

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

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

63 improve computing speed. 

64 

65 This is the default index type used 

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

67 

68 Parameters 

69 ---------- 

70 start : int (default: 0), range, or other RangeIndex instance 

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

72 stop : int (default: 0) 

73 step : int (default: 1) 

74 dtype : np.int64 

75 Unused, accepted for homogeneity with other index types. 

76 copy : bool, default False 

77 Unused, accepted for homogeneity with other index types. 

78 name : object, optional 

79 Name to be stored in the index. 

80 

81 Attributes 

82 ---------- 

83 start 

84 stop 

85 step 

86 

87 Methods 

88 ------- 

89 from_range 

90 

91 See Also 

92 -------- 

93 Index : The base pandas Index type. 

94 """ 

95 

96 _typ = "rangeindex" 

97 _dtype_validation_metadata = (is_signed_integer_dtype, "signed integer") 

98 _range: range 

99 _values: np.ndarray 

100 

101 @property 

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

103 return libindex.Int64Engine 

104 

105 # -------------------------------------------------------------------- 

106 # Constructors 

107 

108 def __new__( 

109 cls, 

110 start=None, 

111 stop=None, 

112 step=None, 

113 dtype: Dtype | None = None, 

114 copy: bool = False, 

115 name: Hashable = None, 

116 ) -> RangeIndex: 

117 cls._validate_dtype(dtype) 

118 name = maybe_extract_name(name, start, cls) 

119 

120 # RangeIndex 

121 if isinstance(start, RangeIndex): 

122 return start.copy(name=name) 

123 elif isinstance(start, range): 

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

125 

126 # validate the arguments 

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

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

129 

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

131 

132 if stop is None: 

133 start, stop = 0, start 

134 else: 

135 stop = ensure_python_int(stop) 

136 

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

138 if step == 0: 

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

140 

141 rng = range(start, stop, step) 

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

143 

144 @classmethod 

145 def from_range( 

146 cls, data: range, name=None, dtype: Dtype | None = None 

147 ) -> RangeIndex: 

148 """ 

149 Create RangeIndex from a range object. 

150 

151 Returns 

152 ------- 

153 RangeIndex 

154 """ 

155 if not isinstance(data, range): 

156 raise TypeError( 

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

158 f"range, {repr(data)} was passed" 

159 ) 

160 cls._validate_dtype(dtype) 

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

162 

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

164 # supertype defines the argument type as 

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

166 @classmethod 

167 def _simple_new( # type: ignore[override] 

168 cls, values: range, name: Hashable = None 

169 ) -> RangeIndex: 

170 result = object.__new__(cls) 

171 

172 assert isinstance(values, range) 

173 

174 result._range = values 

175 result._name = name 

176 result._cache = {} 

177 result._reset_identity() 

178 result._references = None 

179 return result 

180 

181 @classmethod 

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

183 if dtype is None: 

184 return 

185 

186 validation_func, expected = cls._dtype_validation_metadata 

187 if not validation_func(dtype): 

188 raise ValueError( 

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

190 ) 

191 

192 # -------------------------------------------------------------------- 

193 

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

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

196 @cache_readonly 

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

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

199 return Index 

200 

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

202 @cache_readonly 

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

204 """ 

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

206 

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

208 """ 

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

210 

211 def _get_data_as_items(self): 

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

213 rng = self._range 

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

215 

216 def __reduce__(self): 

217 d = {"name": self.name} 

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

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

220 

221 # -------------------------------------------------------------------- 

222 # Rendering Methods 

223 

224 def _format_attrs(self): 

225 """ 

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

227 """ 

228 attrs = self._get_data_as_items() 

229 if self.name is not None: 

230 attrs.append(("name", ibase.default_pprint(self.name))) 

231 return attrs 

232 

233 def _format_data(self, name=None): 

234 # we are formatting thru the attributes 

235 return None 

236 

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

238 # Equivalent to Index implementation, but faster 

239 if not len(self._range): 

240 return header 

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

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

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

244 

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

246 

247 # -------------------------------------------------------------------- 

248 

249 @property 

250 def start(self) -> int: 

251 """ 

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

253 """ 

254 # GH 25710 

255 return self._range.start 

256 

257 @property 

258 def stop(self) -> int: 

259 """ 

260 The value of the `stop` parameter. 

261 """ 

262 return self._range.stop 

263 

264 @property 

265 def step(self) -> int: 

266 """ 

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

268 """ 

269 # GH 25710 

270 return self._range.step 

271 

272 @cache_readonly 

273 def nbytes(self) -> int: 

274 """ 

275 Return the number of bytes in the underlying data. 

276 """ 

277 rng = self._range 

278 return getsizeof(rng) + sum( 

279 getsizeof(getattr(rng, attr_name)) 

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

281 ) 

282 

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

284 """ 

285 Memory usage of my values 

286 

287 Parameters 

288 ---------- 

289 deep : bool 

290 Introspect the data deeply, interrogate 

291 `object` dtypes for system-level memory consumption 

292 

293 Returns 

294 ------- 

295 bytes used 

296 

297 Notes 

298 ----- 

299 Memory usage does not include memory consumed by elements that 

300 are not components of the array if deep=False 

301 

302 See Also 

303 -------- 

304 numpy.ndarray.nbytes 

305 """ 

306 return self.nbytes 

307 

308 @property 

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

310 return np.dtype(np.int64) 

311 

312 @property 

313 def is_unique(self) -> bool: 

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

315 return True 

316 

317 @cache_readonly 

318 def is_monotonic_increasing(self) -> bool: 

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

320 

321 @cache_readonly 

322 def is_monotonic_decreasing(self) -> bool: 

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

324 

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

326 hash(key) 

327 try: 

328 key = ensure_python_int(key) 

329 except TypeError: 

330 return False 

331 return key in self._range 

332 

333 @property 

334 def inferred_type(self) -> str: 

335 return "integer" 

336 

337 # -------------------------------------------------------------------- 

338 # Indexing Methods 

339 

340 @doc(Index.get_loc) 

341 def get_loc(self, key): 

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

343 new_key = int(key) 

344 try: 

345 return self._range.index(new_key) 

346 except ValueError as err: 

347 raise KeyError(key) from err 

348 if isinstance(key, Hashable): 

349 raise KeyError(key) 

350 self._check_indexing_error(key) 

351 raise KeyError(key) 

352 

353 def _get_indexer( 

354 self, 

355 target: Index, 

356 method: str | None = None, 

357 limit: int | None = None, 

358 tolerance=None, 

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

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

361 return super()._get_indexer( 

362 target, method=method, tolerance=tolerance, limit=limit 

363 ) 

364 

365 if self.step > 0: 

366 start, stop, step = self.start, self.stop, self.step 

367 else: 

368 # GH 28678: work on reversed range for simplicity 

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

370 start, stop, step = reverse.start, reverse.stop, reverse.step 

371 

372 target_array = np.asarray(target) 

373 locs = target_array - start 

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

375 locs[~valid] = -1 

376 locs[valid] = locs[valid] / step 

377 

378 if step != self.step: 

379 # We reversed this range: transform to original locs 

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

381 return ensure_platform_int(locs) 

382 

383 @cache_readonly 

384 def _should_fallback_to_positional(self) -> bool: 

385 """ 

386 Should an integer key be treated as positional? 

387 """ 

388 return False 

389 

390 # -------------------------------------------------------------------- 

391 

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

393 return list(self._range) 

394 

395 @doc(Index.__iter__) 

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

397 yield from self._range 

398 

399 @doc(Index._shallow_copy) 

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

401 name = self.name if name is no_default else name 

402 

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

404 return Index(values, name=name, dtype=np.float64) 

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

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

407 unique_diffs = unique_deltas(values) 

408 if len(unique_diffs) == 1 and unique_diffs[0] != 0: 

409 diff = unique_diffs[0] 

410 new_range = range(values[0], values[-1] + diff, diff) 

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

412 else: 

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

414 

415 def _view(self: RangeIndex) -> RangeIndex: 

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

417 result._cache = self._cache 

418 return result 

419 

420 @doc(Index.copy) 

421 def copy(self, name: Hashable = None, deep: bool = False): 

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

423 new_index = self._rename(name=name) 

424 return new_index 

425 

426 def _minmax(self, meth: str): 

427 no_steps = len(self) - 1 

428 if no_steps == -1: 

429 return np.nan 

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

431 return self.start 

432 

433 return self.start + self.step * no_steps 

434 

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

436 """The minimum value of the RangeIndex""" 

437 nv.validate_minmax_axis(axis) 

438 nv.validate_min(args, kwargs) 

439 return self._minmax("min") 

440 

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

442 """The maximum value of the RangeIndex""" 

443 nv.validate_minmax_axis(axis) 

444 nv.validate_max(args, kwargs) 

445 return self._minmax("max") 

446 

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

448 """ 

449 Returns the indices that would sort the index and its 

450 underlying data. 

451 

452 Returns 

453 ------- 

454 np.ndarray[np.intp] 

455 

456 See Also 

457 -------- 

458 numpy.ndarray.argsort 

459 """ 

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

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

462 nv.validate_argsort(args, kwargs) 

463 

464 if self._range.step > 0: 

465 result = np.arange(len(self), dtype=np.intp) 

466 else: 

467 result = np.arange(len(self) - 1, -1, -1, dtype=np.intp) 

468 

469 if not ascending: 

470 result = result[::-1] 

471 return result 

472 

473 def factorize( 

474 self, 

475 sort: bool = False, 

476 use_na_sentinel: bool = True, 

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

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

479 uniques = self 

480 if sort and self.step < 0: 

481 codes = codes[::-1] 

482 uniques = uniques[::-1] 

483 return codes, uniques 

484 

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

486 """ 

487 Determines if two Index objects contain the same elements. 

488 """ 

489 if isinstance(other, RangeIndex): 

490 return self._range == other._range 

491 return super().equals(other) 

492 

493 def sort_values( 

494 self, 

495 return_indexer: bool = False, 

496 ascending: bool = True, 

497 na_position: str = "last", 

498 key: Callable | None = None, 

499 ): 

500 if key is not None: 

501 return super().sort_values( 

502 return_indexer=return_indexer, 

503 ascending=ascending, 

504 na_position=na_position, 

505 key=key, 

506 ) 

507 else: 

508 sorted_index = self 

509 inverse_indexer = False 

510 if ascending: 

511 if self.step < 0: 

512 sorted_index = self[::-1] 

513 inverse_indexer = True 

514 else: 

515 if self.step > 0: 

516 sorted_index = self[::-1] 

517 inverse_indexer = True 

518 

519 if return_indexer: 

520 if inverse_indexer: 

521 rng = range(len(self) - 1, -1, -1) 

522 else: 

523 rng = range(len(self)) 

524 return sorted_index, RangeIndex(rng) 

525 else: 

526 return sorted_index 

527 

528 # -------------------------------------------------------------------- 

529 # Set Operations 

530 

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

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

533 

534 if not isinstance(other, RangeIndex): 

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

536 

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

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

539 

540 # check whether intervals intersect 

541 # deals with in- and decreasing ranges 

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

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

544 if int_high <= int_low: 

545 return self._simple_new(_empty_range) 

546 

547 # Method hint: linear Diophantine equation 

548 # solve intersection problem 

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

550 # cheaper alternative 

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

552 

553 # check whether element sets intersect 

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

555 return self._simple_new(_empty_range) 

556 

557 # calculate parameters for the RangeIndex describing the 

558 # intersection disregarding the lower bounds 

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

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

561 new_range = range(tmp_start, int_high, new_step) 

562 new_index = self._simple_new(new_range) 

563 

564 # adjust index to limiting interval 

565 new_start = new_index._min_fitting_element(int_low) 

566 new_range = range(new_start, new_index.stop, new_index.step) 

567 new_index = self._simple_new(new_range) 

568 

569 if (self.step < 0 and other.step < 0) is not (new_index.step < 0): 

570 new_index = new_index[::-1] 

571 

572 if sort is None: 

573 new_index = new_index.sort_values() 

574 

575 return new_index 

576 

577 def _min_fitting_element(self, lower_limit: int) -> int: 

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

579 no_steps = -(-(lower_limit - self.start) // abs(self.step)) 

580 return self.start + abs(self.step) * no_steps 

581 

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

583 """ 

584 Extended Euclidean algorithms to solve Bezout's identity: 

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

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

587 Returns: gcd, s, t 

588 """ 

589 s, old_s = 0, 1 

590 t, old_t = 1, 0 

591 r, old_r = b, a 

592 while r: 

593 quotient = old_r // r 

594 old_r, r = r, old_r - quotient * r 

595 old_s, s = s, old_s - quotient * s 

596 old_t, t = t, old_t - quotient * t 

597 return old_r, old_s, old_t 

598 

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

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

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

602 if not other: 

603 return True 

604 if not self._range: 

605 return False 

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

607 return False 

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

609 

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

611 """ 

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

613 

614 Parameters 

615 ---------- 

616 other : Index or array-like 

617 

618 sort : bool or None, default None 

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

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

621 ``Index`` with a int64 dtype if not. 

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

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

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

625 

626 Returns 

627 ------- 

628 union : Index 

629 """ 

630 if isinstance(other, RangeIndex): 

631 if sort in (None, True) or ( 

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

633 ): 

634 # GH 47557: Can still return a RangeIndex 

635 # if other range in self and sort=False 

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

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

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

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

640 if self.step < 0: 

641 start_s, step_s, end_s = end_s, -step_s, start_s 

642 if other.step < 0: 

643 start_o, step_o, end_o = end_o, -step_o, start_o 

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

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

646 elif len(self) == 1: 

647 step_s = step_o 

648 elif len(other) == 1: 

649 step_o = step_s 

650 start_r = min(start_s, start_o) 

651 end_r = max(end_s, end_o) 

652 if step_o == step_s: 

653 if ( 

654 (start_s - start_o) % step_s == 0 

655 and (start_s - end_o) <= step_s 

656 and (start_o - end_s) <= step_s 

657 ): 

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

659 if ( 

660 (step_s % 2 == 0) 

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

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

663 ): 

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

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

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

667 

668 elif step_o % step_s == 0: 

669 if ( 

670 (start_o - start_s) % step_s == 0 

671 and (start_o + step_s >= start_s) 

672 and (end_o - step_s <= end_s) 

673 ): 

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

675 elif step_s % step_o == 0: 

676 if ( 

677 (start_s - start_o) % step_o == 0 

678 and (start_s + step_o >= start_o) 

679 and (end_s - step_o <= end_o) 

680 ): 

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

682 

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

684 

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

686 # optimized set operation if we have another RangeIndex 

687 self._validate_sort_keyword(sort) 

688 self._assert_can_do_setop(other) 

689 other, result_name = self._convert_can_do_setop(other) 

690 

691 if not isinstance(other, RangeIndex): 

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

693 

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

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

696 

697 res_name = ops.get_op_result_name(self, other) 

698 

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

700 overlap = self.intersection(other) 

701 if overlap.step < 0: 

702 overlap = overlap[::-1] 

703 

704 if len(overlap) == 0: 

705 return self.rename(name=res_name) 

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

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

708 

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

710 

711 if len(overlap) == 1: 

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

713 return self[1:] 

714 

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

716 return self[:-1] 

717 

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

719 return self[::2] 

720 

721 else: 

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

723 

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

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

726 return self[1:-1] 

727 

728 if overlap.step == first.step: 

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

730 # The difference is everything after the intersection 

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

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

733 # The difference is everything before the intersection 

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

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

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

737 step = len(first) - 1 

738 new_rng = first[::step] 

739 else: 

740 # The difference is not range-like 

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

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

743 

744 else: 

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

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

747 assert len(self) > 1 

748 

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

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

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

752 new_rng = first[1::2] 

753 

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

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

756 new_rng = first[::2] 

757 

758 else: 

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

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

761 

762 else: 

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

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

765 

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

767 if first is not self._range: 

768 new_index = new_index[::-1] 

769 

770 return new_index 

771 

772 def symmetric_difference(self, other, result_name: Hashable = None, sort=None): 

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

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

775 

776 left = self.difference(other) 

777 right = other.difference(self) 

778 result = left.union(right) 

779 

780 if result_name is not None: 

781 result = result.rename(result_name) 

782 return result 

783 

784 # -------------------------------------------------------------------- 

785 

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

787 # "RangeIndex" in supertype "Index" 

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

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

790 # DatetimeTimedeltaMixin._get_delete_Freq 

791 if is_integer(loc): 

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

793 return self[1:] 

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

795 return self[:-1] 

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

797 return self[::2] 

798 

799 elif lib.is_list_like(loc): 

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

801 

802 if isinstance(slc, slice): 

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

804 # a RangeIndex whenever possible 

805 other = self[slc] 

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

807 

808 return super().delete(loc) 

809 

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

811 if len(self) and (is_integer(item) or is_float(item)): 

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

813 # or right in the middle. 

814 rng = self._range 

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

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

817 return type(self)._simple_new(new_rng, name=self.name) 

818 

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

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

821 return type(self)._simple_new(new_rng, name=self.name) 

822 

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

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

825 step = int(self.step / 2) 

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

827 return type(self)._simple_new(new_rng, name=self.name) 

828 

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

830 

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

832 """ 

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

834 

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

836 RangeIndex if possible, Index with a int64 dtype otherwise. E.g.: 

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

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

839 """ 

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

841 return super()._concat(indexes, name) 

842 

843 elif len(indexes) == 1: 

844 return indexes[0] 

845 

846 rng_indexes = cast(List[RangeIndex], indexes) 

847 

848 start = step = next_ = None 

849 

850 # Filter the empty indexes 

851 non_empty_indexes = [obj for obj in rng_indexes if len(obj)] 

852 

853 for obj in non_empty_indexes: 

854 rng = obj._range 

855 

856 if start is None: 

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

858 start = rng.start 

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

860 step = rng.step 

861 elif step is None: 

862 # First non-empty index had only one element 

863 if rng.start == start: 

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

865 result = self._constructor(values) 

866 return result.rename(name) 

867 

868 step = rng.start - start 

869 

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

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

872 ) 

873 if non_consecutive: 

874 result = self._constructor( 

875 np.concatenate([x._values for x in rng_indexes]) 

876 ) 

877 return result.rename(name) 

878 

879 if step is not None: 

880 next_ = rng[-1] + step 

881 

882 if non_empty_indexes: 

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

884 # from the last non-empty index 

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

886 return RangeIndex(start, stop, step).rename(name) 

887 

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

889 # In this case return an empty range index. 

890 return RangeIndex(0, 0).rename(name) 

891 

892 def __len__(self) -> int: 

893 """ 

894 return the length of the RangeIndex 

895 """ 

896 return len(self._range) 

897 

898 @property 

899 def size(self) -> int: 

900 return len(self) 

901 

902 def __getitem__(self, key): 

903 """ 

904 Conserve RangeIndex type for scalar and slice keys. 

905 """ 

906 if isinstance(key, slice): 

907 new_range = self._range[key] 

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

909 elif is_integer(key): 

910 new_key = int(key) 

911 try: 

912 return self._range[new_key] 

913 except IndexError as err: 

914 raise IndexError( 

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

916 ) from err 

917 elif is_scalar(key): 

918 raise IndexError( 

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

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

921 "and integer or boolean " 

922 "arrays are valid indices" 

923 ) 

924 return super().__getitem__(key) 

925 

926 def _getitem_slice(self: RangeIndex, slobj: slice) -> RangeIndex: 

927 """ 

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

929 """ 

930 res = self._range[slobj] 

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

932 

933 @unpack_zerodim_and_defer("__floordiv__") 

934 def __floordiv__(self, other): 

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

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

937 start = self.start // other 

938 step = self.step // other 

939 stop = start + len(self) * step 

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

941 return self._simple_new(new_range, name=self.name) 

942 if len(self) == 1: 

943 start = self.start // other 

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

945 return self._simple_new(new_range, name=self.name) 

946 

947 return super().__floordiv__(other) 

948 

949 # -------------------------------------------------------------------- 

950 # Reductions 

951 

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

953 return 0 not in self._range 

954 

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

956 return any(self._range) 

957 

958 # -------------------------------------------------------------------- 

959 

960 def _cmp_method(self, other, op): 

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

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

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

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

965 

966 def _arith_method(self, other, op): 

967 """ 

968 Parameters 

969 ---------- 

970 other : Any 

971 op : callable that accepts 2 params 

972 perform the binary op 

973 """ 

974 

975 if isinstance(other, ABCTimedeltaIndex): 

976 # Defer to TimedeltaIndex implementation 

977 return NotImplemented 

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

979 # GH#19333 is_integer evaluated True on timedelta64, 

980 # so we need to catch these explicitly 

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

982 elif is_timedelta64_dtype(other): 

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

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

985 

986 if op in [ 

987 operator.pow, 

988 ops.rpow, 

989 operator.mod, 

990 ops.rmod, 

991 operator.floordiv, 

992 ops.rfloordiv, 

993 divmod, 

994 ops.rdivmod, 

995 ]: 

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

997 

998 step: Callable | None = None 

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

1000 step = op 

1001 

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

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

1004 left = self 

1005 

1006 try: 

1007 # apply if we have an override 

1008 if step: 

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

1010 rstep = step(left.step, right) 

1011 

1012 # we don't have a representable op 

1013 # so return a base index 

1014 if not is_integer(rstep) or not rstep: 

1015 raise ValueError 

1016 

1017 else: 

1018 rstep = left.step 

1019 

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

1021 rstart = op(left.start, right) 

1022 rstop = op(left.stop, right) 

1023 

1024 res_name = ops.get_op_result_name(self, other) 

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

1026 

1027 # for compat with numpy / Index with int64 dtype 

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

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

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

1031 result = result.astype("float64") 

1032 

1033 return result 

1034 

1035 except (ValueError, TypeError, ZeroDivisionError): 

1036 # test_arithmetic_explicit_conversions 

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