1117 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1117 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
""" define the IntervalIndex """
 | 
						|
from __future__ import annotations
 | 
						|
 | 
						|
from operator import (
 | 
						|
    le,
 | 
						|
    lt,
 | 
						|
)
 | 
						|
import textwrap
 | 
						|
from typing import (
 | 
						|
    Any,
 | 
						|
    Hashable,
 | 
						|
)
 | 
						|
 | 
						|
import numpy as np
 | 
						|
 | 
						|
from pandas._libs import lib
 | 
						|
from pandas._libs.interval import (
 | 
						|
    Interval,
 | 
						|
    IntervalMixin,
 | 
						|
    IntervalTree,
 | 
						|
)
 | 
						|
from pandas._libs.tslibs import (
 | 
						|
    BaseOffset,
 | 
						|
    Timedelta,
 | 
						|
    Timestamp,
 | 
						|
    to_offset,
 | 
						|
)
 | 
						|
from pandas._typing import (
 | 
						|
    Dtype,
 | 
						|
    DtypeObj,
 | 
						|
    npt,
 | 
						|
)
 | 
						|
from pandas.errors import InvalidIndexError
 | 
						|
from pandas.util._decorators import (
 | 
						|
    Appender,
 | 
						|
    cache_readonly,
 | 
						|
)
 | 
						|
from pandas.util._exceptions import rewrite_exception
 | 
						|
 | 
						|
from pandas.core.dtypes.cast import (
 | 
						|
    find_common_type,
 | 
						|
    infer_dtype_from_scalar,
 | 
						|
    maybe_box_datetimelike,
 | 
						|
    maybe_downcast_numeric,
 | 
						|
)
 | 
						|
from pandas.core.dtypes.common import (
 | 
						|
    ensure_platform_int,
 | 
						|
    is_datetime64tz_dtype,
 | 
						|
    is_datetime_or_timedelta_dtype,
 | 
						|
    is_dtype_equal,
 | 
						|
    is_float,
 | 
						|
    is_float_dtype,
 | 
						|
    is_integer,
 | 
						|
    is_integer_dtype,
 | 
						|
    is_interval_dtype,
 | 
						|
    is_list_like,
 | 
						|
    is_number,
 | 
						|
    is_object_dtype,
 | 
						|
    is_scalar,
 | 
						|
)
 | 
						|
from pandas.core.dtypes.dtypes import IntervalDtype
 | 
						|
from pandas.core.dtypes.missing import is_valid_na_for_dtype
 | 
						|
 | 
						|
from pandas.core.algorithms import unique
 | 
						|
from pandas.core.arrays.interval import (
 | 
						|
    IntervalArray,
 | 
						|
    _interval_shared_docs,
 | 
						|
)
 | 
						|
import pandas.core.common as com
 | 
						|
from pandas.core.indexers import is_valid_positional_slice
 | 
						|
import pandas.core.indexes.base as ibase
 | 
						|
from pandas.core.indexes.base import (
 | 
						|
    Index,
 | 
						|
    _index_shared_docs,
 | 
						|
    ensure_index,
 | 
						|
    maybe_extract_name,
 | 
						|
)
 | 
						|
from pandas.core.indexes.datetimes import (
 | 
						|
    DatetimeIndex,
 | 
						|
    date_range,
 | 
						|
)
 | 
						|
from pandas.core.indexes.extension import (
 | 
						|
    ExtensionIndex,
 | 
						|
    inherit_names,
 | 
						|
)
 | 
						|
from pandas.core.indexes.multi import MultiIndex
 | 
						|
from pandas.core.indexes.timedeltas import (
 | 
						|
    TimedeltaIndex,
 | 
						|
    timedelta_range,
 | 
						|
)
 | 
						|
 | 
						|
_index_doc_kwargs = dict(ibase._index_doc_kwargs)
 | 
						|
 | 
						|
_index_doc_kwargs.update(
 | 
						|
    {
 | 
						|
        "klass": "IntervalIndex",
 | 
						|
        "qualname": "IntervalIndex",
 | 
						|
        "target_klass": "IntervalIndex or list of Intervals",
 | 
						|
        "name": textwrap.dedent(
 | 
						|
            """\
 | 
						|
         name : object, optional
 | 
						|
              Name to be stored in the index.
 | 
						|
         """
 | 
						|
        ),
 | 
						|
    }
 | 
						|
)
 | 
						|
 | 
						|
 | 
						|
def _get_next_label(label):
 | 
						|
    dtype = getattr(label, "dtype", type(label))
 | 
						|
    if isinstance(label, (Timestamp, Timedelta)):
 | 
						|
        dtype = "datetime64"
 | 
						|
    if is_datetime_or_timedelta_dtype(dtype) or is_datetime64tz_dtype(dtype):
 | 
						|
        return label + np.timedelta64(1, "ns")
 | 
						|
    elif is_integer_dtype(dtype):
 | 
						|
        return label + 1
 | 
						|
    elif is_float_dtype(dtype):
 | 
						|
        return np.nextafter(label, np.infty)
 | 
						|
    else:
 | 
						|
        raise TypeError(f"cannot determine next label for type {repr(type(label))}")
 | 
						|
 | 
						|
 | 
						|
def _get_prev_label(label):
 | 
						|
    dtype = getattr(label, "dtype", type(label))
 | 
						|
    if isinstance(label, (Timestamp, Timedelta)):
 | 
						|
        dtype = "datetime64"
 | 
						|
    if is_datetime_or_timedelta_dtype(dtype) or is_datetime64tz_dtype(dtype):
 | 
						|
        return label - np.timedelta64(1, "ns")
 | 
						|
    elif is_integer_dtype(dtype):
 | 
						|
        return label - 1
 | 
						|
    elif is_float_dtype(dtype):
 | 
						|
        return np.nextafter(label, -np.infty)
 | 
						|
    else:
 | 
						|
        raise TypeError(f"cannot determine next label for type {repr(type(label))}")
 | 
						|
 | 
						|
 | 
						|
def _new_IntervalIndex(cls, d):
 | 
						|
    """
 | 
						|
    This is called upon unpickling, rather than the default which doesn't have
 | 
						|
    arguments and breaks __new__.
 | 
						|
    """
 | 
						|
    return cls.from_arrays(**d)
 | 
						|
 | 
						|
 | 
						|
