Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/pandas/core/indexes/range.py: 16%
518 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1from __future__ import annotations
3from datetime import timedelta
4import operator
5from sys import getsizeof
6from typing import (
7 TYPE_CHECKING,
8 Any,
9 Callable,
10 Hashable,
11 Iterator,
12 List,
13 cast,
14)
15import warnings
17import numpy as np
19from pandas._libs import (
20 index as libindex,
21 lib,
22)
23from pandas._libs.algos import unique_deltas
24from pandas._libs.lib import no_default
25from pandas._typing import (
26 Dtype,
27 npt,
28)
29from pandas.compat.numpy import function as nv
30from pandas.util._decorators import (
31 cache_readonly,
32 doc,
33)
34from pandas.util._exceptions import find_stack_level
36from pandas.core.dtypes.common import (
37 ensure_platform_int,
38 ensure_python_int,
39 is_float,
40 is_integer,
41 is_scalar,
42 is_signed_integer_dtype,
43 is_timedelta64_dtype,
44)
45from pandas.core.dtypes.generic import ABCTimedeltaIndex
47from pandas.core import ops
48from pandas.core.algorithms import resolve_na_sentinel
49import pandas.core.common as com
50from pandas.core.construction import extract_array
51import pandas.core.indexes.base as ibase
52from pandas.core.indexes.base import maybe_extract_name
53from pandas.core.indexes.numeric import (
54 Float64Index,
55 Int64Index,
56 NumericIndex,
57)
58from pandas.core.ops.common import unpack_zerodim_and_defer
60if TYPE_CHECKING: 60 ↛ 61line 60 didn't jump to line 61, because the condition on line 60 was never true
61 from pandas import Index
63_empty_range = range(0)
66class RangeIndex(NumericIndex):
67 """
68 Immutable Index implementing a monotonic integer range.
70 RangeIndex is a memory-saving special case of Int64Index limited to
71 representing monotonic ranges. Using RangeIndex may in some instances
72 improve computing speed.
74 This is the default index type used
75 by DataFrame and Series when no explicit index is provided by the user.
77 Parameters
78 ----------
79 start : int (default: 0), range, or other RangeIndex instance
80 If int and "stop" is not given, interpreted as "stop" instead.
81 stop : int (default: 0)
82 step : int (default: 1)
83 dtype : np.int64
84 Unused, accepted for homogeneity with other index types.
85 copy : bool, default False
86 Unused, accepted for homogeneity with other index types.
87 name : object, optional
88 Name to be stored in the index.
90 Attributes
91 ----------
92 start
93 stop
94 step
96 Methods
97 -------
98 from_range
100 See Also
101 --------
102 Index : The base pandas Index type.
103 Int64Index : Index of int64 data.
104 """
106 _typ = "rangeindex"
107 _dtype_validation_metadata = (is_signed_integer_dtype, "signed integer")
108 _range: range
109 _is_backward_compat_public_numeric_index: bool = False
111 @property
112 def _engine_type(self) -> type[libindex.Int64Engine]:
113 return libindex.Int64Engine
115 # --------------------------------------------------------------------
116 # Constructors
118 def __new__(
119 cls,
120 start=None,
121 stop=None,
122 step=None,
123 dtype: Dtype | None = None,
124 copy: bool = False,
125 name: Hashable = None,
126 ) -> RangeIndex:
127 cls._validate_dtype(dtype)
128 name = maybe_extract_name(name, start, cls)
130 # RangeIndex
131 if isinstance(start, RangeIndex):
132 return start.copy(name=name)
133 elif isinstance(start, range):
134 return cls._simple_new(start, name=name)
136 # validate the arguments
137 if com.all_none(start, stop, step):
138 raise TypeError("RangeIndex(...) must be called with integers")
140 start = ensure_python_int(start) if start is not None else 0
142 if stop is None:
143 start, stop = 0, start
144 else:
145 stop = ensure_python_int(stop)
147 step = ensure_python_int(step) if step is not None else 1
148 if step == 0:
149 raise ValueError("Step must not be zero")
151 rng = range(start, stop, step)
152 return cls._simple_new(rng, name=name)
154 @classmethod
155 def from_range(
156 cls, data: range, name=None, dtype: Dtype | None = None
157 ) -> RangeIndex:
158 """
159 Create RangeIndex from a range object.
161 Returns
162 -------
163 RangeIndex
164 """
165 if not isinstance(data, range):
166 raise TypeError(
167 f"{cls.__name__}(...) must be called with object coercible to a "
168 f"range, {repr(data)} was passed"
169 )
170 cls._validate_dtype(dtype)
171 return cls._simple_new(data, name=name)
173 @classmethod
174 def _simple_new(cls, values: range, name: Hashable = None) -> RangeIndex:
175 result = object.__new__(cls)
177 assert isinstance(values, range)
179 result._range = values
180 result._name = name
181 result._cache = {}
182 result._reset_identity()
183 return result
185 # --------------------------------------------------------------------
187 # error: Return type "Type[Int64Index]" of "_constructor" incompatible with return
188 # type "Type[RangeIndex]" in supertype "Index"
189 @cache_readonly
190 def _constructor(self) -> type[Int64Index]: # type: ignore[override]
191 """return the class to use for construction"""
192 return Int64Index
194 # error: Signature of "_data" incompatible with supertype "Index"
195 @cache_readonly
196 def _data(self) -> np.ndarray: # type: ignore[override]
197 """
198 An int array that for performance reasons is created only when needed.
200 The constructed array is saved in ``_cache``.
201 """
202 return np.arange(self.start, self.stop, self.step, dtype=np.int64)
204 def _get_data_as_items(self):
205 """return a list of tuples of start, stop, step"""
206 rng = self._range
207 return [("start", rng.start), ("stop", rng.stop), ("step", rng.step)]
209 def __reduce__(self):
210 d = {"name": self.name}
211 d.update(dict(self._get_data_as_items()))
212 return ibase._new_Index, (type(self), d), None
214 # --------------------------------------------------------------------
215 # Rendering Methods
217 def _format_attrs(self):
218 """
219 Return a list of tuples of the (attr, formatted_value)
220 """
221 attrs = self._get_data_as_items()
222 if self.name is not None:
223 attrs.append(("name", ibase.default_pprint(self.name)))
224 return attrs
226 def _format_data(self, name=None):
227 # we are formatting thru the attributes
228 return None
230 def _format_with_header(self, header: list[str], na_rep: str) -> list[str]:
231 # Equivalent to Index implementation, but faster
232 if not len(self._range):
233 return header
234 first_val_str = str(self._range[0])
235 last_val_str = str(self._range[-1])
236 max_length = max(len(first_val_str), len(last_val_str))
238 return header + [f"{x:<{max_length}}" for x in self._range]
240 # --------------------------------------------------------------------
241 _deprecation_message = (
242 "RangeIndex.{} is deprecated and will be "
243 "removed in a future version. Use RangeIndex.{} "
244 "instead"
245 )
247 @property
248 def start(self) -> int:
249 """
250 The value of the `start` parameter (``0`` if this was not supplied).
251 """
252 # GH 25710
253 return self._range.start
255 @property
256 def _start(self) -> int:
257 """
258 The value of the `start` parameter (``0`` if this was not supplied).
260 .. deprecated:: 0.25.0
261 Use ``start`` instead.
262 """
263 warnings.warn(
264 self._deprecation_message.format("_start", "start"),
265 FutureWarning,
266 stacklevel=find_stack_level(),
267 )
268 return self.start
270 @property
271 def stop(self) -> int:
272 """
273 The value of the `stop` parameter.
274 """
275 return self._range.stop
277 @property
278 def _stop(self) -> int:
279 """
280 The value of the `stop` parameter.
282 .. deprecated:: 0.25.0
283 Use ``stop`` instead.
284 """
285 # GH 25710
286 warnings.warn(
287 self._deprecation_message.format("_stop", "stop"),
288 FutureWarning,
289 stacklevel=find_stack_level(),
290 )
291 return self.stop
293 @property
294 def step(self) -> int:
295 """
296 The value of the `step` parameter (``1`` if this was not supplied).
297 """
298 # GH 25710
299 return self._range.step
301 @property
302 def _step(self) -> int:
303 """
304 The value of the `step` parameter (``1`` if this was not supplied).
306 .. deprecated:: 0.25.0
307 Use ``step`` instead.
308 """
309 # GH 25710
310 warnings.warn(
311 self._deprecation_message.format("_step", "step"),
312 FutureWarning,
313 stacklevel=find_stack_level(),
314 )
315 return self.step
317 @cache_readonly
318 def nbytes(self) -> int:
319 """
320 Return the number of bytes in the underlying data.
321 """
322 rng = self._range
323 return getsizeof(rng) + sum(
324 getsizeof(getattr(rng, attr_name))
325 for attr_name in ["start", "stop", "step"]
326 )
328 def memory_usage(self, deep: bool = False) -> int:
329 """
330 Memory usage of my values
332 Parameters
333 ----------
334 deep : bool
335 Introspect the data deeply, interrogate
336 `object` dtypes for system-level memory consumption
338 Returns
339 -------
340 bytes used
342 Notes
343 -----
344 Memory usage does not include memory consumed by elements that
345 are not components of the array if deep=False
347 See Also
348 --------
349 numpy.ndarray.nbytes
350 """
351 return self.nbytes
353 @property
354 def dtype(self) -> np.dtype:
355 return np.dtype(np.int64)
357 @property
358 def is_unique(self) -> bool:
359 """return if the index has unique values"""
360 return True
362 @cache_readonly
363 def is_monotonic_increasing(self) -> bool:
364 return self._range.step > 0 or len(self) <= 1
366 @cache_readonly
367 def is_monotonic_decreasing(self) -> bool:
368 return self._range.step < 0 or len(self) <= 1
370 def __contains__(self, key: Any) -> bool:
371 hash(key)
372 try:
373 key = ensure_python_int(key)
374 except TypeError:
375 return False
376 return key in self._range
378 @property
379 def inferred_type(self) -> str:
380 return "integer"
382 # --------------------------------------------------------------------
383 # Indexing Methods
385 @doc(Int64Index.get_loc)
386 def get_loc(self, key, method=None, tolerance=None):
387 if method is None and tolerance is None:
388 if is_integer(key) or (is_float(key) and key.is_integer()):
389 new_key = int(key)
390 try:
391 return self._range.index(new_key)
392 except ValueError as err:
393 raise KeyError(key) from err
394 self._check_indexing_error(key)
395 raise KeyError(key)
396 return super().get_loc(key, method=method, tolerance=tolerance)
398 def _get_indexer(
399 self,
400 target: Index,
401 method: str | None = None,
402 limit: int | None = None,
403 tolerance=None,
404 ) -> npt.NDArray[np.intp]:
405 if com.any_not_none(method, tolerance, limit):
406 return super()._get_indexer(
407 target, method=method, tolerance=tolerance, limit=limit
408 )
410 if self.step > 0:
411 start, stop, step = self.start, self.stop, self.step
412 else:
413 # GH 28678: work on reversed range for simplicity
414 reverse = self._range[::-1]
415 start, stop, step = reverse.start, reverse.stop, reverse.step
417 target_array = np.asarray(target)
418 locs = target_array - start
419 valid = (locs % step == 0) & (locs >= 0) & (target_array < stop)
420 locs[~valid] = -1
421 locs[valid] = locs[valid] / step
423 if step != self.step:
424 # We reversed this range: transform to original locs
425 locs[valid] = len(self) - 1 - locs[valid]
426 return ensure_platform_int(locs)
428 # --------------------------------------------------------------------
430 def tolist(self) -> list[int]:
431 return list(self._range)
433 @doc(Int64Index.__iter__)
434 def __iter__(self) -> Iterator[int]:
435 yield from self._range
437 @doc(Int64Index._shallow_copy)
438 def _shallow_copy(self, values, name: Hashable = no_default):
439 name = self.name if name is no_default else name
441 if values.dtype.kind == "f":
442 return Float64Index(values, name=name)
443 # GH 46675 & 43885: If values is equally spaced, return a
444 # more memory-compact RangeIndex instead of Int64Index
445 unique_diffs = unique_deltas(values)
446 if len(unique_diffs) == 1 and unique_diffs[0] != 0:
447 diff = unique_diffs[0]
448 new_range = range(values[0], values[-1] + diff, diff)
449 return type(self)._simple_new(new_range, name=name)
450 else:
451 return Int64Index._simple_new(values, name=name)
453 def _view(self: RangeIndex) -> RangeIndex:
454 result = type(self)._simple_new(self._range, name=self._name)
455 result._cache = self._cache
456 return result
458 @doc(Int64Index.copy)
459 def copy(
460 self,
461 name: Hashable = None,
462 deep: bool = False,
463 dtype: Dtype | None = None,
464 names=None,
465 ):
466 name = self._validate_names(name=name, names=names, deep=deep)[0]
467 new_index = self._rename(name=name)
469 if dtype:
470 warnings.warn(
471 "parameter dtype is deprecated and will be removed in a future "
472 "version. Use the astype method instead.",
473 FutureWarning,
474 stacklevel=find_stack_level(),
475 )
476 new_index = new_index.astype(dtype)
477 return new_index
479 def _minmax(self, meth: str):
480 no_steps = len(self) - 1
481 if no_steps == -1:
482 return np.nan
483 elif (meth == "min" and self.step > 0) or (meth == "max" and self.step < 0):
484 return self.start
486 return self.start + self.step * no_steps
488 def min(self, axis=None, skipna: bool = True, *args, **kwargs) -> int:
489 """The minimum value of the RangeIndex"""
490 nv.validate_minmax_axis(axis)
491 nv.validate_min(args, kwargs)
492 return self._minmax("min")
494 def max(self, axis=None, skipna: bool = True, *args, **kwargs) -> int:
495 """The maximum value of the RangeIndex"""
496 nv.validate_minmax_axis(axis)
497 nv.validate_max(args, kwargs)
498 return self._minmax("max")
500 def argsort(self, *args, **kwargs) -> npt.NDArray[np.intp]:
501 """
502 Returns the indices that would sort the index and its
503 underlying data.
505 Returns
506 -------
507 np.ndarray[np.intp]
509 See Also
510 --------
511 numpy.ndarray.argsort
512 """
513 ascending = kwargs.pop("ascending", True) # EA compat
514 kwargs.pop("kind", None) # e.g. "mergesort" is irrelevant
515 nv.validate_argsort(args, kwargs)
517 if self._range.step > 0:
518 result = np.arange(len(self), dtype=np.intp)
519 else:
520 result = np.arange(len(self) - 1, -1, -1, dtype=np.intp)
522 if not ascending:
523 result = result[::-1]
524 return result
526 def factorize(
527 self,
528 sort: bool = False,
529 na_sentinel: int | lib.NoDefault = lib.no_default,
530 use_na_sentinel: bool | lib.NoDefault = lib.no_default,
531 ) -> tuple[npt.NDArray[np.intp], RangeIndex]:
532 # resolve to emit warning if appropriate
533 resolve_na_sentinel(na_sentinel, use_na_sentinel)
534 codes = np.arange(len(self), dtype=np.intp)
535 uniques = self
536 if sort and self.step < 0:
537 codes = codes[::-1]
538 uniques = uniques[::-1]
539 return codes, uniques
541 def equals(self, other: object) -> bool:
542 """
543 Determines if two Index objects contain the same elements.
544 """
545 if isinstance(other, RangeIndex):
546 return self._range == other._range
547 return super().equals(other)
549 def sort_values(
550 self,
551 return_indexer: bool = False,
552 ascending: bool = True,
553 na_position: str = "last",
554 key: Callable | None = None,
555 ):
556 sorted_index = self
557 indexer = RangeIndex(range(len(self)))
558 if key is not None:
559 return super().sort_values(
560 return_indexer=return_indexer,
561 ascending=ascending,
562 na_position=na_position,
563 key=key,
564 )
565 else:
566 sorted_index = self
567 if ascending:
568 if self.step < 0:
569 sorted_index = self[::-1]
570 indexer = indexer[::-1]
571 else:
572 if self.step > 0:
573 sorted_index = self[::-1]
574 indexer = indexer = indexer[::-1]
576 if return_indexer:
577 return sorted_index, indexer
578 else:
579 return sorted_index
581 # --------------------------------------------------------------------
582 # Set Operations
584 def _intersection(self, other: Index, sort=False):
585 # caller is responsible for checking self and other are both non-empty
587 if not isinstance(other, RangeIndex):
588 # Int64Index
589 return super()._intersection(other, sort=sort)
591 first = self._range[::-1] if self.step < 0 else self._range
592 second = other._range[::-1] if other.step < 0 else other._range
594 # check whether intervals intersect
595 # deals with in- and decreasing ranges
596 int_low = max(first.start, second.start)
597 int_high = min(first.stop, second.stop)
598 if int_high <= int_low:
599 return self._simple_new(_empty_range)
601 # Method hint: linear Diophantine equation
602 # solve intersection problem
603 # performance hint: for identical step sizes, could use
604 # cheaper alternative
605 gcd, s, _ = self._extended_gcd(first.step, second.step)
607 # check whether element sets intersect
608 if (first.start - second.start) % gcd:
609 return self._simple_new(_empty_range)
611 # calculate parameters for the RangeIndex describing the
612 # intersection disregarding the lower bounds
613 tmp_start = first.start + (second.start - first.start) * first.step // gcd * s
614 new_step = first.step * second.step // gcd
615 new_range = range(tmp_start, int_high, new_step)
616 new_index = self._simple_new(new_range)
618 # adjust index to limiting interval
619 new_start = new_index._min_fitting_element(int_low)
620 new_range = range(new_start, new_index.stop, new_index.step)
621 new_index = self._simple_new(new_range)
623 if (self.step < 0 and other.step < 0) is not (new_index.step < 0):
624 new_index = new_index[::-1]
626 if sort is None:
627 new_index = new_index.sort_values()
629 return new_index
631 def _min_fitting_element(self, lower_limit: int) -> int:
632 """Returns the smallest element greater than or equal to the limit"""
633 no_steps = -(-(lower_limit - self.start) // abs(self.step))
634 return self.start + abs(self.step) * no_steps
636 def _extended_gcd(self, a: int, b: int) -> tuple[int, int, int]:
637 """
638 Extended Euclidean algorithms to solve Bezout's identity:
639 a*x + b*y = gcd(x, y)
640 Finds one particular solution for x, y: s, t
641 Returns: gcd, s, t
642 """
643 s, old_s = 0, 1
644 t, old_t = 1, 0
645 r, old_r = b, a
646 while r:
647 quotient = old_r // r
648 old_r, r = r, old_r - quotient * r
649 old_s, s = s, old_s - quotient * s
650 old_t, t = t, old_t - quotient * t
651 return old_r, old_s, old_t
653 def _range_in_self(self, other: range) -> bool:
654 """Check if other range is contained in self"""
655 # https://stackoverflow.com/a/32481015
656 if not other:
657 return True
658 if not self._range:
659 return False
660 if len(other) > 1 and other.step % self._range.step:
661 return False
662 return other.start in self._range and other[-1] in self._range
664 def _union(self, other: Index, sort):
665 """
666 Form the union of two Index objects and sorts if possible
668 Parameters
669 ----------
670 other : Index or array-like
672 sort : False or None, default None
673 Whether to sort (monotonically increasing) the resulting index.
674 ``sort=None`` returns a ``RangeIndex`` if possible or a sorted
675 ``Int64Index`` if not.
676 ``sort=False`` can return a ``RangeIndex`` if self is monotonically
677 increasing and other is fully contained in self. Otherwise, returns
678 an unsorted ``Int64Index``
680 .. versionadded:: 0.25.0
682 Returns
683 -------
684 union : Index
685 """
686 if isinstance(other, RangeIndex):
687 if sort is None or (
688 sort is False and self.step > 0 and self._range_in_self(other._range)
689 ):
690 # GH 47557: Can still return a RangeIndex
691 # if other range in self and sort=False
692 start_s, step_s = self.start, self.step
693 end_s = self.start + self.step * (len(self) - 1)
694 start_o, step_o = other.start, other.step
695 end_o = other.start + other.step * (len(other) - 1)
696 if self.step < 0:
697 start_s, step_s, end_s = end_s, -step_s, start_s
698 if other.step < 0:
699 start_o, step_o, end_o = end_o, -step_o, start_o
700 if len(self) == 1 and len(other) == 1:
701 step_s = step_o = abs(self.start - other.start)
702 elif len(self) == 1:
703 step_s = step_o
704 elif len(other) == 1:
705 step_o = step_s
706 start_r = min(start_s, start_o)
707 end_r = max(end_s, end_o)
708 if step_o == step_s:
709 if (
710 (start_s - start_o) % step_s == 0
711 and (start_s - end_o) <= step_s
712 and (start_o - end_s) <= step_s
713 ):
714 return type(self)(start_r, end_r + step_s, step_s)
715 if (
716 (step_s % 2 == 0)
717 and (abs(start_s - start_o) == step_s / 2)
718 and (abs(end_s - end_o) == step_s / 2)
719 ):
720 # e.g. range(0, 10, 2) and range(1, 11, 2)
721 # but not range(0, 20, 4) and range(1, 21, 4) GH#44019
722 return type(self)(start_r, end_r + step_s / 2, step_s / 2)
724 elif step_o % step_s == 0:
725 if (
726 (start_o - start_s) % step_s == 0
727 and (start_o + step_s >= start_s)
728 and (end_o - step_s <= end_s)
729 ):
730 return type(self)(start_r, end_r + step_s, step_s)
731 elif step_s % step_o == 0:
732 if (
733 (start_s - start_o) % step_o == 0
734 and (start_s + step_o >= start_o)
735 and (end_s - step_o <= end_o)
736 ):
737 return type(self)(start_r, end_r + step_o, step_o)
739 return super()._union(other, sort=sort)
741 def _difference(self, other, sort=None):
742 # optimized set operation if we have another RangeIndex
743 self._validate_sort_keyword(sort)
744 self._assert_can_do_setop(other)
745 other, result_name = self._convert_can_do_setop(other)
747 if not isinstance(other, RangeIndex):
748 return super()._difference(other, sort=sort)
750 if sort is None and self.step < 0:
751 return self[::-1]._difference(other)
753 res_name = ops.get_op_result_name(self, other)
755 first = self._range[::-1] if self.step < 0 else self._range
756 overlap = self.intersection(other)
757 if overlap.step < 0:
758 overlap = overlap[::-1]
760 if len(overlap) == 0:
761 return self.rename(name=res_name)
762 if len(overlap) == len(self):
763 return self[:0].rename(res_name)
765 # overlap.step will always be a multiple of self.step (see _intersection)
767 if len(overlap) == 1:
768 if overlap[0] == self[0]:
769 return self[1:]
771 elif overlap[0] == self[-1]:
772 return self[:-1]
774 elif len(self) == 3 and overlap[0] == self[1]:
775 return self[::2]
777 else:
778 return super()._difference(other, sort=sort)
780 elif len(overlap) == 2 and overlap[0] == first[0] and overlap[-1] == first[-1]:
781 # e.g. range(-8, 20, 7) and range(13, -9, -3)
782 return self[1:-1]
784 if overlap.step == first.step:
785 if overlap[0] == first.start:
786 # The difference is everything after the intersection
787 new_rng = range(overlap[-1] + first.step, first.stop, first.step)
788 elif overlap[-1] == first[-1]:
789 # The difference is everything before the intersection
790 new_rng = range(first.start, overlap[0], first.step)
791 elif overlap._range == first[1:-1]:
792 # e.g. range(4) and range(1, 3)
793 step = len(first) - 1
794 new_rng = first[::step]
795 else:
796 # The difference is not range-like
797 # e.g. range(1, 10, 1) and range(3, 7, 1)
798 return super()._difference(other, sort=sort)
800 else:
801 # We must have len(self) > 1, bc we ruled out above
802 # len(overlap) == 0 and len(overlap) == len(self)
803 assert len(self) > 1
805 if overlap.step == first.step * 2:
806 if overlap[0] == first[0] and overlap[-1] in (first[-1], first[-2]):
807 # e.g. range(1, 10, 1) and range(1, 10, 2)
808 new_rng = first[1::2]
810 elif overlap[0] == first[1] and overlap[-1] in (first[-1], first[-2]):
811 # e.g. range(1, 10, 1) and range(2, 10, 2)
812 new_rng = first[::2]
814 else:
815 # We can get here with e.g. range(20) and range(0, 10, 2)
816 return super()._difference(other, sort=sort)
818 else:
819 # e.g. range(10) and range(0, 10, 3)
820 return super()._difference(other, sort=sort)
822 new_index = type(self)._simple_new(new_rng, name=res_name)
823 if first is not self._range:
824 new_index = new_index[::-1]
826 return new_index
828 def symmetric_difference(self, other, result_name: Hashable = None, sort=None):
829 if not isinstance(other, RangeIndex) or sort is not None:
830 return super().symmetric_difference(other, result_name, sort)
832 left = self.difference(other)
833 right = other.difference(self)
834 result = left.union(right)
836 if result_name is not None:
837 result = result.rename(result_name)
838 return result
840 # --------------------------------------------------------------------
842 # error: Return type "Index" of "delete" incompatible with return type
843 # "RangeIndex" in supertype "Index"
844 def delete(self, loc) -> Index: # type: ignore[override]
845 # In some cases we can retain RangeIndex, see also
846 # DatetimeTimedeltaMixin._get_delete_Freq
847 if is_integer(loc):
848 if loc == 0 or loc == -len(self):
849 return self[1:]
850 if loc == -1 or loc == len(self) - 1:
851 return self[:-1]
852 if len(self) == 3 and (loc == 1 or loc == -2):
853 return self[::2]
855 elif lib.is_list_like(loc):
856 slc = lib.maybe_indices_to_slice(np.asarray(loc, dtype=np.intp), len(self))
858 if isinstance(slc, slice):
859 # defer to RangeIndex._difference, which is optimized to return
860 # a RangeIndex whenever possible
861 other = self[slc]
862 return self.difference(other, sort=False)
864 return super().delete(loc)
866 def insert(self, loc: int, item) -> Index:
867 if len(self) and (is_integer(item) or is_float(item)):
868 # We can retain RangeIndex is inserting at the beginning or end,
869 # or right in the middle.
870 rng = self._range
871 if loc == 0 and item == self[0] - self.step:
872 new_rng = range(rng.start - rng.step, rng.stop, rng.step)
873 return type(self)._simple_new(new_rng, name=self.name)
875 elif loc == len(self) and item == self[-1] + self.step:
876 new_rng = range(rng.start, rng.stop + rng.step, rng.step)
877 return type(self)._simple_new(new_rng, name=self.name)
879 elif len(self) == 2 and item == self[0] + self.step / 2:
880 # e.g. inserting 1 into [0, 2]
881 step = int(self.step / 2)
882 new_rng = range(self.start, self.stop, step)
883 return type(self)._simple_new(new_rng, name=self.name)
885 return super().insert(loc, item)
887 def _concat(self, indexes: list[Index], name: Hashable) -> Index:
888 """
889 Overriding parent method for the case of all RangeIndex instances.
891 When all members of "indexes" are of type RangeIndex: result will be
892 RangeIndex if possible, Int64Index otherwise. E.g.:
893 indexes = [RangeIndex(3), RangeIndex(3, 6)] -> RangeIndex(6)
894 indexes = [RangeIndex(3), RangeIndex(4, 6)] -> Int64Index([0,1,2,4,5])
895 """
896 if not all(isinstance(x, RangeIndex) for x in indexes):
897 return super()._concat(indexes, name)
899 elif len(indexes) == 1:
900 return indexes[0]
902 rng_indexes = cast(List[RangeIndex], indexes)
904 start = step = next_ = None
906 # Filter the empty indexes
907 non_empty_indexes = [obj for obj in rng_indexes if len(obj)]
909 for obj in non_empty_indexes:
910 rng = obj._range
912 if start is None:
913 # This is set by the first non-empty index
914 start = rng.start
915 if step is None and len(rng) > 1:
916 step = rng.step
917 elif step is None:
918 # First non-empty index had only one element
919 if rng.start == start:
920 values = np.concatenate([x._values for x in rng_indexes])
921 result = Int64Index(values)
922 return result.rename(name)
924 step = rng.start - start
926 non_consecutive = (step != rng.step and len(rng) > 1) or (
927 next_ is not None and rng.start != next_
928 )
929 if non_consecutive:
930 result = Int64Index(np.concatenate([x._values for x in rng_indexes]))
931 return result.rename(name)
933 if step is not None:
934 next_ = rng[-1] + step
936 if non_empty_indexes:
937 # Get the stop value from "next" or alternatively
938 # from the last non-empty index
939 stop = non_empty_indexes[-1].stop if next_ is None else next_
940 return RangeIndex(start, stop, step).rename(name)
942 # Here all "indexes" had 0 length, i.e. were empty.
943 # In this case return an empty range index.
944 return RangeIndex(0, 0).rename(name)
946 def __len__(self) -> int:
947 """
948 return the length of the RangeIndex
949 """
950 return len(self._range)
952 @property
953 def size(self) -> int:
954 return len(self)
956 def __getitem__(self, key):
957 """
958 Conserve RangeIndex type for scalar and slice keys.
959 """
960 if isinstance(key, slice):
961 new_range = self._range[key]
962 return self._simple_new(new_range, name=self._name)
963 elif is_integer(key):
964 new_key = int(key)
965 try:
966 return self._range[new_key]
967 except IndexError as err:
968 raise IndexError(
969 f"index {key} is out of bounds for axis 0 with size {len(self)}"
970 ) from err
971 elif is_scalar(key):
972 raise IndexError(
973 "only integers, slices (`:`), "
974 "ellipsis (`...`), numpy.newaxis (`None`) "
975 "and integer or boolean "
976 "arrays are valid indices"
977 )
978 # fall back to Int64Index
979 return super().__getitem__(key)
981 def _getitem_slice(self: RangeIndex, slobj: slice) -> RangeIndex:
982 """
983 Fastpath for __getitem__ when we know we have a slice.
984 """
985 res = self._range[slobj]
986 return type(self)._simple_new(res, name=self._name)
988 @unpack_zerodim_and_defer("__floordiv__")
989 def __floordiv__(self, other):
991 if is_integer(other) and other != 0:
992 if len(self) == 0 or self.start % other == 0 and self.step % other == 0:
993 start = self.start // other
994 step = self.step // other
995 stop = start + len(self) * step
996 new_range = range(start, stop, step or 1)
997 return self._simple_new(new_range, name=self.name)
998 if len(self) == 1:
999 start = self.start // other
1000 new_range = range(start, start + 1, 1)
1001 return self._simple_new(new_range, name=self.name)
1003 return super().__floordiv__(other)
1005 # --------------------------------------------------------------------
1006 # Reductions
1008 def all(self, *args, **kwargs) -> bool:
1009 return 0 not in self._range
1011 def any(self, *args, **kwargs) -> bool:
1012 return any(self._range)
1014 # --------------------------------------------------------------------
1016 def _cmp_method(self, other, op):
1017 if isinstance(other, RangeIndex) and self._range == other._range:
1018 # Both are immutable so if ._range attr. are equal, shortcut is possible
1019 return super()._cmp_method(self, op)
1020 return super()._cmp_method(other, op)
1022 def _arith_method(self, other, op):
1023 """
1024 Parameters
1025 ----------
1026 other : Any
1027 op : callable that accepts 2 params
1028 perform the binary op
1029 """
1031 if isinstance(other, ABCTimedeltaIndex):
1032 # Defer to TimedeltaIndex implementation
1033 return NotImplemented
1034 elif isinstance(other, (timedelta, np.timedelta64)):
1035 # GH#19333 is_integer evaluated True on timedelta64,
1036 # so we need to catch these explicitly
1037 return super()._arith_method(other, op)
1038 elif is_timedelta64_dtype(other):
1039 # Must be an np.ndarray; GH#22390
1040 return super()._arith_method(other, op)
1042 if op in [
1043 operator.pow,
1044 ops.rpow,
1045 operator.mod,
1046 ops.rmod,
1047 operator.floordiv,
1048 ops.rfloordiv,
1049 divmod,
1050 ops.rdivmod,
1051 ]:
1052 return super()._arith_method(other, op)
1054 step: Callable | None = None
1055 if op in [operator.mul, ops.rmul, operator.truediv, ops.rtruediv]:
1056 step = op
1058 # TODO: if other is a RangeIndex we may have more efficient options
1059 right = extract_array(other, extract_numpy=True, extract_range=True)
1060 left = self
1062 try:
1063 # apply if we have an override
1064 if step:
1065 with np.errstate(all="ignore"):
1066 rstep = step(left.step, right)
1068 # we don't have a representable op
1069 # so return a base index
1070 if not is_integer(rstep) or not rstep:
1071 raise ValueError
1073 else:
1074 rstep = left.step
1076 with np.errstate(all="ignore"):
1077 rstart = op(left.start, right)
1078 rstop = op(left.stop, right)
1080 res_name = ops.get_op_result_name(self, other)
1081 result = type(self)(rstart, rstop, rstep, name=res_name)
1083 # for compat with numpy / Int64Index
1084 # even if we can represent as a RangeIndex, return
1085 # as a Float64Index if we have float-like descriptors
1086 if not all(is_integer(x) for x in [rstart, rstop, rstep]):
1087 result = result.astype("float64")
1089 return result
1091 except (ValueError, TypeError, ZeroDivisionError):
1092 # Defer to Int64Index implementation
1093 # test_arithmetic_explicit_conversions
1094 return super()._arith_method(other, op)