@Appender(
 | 
						|
    _interval_shared_docs["class"]
 | 
						|
    % {
 | 
						|
        "klass": "IntervalIndex",
 | 
						|
        "summary": "Immutable index of intervals that are closed on the same side.",
 | 
						|
        "name": _index_doc_kwargs["name"],
 | 
						|
        "versionadded": "0.20.0",
 | 
						|
        "extra_attributes": "is_overlapping\nvalues\n",
 | 
						|
        "extra_methods": "",
 | 
						|
        "examples": textwrap.dedent(
 | 
						|
            """\
 | 
						|
    Examples
 | 
						|
    --------
 | 
						|
    A new ``IntervalIndex`` is typically constructed using
 | 
						|
    :func:`interval_range`:
 | 
						|
 | 
						|
    >>> pd.interval_range(start=0, end=5)
 | 
						|
    IntervalIndex([(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]],
 | 
						|
                  dtype='interval[int64, right]')
 | 
						|
 | 
						|
    It may also be constructed using one of the constructor
 | 
						|
    methods: :meth:`IntervalIndex.from_arrays`,
 | 
						|
    :meth:`IntervalIndex.from_breaks`, and :meth:`IntervalIndex.from_tuples`.
 | 
						|
 | 
						|
    See further examples in the doc strings of ``interval_range`` and the
 | 
						|
    mentioned constructor methods.
 | 
						|
    """
 | 
						|
        ),
 | 
						|
    }
 | 
						|
)
 | 
						|
@inherit_names(["set_closed", "to_tuples"], IntervalArray, wrap=True)
 | 
						|
@inherit_names(
 | 
						|
    [
 | 
						|
        "__array__",
 | 
						|
        "overlaps",
 | 
						|
        "contains",
 | 
						|
        "closed_left",
 | 
						|
        "closed_right",
 | 
						|
        "open_left",
 | 
						|
        "open_right",
 | 
						|
        "is_empty",
 | 
						|
    ],
 | 
						|
    IntervalArray,
 | 
						|
)
 | 
						|
@inherit_names(["is_non_overlapping_monotonic", "closed"], IntervalArray, cache=True)
 | 
						|
class IntervalIndex(ExtensionIndex):
 | 
						|
    _typ = "intervalindex"
 | 
						|
 | 
						|
    # annotate properties pinned via inherit_names
 | 
						|
    closed: str
 | 
						|
    is_non_overlapping_monotonic: bool
 | 
						|
    closed_left: bool
 | 
						|
    closed_right: bool
 | 
						|
 | 
						|
    _data: IntervalArray
 | 
						|
    _values: IntervalArray
 | 
						|
    _can_hold_strings = False
 | 
						|
    _data_cls = IntervalArray
 | 
						|
 | 
						|
    # --------------------------------------------------------------------
 | 
						|
    # Constructors
 | 
						|
 | 
						|
    def __new__(
 | 
						|
        cls,
 | 
						|
        data,
 | 
						|
        closed=None,
 | 
						|
        dtype: Dtype | None = None,
 | 
						|
        copy: bool = False,
 | 
						|
        name: Hashable = None,
 | 
						|
        verify_integrity: bool = True,
 | 
						|
    ) -> IntervalIndex:
 | 
						|
 | 
						|
        name = maybe_extract_name(name, data, cls)
 | 
						|
 | 
						|
        with rewrite_exception("IntervalArray", cls.__name__):
 | 
						|
            array = IntervalArray(
 | 
						|
                data,
 | 
						|
                closed=closed,
 | 
						|
                copy=copy,
 | 
						|
                dtype=dtype,
 | 
						|
                verify_integrity=verify_integrity,
 | 
						|
            )
 | 
						|
 | 
						|
        return cls._simple_new(array, name)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    @Appender(
 | 
						|
        _interval_shared_docs["from_breaks"]
 | 
						|
        % {
 | 
						|
            "klass": "IntervalIndex",
 | 
						|
            "examples": textwrap.dedent(
 | 
						|
                """\
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        >>> pd.IntervalIndex.from_breaks([0, 1, 2, 3])
 | 
						|
        IntervalIndex([(0, 1], (1, 2], (2, 3]],
 | 
						|
                      dtype='interval[int64, right]')
 | 
						|
        """
 | 
						|
            ),
 | 
						|
        }
 | 
						|
    )
 | 
						|
    def from_breaks(
 | 
						|
        cls,
 | 
						|
        breaks,
 | 
						|
        closed: str = "right",
 | 
						|
        name: Hashable = None,
 | 
						|
        copy: bool = False,
 | 
						|
        dtype: Dtype | None = None,
 | 
						|
    ) -> IntervalIndex:
 | 
						|
        with rewrite_exception("IntervalArray", cls.__name__):
 | 
						|
            array = IntervalArray.from_breaks(
 | 
						|
                breaks, closed=closed, copy=copy, dtype=dtype
 | 
						|
            )
 | 
						|
        return cls._simple_new(array, name=name)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    @Appender(
 | 
						|
        _interval_shared_docs["from_arrays"]
 | 
						|
        % {
 | 
						|
            "klass": "IntervalIndex",
 | 
						|
            "examples": textwrap.dedent(
 | 
						|
                """\
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        >>> pd.IntervalIndex.from_arrays([0, 1, 2], [1, 2, 3])
 | 
						|
        IntervalIndex([(0, 1], (1, 2], (2, 3]],
 | 
						|
                      dtype='interval[int64, right]')
 | 
						|
        """
 | 
						|
            ),
 | 
						|
        }
 | 
						|
    )
 | 
						|
    def from_arrays(
 | 
						|
        cls,
 | 
						|
        left,
 | 
						|
        right,
 | 
						|
        closed: str = "right",
 | 
						|
        name: Hashable = None,
 | 
						|
        copy: bool = False,
 | 
						|
        dtype: Dtype | None = None,
 | 
						|
    ) -> IntervalIndex:
 | 
						|
        with rewrite_exception("IntervalArray", cls.__name__):
 | 
						|
            array = IntervalArray.from_arrays(
 | 
						|
                left, right, closed, copy=copy, dtype=dtype
 | 
						|
            )
 | 
						|
        return cls._simple_new(array, name=name)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    @Appender(
 | 
						|
        _interval_shared_docs["from_tuples"]
 | 
						|
        % {
 | 
						|
            "klass": "IntervalIndex",
 | 
						|
            "examples": textwrap.dedent(
 | 
						|
                """\
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        >>> pd.IntervalIndex.from_tuples([(0, 1), (1, 2)])
 | 
						|
        IntervalIndex([(0, 1], (1, 2]],
 | 
						|
                       dtype='interval[int64, right]')
 | 
						|
        """
 | 
						|
            ),
 | 
						|
        }
 | 
						|
    )
 | 
						|
    def from_tuples(
 | 
						|
        cls,
 | 
						|
        data,
 | 
						|
        closed: str = "right",
 | 
						|
        name: Hashable = None,
 | 
						|
        copy: bool = False,
 | 
						|
        dtype: Dtype | None = None,
 | 
						|
    ) -> IntervalIndex:
 | 
						|
        with rewrite_exception("IntervalArray", cls.__name__):
 | 
						|
            arr = IntervalArray.from_tuples(data, closed=closed, copy=copy, dtype=dtype)
 | 
						|
        return cls._simple_new(arr, name=name)
 | 
						|
 | 
						|
    # --------------------------------------------------------------------
 | 
						|
 | 
						|
    @cache_readonly
 | 
						|
    def _engine(self) -> IntervalTree:
 | 
						|
        left = self._maybe_convert_i8(self.left)
 | 
						|
        right = self._maybe_convert_i8(self.right)
 | 
						|
        return IntervalTree(left, right, closed=self.closed)
 | 
						|
 | 
						|
    def __contains__(self, key: Any) -> bool:
 | 
						|
        """
 | 
						|
        return a boolean if this key is IN the index
 | 
						|
        We *only* accept an Interval
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        key : Interval
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        bool
 | 
						|
        """
 | 
						|
        hash(key)
 | 
						|
        if not isinstance(key, Interval):
 | 
						|
            if is_valid_na_for_dtype(key, self.dtype):
 | 
						|
                return self.hasnans
 | 
						|
            return False
 | 
						|
 | 
						|
        try:
 | 
						|
            self.get_loc(key)
 | 
						|
            return True
 | 
						|
        except KeyError:
 | 
						|
            return False
 | 
						|
 | 
						|
    @cache_readonly
 | 
						|
    def _multiindex(self) -> MultiIndex:
 | 
						|
        return MultiIndex.from_arrays([self.left, self.right], names=["left", "right"])
 | 
						|
 | 
						|
    def __reduce__(self):
 | 
						|
        d = {
 | 
						|
            "left": self.left,
 | 
						|
            "right": self.right,
 | 
						|
            "closed": self.closed,
 | 
						|
            "name": self.name,
 | 
						|
        }
 | 
						|
        return _new_IntervalIndex, (type(self), d), None
 | 
						|
 | 
						|
    @property
 | 
						|
    def inferred_type(self) -> str:
 | 
						|
        """Return a string of the type inferred from the values"""
 | 
						|
        return "interval"
 | 
						|
 | 
						|
    @Appender(Index.memory_usage.__doc__)
 | 
						|
    def memory_usage(self, deep: bool = False) -> int:
 | 
						|
        # we don't use an explicit engine
 | 
						|
        # so return the bytes here
 | 
						|
        return self.left.memory_usage(deep=deep) + self.right.memory_usage(deep=deep)
 | 
						|
 | 
						|
    # IntervalTree doesn't have a is_monotonic_decreasing, so have to override
 | 
						|
    #  the Index implementation
 | 
						|
    @cache_readonly
 | 
						|
    def is_monotonic_decreasing(self) -> bool:
 | 
						|
        """
 | 
						|
        Return True if the IntervalIndex is monotonic decreasing (only equal or
 | 
						|
        decreasing values), else False
 | 
						|
        """
 | 
						|
        return self[::-1].is_monotonic_increasing
 | 
						|
 | 
						|
    @cache_readonly
 | 
						|
    def is_unique(self) -> bool:
 | 
						|
        """
 | 
						|
        Return True if the IntervalIndex contains unique elements, else False.
 | 
						|
        """
 | 
						|
        left = self.left
 | 
						|
        right = self.right
 | 
						|
 | 
						|
        if self.isna().sum() > 1:
 | 
						|
            return False
 | 
						|
 | 
						|
        if left.is_unique or right.is_unique:
 | 
						|
            return True
 | 
						|
 | 
						|
        seen_pairs = set()
 | 
						|
        check_idx = np.where(left.duplicated(keep=False))[0]
 | 
						|
        for idx in check_idx:
 | 
						|
            pair = (left[idx], right[idx])
 | 
						|
            if pair in seen_pairs:
 | 
						|
                return False
 | 
						|
            seen_pairs.add(pair)
 | 
						|
 | 
						|
        return True
 | 
						|
 | 
						|
    @property
 | 
						|
    def is_overlapping(self) -> bool:
 | 
						|
        """
 | 
						|
        Return True if the IntervalIndex has overlapping intervals, else False.
 | 
						|
 | 
						|
        Two intervals overlap if they share a common point, including closed
 | 
						|
        endpoints. Intervals that only have an open endpoint in common do not
 | 
						|
        overlap.
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        bool
 | 
						|
            Boolean indicating if the IntervalIndex has overlapping intervals.
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        Interval.overlaps : Check whether two Interval objects overlap.
 | 
						|
        IntervalIndex.overlaps : Check an IntervalIndex elementwise for
 | 
						|
            overlaps.
 | 
						|
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        >>> index = pd.IntervalIndex.from_tuples([(0, 2), (1, 3), (4, 5)])
 | 
						|
        >>> index
 | 
						|
        IntervalIndex([(0, 2], (1, 3], (4, 5]],
 | 
						|
              dtype='interval[int64, right]')
 | 
						|
        >>> index.is_overlapping
 | 
						|
        True
 | 
						|
 | 
						|
        Intervals that share closed endpoints overlap:
 | 
						|
 | 
						|
        >>> index = pd.interval_range(0, 3, closed='both')
 | 
						|
        >>> index
 | 
						|
        IntervalIndex([[0, 1], [1, 2], [2, 3]],
 | 
						|
              dtype='interval[int64, both]')
 | 
						|
        >>> index.is_overlapping
 | 
						|
        True
 | 
						|
 | 
						|
        Intervals that only have an open endpoint in common do not overlap:
 | 
						|
 | 
						|
        >>> index = pd.interval_range(0, 3, closed='left')
 | 
						|
        >>> index
 | 
						|
        IntervalIndex([[0, 1), [1, 2), [2, 3)],
 | 
						|
              dtype='interval[int64, left]')
 | 
						|
        >>> index.is_overlapping
 | 
						|
        False
 | 
						|
        """
 | 
						|
        # GH 23309
 | 
						|
        return self._engine.is_overlapping
 | 
						|
 | 
						|
    def _needs_i8_conversion(self, key) -> bool:
 | 
						|
        """
 | 
						|
        Check if a given key needs i8 conversion. Conversion is necessary for
 | 
						|
        Timestamp, Timedelta, DatetimeIndex, and TimedeltaIndex keys. An
 | 
						|
        Interval-like requires conversion if its endpoints are one of the
 | 
						|
        aforementioned types.
 | 
						|
 | 
						|
        Assumes that any list-like data has already been cast to an Index.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        key : scalar or Index-like
 | 
						|
            The key that should be checked for i8 conversion
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        bool
 | 
						|
        """
 | 
						|
        if is_interval_dtype(key) or isinstance(key, Interval):
 | 
						|
            return self._needs_i8_conversion(key.left)
 | 
						|
 | 
						|
        i8_types = (Timestamp, Timedelta, DatetimeIndex, TimedeltaIndex)
 | 
						|
        return isinstance(key, i8_types)
 | 
						|
 | 
						|
    def _maybe_convert_i8(self, key):
 | 
						|
        """
 | 
						|
        Maybe convert a given key to its equivalent i8 value(s). Used as a
 | 
						|
        preprocessing step prior to IntervalTree queries (self._engine), which
 | 
						|
        expects numeric data.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        key : scalar or list-like
 | 
						|
            The key that should maybe be converted to i8.
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        scalar or list-like
 | 
						|
            The original key if no conversion occurred, int if converted scalar,
 | 
						|
            Int64Index if converted list-like.
 | 
						|
        """
 | 
						|
        original = key
 | 
						|
        if is_list_like(key):
 | 
						|
            key = ensure_index(key)
 | 
						|
 | 
						|
        if not self._needs_i8_conversion(key):
 | 
						|
            return original
 | 
						|
 | 
						|
        scalar = is_scalar(key)
 | 
						|
        if is_interval_dtype(key) or isinstance(key, Interval):
 | 
						|
            # convert left/right and reconstruct
 | 
						|
            left = self._maybe_convert_i8(key.left)
 | 
						|
            right = self._maybe_convert_i8(key.right)
 | 
						|
            constructor = Interval if scalar else IntervalIndex.from_arrays
 | 
						|
            return constructor(left, right, closed=self.closed)
 | 
						|
 | 
						|
        if scalar:
 | 
						|
            # Timestamp/Timedelta
 | 
						|
            key_dtype, key_i8 = infer_dtype_from_scalar(key, pandas_dtype=True)
 | 
						|
            if lib.is_period(key):
 | 
						|
                key_i8 = key.ordinal
 | 
						|
            elif isinstance(key_i8, Timestamp):
 | 
						|
                key_i8 = key_i8.value
 | 
						|
            elif isinstance(key_i8, (np.datetime64, np.timedelta64)):
 | 
						|
                key_i8 = key_i8.view("i8")
 | 
						|
        else:
 | 
						|
            # DatetimeIndex/TimedeltaIndex
 | 
						|
            key_dtype, key_i8 = key.dtype, Index(key.asi8)
 | 
						|
            if key.hasnans:
 | 
						|
                # convert NaT from its i8 value to np.nan so it's not viewed
 | 
						|
                # as a valid value, maybe causing errors (e.g. is_overlapping)
 | 
						|
                key_i8 = key_i8.where(~key._isnan)
 | 
						|
 | 
						|
        # ensure consistency with IntervalIndex subtype
 | 
						|
        # error: Item "ExtensionDtype"/"dtype[Any]" of "Union[dtype[Any],
 | 
						|
        # ExtensionDtype]" has no attribute "subtype"
 | 
						|
        subtype = self.dtype.subtype  # type: ignore[union-attr]
 | 
						|
 | 
						|
        if not is_dtype_equal(subtype, key_dtype):
 | 
						|
            raise ValueError(
 | 
						|
                f"Cannot index an IntervalIndex of subtype {subtype} with "
 | 
						|
                f"values of dtype {key_dtype}"
 | 
						|
            )
 | 
						|
 | 
						|
        return key_i8
 | 
						|
 | 
						|
    def _searchsorted_monotonic(self, label, side: str = "left"):
 | 
						|
        if not self.is_non_overlapping_monotonic:
 | 
						|
            raise KeyError(
 | 
						|
                "can only get slices from an IntervalIndex if bounds are "
 | 
						|
                "non-overlapping and all monotonic increasing or decreasing"
 | 
						|
            )
 | 
						|
 | 
						|
        if isinstance(label, (IntervalMixin, IntervalIndex)):
 | 
						|
            raise NotImplementedError("Interval objects are not currently supported")
 | 
						|
 | 
						|
        # GH 20921: "not is_monotonic_increasing" for the second condition
 | 
						|
        # instead of "is_monotonic_decreasing" to account for single element
 | 
						|
        # indexes being both increasing and decreasing
 | 
						|
        if (side == "left" and self.left.is_monotonic_increasing) or (
 | 
						|
            side == "right" and not self.left.is_monotonic_increasing
 | 
						|
        ):
 | 
						|
            sub_idx = self.right
 | 
						|
            if self.open_right:
 | 
						|
                label = _get_next_label(label)
 | 
						|
        else:
 | 
						|
            sub_idx = self.left
 | 
						|
            if self.open_left:
 | 
						|
                label = _get_prev_label(label)
 | 
						|
 | 
						|
        return sub_idx._searchsorted_monotonic(label, side)
 | 
						|
 | 
						|
    # --------------------------------------------------------------------
 | 
						|
    # Indexing Methods
 | 
						|
 | 
						|
    def get_loc(
 | 
						|
        self, key, method: str | None = None, tolerance=None
 | 
						|
    ) -> int | slice | np.ndarray:
 | 
						|
        """
 | 
						|
        Get integer location, slice or boolean mask for requested label.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        key : label
 | 
						|
        method : {None}, optional
 | 
						|
            * default: matches where the label is within an interval only.
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        int if unique index, slice if monotonic index, else mask
 | 
						|
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        >>> i1, i2 = pd.Interval(0, 1), pd.Interval(1, 2)
 | 
						|
        >>> index = pd.IntervalIndex([i1, i2])
 | 
						|
        >>> index.get_loc(1)
 | 
						|
        0
 | 
						|
 | 
						|
        You can also supply a point inside an interval.
 | 
						|
 | 
						|
        >>> index.get_loc(1.5)
 | 
						|
        1
 | 
						|
 | 
						|
        If a label is in several intervals, you get the locations of all the
 | 
						|
        relevant intervals.
 | 
						|
 | 
						|
        >>> i3 = pd.Interval(0, 2)
 | 
						|
        >>> overlapping_index = pd.IntervalIndex([i1, i2, i3])
 | 
						|
        >>> overlapping_index.get_loc(0.5)
 | 
						|
        array([ True, False,  True])
 | 
						|
 | 
						|
        Only exact matches will be returned if an interval is provided.
 | 
						|
 | 
						|
        >>> index.get_loc(pd.Interval(0, 1))
 | 
						|
        0
 | 
						|
        """
 | 
						|
        self._check_indexing_method(method)
 | 
						|
        self._check_indexing_error(key)
 | 
						|
 | 
						|
        if isinstance(key, Interval):
 | 
						|
            if self.closed != key.closed:
 | 
						|
                raise KeyError(key)
 | 
						|
            mask = (self.left == key.left) & (self.right == key.right)
 | 
						|
        elif is_valid_na_for_dtype(key, self.dtype):
 | 
						|
            mask = self.isna()
 | 
						|
        else:
 | 
						|
            # assume scalar
 | 
						|
            op_left = le if self.closed_left else lt
 | 
						|
            op_right = le if self.closed_right else lt
 | 
						|
            try:
 | 
						|
                mask = op_left(self.left, key) & op_right(key, self.right)
 | 
						|
            except TypeError as err:
 | 
						|
                # scalar is not comparable to II subtype --> invalid label
 | 
						|
                raise KeyError(key) from err
 | 
						|
 | 
						|
        matches = mask.sum()
 | 
						|
        if matches == 0:
 | 
						|
            raise KeyError(key)
 | 
						|
        elif matches == 1:
 | 
						|
            return mask.argmax()
 | 
						|
 | 
						|
        res = lib.maybe_booleans_to_slice(mask.view("u1"))
 | 
						|
        if isinstance(res, slice) and res.stop is None:
 | 
						|
            # TODO: DO this in maybe_booleans_to_slice?
 | 
						|
            res = slice(res.start, len(self), res.step)
 | 
						|
        return res
 | 
						|
 | 
						|
    def _get_indexer(
 | 
						|
        self,
 | 
						|
        target: Index,
 | 
						|
        method: str | None = None,
 | 
						|
        limit: int | None = None,
 | 
						|
        tolerance: Any | None = None,
 | 
						|
    ) -> npt.NDArray[np.intp]:
 | 
						|
 | 
						|
        if isinstance(target, IntervalIndex):
 | 
						|
            # We only get here with not self.is_overlapping
 | 
						|
            # -> at most one match per interval in target
 | 
						|
            # want exact matches -> need both left/right to match, so defer to
 | 
						|
            # left/right get_indexer, compare elementwise, equality -> match
 | 
						|
            indexer = self._get_indexer_unique_sides(target)
 | 
						|
 | 
						|
        elif not is_object_dtype(target.dtype):
 | 
						|
            # homogeneous scalar index: use IntervalTree
 | 
						|
            # we should always have self._should_partial_index(target) here
 | 
						|
            target = self._maybe_convert_i8(target)
 | 
						|
            indexer = self._engine.get_indexer(target.values)
 | 
						|
        else:
 | 
						|
            # heterogeneous scalar index: defer elementwise to get_loc
 | 
						|
            # we should always have self._should_partial_index(target) here
 | 
						|
            return self._get_indexer_pointwise(target)[0]
 | 
						|
 | 
						|
        return ensure_platform_int(indexer)
 | 
						|
 | 
						|
    @Appender(_index_shared_docs["get_indexer_non_unique"] % _index_doc_kwargs)
 | 
						|
    def get_indexer_non_unique(
 | 
						|
        self, target: Index
 | 
						|
    ) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
 | 
						|
        target = ensure_index(target)
 | 
						|
 | 
						|
        if not self._should_compare(target) and not self._should_partial_index(target):
 | 
						|
            # e.g. IntervalIndex with different closed or incompatible subtype
 | 
						|
            #  -> no matches
 | 
						|
            return self._get_indexer_non_comparable(target, None, unique=False)
 | 
						|
 | 
						|
        elif isinstance(target, IntervalIndex):
 | 
						|
            if self.left.is_unique and self.right.is_unique:
 | 
						|
                # fastpath available even if we don't have self._index_as_unique
 | 
						|
                indexer = self._get_indexer_unique_sides(target)
 | 
						|
                missing = (indexer == -1).nonzero()[0]
 | 
						|
            else:
 | 
						|
                return self._get_indexer_pointwise(target)
 | 
						|
 | 
						|
        elif is_object_dtype(target.dtype) or not self._should_partial_index(target):
 | 
						|
            # target might contain intervals: defer elementwise to get_loc
 | 
						|
            return self._get_indexer_pointwise(target)
 | 
						|
 | 
						|
        else:
 | 
						|
            # Note: this case behaves differently from other Index subclasses
 | 
						|
            #  because IntervalIndex does partial-int indexing
 | 
						|
            target = self._maybe_convert_i8(target)
 | 
						|
            indexer, missing = self._engine.get_indexer_non_unique(target.values)
 | 
						|
 | 
						|
        return ensure_platform_int(indexer), ensure_platform_int(missing)
 | 
						|
 | 
						|
    def _get_indexer_unique_sides(self, target: IntervalIndex) -> npt.NDArray[np.intp]:
 | 
						|
        """
 | 
						|
        _get_indexer specialized to the case where both of our sides are unique.
 | 
						|
        """
 | 
						|
        # Caller is responsible for checking
 | 
						|
        #  `self.left.is_unique and self.right.is_unique`
 | 
						|
 | 
						|
        left_indexer = self.left.get_indexer(target.left)
 | 
						|
        right_indexer = self.right.get_indexer(target.right)
 | 
						|
        indexer = np.where(left_indexer == right_indexer, left_indexer, -1)
 | 
						|
        return indexer
 | 
						|
 | 
						|
    def _get_indexer_pointwise(
 | 
						|
        self, target: Index
 | 
						|
    ) -> tuple[npt.NDArray[np.intp], npt.NDArray[np.intp]]:
 | 
						|
        """
 | 
						|
        pointwise implementation for get_indexer and get_indexer_non_unique.
 | 
						|
        """
 | 
						|
        indexer, missing = [], []
 | 
						|
        for i, key in enumerate(target):
 | 
						|
            try:
 | 
						|
                locs = self.get_loc(key)
 | 
						|
                if isinstance(locs, slice):
 | 
						|
                    # Only needed for get_indexer_non_unique
 | 
						|
                    locs = np.arange(locs.start, locs.stop, locs.step, dtype="intp")
 | 
						|
                elif lib.is_integer(locs):
 | 
						|
                    locs = np.array(locs, ndmin=1)
 | 
						|
                else:
 | 
						|
                    # otherwise we have ndarray[bool]
 | 
						|
                    locs = np.where(locs)[0]
 | 
						|
            except KeyError:
 | 
						|
                missing.append(i)
 | 
						|
                locs = np.array([-1])
 | 
						|
            except InvalidIndexError:
 | 
						|
                # i.e. non-scalar key e.g. a tuple.
 | 
						|
                # see test_append_different_columns_types_raises
 | 
						|
                missing.append(i)
 | 
						|
                locs = np.array([-1])
 | 
						|
 | 
						|
            indexer.append(locs)
 | 
						|
 | 
						|
        indexer = np.concatenate(indexer)
 | 
						|
        return ensure_platform_int(indexer), ensure_platform_int(missing)
 | 
						|
 | 
						|
    @cache_readonly
 | 
						|
    def _index_as_unique(self) -> bool:
 | 
						|
        return not self.is_overlapping and self._engine._na_count < 2
 | 
						|
 | 
						|
    _requires_unique_msg = (
 | 
						|
        "cannot handle overlapping indices; use IntervalIndex.get_indexer_non_unique"
 | 
						|
    )
 | 
						|
 | 
						|
    def _convert_slice_indexer(self, key: slice, kind: str):
 | 
						|
        if not (key.step is None or key.step == 1):
 | 
						|
            # GH#31658 if label-based, we require step == 1,
 | 
						|
            #  if positional, we disallow float start/stop
 | 
						|
            msg = "label-based slicing with step!=1 is not supported for IntervalIndex"
 | 
						|
            if kind == "loc":
 | 
						|
                raise ValueError(msg)
 | 
						|
            elif kind == "getitem":
 | 
						|
                if not is_valid_positional_slice(key):
 | 
						|
                    # i.e. this cannot be interpreted as a positional slice
 | 
						|
                    raise ValueError(msg)
 | 
						|
 | 
						|
        return super()._convert_slice_indexer(key, kind)
 | 
						|
 | 
						|
    @cache_readonly
 | 
						|
    def _should_fallback_to_positional(self) -> bool:
 | 
						|
        # integer lookups in Series.__getitem__ are unambiguously
 | 
						|
        #  positional in this case
 | 
						|
        # error: Item "ExtensionDtype"/"dtype[Any]" of "Union[dtype[Any],
 | 
						|
        # ExtensionDtype]" has no attribute "subtype"
 | 
						|
        return self.dtype.subtype.kind in ["m", "M"]  # type: ignore[union-attr]
 | 
						|
 | 
						|
    def _maybe_cast_slice_bound(self, label, side: str, kind=lib.no_default):
 | 
						|
        self._deprecated_arg(kind, "kind", "_maybe_cast_slice_bound")
 | 
						|
        return getattr(self, side)._maybe_cast_slice_bound(label, side)
 | 
						|
 | 
						|
    def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
 | 
						|
        if not isinstance(dtype, IntervalDtype):
 | 
						|
            return False
 | 
						|
        common_subtype = find_common_type([self.dtype, dtype])
 | 
						|
        return not is_object_dtype(common_subtype)
 | 
						|
 | 
						|
    # --------------------------------------------------------------------
 | 
						|
 | 
						|
    @cache_readonly
 | 
						|
    def left(self) -> Index:
 | 
						|
        return Index(self._data.left, copy=False)
 | 
						|
 | 
						|
    @cache_readonly
 | 
						|
    def right(self) -> Index:
 | 
						|
        return Index(self._data.right, copy=False)
 | 
						|
 | 
						|
    @cache_readonly
 | 
						|
    def mid(self) -> Index:
 | 
						|
        return Index(self._data.mid, copy=False)
 | 
						|
 | 
						|
    @property
 | 
						|
    def length(self) -> Index:
 | 
						|
        return Index(self._data.length, copy=False)
 | 
						|
 | 
						|
    # --------------------------------------------------------------------
 | 
						|
    # Rendering Methods
 | 
						|
    # __repr__ associated methods are based on MultiIndex
 | 
						|
 | 
						|
    def _format_with_header(self, header: list[str], na_rep: str) -> list[str]:
 | 
						|
        # matches base class except for whitespace padding
 | 
						|
        return header + list(self._format_native_types(na_rep=na_rep))
 | 
						|
 | 
						|
    def _format_native_types(self, *, na_rep="NaN", quoting=None, **kwargs):
 | 
						|
        # GH 28210: use base method but with different default na_rep
 | 
						|
        return super()._format_native_types(na_rep=na_rep, quoting=quoting, **kwargs)
 | 
						|
 | 
						|
    def _format_data(self, name=None) -> str:
 | 
						|
        # TODO: integrate with categorical and make generic
 | 
						|
        # name argument is unused here; just for compat with base / categorical
 | 
						|
        return self._data._format_data() + "," + self._format_space()
 | 
						|
 | 
						|
    # --------------------------------------------------------------------
 | 
						|
    # Set Operations
 | 
						|
 | 
						|
    def _intersection(self, other, sort):
 | 
						|
        """
 | 
						|
        intersection specialized to the case with matching dtypes.
 | 
						|
        """
 | 
						|
        # For IntervalIndex we also know other.closed == self.closed
 | 
						|
        if self.left.is_unique and self.right.is_unique:
 | 
						|
            taken = self._intersection_unique(other)
 | 
						|
        elif other.left.is_unique and other.right.is_unique and self.isna().sum() <= 1:
 | 
						|
            # Swap other/self if other is unique and self does not have
 | 
						|
            # multiple NaNs
 | 
						|
            taken = other._intersection_unique(self)
 | 
						|
        else:
 | 
						|
            # duplicates
 | 
						|
            taken = self._intersection_non_unique(other)
 | 
						|
 | 
						|
        if sort is None:
 | 
						|
            taken = taken.sort_values()
 | 
						|
 | 
						|
        return taken
 | 
						|
 | 
						|
    def _intersection_unique(self, other: IntervalIndex) -> IntervalIndex:
 | 
						|
        """
 | 
						|
        Used when the IntervalIndex does not have any common endpoint,
 | 
						|
        no matter left or right.
 | 
						|
        Return the intersection with another IntervalIndex.
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        other : IntervalIndex
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        IntervalIndex
 | 
						|
        """
 | 
						|
        # Note: this is much more performant than super()._intersection(other)
 | 
						|
        lindexer = self.left.get_indexer(other.left)
 | 
						|
        rindexer = self.right.get_indexer(other.right)
 | 
						|
 | 
						|
        match = (lindexer == rindexer) & (lindexer != -1)
 | 
						|
        indexer = lindexer.take(match.nonzero()[0])
 | 
						|
        indexer = unique(indexer)
 | 
						|
 | 
						|
        return self.take(indexer)
 | 
						|
 | 
						|
    def _intersection_non_unique(self, other: IntervalIndex) -> IntervalIndex:
 | 
						|
        """
 | 
						|
        Used when the IntervalIndex does have some common endpoints,
 | 
						|
        on either sides.
 | 
						|
        Return the intersection with another IntervalIndex.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        other : IntervalIndex
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        IntervalIndex
 | 
						|
        """
 | 
						|
        # Note: this is about 3.25x faster than super()._intersection(other)
 | 
						|
        #  in IntervalIndexMethod.time_intersection_both_duplicate(1000)
 | 
						|
        mask = np.zeros(len(self), dtype=bool)
 | 
						|
 | 
						|
        if self.hasnans and other.hasnans:
 | 
						|
            first_nan_loc = np.arange(len(self))[self.isna()][0]
 | 
						|
            mask[first_nan_loc] = True
 | 
						|
 | 
						|
        other_tups = set(zip(other.left, other.right))
 | 
						|
        for i, tup in enumerate(zip(self.left, self.right)):
 | 
						|
            if tup in other_tups:
 | 
						|
                mask[i] = True
 | 
						|
 | 
						|
        return self[mask]
 | 
						|
 | 
						|
    # --------------------------------------------------------------------
 | 
						|
 | 
						|
    @property
 | 
						|
    def _is_all_dates(self) -> bool:
 | 
						|
        """
 | 
						|
        This is False even when left/right contain datetime-like objects,
 | 
						|
        as the check is done on the Interval itself
 | 
						|
        """
 | 
						|
        return False
 | 
						|
 | 
						|
    def _get_engine_target(self) -> np.ndarray:
 | 
						|
        # Note: we _could_ use libjoin functions by either casting to object
 | 
						|
        #  dtype or constructing tuples (faster than constructing Intervals)
 | 
						|
        #  but the libjoin fastpaths are no longer fast in these cases.
 | 
						|
        raise NotImplementedError(
 | 
						|
            "IntervalIndex does not use libjoin fastpaths or pass values to "
 | 
						|
            "IndexEngine objects"
 | 
						|
        )
 | 
						|
 | 
						|
    def _from_join_target(self, result):
 | 
						|
        raise NotImplementedError("IntervalIndex does not use libjoin fastpaths")
 | 
						|
 | 
						|
    # TODO: arithmetic operations
 | 
						|
 | 
						|
 | 
						|
def _is_valid_endpoint(endpoint) -> bool:
 | 
						|
    """
 | 
						|
    Helper for interval_range to check if start/end are valid types.
 | 
						|
    """
 | 
						|
    return any(
 | 
						|
        [
 | 
						|
            is_number(endpoint),
 | 
						|
            isinstance(endpoint, Timestamp),
 | 
						|
            isinstance(endpoint, Timedelta),
 | 
						|
            endpoint is None,
 | 
						|
        ]
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
def _is_type_compatible(a, b) -> bool:
 | 
						|
    """
 | 
						|
    Helper for interval_range to check type compat of start/end/freq.
 | 
						|
    """
 | 
						|
    is_ts_compat = lambda x: isinstance(x, (Timestamp, BaseOffset))
 | 
						|
    is_td_compat = lambda x: isinstance(x, (Timedelta, BaseOffset))
 | 
						|
    return (
 | 
						|
        (is_number(a) and is_number(b))
 | 
						|
        or (is_ts_compat(a) and is_ts_compat(b))
 | 
						|
        or (is_td_compat(a) and is_td_compat(b))
 | 
						|
        or com.any_none(a, b)
 | 
						|
    )
 | 
						|
 | 
						|
 | 
						|
def interval_range(
 | 
						|
    start=None, end=None, periods=None, freq=None, name: Hashable = None, closed="right"
 | 
						|
) -> IntervalIndex:
 | 
						|
    """
 | 
						|
    Return a fixed frequency IntervalIndex.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    start : numeric or datetime-like, default None
 | 
						|
        Left bound for generating intervals.
 | 
						|
    end : numeric or datetime-like, default None
 | 
						|
        Right bound for generating intervals.
 | 
						|
    periods : int, default None
 | 
						|
        Number of periods to generate.
 | 
						|
    freq : numeric, str, or DateOffset, default None
 | 
						|
        The length of each interval. Must be consistent with the type of start
 | 
						|
        and end, e.g. 2 for numeric, or '5H' for datetime-like.  Default is 1
 | 
						|
        for numeric and 'D' for datetime-like.
 | 
						|
    name : str, default None
 | 
						|
        Name of the resulting IntervalIndex.
 | 
						|
    closed : {'left', 'right', 'both', 'neither'}, default 'right'
 | 
						|
        Whether the intervals are closed on the left-side, right-side, both
 | 
						|
        or neither.
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    IntervalIndex
 | 
						|
 | 
						|
    See Also
 | 
						|
    --------
 | 
						|
    IntervalIndex : An Index of intervals that are all closed on the same side.
 | 
						|
 | 
						|
    Notes
 | 
						|
    -----
 | 
						|
    Of the four parameters ``start``, ``end``, ``periods``, and ``freq``,
 | 
						|
    exactly three must be specified. If ``freq`` is omitted, the resulting
 | 
						|
    ``IntervalIndex`` will have ``periods`` linearly spaced elements between
 | 
						|
    ``start`` and ``end``, inclusively.
 | 
						|
 | 
						|
    To learn more about datetime-like frequency strings, please see `this link
 | 
						|
    <https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases>`__.
 | 
						|
 | 
						|
    Examples
 | 
						|
    --------
 | 
						|
    Numeric ``start`` and  ``end`` is supported.
 | 
						|
 | 
						|
    >>> pd.interval_range(start=0, end=5)
 | 
						|
    IntervalIndex([(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]],
 | 
						|
                  dtype='interval[int64, right]')
 | 
						|
 | 
						|
    Additionally, datetime-like input is also supported.
 | 
						|
 | 
						|
    >>> pd.interval_range(start=pd.Timestamp('2017-01-01'),
 | 
						|
    ...                   end=pd.Timestamp('2017-01-04'))
 | 
						|
    IntervalIndex([(2017-01-01, 2017-01-02], (2017-01-02, 2017-01-03],
 | 
						|
                   (2017-01-03, 2017-01-04]],
 | 
						|
                  dtype='interval[datetime64[ns], right]')
 | 
						|
 | 
						|
    The ``freq`` parameter specifies the frequency between the left and right.
 | 
						|
    endpoints of the individual intervals within the ``IntervalIndex``.  For
 | 
						|
    numeric ``start`` and ``end``, the frequency must also be numeric.
 | 
						|
 | 
						|
    >>> pd.interval_range(start=0, periods=4, freq=1.5)
 | 
						|
    IntervalIndex([(0.0, 1.5], (1.5, 3.0], (3.0, 4.5], (4.5, 6.0]],
 | 
						|
                  dtype='interval[float64, right]')
 | 
						|
 | 
						|
    Similarly, for datetime-like ``start`` and ``end``, the frequency must be
 | 
						|
    convertible to a DateOffset.
 | 
						|
 | 
						|
    >>> pd.interval_range(start=pd.Timestamp('2017-01-01'),
 | 
						|
    ...                   periods=3, freq='MS')
 | 
						|
    IntervalIndex([(2017-01-01, 2017-02-01], (2017-02-01, 2017-03-01],
 | 
						|
                   (2017-03-01, 2017-04-01]],
 | 
						|
                  dtype='interval[datetime64[ns], right]')
 | 
						|
 | 
						|
    Specify ``start``, ``end``, and ``periods``; the frequency is generated
 | 
						|
    automatically (linearly spaced).
 | 
						|
 | 
						|
    >>> pd.interval_range(start=0, end=6, periods=4)
 | 
						|
    IntervalIndex([(0.0, 1.5], (1.5, 3.0], (3.0, 4.5], (4.5, 6.0]],
 | 
						|
              dtype='interval[float64, right]')
 | 
						|
 | 
						|
    The ``closed`` parameter specifies which endpoints of the individual
 | 
						|
    intervals within the ``IntervalIndex`` are closed.
 | 
						|
 | 
						|
    >>> pd.interval_range(end=5, periods=4, closed='both')
 | 
						|
    IntervalIndex([[1, 2], [2, 3], [3, 4], [4, 5]],
 | 
						|
                  dtype='interval[int64, both]')
 | 
						|
    """
 | 
						|
    start = maybe_box_datetimelike(start)
 | 
						|
    end = maybe_box_datetimelike(end)
 | 
						|
    endpoint = start if start is not None else end
 | 
						|
 | 
						|
    if freq is None and com.any_none(periods, start, end):
 | 
						|
        freq = 1 if is_number(endpoint) else "D"
 | 
						|
 | 
						|
    if com.count_not_none(start, end, periods, freq) != 3:
 | 
						|
        raise ValueError(
 | 
						|
            "Of the four parameters: start, end, periods, and "
 | 
						|
            "freq, exactly three must be specified"
 | 
						|
        )
 | 
						|
 | 
						|
    if not _is_valid_endpoint(start):
 | 
						|
        raise ValueError(f"start must be numeric or datetime-like, got {start}")
 | 
						|
    elif not _is_valid_endpoint(end):
 | 
						|
        raise ValueError(f"end must be numeric or datetime-like, got {end}")
 | 
						|
 | 
						|
    if is_float(periods):
 | 
						|
        periods = int(periods)
 | 
						|
    elif not is_integer(periods) and periods is not None:
 | 
						|
        raise TypeError(f"periods must be a number, got {periods}")
 | 
						|
 | 
						|
    if freq is not None and not is_number(freq):
 | 
						|
        try:
 | 
						|
            freq = to_offset(freq)
 | 
						|
        except ValueError as err:
 | 
						|
            raise ValueError(
 | 
						|
                f"freq must be numeric or convertible to DateOffset, got {freq}"
 | 
						|
            ) from err
 | 
						|
 | 
						|
    # verify type compatibility
 | 
						|
    if not all(
 | 
						|
        [
 | 
						|
            _is_type_compatible(start, end),
 | 
						|
            _is_type_compatible(start, freq),
 | 
						|
            _is_type_compatible(end, freq),
 | 
						|
        ]
 | 
						|
    ):
 | 
						|
        raise TypeError("start, end, freq need to be type compatible")
 | 
						|
 | 
						|
    # +1 to convert interval count to breaks count (n breaks = n-1 intervals)
 | 
						|
    if periods is not None:
 | 
						|
        periods += 1
 | 
						|
 | 
						|
    breaks: np.ndarray | TimedeltaIndex | DatetimeIndex
 | 
						|
 | 
						|
    if is_number(endpoint):
 | 
						|
        # force consistency between start/end/freq (lower end if freq skips it)
 | 
						|
        if com.all_not_none(start, end, freq):
 | 
						|
            end -= (end - start) % freq
 | 
						|
 | 
						|
        # compute the period/start/end if unspecified (at most one)
 | 
						|
        if periods is None:
 | 
						|
            periods = int((end - start) // freq) + 1
 | 
						|
        elif start is None:
 | 
						|
            start = end - (periods - 1) * freq
 | 
						|
        elif end is None:
 | 
						|
            end = start + (periods - 1) * freq
 | 
						|
 | 
						|
        breaks = np.linspace(start, end, periods)
 | 
						|
        if all(is_integer(x) for x in com.not_none(start, end, freq)):
 | 
						|
            # np.linspace always produces float output
 | 
						|
 | 
						|
            # error: Incompatible types in assignment (expression has type
 | 
						|
            # "Union[ExtensionArray, ndarray]", variable has type "ndarray")
 | 
						|
            breaks = maybe_downcast_numeric(  # type: ignore[assignment]
 | 
						|
                breaks, np.dtype("int64")
 | 
						|
            )
 | 
						|
    else:
 | 
						|
        # delegate to the appropriate range function
 | 
						|
        if isinstance(endpoint, Timestamp):
 | 
						|
            breaks = date_range(start=start, end=end, periods=periods, freq=freq)
 | 
						|
        else:
 | 
						|
            breaks = timedelta_range(start=start, end=end, periods=periods, freq=freq)
 | 
						|
 | 
						|
    return IntervalIndex.from_breaks(breaks, name=name, closed=closed)
 |