diff --git a/asv_bench/benchmarks/reshape.py b/asv_bench/benchmarks/reshape.py index 89c627865049e..05e12630d7540 100644 --- a/asv_bench/benchmarks/reshape.py +++ b/asv_bench/benchmarks/reshape.py @@ -268,9 +268,7 @@ def setup(self, bins): self.datetime_series = pd.Series( np.random.randint(N, size=N), dtype="datetime64[ns]" ) - self.interval_bins = pd.IntervalIndex.from_breaks( - np.linspace(0, N, bins), "right" - ) + self.interval_bins = pd.IntervalIndex.from_breaks(np.linspace(0, N, bins)) def time_cut_int(self, bins): pd.cut(self.int_series, bins) diff --git a/doc/redirects.csv b/doc/redirects.csv index 90ddf6c4dc582..9b8a5a73dedff 100644 --- a/doc/redirects.csv +++ b/doc/redirects.csv @@ -741,11 +741,11 @@ generated/pandas.Index.values,../reference/api/pandas.Index.values generated/pandas.Index.view,../reference/api/pandas.Index.view generated/pandas.Index.where,../reference/api/pandas.Index.where generated/pandas.infer_freq,../reference/api/pandas.infer_freq -generated/pandas.Interval.inclusive,../reference/api/pandas.Interval.inclusive +generated/pandas.Interval.closed,../reference/api/pandas.Interval.closed generated/pandas.Interval.closed_left,../reference/api/pandas.Interval.closed_left generated/pandas.Interval.closed_right,../reference/api/pandas.Interval.closed_right generated/pandas.Interval,../reference/api/pandas.Interval -generated/pandas.IntervalIndex.inclusive,../reference/api/pandas.IntervalIndex.inclusive +generated/pandas.IntervalIndex.closed,../reference/api/pandas.IntervalIndex.closed generated/pandas.IntervalIndex.contains,../reference/api/pandas.IntervalIndex.contains generated/pandas.IntervalIndex.from_arrays,../reference/api/pandas.IntervalIndex.from_arrays generated/pandas.IntervalIndex.from_breaks,../reference/api/pandas.IntervalIndex.from_breaks @@ -761,7 +761,6 @@ generated/pandas.IntervalIndex.mid,../reference/api/pandas.IntervalIndex.mid generated/pandas.IntervalIndex.overlaps,../reference/api/pandas.IntervalIndex.overlaps generated/pandas.IntervalIndex.right,../reference/api/pandas.IntervalIndex.right generated/pandas.IntervalIndex.set_closed,../reference/api/pandas.IntervalIndex.set_closed -generated/pandas.IntervalIndex.set_inclusive,../reference/api/pandas.IntervalIndex.set_inclusive generated/pandas.IntervalIndex.to_tuples,../reference/api/pandas.IntervalIndex.to_tuples generated/pandas.IntervalIndex.values,../reference/api/pandas.IntervalIndex.values generated/pandas.Interval.left,../reference/api/pandas.Interval.left diff --git a/doc/source/reference/arrays.rst b/doc/source/reference/arrays.rst index cd0ce581519a8..1b8e0fdb856b5 100644 --- a/doc/source/reference/arrays.rst +++ b/doc/source/reference/arrays.rst @@ -303,7 +303,6 @@ Properties .. autosummary:: :toctree: api/ - Interval.inclusive Interval.closed Interval.closed_left Interval.closed_right @@ -341,7 +340,7 @@ A collection of intervals may be stored in an :class:`arrays.IntervalArray`. arrays.IntervalArray.left arrays.IntervalArray.right - arrays.IntervalArray.inclusive + arrays.IntervalArray.closed arrays.IntervalArray.mid arrays.IntervalArray.length arrays.IntervalArray.is_empty @@ -352,7 +351,6 @@ A collection of intervals may be stored in an :class:`arrays.IntervalArray`. arrays.IntervalArray.contains arrays.IntervalArray.overlaps arrays.IntervalArray.set_closed - arrays.IntervalArray.set_inclusive arrays.IntervalArray.to_tuples diff --git a/doc/source/reference/indexing.rst b/doc/source/reference/indexing.rst index 589a339a1ca60..ddfef14036ef3 100644 --- a/doc/source/reference/indexing.rst +++ b/doc/source/reference/indexing.rst @@ -242,7 +242,7 @@ IntervalIndex components IntervalIndex.left IntervalIndex.right IntervalIndex.mid - IntervalIndex.inclusive + IntervalIndex.closed IntervalIndex.length IntervalIndex.values IntervalIndex.is_empty @@ -251,7 +251,6 @@ IntervalIndex components IntervalIndex.get_loc IntervalIndex.get_indexer IntervalIndex.set_closed - IntervalIndex.set_inclusive IntervalIndex.contains IntervalIndex.overlaps IntervalIndex.to_tuples diff --git a/doc/source/user_guide/advanced.rst b/doc/source/user_guide/advanced.rst index aaff76261b3ad..b8df21ab5a5b4 100644 --- a/doc/source/user_guide/advanced.rst +++ b/doc/source/user_guide/advanced.rst @@ -1020,7 +1020,7 @@ Trying to select an ``Interval`` that is not exactly contained in the ``Interval In [7]: df.loc[pd.Interval(0.5, 2.5)] --------------------------------------------------------------------------- - KeyError: Interval(0.5, 2.5, inclusive='right') + KeyError: Interval(0.5, 2.5, closed='right') Selecting all ``Intervals`` that overlap a given ``Interval`` can be performed using the :meth:`~IntervalIndex.overlaps` method to create a boolean indexer. @@ -1082,14 +1082,14 @@ of :ref:`frequency aliases ` with datetime-like inter pd.interval_range(start=pd.Timedelta("0 days"), periods=3, freq="9H") -Additionally, the ``inclusive`` parameter can be used to specify which side(s) the intervals -are closed on. Intervals are closed on the both side by default. +Additionally, the ``closed`` parameter can be used to specify which side(s) the intervals +are closed on. Intervals are closed on the right side by default. .. ipython:: python - pd.interval_range(start=0, end=4, inclusive="both") + pd.interval_range(start=0, end=4, closed="both") - pd.interval_range(start=0, end=4, inclusive="neither") + pd.interval_range(start=0, end=4, closed="neither") Specifying ``start``, ``end``, and ``periods`` will generate a range of evenly spaced intervals from ``start`` to ``end`` inclusively, with ``periods`` number of elements diff --git a/doc/source/whatsnew/v0.20.0.rst b/doc/source/whatsnew/v0.20.0.rst index a23c977e94b65..faf4b1ac44d5b 100644 --- a/doc/source/whatsnew/v0.20.0.rst +++ b/doc/source/whatsnew/v0.20.0.rst @@ -448,7 +448,7 @@ Selecting via a specific interval: .. ipython:: python - df.loc[pd.Interval(1.5, 3.0, "right")] + df.loc[pd.Interval(1.5, 3.0)] Selecting via a scalar value that is contained *in* the intervals. diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 4f04d5a0ee69d..e4dd6fa091d80 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -584,18 +584,18 @@ this would previously return ``True`` for any ``Interval`` overlapping an ``Inte .. code-block:: python - In [4]: pd.Interval(1, 2, inclusive='neither') in ii + In [4]: pd.Interval(1, 2, closed='neither') in ii Out[4]: True - In [5]: pd.Interval(-10, 10, inclusive='both') in ii + In [5]: pd.Interval(-10, 10, closed='both') in ii Out[5]: True *New behavior*: .. ipython:: python - pd.Interval(1, 2, inclusive='neither') in ii - pd.Interval(-10, 10, inclusive='both') in ii + pd.Interval(1, 2, closed='neither') in ii + pd.Interval(-10, 10, closed='both') in ii The :meth:`~IntervalIndex.get_loc` method now only returns locations for exact matches to ``Interval`` queries, as opposed to the previous behavior of returning locations for overlapping matches. A ``KeyError`` will be raised if an exact match is not found. @@ -619,7 +619,7 @@ returning locations for overlapping matches. A ``KeyError`` will be raised if a In [7]: ii.get_loc(pd.Interval(2, 6)) --------------------------------------------------------------------------- - KeyError: Interval(2, 6, inclusive='right') + KeyError: Interval(2, 6, closed='right') Likewise, :meth:`~IntervalIndex.get_indexer` and :meth:`~IntervalIndex.get_indexer_non_unique` will also only return locations for exact matches to ``Interval`` queries, with ``-1`` denoting that an exact match was not found. @@ -680,11 +680,11 @@ Similarly, a ``KeyError`` will be raised for non-exact matches instead of return In [6]: s[pd.Interval(2, 3)] --------------------------------------------------------------------------- - KeyError: Interval(2, 3, inclusive='right') + KeyError: Interval(2, 3, closed='right') In [7]: s.loc[pd.Interval(2, 3)] --------------------------------------------------------------------------- - KeyError: Interval(2, 3, inclusive='right') + KeyError: Interval(2, 3, closed='right') The :meth:`~IntervalIndex.overlaps` method can be used to create a boolean indexer that replicates the previous behavior of returning overlapping matches. diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 48c808819d788..a7ed072fb0aac 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -823,17 +823,10 @@ Other Deprecations - Deprecated :attr:`Timedelta.freq` and :attr:`Timedelta.is_populated` (:issue:`46430`) - Deprecated :attr:`Timedelta.delta` (:issue:`46476`) - Deprecated passing arguments as positional in :meth:`DataFrame.any` and :meth:`Series.any` (:issue:`44802`) -- Deprecated the ``closed`` argument in :meth:`interval_range` in favor of ``inclusive`` argument; In a future version passing ``closed`` will raise (:issue:`40245`) - Deprecated the methods :meth:`DataFrame.mad`, :meth:`Series.mad`, and the corresponding groupby methods (:issue:`11787`) - Deprecated positional arguments to :meth:`Index.join` except for ``other``, use keyword-only arguments instead of positional arguments (:issue:`46518`) - Deprecated positional arguments to :meth:`StringMethods.rsplit` and :meth:`StringMethods.split` except for ``pat``, use keyword-only arguments instead of positional arguments (:issue:`47423`) - Deprecated indexing on a timezone-naive :class:`DatetimeIndex` using a string representing a timezone-aware datetime (:issue:`46903`, :issue:`36148`) -- Deprecated the ``closed`` argument in :class:`Interval` in favor of ``inclusive`` argument; In a future version passing ``closed`` will raise (:issue:`40245`) -- Deprecated the ``closed`` argument in :class:`IntervalIndex` in favor of ``inclusive`` argument; In a future version passing ``closed`` will raise (:issue:`40245`) -- Deprecated the ``closed`` argument in :class:`IntervalDtype` in favor of ``inclusive`` argument; In a future version passing ``closed`` will raise (:issue:`40245`) -- Deprecated the ``closed`` argument in :class:`.IntervalArray` in favor of ``inclusive`` argument; In a future version passing ``closed`` will raise (:issue:`40245`) -- Deprecated :meth:`.IntervalArray.set_closed` and :meth:`.IntervalIndex.set_closed` in favor of ``set_inclusive``; In a future version ``set_closed`` will get removed (:issue:`40245`) -- Deprecated the ``closed`` argument in :class:`ArrowInterval` in favor of ``inclusive`` argument; In a future version passing ``closed`` will raise (:issue:`40245`) - Deprecated allowing ``unit="M"`` or ``unit="Y"`` in :class:`Timestamp` constructor with a non-round float value (:issue:`47267`) - Deprecated the ``display.column_space`` global configuration option (:issue:`7576`) - Deprecated the argument ``na_sentinel`` in :func:`factorize`, :meth:`Index.factorize`, and :meth:`.ExtensionArray.factorize`; pass ``use_na_sentinel=True`` instead to use the sentinel ``-1`` for NaN values and ``use_na_sentinel=False`` instead of ``na_sentinel=None`` to encode NaN values (:issue:`46910`) diff --git a/pandas/_libs/interval.pyi b/pandas/_libs/interval.pyi index 9b73e9d0bf54a..4c36246e04d23 100644 --- a/pandas/_libs/interval.pyi +++ b/pandas/_libs/interval.pyi @@ -8,14 +8,13 @@ from typing import ( import numpy as np import numpy.typing as npt -from pandas._libs import lib from pandas._typing import ( - IntervalInclusiveType, + IntervalClosedType, Timedelta, Timestamp, ) -VALID_INCLUSIVE: frozenset[str] +VALID_CLOSED: frozenset[str] _OrderableScalarT = TypeVar("_OrderableScalarT", int, float) _OrderableTimesT = TypeVar("_OrderableTimesT", Timestamp, Timedelta) @@ -50,13 +49,7 @@ class IntervalMixin: def open_right(self) -> bool: ... @property def is_empty(self) -> bool: ... - def _check_inclusive_matches( - self, other: IntervalMixin, name: str = ... - ) -> None: ... - -def _warning_interval( - inclusive, closed -) -> tuple[IntervalInclusiveType, lib.NoDefault]: ... + def _check_closed_matches(self, other: IntervalMixin, name: str = ...) -> None: ... class Interval(IntervalMixin, Generic[_OrderableT]): @property @@ -64,17 +57,14 @@ class Interval(IntervalMixin, Generic[_OrderableT]): @property def right(self: Interval[_OrderableT]) -> _OrderableT: ... @property - def inclusive(self) -> IntervalInclusiveType: ... - @property - def closed(self) -> IntervalInclusiveType: ... + def closed(self) -> IntervalClosedType: ... mid: _MidDescriptor length: _LengthDescriptor def __init__( self, left: _OrderableT, right: _OrderableT, - inclusive: IntervalInclusiveType = ..., - closed: IntervalInclusiveType = ..., + closed: IntervalClosedType = ..., ) -> None: ... def __hash__(self) -> int: ... @overload @@ -157,15 +147,15 @@ class Interval(IntervalMixin, Generic[_OrderableT]): def overlaps(self: Interval[_OrderableT], other: Interval[_OrderableT]) -> bool: ... def intervals_to_interval_bounds( - intervals: np.ndarray, validate_inclusive: bool = ... -) -> tuple[np.ndarray, np.ndarray, IntervalInclusiveType]: ... + intervals: np.ndarray, validate_closed: bool = ... +) -> tuple[np.ndarray, np.ndarray, str]: ... class IntervalTree(IntervalMixin): def __init__( self, left: np.ndarray, right: np.ndarray, - inclusive: IntervalInclusiveType = ..., + closed: IntervalClosedType = ..., leaf_size: int = ..., ) -> None: ... @property diff --git a/pandas/_libs/interval.pyx b/pandas/_libs/interval.pyx index 2982110ea35cc..67c92a0f5df23 100644 --- a/pandas/_libs/interval.pyx +++ b/pandas/_libs/interval.pyx @@ -10,8 +10,6 @@ from cpython.datetime cimport ( import_datetime, ) -from pandas.util._exceptions import find_stack_level - import_datetime() cimport cython @@ -43,9 +41,6 @@ from numpy cimport ( cnp.import_array() -import warnings - -from pandas._libs import lib from pandas._libs cimport util from pandas._libs.hashtable cimport Int64Vector @@ -58,7 +53,7 @@ from pandas._libs.tslibs.util cimport ( is_timedelta64_object, ) -VALID_INCLUSIVE = frozenset(['both', 'neither', 'left', 'right']) +VALID_CLOSED = frozenset(['left', 'right', 'both', 'neither']) cdef class IntervalMixin: @@ -75,7 +70,7 @@ cdef class IntervalMixin: bool True if the Interval is closed on the left-side. """ - return self.inclusive in ('left', 'both') + return self.closed in ('left', 'both') @property def closed_right(self): @@ -87,9 +82,9 @@ cdef class IntervalMixin: Returns ------- bool - True if the Interval is closed on the right-side. + True if the Interval is closed on the left-side. """ - return self.inclusive in ('right', 'both') + return self.closed in ('right', 'both') @property def open_left(self): @@ -115,7 +110,7 @@ cdef class IntervalMixin: Returns ------- bool - True if the Interval is not closed on the right-side. + True if the Interval is not closed on the left-side. """ return not self.closed_right @@ -156,43 +151,43 @@ cdef class IntervalMixin: -------- An :class:`Interval` that contains points is not empty: - >>> pd.Interval(0, 1, inclusive='right').is_empty + >>> pd.Interval(0, 1, closed='right').is_empty False An ``Interval`` that does not contain any points is empty: - >>> pd.Interval(0, 0, inclusive='right').is_empty + >>> pd.Interval(0, 0, closed='right').is_empty True - >>> pd.Interval(0, 0, inclusive='left').is_empty + >>> pd.Interval(0, 0, closed='left').is_empty True - >>> pd.Interval(0, 0, inclusive='neither').is_empty + >>> pd.Interval(0, 0, closed='neither').is_empty True An ``Interval`` that contains a single point is not empty: - >>> pd.Interval(0, 0, inclusive='both').is_empty + >>> pd.Interval(0, 0, closed='both').is_empty False An :class:`~arrays.IntervalArray` or :class:`IntervalIndex` returns a boolean ``ndarray`` positionally indicating if an ``Interval`` is empty: - >>> ivs = [pd.Interval(0, 0, inclusive='neither'), - ... pd.Interval(1, 2, inclusive='neither')] + >>> ivs = [pd.Interval(0, 0, closed='neither'), + ... pd.Interval(1, 2, closed='neither')] >>> pd.arrays.IntervalArray(ivs).is_empty array([ True, False]) Missing values are not considered empty: - >>> ivs = [pd.Interval(0, 0, inclusive='neither'), np.nan] + >>> ivs = [pd.Interval(0, 0, closed='neither'), np.nan] >>> pd.IntervalIndex(ivs).is_empty array([ True, False]) """ - return (self.right == self.left) & (self.inclusive != 'both') + return (self.right == self.left) & (self.closed != 'both') - def _check_inclusive_matches(self, other, name='other'): + def _check_closed_matches(self, other, name='other'): """ - Check if the inclusive attribute of `other` matches. + Check if the closed attribute of `other` matches. Note that 'left' and 'right' are considered different from 'both'. @@ -205,44 +200,18 @@ cdef class IntervalMixin: Raises ------ ValueError - When `other` is not inclusive exactly the same as self. + When `other` is not closed exactly the same as self. """ - if self.inclusive != other.inclusive: - raise ValueError(f"'{name}.inclusive' is {repr(other.inclusive)}, " - f"expected {repr(self.inclusive)}.") + if self.closed != other.closed: + raise ValueError(f"'{name}.closed' is {repr(other.closed)}, " + f"expected {repr(self.closed)}.") cdef bint _interval_like(other): return (hasattr(other, 'left') and hasattr(other, 'right') - and hasattr(other, 'inclusive')) + and hasattr(other, 'closed')) -def _warning_interval(inclusive: str | None = None, closed: None | lib.NoDefault = lib.no_default): - """ - warning in interval class for variable inclusive and closed - """ - if inclusive is not None and closed != lib.no_default: - raise ValueError( - "Deprecated argument `closed` cannot be passed " - "if argument `inclusive` is not None" - ) - elif closed != lib.no_default: - warnings.warn( - "Argument `closed` is deprecated in favor of `inclusive`.", - FutureWarning, - stacklevel=find_stack_level(inspect.currentframe()), - ) - if closed is None: - inclusive = "right" - elif closed in ("both", "neither", "left", "right"): - inclusive = closed - else: - raise ValueError( - "Argument `closed` has to be either" - "'both', 'neither', 'left' or 'right'" - ) - - return inclusive, closed cdef class Interval(IntervalMixin): """ @@ -258,17 +227,9 @@ cdef class Interval(IntervalMixin): Whether the interval is closed on the left-side, right-side, both or neither. See the Notes for more detailed explanation. - .. deprecated:: 1.5.0 - - inclusive : {'both', 'neither', 'left', 'right'}, default 'both' - Whether the interval is inclusive on the left-side, right-side, both or - neither. See the Notes for more detailed explanation. - - .. versionadded:: 1.5.0 - See Also -------- - IntervalIndex : An Index of Interval objects that are all inclusive on the + IntervalIndex : An Index of Interval objects that are all closed on the same side. cut : Convert continuous data into discrete bins (Categorical of Interval objects). @@ -281,32 +242,32 @@ cdef class Interval(IntervalMixin): The parameters `left` and `right` must be from the same type, you must be able to compare them and they must satisfy ``left <= right``. - A inclusive interval (in mathematics denoted by square brackets) contains - its endpoints, i.e. the inclusive interval ``[0, 5]`` is characterized by the - conditions ``0 <= x <= 5``. This is what ``inclusive='both'`` stands for. + A closed interval (in mathematics denoted by square brackets) contains + its endpoints, i.e. the closed interval ``[0, 5]`` is characterized by the + conditions ``0 <= x <= 5``. This is what ``closed='both'`` stands for. An open interval (in mathematics denoted by parentheses) does not contain its endpoints, i.e. the open interval ``(0, 5)`` is characterized by the - conditions ``0 < x < 5``. This is what ``inclusive='neither'`` stands for. - Intervals can also be half-open or half-inclusive, i.e. ``[0, 5)`` is - described by ``0 <= x < 5`` (``inclusive='left'``) and ``(0, 5]`` is - described by ``0 < x <= 5`` (``inclusive='right'``). + conditions ``0 < x < 5``. This is what ``closed='neither'`` stands for. + Intervals can also be half-open or half-closed, i.e. ``[0, 5)`` is + described by ``0 <= x < 5`` (``closed='left'``) and ``(0, 5]`` is + described by ``0 < x <= 5`` (``closed='right'``). Examples -------- It is possible to build Intervals of different types, like numeric ones: - >>> iv = pd.Interval(left=0, right=5, inclusive='right') + >>> iv = pd.Interval(left=0, right=5) >>> iv - Interval(0, 5, inclusive='right') + Interval(0, 5, closed='right') You can check if an element belongs to it, or if it contains another interval: >>> 2.5 in iv True - >>> pd.Interval(left=2, right=5, inclusive='both') in iv + >>> pd.Interval(left=2, right=5, closed='both') in iv True - You can test the bounds (``inclusive='right'``, so ``0 < x <= 5``): + You can test the bounds (``closed='right'``, so ``0 < x <= 5``): >>> 0 in iv False @@ -326,16 +287,16 @@ cdef class Interval(IntervalMixin): >>> shifted_iv = iv + 3 >>> shifted_iv - Interval(3, 8, inclusive='right') + Interval(3, 8, closed='right') >>> extended_iv = iv * 10.0 >>> extended_iv - Interval(0.0, 50.0, inclusive='right') + Interval(0.0, 50.0, closed='right') To create a time interval you can use Timestamps as the bounds >>> year_2017 = pd.Interval(pd.Timestamp('2017-01-01 00:00:00'), ... pd.Timestamp('2018-01-01 00:00:00'), - ... inclusive='left') + ... closed='left') >>> pd.Timestamp('2017-01-01 00:00') in year_2017 True >>> year_2017.length @@ -354,27 +315,22 @@ cdef class Interval(IntervalMixin): Right bound for the interval. """ - cdef readonly str inclusive + cdef readonly str closed """ String describing the inclusive side the intervals. Either ``left``, ``right``, ``both`` or ``neither``. """ - def __init__(self, left, right, inclusive: str | None = None, closed: None | lib.NoDefault = lib.no_default): + def __init__(self, left, right, str closed='right'): # note: it is faster to just do these checks than to use a special # constructor (__cinit__/__new__) to avoid them self._validate_endpoint(left) self._validate_endpoint(right) - inclusive, closed = _warning_interval(inclusive, closed) - - if inclusive is None: - inclusive = "right" - - if inclusive not in VALID_INCLUSIVE: - raise ValueError(f"invalid option for 'inclusive': {inclusive}") + if closed not in VALID_CLOSED: + raise ValueError(f"invalid option for 'closed': {closed}") if not left <= right: raise ValueError("left side of interval must be <= right side") if (isinstance(left, _Timestamp) and @@ -384,23 +340,7 @@ cdef class Interval(IntervalMixin): f"{repr(left.tzinfo)}' and {repr(right.tzinfo)}") self.left = left self.right = right - self.inclusive = inclusive - - @property - def closed(self): - """ - String describing the inclusive side the intervals. - - .. deprecated:: 1.5.0 - - Either ``left``, ``right``, ``both`` or ``neither``. - """ - warnings.warn( - "Attribute `closed` is deprecated in favor of `inclusive`.", - FutureWarning, - stacklevel=find_stack_level(inspect.currentframe()), - ) - return self.inclusive + self.closed = closed def _validate_endpoint(self, endpoint): # GH 23013 @@ -410,12 +350,12 @@ cdef class Interval(IntervalMixin): "are allowed when constructing an Interval.") def __hash__(self): - return hash((self.left, self.right, self.inclusive)) + return hash((self.left, self.right, self.closed)) def __contains__(self, key) -> bool: if _interval_like(key): - key_closed_left = key.inclusive in ('left', 'both') - key_closed_right = key.inclusive in ('right', 'both') + key_closed_left = key.closed in ('left', 'both') + key_closed_right = key.closed in ('right', 'both') if self.open_left and key_closed_left: left_contained = self.left < key.left else: @@ -430,8 +370,8 @@ cdef class Interval(IntervalMixin): def __richcmp__(self, other, op: int): if isinstance(other, Interval): - self_tuple = (self.left, self.right, self.inclusive) - other_tuple = (other.left, other.right, other.inclusive) + self_tuple = (self.left, self.right, self.closed) + other_tuple = (other.left, other.right, other.closed) return PyObject_RichCompare(self_tuple, other_tuple, op) elif util.is_array(other): return np.array( @@ -442,7 +382,7 @@ cdef class Interval(IntervalMixin): return NotImplemented def __reduce__(self): - args = (self.left, self.right, self.inclusive) + args = (self.left, self.right, self.closed) return (type(self), args) def _repr_base(self): @@ -460,7 +400,7 @@ cdef class Interval(IntervalMixin): left, right = self._repr_base() name = type(self).__name__ - repr_str = f'{name}({repr(left)}, {repr(right)}, inclusive={repr(self.inclusive)})' + repr_str = f'{name}({repr(left)}, {repr(right)}, closed={repr(self.closed)})' return repr_str def __str__(self) -> str: @@ -476,7 +416,7 @@ cdef class Interval(IntervalMixin): or PyDelta_Check(y) or is_timedelta64_object(y) ): - return Interval(self.left + y, self.right + y, inclusive=self.inclusive) + return Interval(self.left + y, self.right + y, closed=self.closed) elif ( # __radd__ pattern # TODO(cython3): remove this @@ -487,7 +427,7 @@ cdef class Interval(IntervalMixin): or is_timedelta64_object(self) ) ): - return Interval(y.left + self, y.right + self, inclusive=y.inclusive) + return Interval(y.left + self, y.right + self, closed=y.closed) return NotImplemented def __radd__(self, other): @@ -496,7 +436,7 @@ cdef class Interval(IntervalMixin): or PyDelta_Check(other) or is_timedelta64_object(other) ): - return Interval(self.left + other, self.right + other, inclusive=self.inclusive) + return Interval(self.left + other, self.right + other, closed=self.closed) return NotImplemented def __sub__(self, y): @@ -505,40 +445,39 @@ cdef class Interval(IntervalMixin): or PyDelta_Check(y) or is_timedelta64_object(y) ): - return Interval(self.left - y, self.right - y, inclusive=self.inclusive) + return Interval(self.left - y, self.right - y, closed=self.closed) return NotImplemented def __mul__(self, y): if isinstance(y, numbers.Number): - return Interval(self.left * y, self.right * y, inclusive=self.inclusive) + return Interval(self.left * y, self.right * y, closed=self.closed) elif isinstance(y, Interval) and isinstance(self, numbers.Number): # __radd__ semantics # TODO(cython3): remove this - return Interval(y.left * self, y.right * self, inclusive=y.inclusive) - + return Interval(y.left * self, y.right * self, closed=y.closed) return NotImplemented def __rmul__(self, other): if isinstance(other, numbers.Number): - return Interval(self.left * other, self.right * other, inclusive=self.inclusive) + return Interval(self.left * other, self.right * other, closed=self.closed) return NotImplemented def __truediv__(self, y): if isinstance(y, numbers.Number): - return Interval(self.left / y, self.right / y, inclusive=self.inclusive) + return Interval(self.left / y, self.right / y, closed=self.closed) return NotImplemented def __floordiv__(self, y): if isinstance(y, numbers.Number): return Interval( - self.left // y, self.right // y, inclusive=self.inclusive) + self.left // y, self.right // y, closed=self.closed) return NotImplemented def overlaps(self, other): """ Check whether two Interval objects overlap. - Two intervals overlap if they share a common point, including inclusive + Two intervals overlap if they share a common point, including closed endpoints. Intervals that only have an open endpoint in common do not overlap. @@ -567,16 +506,16 @@ cdef class Interval(IntervalMixin): >>> i1.overlaps(i3) False - Intervals that share inclusive endpoints overlap: + Intervals that share closed endpoints overlap: - >>> i4 = pd.Interval(0, 1, inclusive='both') - >>> i5 = pd.Interval(1, 2, inclusive='both') + >>> i4 = pd.Interval(0, 1, closed='both') + >>> i5 = pd.Interval(1, 2, closed='both') >>> i4.overlaps(i5) True Intervals that only have an open endpoint in common do not overlap: - >>> i6 = pd.Interval(1, 2, inclusive='neither') + >>> i6 = pd.Interval(1, 2, closed='neither') >>> i4.overlaps(i6) False """ @@ -584,7 +523,7 @@ cdef class Interval(IntervalMixin): raise TypeError("`other` must be an Interval, " f"got {type(other).__name__}") - # equality is okay if both endpoints are inclusive (overlap at a point) + # equality is okay if both endpoints are closed (overlap at a point) op1 = le if (self.closed_left and other.closed_right) else lt op2 = le if (other.closed_left and self.closed_right) else lt @@ -596,29 +535,29 @@ cdef class Interval(IntervalMixin): @cython.wraparound(False) @cython.boundscheck(False) -def intervals_to_interval_bounds(ndarray intervals, bint validate_inclusive=True): +def intervals_to_interval_bounds(ndarray intervals, bint validate_closed=True): """ Parameters ---------- intervals : ndarray Object array of Intervals / nulls. - validate_inclusive: bool, default True - Boolean indicating if all intervals must be inclusive on the same side. - Mismatching inclusive will raise if True, else return None for inclusive. + validate_closed: bool, default True + Boolean indicating if all intervals must be closed on the same side. + Mismatching closed will raise if True, else return None for closed. Returns ------- tuple of left : ndarray right : ndarray - inclusive: str + closed: str """ cdef: - object inclusive = None, interval + object closed = None, interval Py_ssize_t i, n = len(intervals) ndarray left, right - bint seen_inclusive = False + bint seen_closed = False left = np.empty(n, dtype=intervals.dtype) right = np.empty(n, dtype=intervals.dtype) @@ -636,15 +575,15 @@ def intervals_to_interval_bounds(ndarray intervals, bint validate_inclusive=True left[i] = interval.left right[i] = interval.right - if not seen_inclusive: - seen_inclusive = True - inclusive = interval.inclusive - elif inclusive != interval.inclusive: - inclusive = None - if validate_inclusive: - raise ValueError("intervals must all be inclusive on the same side") - - return left, right, inclusive + if not seen_closed: + seen_closed = True + closed = interval.closed + elif closed != interval.closed: + closed = None + if validate_closed: + raise ValueError("intervals must all be closed on the same side") + + return left, right, closed include "intervaltree.pxi" diff --git a/pandas/_libs/intervaltree.pxi.in b/pandas/_libs/intervaltree.pxi.in index 8bf1a53d56dfb..e7a310513d2fa 100644 --- a/pandas/_libs/intervaltree.pxi.in +++ b/pandas/_libs/intervaltree.pxi.in @@ -3,9 +3,7 @@ Template for intervaltree WARNING: DO NOT edit .pxi FILE directly, .pxi is generated from .pxi.in """ -import warnings -from pandas._libs import lib from pandas._libs.algos import is_monotonic ctypedef fused int_scalar_t: @@ -36,34 +34,27 @@ cdef class IntervalTree(IntervalMixin): ndarray left, right IntervalNode root object dtype - str inclusive + str closed object _is_overlapping, _left_sorter, _right_sorter Py_ssize_t _na_count - def __init__(self, left, right, inclusive: str | None = None, leaf_size=100): + def __init__(self, left, right, closed='right', leaf_size=100): """ Parameters ---------- left, right : np.ndarray[ndim=1] Left and right bounds for each interval. Assumed to contain no NaNs. - - inclusive : {"both", "neither", "left", "right"}, optional + closed : {'left', 'right', 'both', 'neither'}, optional Whether the intervals are closed on the left-side, right-side, both or neither. Defaults to 'right'. - - .. versionadded:: 1.5.0 - leaf_size : int, optional Parameter that controls when the tree switches from creating nodes to brute-force search. Tune this parameter to optimize query performance. """ - if inclusive is None: - inclusive = "right" - - if inclusive not in ['left', 'right', 'both', 'neither']: - raise ValueError("invalid option for 'inclusive': %s" % inclusive) + if closed not in ['left', 'right', 'both', 'neither']: + raise ValueError("invalid option for 'closed': %s" % closed) left = np.asarray(left) right = np.asarray(right) @@ -73,7 +64,7 @@ cdef class IntervalTree(IntervalMixin): indices = np.arange(len(left), dtype='int64') - self.inclusive = inclusive + self.closed = closed # GH 23352: ensure no nan in nodes mask = ~np.isnan(self.left) @@ -82,7 +73,7 @@ cdef class IntervalTree(IntervalMixin): self.right = self.right[mask] indices = indices[mask] - node_cls = NODE_CLASSES[str(self.dtype), inclusive] + node_cls = NODE_CLASSES[str(self.dtype), closed] self.root = node_cls(self.left, self.right, indices, leaf_size) @property @@ -110,8 +101,8 @@ cdef class IntervalTree(IntervalMixin): if self._is_overlapping is not None: return self._is_overlapping - # <= when inclusive on both sides since endpoints can overlap - op = le if self.inclusive == 'both' else lt + # <= when both sides closed since endpoints can overlap + op = le if self.closed == 'both' else lt # overlap if start of current interval < end of previous interval # (current and previous in terms of sorted order by left/start side) @@ -189,9 +180,9 @@ cdef class IntervalTree(IntervalMixin): missing.to_array().astype('intp')) def __repr__(self) -> str: - return (''.format( - dtype=self.dtype, inclusive=self.inclusive, + dtype=self.dtype, closed=self.closed, n_elements=self.root.n_elements)) # compat with IndexEngine interface @@ -254,13 +245,13 @@ cdef class IntervalNode: # we need specialized nodes and leaves to optimize for different dtype and -# inclusive values +# closed values {{py: nodes = [] for dtype in ['float64', 'int64', 'uint64']: - for inclusive, cmp_left, cmp_right in [ + for closed, cmp_left, cmp_right in [ ('left', '<=', '<'), ('right', '<', '<='), ('both', '<=', '<='), @@ -274,7 +265,7 @@ for dtype in ['float64', 'int64', 'uint64']: elif dtype.startswith('float'): fused_prefix = '' nodes.append((dtype, dtype.title(), - inclusive, inclusive.title(), + closed, closed.title(), cmp_left, cmp_right, cmp_left_converse, diff --git a/pandas/_libs/lib.pyx b/pandas/_libs/lib.pyx index 8e4b23f32f48c..65677bbdb0ea9 100644 --- a/pandas/_libs/lib.pyx +++ b/pandas/_libs/lib.pyx @@ -2164,7 +2164,7 @@ cpdef bint is_interval_array(ndarray values): """ cdef: Py_ssize_t i, n = len(values) - str inclusive = None + str closed = None bint numeric = False bint dt64 = False bint td64 = False @@ -2177,15 +2177,15 @@ cpdef bint is_interval_array(ndarray values): val = values[i] if is_interval(val): - if inclusive is None: - inclusive = val.inclusive + if closed is None: + closed = val.closed numeric = ( util.is_float_object(val.left) or util.is_integer_object(val.left) ) td64 = is_timedelta(val.left) dt64 = PyDateTime_Check(val.left) - elif val.inclusive != inclusive: + elif val.closed != closed: # mismatched closedness return False elif numeric: @@ -2208,7 +2208,7 @@ cpdef bint is_interval_array(ndarray values): else: return False - if inclusive is None: + if closed is None: # we saw all-NAs, no actual Intervals return False return True diff --git a/pandas/_testing/asserters.py b/pandas/_testing/asserters.py index 5e0af3c0bc07d..945639ef4b00a 100644 --- a/pandas/_testing/asserters.py +++ b/pandas/_testing/asserters.py @@ -607,7 +607,7 @@ def assert_interval_array_equal( assert_equal(left._left, right._left, obj=f"{obj}.left", **kwargs) assert_equal(left._right, right._right, obj=f"{obj}.left", **kwargs) - assert_attr_equal("inclusive", left, right, obj=obj) + assert_attr_equal("closed", left, right, obj=obj) def assert_period_array_equal(left, right, obj="PeriodArray") -> None: diff --git a/pandas/_typing.py b/pandas/_typing.py index 88d826ec454b2..08096569c61a7 100644 --- a/pandas/_typing.py +++ b/pandas/_typing.py @@ -313,7 +313,7 @@ def closed(self) -> bool: # Interval closed type IntervalLeftRight = Literal["left", "right"] -IntervalInclusiveType = Union[IntervalLeftRight, Literal["both", "neither"]] +IntervalClosedType = Union[IntervalLeftRight, Literal["both", "neither"]] # datetime and NaTType DatetimeNaTType = Union[datetime, "NaTType"] diff --git a/pandas/conftest.py b/pandas/conftest.py index 54c24b4c0b58a..5f7b6d509c233 100644 --- a/pandas/conftest.py +++ b/pandas/conftest.py @@ -620,7 +620,7 @@ def _create_mi_with_dt64tz_level(): "bool-object": tm.makeBoolIndex(10).astype(object), "bool-dtype": Index(np.random.randn(10) < 0), "categorical": tm.makeCategoricalIndex(100), - "interval": tm.makeIntervalIndex(100, inclusive="right"), + "interval": tm.makeIntervalIndex(100), "empty": Index([]), "tuples": MultiIndex.from_tuples(zip(["foo", "bar", "baz"], [1, 2, 3])), "mi-with-dt64tz-level": _create_mi_with_dt64tz_level(), @@ -933,14 +933,8 @@ def rand_series_with_duplicate_datetimeindex() -> Series: # ---------------------------------------------------------------- @pytest.fixture( params=[ - ( - Interval(left=0, right=5, inclusive="right"), - IntervalDtype("int64", inclusive="right"), - ), - ( - Interval(left=0.1, right=0.5, inclusive="right"), - IntervalDtype("float64", inclusive="right"), - ), + (Interval(left=0, right=5), IntervalDtype("int64", "right")), + (Interval(left=0.1, right=0.5), IntervalDtype("float64", "right")), (Period("2012-01", freq="M"), "period[M]"), (Period("2012-02-01", freq="D"), "period[D]"), ( diff --git a/pandas/core/arrays/arrow/extension_types.py b/pandas/core/arrays/arrow/extension_types.py index a2b3c6d4da080..c9badb2bd305b 100644 --- a/pandas/core/arrays/arrow/extension_types.py +++ b/pandas/core/arrays/arrow/extension_types.py @@ -1,15 +1,12 @@ from __future__ import annotations import json -import warnings import pyarrow -from pandas._typing import IntervalInclusiveType -from pandas.util._decorators import deprecate_kwarg -from pandas.util._exceptions import find_stack_level +from pandas._typing import IntervalClosedType -from pandas.core.arrays.interval import VALID_INCLUSIVE +from pandas.core.arrays.interval import VALID_CLOSED class ArrowPeriodType(pyarrow.ExtensionType): @@ -53,12 +50,11 @@ def to_pandas_dtype(self): class ArrowIntervalType(pyarrow.ExtensionType): - @deprecate_kwarg(old_arg_name="closed", new_arg_name="inclusive") - def __init__(self, subtype, inclusive: IntervalInclusiveType) -> None: + def __init__(self, subtype, closed: IntervalClosedType) -> None: # attributes need to be set first before calling # super init (as that calls serialize) - assert inclusive in VALID_INCLUSIVE - self._inclusive: IntervalInclusiveType = inclusive + assert closed in VALID_CLOSED + self._closed: IntervalClosedType = closed if not isinstance(subtype, pyarrow.DataType): subtype = pyarrow.type_for_alias(str(subtype)) self._subtype = subtype @@ -71,46 +67,37 @@ def subtype(self): return self._subtype @property - def inclusive(self) -> IntervalInclusiveType: - return self._inclusive - - @property - def closed(self) -> IntervalInclusiveType: - warnings.warn( - "Attribute `closed` is deprecated in favor of `inclusive`.", - FutureWarning, - stacklevel=find_stack_level(), - ) - return self._inclusive + def closed(self) -> IntervalClosedType: + return self._closed def __arrow_ext_serialize__(self) -> bytes: - metadata = {"subtype": str(self.subtype), "inclusive": self.inclusive} + metadata = {"subtype": str(self.subtype), "closed": self.closed} return json.dumps(metadata).encode() @classmethod def __arrow_ext_deserialize__(cls, storage_type, serialized) -> ArrowIntervalType: metadata = json.loads(serialized.decode()) subtype = pyarrow.type_for_alias(metadata["subtype"]) - inclusive = metadata["inclusive"] - return ArrowIntervalType(subtype, inclusive) + closed = metadata["closed"] + return ArrowIntervalType(subtype, closed) def __eq__(self, other): if isinstance(other, pyarrow.BaseExtensionType): return ( type(self) == type(other) and self.subtype == other.subtype - and self.inclusive == other.inclusive + and self.closed == other.closed ) else: return NotImplemented def __hash__(self) -> int: - return hash((str(self), str(self.subtype), self.inclusive)) + return hash((str(self), str(self.subtype), self.closed)) def to_pandas_dtype(self): import pandas as pd - return pd.IntervalDtype(self.subtype.to_pandas_dtype(), self.inclusive) + return pd.IntervalDtype(self.subtype.to_pandas_dtype(), self.closed) # register the type with a dummy instance diff --git a/pandas/core/arrays/interval.py b/pandas/core/arrays/interval.py index e6def0f4d9402..8f01dfaf867e7 100644 --- a/pandas/core/arrays/interval.py +++ b/pandas/core/arrays/interval.py @@ -1,6 +1,5 @@ from __future__ import annotations -import inspect import operator from operator import ( le, @@ -16,7 +15,6 @@ cast, overload, ) -import warnings import numpy as np @@ -24,7 +22,7 @@ from pandas._libs import lib from pandas._libs.interval import ( - VALID_INCLUSIVE, + VALID_CLOSED, Interval, IntervalMixin, intervals_to_interval_bounds, @@ -33,7 +31,7 @@ from pandas._typing import ( ArrayLike, Dtype, - IntervalInclusiveType, + IntervalClosedType, NpDtype, PositionalIndexer, ScalarIndexer, @@ -44,10 +42,8 @@ from pandas.errors import IntCastingNaNError from pandas.util._decorators import ( Appender, - deprecate_kwarg, deprecate_nonkeyword_arguments, ) -from pandas.util._exceptions import find_stack_level from pandas.core.dtypes.cast import LossySetitemError from pandas.core.dtypes.common import ( @@ -130,8 +126,8 @@ data : array-like (1-dimensional) Array-like containing Interval objects from which to build the %(klass)s. -inclusive : {'left', 'right', 'both', 'neither'}, default 'right' - Whether the intervals are inclusive on the left-side, right-side, both or +closed : {'left', 'right', 'both', 'neither'}, default 'right' + Whether the intervals are closed on the left-side, right-side, both or neither. dtype : dtype or None, default None If None, dtype will be inferred. @@ -145,7 +141,7 @@ ---------- left right -inclusive +closed mid length is_empty @@ -160,7 +156,6 @@ contains overlaps set_closed -set_inclusive to_tuples %(extra_methods)s\ @@ -186,8 +181,7 @@ _interval_shared_docs["class"] % { "klass": "IntervalArray", - "summary": "Pandas array for interval data that are inclusive on the same " - "side.", + "summary": "Pandas array for interval data that are closed on the same side.", "versionadded": "0.24.0", "name": "", "extra_attributes": "", @@ -199,8 +193,7 @@ A new ``IntervalArray`` can be constructed directly from an array-like of ``Interval`` objects: - >>> pd.arrays.IntervalArray([pd.Interval(0, 1, "right"), - ... pd.Interval(1, 5, "right")]) + >>> pd.arrays.IntervalArray([pd.Interval(0, 1), pd.Interval(1, 5)]) [(0, 1], (1, 5]] Length: 2, dtype: interval[int64, right] @@ -228,11 +221,10 @@ def ndim(self) -> Literal[1]: # --------------------------------------------------------------------- # Constructors - @deprecate_kwarg(old_arg_name="closed", new_arg_name="inclusive") def __new__( cls: type[IntervalArrayT], data, - inclusive: IntervalInclusiveType | None = None, + closed=None, dtype: Dtype | None = None, copy: bool = False, verify_integrity: bool = True, @@ -243,7 +235,7 @@ def __new__( if isinstance(data, cls): left = data._left right = data._right - inclusive = inclusive or data.inclusive + closed = closed or data.closed else: # don't allow scalars @@ -256,41 +248,39 @@ def __new__( # might need to convert empty or purely na data data = _maybe_convert_platform_interval(data) - left, right, infer_inclusive = intervals_to_interval_bounds( - data, validate_inclusive=inclusive is None + left, right, infer_closed = intervals_to_interval_bounds( + data, validate_closed=closed is None ) if left.dtype == object: left = lib.maybe_convert_objects(left) right = lib.maybe_convert_objects(right) - inclusive = inclusive or infer_inclusive + closed = closed or infer_closed return cls._simple_new( left, right, - inclusive=inclusive, + closed, copy=copy, dtype=dtype, verify_integrity=verify_integrity, ) @classmethod - @deprecate_kwarg(old_arg_name="closed", new_arg_name="inclusive") def _simple_new( cls: type[IntervalArrayT], left, right, - inclusive: IntervalInclusiveType | None = None, + closed: IntervalClosedType | None = None, copy: bool = False, dtype: Dtype | None = None, verify_integrity: bool = True, ) -> IntervalArrayT: result = IntervalMixin.__new__(cls) - if inclusive is None and isinstance(dtype, IntervalDtype): - inclusive = dtype.inclusive - - inclusive = inclusive or "right" + if closed is None and isinstance(dtype, IntervalDtype): + closed = dtype.closed + closed = closed or "right" left = ensure_index(left, copy=copy) right = ensure_index(right, copy=copy) @@ -305,11 +295,12 @@ def _simple_new( else: msg = f"dtype must be an IntervalDtype, got {dtype}" raise TypeError(msg) - if dtype.inclusive is None: + + if dtype.closed is None: # possibly loading an old pickle - dtype = IntervalDtype(dtype.subtype, inclusive) - elif inclusive != dtype.inclusive: - raise ValueError("inclusive keyword does not match dtype.inclusive") + dtype = IntervalDtype(dtype.subtype, closed) + elif closed != dtype.closed: + raise ValueError("closed keyword does not match dtype.closed") # coerce dtypes to match if needed if is_float_dtype(left) and is_integer_dtype(right): @@ -352,7 +343,7 @@ def _simple_new( # If these share data, then setitem could corrupt our IA right = right.copy() - dtype = IntervalDtype(left.dtype, inclusive=inclusive) + dtype = IntervalDtype(left.dtype, closed=closed) result._dtype = dtype result._left = left @@ -380,7 +371,7 @@ def _from_factorized( # a new IA from an (empty) object-dtype array, so turn it into the # correct dtype. values = values.astype(original.dtype.subtype) - return cls(values, inclusive=original.inclusive) + return cls(values, closed=original.closed) _interval_shared_docs["from_breaks"] = textwrap.dedent( """ @@ -390,8 +381,8 @@ def _from_factorized( ---------- breaks : array-like (1-dimensional) Left and right bounds for each interval. - inclusive : {'left', 'right', 'both', 'neither'}, default 'right' - Whether the intervals are inclusive on the left-side, right-side, both + closed : {'left', 'right', 'both', 'neither'}, default 'right' + Whether the intervals are closed on the left-side, right-side, both or neither. copy : bool, default False Copy the data. @@ -421,7 +412,7 @@ def _from_factorized( """\ Examples -------- - >>> pd.arrays.IntervalArray.from_breaks([0, 1, 2, 3], "right") + >>> pd.arrays.IntervalArray.from_breaks([0, 1, 2, 3]) [(0, 1], (1, 2], (2, 3]] Length: 3, dtype: interval[int64, right] @@ -429,22 +420,16 @@ def _from_factorized( ), } ) - @deprecate_kwarg(old_arg_name="closed", new_arg_name="inclusive") def from_breaks( cls: type[IntervalArrayT], breaks, - inclusive: IntervalInclusiveType | None = None, + closed: IntervalClosedType | None = "right", copy: bool = False, dtype: Dtype | None = None, ) -> IntervalArrayT: - if inclusive is None: - inclusive = "right" - breaks = _maybe_convert_platform_interval(breaks) - return cls.from_arrays( - breaks[:-1], breaks[1:], inclusive, copy=copy, dtype=dtype - ) + return cls.from_arrays(breaks[:-1], breaks[1:], closed, copy=copy, dtype=dtype) _interval_shared_docs["from_arrays"] = textwrap.dedent( """ @@ -456,8 +441,8 @@ def from_breaks( Left bounds for each interval. right : array-like (1-dimensional) Right bounds for each interval. - inclusive : {'left', 'right', 'both', 'neither'}, default 'right' - Whether the intervals are inclusive on the left-side, right-side, both + closed : {'left', 'right', 'both', 'neither'}, default 'right' + Whether the intervals are closed on the left-side, right-side, both or neither. copy : bool, default False Copy the data. @@ -502,7 +487,7 @@ def from_breaks( "klass": "IntervalArray", "examples": textwrap.dedent( """\ - >>> pd.arrays.IntervalArray.from_arrays([0, 1, 2], [1, 2, 3], inclusive="right") + >>> pd.arrays.IntervalArray.from_arrays([0, 1, 2], [1, 2, 3]) [(0, 1], (1, 2], (2, 3]] Length: 3, dtype: interval[int64, right] @@ -510,29 +495,19 @@ def from_breaks( ), } ) - @deprecate_kwarg(old_arg_name="closed", new_arg_name="inclusive") def from_arrays( cls: type[IntervalArrayT], left, right, - inclusive: IntervalInclusiveType | None = None, + closed: IntervalClosedType | None = "right", copy: bool = False, dtype: Dtype | None = None, ) -> IntervalArrayT: - - if inclusive is None: - inclusive = "right" - left = _maybe_convert_platform_interval(left) right = _maybe_convert_platform_interval(right) return cls._simple_new( - left, - right, - inclusive=inclusive, - copy=copy, - dtype=dtype, - verify_integrity=True, + left, right, closed, copy=copy, dtype=dtype, verify_integrity=True ) _interval_shared_docs["from_tuples"] = textwrap.dedent( @@ -543,8 +518,8 @@ def from_arrays( ---------- data : array-like (1-dimensional) Array of tuples. - inclusive : {'left', 'right', 'both', 'neither'}, default 'right' - Whether the intervals are inclusive on the left-side, right-side, both + closed : {'left', 'right', 'both', 'neither'}, default 'right' + Whether the intervals are closed on the left-side, right-side, both or neither. copy : bool, default False By-default copy the data, this is compat only and ignored. @@ -576,7 +551,7 @@ def from_arrays( """\ Examples -------- - >>> pd.arrays.IntervalArray.from_tuples([(0, 1), (1, 2)], inclusive="right") + >>> pd.arrays.IntervalArray.from_tuples([(0, 1), (1, 2)]) [(0, 1], (1, 2]] Length: 2, dtype: interval[int64, right] @@ -584,17 +559,13 @@ def from_arrays( ), } ) - @deprecate_kwarg(old_arg_name="closed", new_arg_name="inclusive") def from_tuples( cls: type[IntervalArrayT], data, - inclusive: IntervalInclusiveType | None = None, + closed="right", copy: bool = False, dtype: Dtype | None = None, ) -> IntervalArrayT: - if inclusive is None: - inclusive = "right" - if len(data): left, right = [], [] else: @@ -618,7 +589,7 @@ def from_tuples( left.append(lhs) right.append(rhs) - return cls.from_arrays(left, right, inclusive, copy=False, dtype=dtype) + return cls.from_arrays(left, right, closed, copy=False, dtype=dtype) def _validate(self): """ @@ -626,13 +597,13 @@ def _validate(self): Checks that - * inclusive is valid + * closed is valid * left and right match lengths * left and right have the same missing values * left is always below right """ - if self.inclusive not in VALID_INCLUSIVE: - msg = f"invalid option for 'inclusive': {self.inclusive}" + if self.closed not in VALID_CLOSED: + msg = f"invalid option for 'closed': {self.closed}" raise ValueError(msg) if len(self._left) != len(self._right): msg = "left and right must have the same length" @@ -660,9 +631,7 @@ def _shallow_copy(self: IntervalArrayT, left, right) -> IntervalArrayT: right : Index Values to be used for the right-side of the intervals. """ - return self._simple_new( - left, right, inclusive=self.inclusive, verify_integrity=False - ) + return self._simple_new(left, right, closed=self.closed, verify_integrity=False) # --------------------------------------------------------------------- # Descriptive @@ -708,7 +677,7 @@ def __getitem__( # scalar if is_scalar(left) and isna(left): return self._fill_value - return Interval(left, right, inclusive=self.inclusive) + return Interval(left, right, self.closed) if np.ndim(left) > 1: # GH#30588 multi-dimensional indexer disallowed raise ValueError("multi-dimensional indexing not allowed") @@ -747,18 +716,18 @@ def _cmp_method(self, other, op): # for categorical defer to categories for dtype other_dtype = other.categories.dtype - # extract intervals if we have interval categories with matching inclusive + # extract intervals if we have interval categories with matching closed if is_interval_dtype(other_dtype): - if self.inclusive != other.categories.inclusive: + if self.closed != other.categories.closed: return invalid_comparison(self, other, op) other = other.categories.take( other.codes, allow_fill=True, fill_value=other.categories._na_value ) - # interval-like -> need same inclusive and matching endpoints + # interval-like -> need same closed and matching endpoints if is_interval_dtype(other_dtype): - if self.inclusive != other.inclusive: + if self.closed != other.closed: return invalid_comparison(self, other, op) elif not isinstance(other, Interval): other = type(self)(other) @@ -974,7 +943,7 @@ def equals(self, other) -> bool: return False return bool( - self.inclusive == other.inclusive + self.closed == other.closed and self.left.equals(other.left) and self.right.equals(other.right) ) @@ -994,14 +963,14 @@ def _concat_same_type( ------- IntervalArray """ - inclusive_set = {interval.inclusive for interval in to_concat} - if len(inclusive_set) != 1: - raise ValueError("Intervals must all be inclusive on the same side.") - inclusive = inclusive_set.pop() + closed_set = {interval.closed for interval in to_concat} + if len(closed_set) != 1: + raise ValueError("Intervals must all be closed on the same side.") + closed = closed_set.pop() left = np.concatenate([interval.left for interval in to_concat]) right = np.concatenate([interval.right for interval in to_concat]) - return cls._simple_new(left, right, inclusive=inclusive, copy=False) + return cls._simple_new(left, right, closed=closed, copy=False) def copy(self: IntervalArrayT) -> IntervalArrayT: """ @@ -1013,9 +982,9 @@ def copy(self: IntervalArrayT) -> IntervalArrayT: """ left = self._left.copy() right = self._right.copy() - inclusive = self.inclusive + closed = self.closed # TODO: Could skip verify_integrity here. - return type(self).from_arrays(left, right, inclusive=inclusive) + return type(self).from_arrays(left, right, closed=closed) def isna(self) -> np.ndarray: return isna(self._left) @@ -1037,7 +1006,7 @@ def shift(self, periods: int = 1, fill_value: object = None) -> IntervalArray: from pandas import Index fill_value = Index(self._left, copy=False)._na_value - empty = IntervalArray.from_breaks([fill_value] * (empty_len + 1), "right") + empty = IntervalArray.from_breaks([fill_value] * (empty_len + 1)) else: empty = self._from_sequence([fill_value] * empty_len) @@ -1122,7 +1091,7 @@ def _validate_listlike(self, value): # list-like of intervals try: array = IntervalArray(value) - self._check_inclusive_matches(array, name="value") + self._check_closed_matches(array, name="value") value_left, value_right = array.left, array.right except TypeError as err: # wrong type: not interval or NA @@ -1142,7 +1111,7 @@ def _validate_listlike(self, value): def _validate_scalar(self, value): if isinstance(value, Interval): - self._check_inclusive_matches(value, name="value") + self._check_closed_matches(value, name="value") left, right = value.left, value.right # TODO: check subdtype match like _validate_setitem_value? elif is_valid_na_for_dtype(value, self.left.dtype): @@ -1167,8 +1136,8 @@ def _validate_setitem_value(self, value): value_left, value_right = value, value elif isinstance(value, Interval): - # scalar - self._check_inclusive_matches(value, name="value") + # scalar interval + self._check_closed_matches(value, name="value") value_left, value_right = value.left, value.right self.left._validate_fill_value(value_left) self.left._validate_fill_value(value_right) @@ -1292,7 +1261,7 @@ def mid(self) -> Index: """ Check elementwise if an Interval overlaps the values in the %(klass)s. - Two intervals overlap if they share a common point, including inclusive + Two intervals overlap if they share a common point, including closed endpoints. Intervals that only have an open endpoint in common do not overlap. @@ -1316,14 +1285,14 @@ def mid(self) -> Index: >>> intervals.overlaps(pd.Interval(0.5, 1.5)) array([ True, True, False]) - Intervals that share inclusive endpoints overlap: + Intervals that share closed endpoints overlap: - >>> intervals.overlaps(pd.Interval(1, 3, inclusive='left')) + >>> intervals.overlaps(pd.Interval(1, 3, closed='left')) array([ True, True, True]) Intervals that only have an open endpoint in common do not overlap: - >>> intervals.overlaps(pd.Interval(1, 2, inclusive='right')) + >>> intervals.overlaps(pd.Interval(1, 2, closed='right')) array([False, True, False]) """ ) @@ -1335,7 +1304,7 @@ def mid(self) -> Index: "examples": textwrap.dedent( """\ >>> data = [(0, 1), (1, 3), (2, 4)] - >>> intervals = pd.arrays.IntervalArray.from_tuples(data, "right") + >>> intervals = pd.arrays.IntervalArray.from_tuples(data) >>> intervals [(0, 1], (1, 3], (2, 4]] @@ -1351,7 +1320,7 @@ def overlaps(self, other): msg = f"`other` must be Interval-like, got {type(other).__name__}" raise TypeError(msg) - # equality is okay if both endpoints are inclusive (overlap at a point) + # equality is okay if both endpoints are closed (overlap at a point) op1 = le if (self.closed_left and other.closed_right) else lt op2 = le if (other.closed_left and self.closed_right) else lt @@ -1363,34 +1332,18 @@ def overlaps(self, other): # --------------------------------------------------------------------- @property - def inclusive(self) -> IntervalInclusiveType: + def closed(self) -> IntervalClosedType: """ String describing the inclusive side the intervals. Either ``left``, ``right``, ``both`` or ``neither``. """ - return self.dtype.inclusive - - @property - def closed(self) -> IntervalInclusiveType: - """ - String describing the inclusive side the intervals. - - Either ``left``, ``right``, ``both`` or ``neither`. - """ - warnings.warn( - "Attribute `closed` is deprecated in favor of `inclusive`.", - FutureWarning, - stacklevel=find_stack_level(inspect.currentframe()), - ) - return self.dtype.inclusive + return self.dtype.closed _interval_shared_docs["set_closed"] = textwrap.dedent( """ Return an identical %(klass)s closed on the specified side. - .. deprecated:: 1.5.0 - Parameters ---------- closed : {'left', 'right', 'both', 'neither'} @@ -1413,7 +1366,7 @@ def closed(self) -> IntervalInclusiveType: """\ Examples -------- - >>> index = pd.arrays.IntervalArray.from_breaks(range(4), "right") + >>> index = pd.arrays.IntervalArray.from_breaks(range(4)) >>> index [(0, 1], (1, 2], (2, 3]] @@ -1426,70 +1379,13 @@ def closed(self) -> IntervalInclusiveType: ), } ) - def set_closed( - self: IntervalArrayT, closed: IntervalInclusiveType - ) -> IntervalArrayT: - warnings.warn( - "set_closed is deprecated and will be removed in a future version. " - "Use set_inclusive instead.", - FutureWarning, - stacklevel=find_stack_level(inspect.currentframe()), - ) - return self.set_inclusive(closed) - - _interval_shared_docs["set_inclusive"] = textwrap.dedent( - """ - Return an identical %(klass)s but closed on the specified side. - - .. versionadded:: 1.5 - - Parameters - ---------- - inclusive : {'left', 'right', 'both', 'neither'} - Whether the intervals are closed on the left-side, right-side, both - or neither. - - Returns - ------- - new_index : %(klass)s - - %(examples)s\ - """ - ) - - @Appender( - _interval_shared_docs["set_inclusive"] - % { - "klass": "IntervalArray", - "examples": textwrap.dedent( - """\ - Examples - -------- - >>> index = pd.arrays.IntervalArray.from_breaks(range(4), "right") - >>> index - - [(0, 1], (1, 2], (2, 3]] - Length: 3, dtype: interval[int64, right] - >>> index.set_inclusive('both') - - [[0, 1], [1, 2], [2, 3]] - Length: 3, dtype: interval[int64, both] - """ - ), - } - ) - def set_inclusive( - self: IntervalArrayT, inclusive: IntervalInclusiveType - ) -> IntervalArrayT: - if inclusive not in VALID_INCLUSIVE: - msg = f"invalid option for 'inclusive': {inclusive}" + def set_closed(self: IntervalArrayT, closed: IntervalClosedType) -> IntervalArrayT: + if closed not in VALID_CLOSED: + msg = f"invalid option for 'closed': {closed}" raise ValueError(msg) return type(self)._simple_new( - left=self._left, - right=self._right, - inclusive=inclusive, - verify_integrity=False, + left=self._left, right=self._right, closed=closed, verify_integrity=False ) _interval_shared_docs[ @@ -1512,15 +1408,15 @@ def is_non_overlapping_monotonic(self) -> bool: # or decreasing (e.g., [-1, 0), [-2, -1), [-3, -2), ...) # we already require left <= right - # strict inequality for inclusive == 'both'; equality implies overlapping + # strict inequality for closed == 'both'; equality implies overlapping # at a point when both sides of intervals are included - if self.inclusive == "both": + if self.closed == "both": return bool( (self._right[:-1] < self._left[1:]).all() or (self._left[:-1] > self._right[1:]).all() ) - # non-strict inequality when inclusive != 'both'; at least one side is + # non-strict inequality when closed != 'both'; at least one side is # not included in the intervals, so equality does not imply overlapping return bool( (self._right[:-1] <= self._left[1:]).all() @@ -1538,14 +1434,14 @@ def __array__(self, dtype: NpDtype | None = None) -> np.ndarray: left = self._left right = self._right mask = self.isna() - inclusive = self.inclusive + closed = self.closed result = np.empty(len(left), dtype=object) for i in range(len(left)): if mask[i]: result[i] = np.nan else: - result[i] = Interval(left[i], right[i], inclusive=inclusive) + result[i] = Interval(left[i], right[i], closed) return result def __arrow_array__(self, type=None): @@ -1563,7 +1459,7 @@ def __arrow_array__(self, type=None): f"Conversion to arrow with subtype '{self.dtype.subtype}' " "is not supported" ) from err - interval_type = ArrowIntervalType(subtype, self.inclusive) + interval_type = ArrowIntervalType(subtype, self.closed) storage_array = pyarrow.StructArray.from_arrays( [ pyarrow.array(self._left, type=subtype, from_pandas=True), @@ -1586,13 +1482,12 @@ def __arrow_array__(self, type=None): if type.equals(interval_type.storage_type): return storage_array elif isinstance(type, ArrowIntervalType): - # ensure we have the same subtype and inclusive attributes + # ensure we have the same subtype and closed attributes if not type.equals(interval_type): raise TypeError( "Not supported to convert IntervalArray to type with " f"different 'subtype' ({self.dtype.subtype} vs {type.subtype}) " - f"and 'inclusive' ({self.inclusive} vs {type.inclusive}) " - f"attributes" + f"and 'closed' ({self.closed} vs {type.closed}) attributes" ) else: raise TypeError( @@ -1720,8 +1615,7 @@ def repeat( "klass": "IntervalArray", "examples": textwrap.dedent( """\ - >>> intervals = pd.arrays.IntervalArray.from_tuples([(0, 1), (1, 3), (2, 4)] - ... , "right") + >>> intervals = pd.arrays.IntervalArray.from_tuples([(0, 1), (1, 3), (2, 4)]) >>> intervals [(0, 1], (1, 3], (2, 4]] @@ -1744,7 +1638,7 @@ def isin(self, values) -> npt.NDArray[np.bool_]: values = extract_array(values, extract_numpy=True) if is_interval_dtype(values.dtype): - if self.inclusive != values.inclusive: + if self.closed != values.closed: # not comparable -> no overlap return np.zeros(self.shape, dtype=bool) diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index 3dfc544273a64..4244217da7865 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -516,7 +516,7 @@ def ensure_dtype_can_hold_na(dtype: DtypeObj) -> DtypeObj: elif isinstance(dtype, IntervalDtype): # TODO(GH#45349): don't special-case IntervalDtype, allow # overriding instead of returning object below. - return IntervalDtype(np.float64, inclusive=dtype.inclusive) + return IntervalDtype(np.float64, closed=dtype.closed) return _dtype_obj elif dtype.kind == "b": return _dtype_obj @@ -841,7 +841,7 @@ def infer_dtype_from_scalar(val, pandas_dtype: bool = False) -> tuple[DtypeObj, dtype = PeriodDtype(freq=val.freq) elif lib.is_interval(val): subtype = infer_dtype_from_scalar(val.left, pandas_dtype=True)[0] - dtype = IntervalDtype(subtype=subtype, inclusive=val.inclusive) + dtype = IntervalDtype(subtype=subtype, closed=val.closed) return dtype, val diff --git a/pandas/core/dtypes/common.py b/pandas/core/dtypes/common.py index f0e4a54c3f05c..606fefd30f37f 100644 --- a/pandas/core/dtypes/common.py +++ b/pandas/core/dtypes/common.py @@ -481,7 +481,7 @@ def is_interval_dtype(arr_or_dtype) -> bool: >>> is_interval_dtype([1, 2, 3]) False >>> - >>> interval = pd.Interval(1, 2, inclusive="right") + >>> interval = pd.Interval(1, 2, closed="right") >>> is_interval_dtype(interval) False >>> is_interval_dtype(pd.IntervalIndex([interval])) diff --git a/pandas/core/dtypes/dtypes.py b/pandas/core/dtypes/dtypes.py index c2c600adbbe09..e2570e6be4879 100644 --- a/pandas/core/dtypes/dtypes.py +++ b/pandas/core/dtypes/dtypes.py @@ -3,7 +3,6 @@ """ from __future__ import annotations -import inspect import re from typing import ( TYPE_CHECKING, @@ -11,19 +10,12 @@ MutableMapping, cast, ) -import warnings import numpy as np import pytz -from pandas._libs import ( - lib, - missing as libmissing, -) -from pandas._libs.interval import ( - Interval, - _warning_interval, -) +from pandas._libs import missing as libmissing +from pandas._libs.interval import Interval from pandas._libs.properties import cache_readonly from pandas._libs.tslibs import ( BaseOffset, @@ -39,12 +31,10 @@ from pandas._typing import ( Dtype, DtypeObj, - IntervalInclusiveType, Ordered, npt, type_t, ) -from pandas.util._exceptions import find_stack_level from pandas.core.dtypes.base import ( ExtensionDtype, @@ -1072,7 +1062,7 @@ class IntervalDtype(PandasExtensionDtype): Examples -------- - >>> pd.IntervalDtype(subtype='int64', inclusive='both') + >>> pd.IntervalDtype(subtype='int64', closed='both') interval[int64, both] """ @@ -1083,44 +1073,30 @@ class IntervalDtype(PandasExtensionDtype): num = 103 _metadata = ( "subtype", - "inclusive", + "closed", ) _match = re.compile( r"(I|i)nterval\[(?P[^,]+(\[.+\])?)" - r"(, (?P(right|left|both|neither)))?\]" + r"(, (?P(right|left|both|neither)))?\]" ) _cache_dtypes: dict[str_type, PandasExtensionDtype] = {} - def __new__( - cls, - subtype=None, - inclusive: IntervalInclusiveType | None = None, - closed: None | lib.NoDefault = lib.no_default, - ): + def __new__(cls, subtype=None, closed: str_type | None = None): from pandas.core.dtypes.common import ( is_string_dtype, pandas_dtype, ) - inclusive, closed = _warning_interval(inclusive, closed) - - if inclusive is not None and inclusive not in { - "right", - "left", - "both", - "neither", - }: - raise ValueError( - "inclusive must be one of 'right', 'left', 'both', 'neither'" - ) + if closed is not None and closed not in {"right", "left", "both", "neither"}: + raise ValueError("closed must be one of 'right', 'left', 'both', 'neither'") if isinstance(subtype, IntervalDtype): - if inclusive is not None and inclusive != subtype.inclusive: + if closed is not None and closed != subtype.closed: raise ValueError( - "dtype.inclusive and 'inclusive' do not match. " - "Try IntervalDtype(dtype.subtype, inclusive) instead." + "dtype.closed and 'closed' do not match. " + "Try IntervalDtype(dtype.subtype, closed) instead." ) return subtype elif subtype is None: @@ -1128,7 +1104,7 @@ def __new__( # generally for pickle compat u = object.__new__(cls) u._subtype = None - u._inclusive = inclusive + u._closed = closed return u elif isinstance(subtype, str) and subtype.lower() == "interval": subtype = None @@ -1138,18 +1114,14 @@ def __new__( if m is not None: gd = m.groupdict() subtype = gd["subtype"] - if gd.get("inclusive", None) is not None: - if inclusive is not None: - if inclusive != gd["inclusive"]: + if gd.get("closed", None) is not None: + if closed is not None: + if closed != gd["closed"]: raise ValueError( - "'inclusive' keyword does not match value " + "'closed' keyword does not match value " "specified in dtype string" ) - # Incompatible types in assignment (expression has type - # "Union[str, Any]", variable has type - # "Optional[Union[Literal['left', 'right'], - # Literal['both', 'neither']]]") - inclusive = gd["inclusive"] # type: ignore[assignment] + closed = gd["closed"] try: subtype = pandas_dtype(subtype) @@ -1164,13 +1136,13 @@ def __new__( ) raise TypeError(msg) - key = str(subtype) + str(inclusive) + key = str(subtype) + str(closed) try: return cls._cache_dtypes[key] except KeyError: u = object.__new__(cls) u._subtype = subtype - u._inclusive = inclusive + u._closed = closed cls._cache_dtypes[key] = u return u @@ -1186,18 +1158,9 @@ def _can_hold_na(self) -> bool: return False return True - @property - def inclusive(self): - return self._inclusive - @property def closed(self): - warnings.warn( - "Attribute `closed` is deprecated in favor of `inclusive`.", - FutureWarning, - stacklevel=find_stack_level(inspect.currentframe()), - ) - return self._inclusive + return self._closed @property def subtype(self): @@ -1248,10 +1211,10 @@ def type(self) -> type[Interval]: def __str__(self) -> str_type: if self.subtype is None: return "interval" - if self.inclusive is None: + if self.closed is None: # Only partially initialized GH#38394 return f"interval[{self.subtype}]" - return f"interval[{self.subtype}, {self.inclusive}]" + return f"interval[{self.subtype}, {self.closed}]" def __hash__(self) -> int: # make myself hashable @@ -1265,7 +1228,7 @@ def __eq__(self, other: Any) -> bool: elif self.subtype is None or other.subtype is None: # None should match any subtype return True - elif self.inclusive != other.inclusive: + elif self.closed != other.closed: return False else: from pandas.core.dtypes.common import is_dtype_equal @@ -1277,8 +1240,9 @@ def __setstate__(self, state) -> None: # PandasExtensionDtype superclass and uses the public properties to # pickle -> need to set the settable private ones here (see GH26067) self._subtype = state["subtype"] - # backward-compat older pickles won't have "inclusive" key - self._inclusive = state.pop("inclusive", None) + + # backward-compat older pickles won't have "closed" key + self._closed = state.pop("closed", None) @classmethod def is_dtype(cls, dtype: object) -> bool: @@ -1320,14 +1284,14 @@ def __from_arrow__( arr = arr.storage left = np.asarray(arr.field("left"), dtype=self.subtype) right = np.asarray(arr.field("right"), dtype=self.subtype) - iarr = IntervalArray.from_arrays(left, right, inclusive=self.inclusive) + iarr = IntervalArray.from_arrays(left, right, closed=self.closed) results.append(iarr) if not results: return IntervalArray.from_arrays( np.array([], dtype=self.subtype), np.array([], dtype=self.subtype), - inclusive=self.inclusive, + closed=self.closed, ) return IntervalArray._concat_same_type(results) @@ -1335,8 +1299,8 @@ def _get_common_dtype(self, dtypes: list[DtypeObj]) -> DtypeObj | None: if not all(isinstance(x, IntervalDtype) for x in dtypes): return None - inclusive = cast("IntervalDtype", dtypes[0]).inclusive - if not all(cast("IntervalDtype", x).inclusive == inclusive for x in dtypes): + closed = cast("IntervalDtype", dtypes[0]).closed + if not all(cast("IntervalDtype", x).closed == closed for x in dtypes): return np.dtype(object) from pandas.core.dtypes.cast import find_common_type @@ -1344,7 +1308,7 @@ def _get_common_dtype(self, dtypes: list[DtypeObj]) -> DtypeObj | None: common = find_common_type([cast("IntervalDtype", x).subtype for x in dtypes]) if common == object: return np.dtype(object) - return IntervalDtype(common, inclusive=inclusive) + return IntervalDtype(common, closed=closed) class PandasDtype(ExtensionDtype): diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 7ed6e0d84445c..5686ae324dd51 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -56,7 +56,7 @@ IgnoreRaise, IndexKeyFunc, IndexLabel, - IntervalInclusiveType, + IntervalClosedType, JSONSerializable, Level, Manager, @@ -8257,7 +8257,7 @@ def between_time( end_time, include_start: bool_t | lib.NoDefault = lib.no_default, include_end: bool_t | lib.NoDefault = lib.no_default, - inclusive: IntervalInclusiveType | None = None, + inclusive: IntervalClosedType | None = None, axis=None, ) -> NDFrameT: """ @@ -8363,7 +8363,7 @@ def between_time( left = True if include_start is lib.no_default else include_start right = True if include_end is lib.no_default else include_end - inc_dict: dict[tuple[bool_t, bool_t], IntervalInclusiveType] = { + inc_dict: dict[tuple[bool_t, bool_t], IntervalClosedType] = { (True, True): "both", (True, False): "left", (False, True): "right", diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 835d6a3948724..c3892c8b2e0de 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -37,7 +37,7 @@ from pandas._typing import ( Dtype, DtypeObj, - IntervalInclusiveType, + IntervalClosedType, IntervalLeftRight, npt, ) @@ -926,7 +926,7 @@ def date_range( normalize: bool = False, name: Hashable = None, closed: Literal["left", "right"] | None | lib.NoDefault = lib.no_default, - inclusive: IntervalInclusiveType | None = None, + inclusive: IntervalClosedType | None = None, **kwargs, ) -> DatetimeIndex: """ @@ -1132,7 +1132,7 @@ def bdate_range( weekmask=None, holidays=None, closed: IntervalLeftRight | lib.NoDefault | None = lib.no_default, - inclusive: IntervalInclusiveType | None = None, + inclusive: IntervalClosedType | None = None, **kwargs, ) -> DatetimeIndex: """ diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py index 5ed8f79bbbefe..e686e8453f0d9 100644 --- a/pandas/core/indexes/interval.py +++ b/pandas/core/indexes/interval.py @@ -1,7 +1,6 @@ """ define the IntervalIndex """ from __future__ import annotations -import inspect from operator import ( le, lt, @@ -12,7 +11,6 @@ Hashable, Literal, ) -import warnings import numpy as np @@ -31,19 +29,15 @@ from pandas._typing import ( Dtype, DtypeObj, - IntervalInclusiveType, + IntervalClosedType, npt, ) from pandas.errors import InvalidIndexError from pandas.util._decorators import ( Appender, cache_readonly, - deprecate_kwarg, -) -from pandas.util._exceptions import ( - find_stack_level, - rewrite_exception, ) +from pandas.util._exceptions import rewrite_exception from pandas.core.dtypes.cast import ( find_common_type, @@ -154,7 +148,7 @@ def _new_IntervalIndex(cls, d): _interval_shared_docs["class"] % { "klass": "IntervalIndex", - "summary": "Immutable index of intervals that are inclusive on the same side.", + "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", @@ -166,7 +160,7 @@ def _new_IntervalIndex(cls, d): A new ``IntervalIndex`` is typically constructed using :func:`interval_range`: - >>> pd.interval_range(start=0, end=5, inclusive="right") + >>> pd.interval_range(start=0, end=5) IntervalIndex([(0, 1], (1, 2], (2, 3], (3, 4], (4, 5]], dtype='interval[int64, right]') @@ -180,7 +174,7 @@ def _new_IntervalIndex(cls, d): ), } ) -@inherit_names(["set_closed", "set_inclusive", "to_tuples"], IntervalArray, wrap=True) +@inherit_names(["set_closed", "to_tuples"], IntervalArray, wrap=True) @inherit_names( [ "__array__", @@ -194,12 +188,12 @@ def _new_IntervalIndex(cls, d): ], IntervalArray, ) -@inherit_names(["is_non_overlapping_monotonic", "inclusive"], IntervalArray, cache=True) +@inherit_names(["is_non_overlapping_monotonic", "closed"], IntervalArray, cache=True) class IntervalIndex(ExtensionIndex): _typ = "intervalindex" # annotate properties pinned via inherit_names - inclusive: IntervalInclusiveType + closed: IntervalClosedType is_non_overlapping_monotonic: bool closed_left: bool closed_right: bool @@ -214,11 +208,10 @@ class IntervalIndex(ExtensionIndex): # -------------------------------------------------------------------- # Constructors - @deprecate_kwarg(old_arg_name="closed", new_arg_name="inclusive") def __new__( cls, data, - inclusive: IntervalInclusiveType | None = None, + closed=None, dtype: Dtype | None = None, copy: bool = False, name: Hashable = None, @@ -230,7 +223,7 @@ def __new__( with rewrite_exception("IntervalArray", cls.__name__): array = IntervalArray( data, - inclusive=inclusive, + closed=closed, copy=copy, dtype=dtype, verify_integrity=verify_integrity, @@ -238,15 +231,6 @@ def __new__( return cls._simple_new(array, name) - @property - def closed(self): - warnings.warn( - "Attribute `closed` is deprecated in favor of `inclusive`.", - FutureWarning, - stacklevel=find_stack_level(inspect.currentframe()), - ) - return self.inclusive - @classmethod @Appender( _interval_shared_docs["from_breaks"] @@ -256,29 +240,24 @@ def closed(self): """\ Examples -------- - >>> pd.IntervalIndex.from_breaks([0, 1, 2, 3], "right") + >>> pd.IntervalIndex.from_breaks([0, 1, 2, 3]) IntervalIndex([(0, 1], (1, 2], (2, 3]], dtype='interval[int64, right]') """ ), } ) - @deprecate_kwarg(old_arg_name="closed", new_arg_name="inclusive") def from_breaks( cls, breaks, - inclusive: IntervalInclusiveType | None = None, + closed: IntervalClosedType | None = "right", name: Hashable = None, copy: bool = False, dtype: Dtype | None = None, ) -> IntervalIndex: - - if inclusive is None: - inclusive = "right" - with rewrite_exception("IntervalArray", cls.__name__): array = IntervalArray.from_breaks( - breaks, inclusive=inclusive, copy=copy, dtype=dtype + breaks, closed=closed, copy=copy, dtype=dtype ) return cls._simple_new(array, name=name) @@ -291,30 +270,25 @@ def from_breaks( """\ Examples -------- - >>> pd.IntervalIndex.from_arrays([0, 1, 2], [1, 2, 3], "right") + >>> pd.IntervalIndex.from_arrays([0, 1, 2], [1, 2, 3]) IntervalIndex([(0, 1], (1, 2], (2, 3]], dtype='interval[int64, right]') """ ), } ) - @deprecate_kwarg(old_arg_name="closed", new_arg_name="inclusive") def from_arrays( cls, left, right, - inclusive: IntervalInclusiveType | None = None, + closed: IntervalClosedType = "right", name: Hashable = None, copy: bool = False, dtype: Dtype | None = None, ) -> IntervalIndex: - - if inclusive is None: - inclusive = "right" - with rewrite_exception("IntervalArray", cls.__name__): array = IntervalArray.from_arrays( - left, right, inclusive, copy=copy, dtype=dtype + left, right, closed, copy=copy, dtype=dtype ) return cls._simple_new(array, name=name) @@ -327,30 +301,23 @@ def from_arrays( """\ Examples -------- - >>> pd.IntervalIndex.from_tuples([(0, 1), (1, 2)], "right") + >>> pd.IntervalIndex.from_tuples([(0, 1), (1, 2)]) IntervalIndex([(0, 1], (1, 2]], dtype='interval[int64, right]') """ ), } ) - @deprecate_kwarg(old_arg_name="closed", new_arg_name="inclusive") def from_tuples( cls, data, - inclusive: IntervalInclusiveType | None = None, + closed: str = "right", name: Hashable = None, copy: bool = False, dtype: Dtype | None = None, ) -> IntervalIndex: - - if inclusive is None: - inclusive = "right" - with rewrite_exception("IntervalArray", cls.__name__): - arr = IntervalArray.from_tuples( - data, inclusive=inclusive, copy=copy, dtype=dtype - ) + arr = IntervalArray.from_tuples(data, closed=closed, copy=copy, dtype=dtype) return cls._simple_new(arr, name=name) # -------------------------------------------------------------------- @@ -360,7 +327,7 @@ def from_tuples( def _engine(self) -> IntervalTree: # type: ignore[override] left = self._maybe_convert_i8(self.left) right = self._maybe_convert_i8(self.right) - return IntervalTree(left, right, inclusive=self.inclusive) + return IntervalTree(left, right, closed=self.closed) def __contains__(self, key: Any) -> bool: """ @@ -395,7 +362,7 @@ def __reduce__(self): d = { "left": self.left, "right": self.right, - "inclusive": self.inclusive, + "closed": self.closed, "name": self.name, } return _new_IntervalIndex, (type(self), d), None @@ -450,7 +417,7 @@ 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 inclusive + Two intervals overlap if they share a common point, including closed endpoints. Intervals that only have an open endpoint in common do not overlap. @@ -467,16 +434,16 @@ def is_overlapping(self) -> bool: Examples -------- - >>> index = pd.IntervalIndex.from_tuples([(0, 2), (1, 3), (4, 5)], "right") + >>> 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 inclusive endpoints overlap: + Intervals that share closed endpoints overlap: - >>> index = pd.interval_range(0, 3, inclusive='both') + >>> index = pd.interval_range(0, 3, closed='both') >>> index IntervalIndex([[0, 1], [1, 2], [2, 3]], dtype='interval[int64, both]') @@ -485,7 +452,7 @@ def is_overlapping(self) -> bool: Intervals that only have an open endpoint in common do not overlap: - >>> index = pd.interval_range(0, 3, inclusive='left') + >>> index = pd.interval_range(0, 3, closed='left') >>> index IntervalIndex([[0, 1), [1, 2), [2, 3)], dtype='interval[int64, left]') @@ -551,7 +518,7 @@ def _maybe_convert_i8(self, key): constructor = Interval if scalar else IntervalIndex.from_arrays # error: "object" not callable return constructor( - left, right, inclusive=self.inclusive + left, right, closed=self.closed ) # type: ignore[operator] if scalar: @@ -632,7 +599,7 @@ def get_loc( Examples -------- >>> i1, i2 = pd.Interval(0, 1), pd.Interval(1, 2) - >>> index = pd.IntervalIndex([i1, i2], "right") + >>> index = pd.IntervalIndex([i1, i2]) >>> index.get_loc(1) 0 @@ -645,20 +612,20 @@ def get_loc( relevant intervals. >>> i3 = pd.Interval(0, 2) - >>> overlapping_index = pd.IntervalIndex([i1, i2, i3], "right") + >>> 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, "right")) + >>> index.get_loc(pd.Interval(0, 1)) 0 """ self._check_indexing_method(method) self._check_indexing_error(key) if isinstance(key, Interval): - if self.inclusive != key.inclusive: + 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): @@ -719,7 +686,7 @@ def get_indexer_non_unique( target = ensure_index(target) if not self._should_compare(target) and not self._should_partial_index(target): - # e.g. IntervalIndex with different inclusive or incompatible subtype + # e.g. IntervalIndex with different closed or incompatible subtype # -> no matches return self._get_indexer_non_comparable(target, None, unique=False) @@ -871,7 +838,7 @@ def _intersection(self, other, sort): """ intersection specialized to the case with matching dtypes. """ - # For IntervalIndex we also know other.inclusive == self.inclusive + # 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: @@ -983,14 +950,13 @@ def _is_type_compatible(a, b) -> bool: ) -@deprecate_kwarg(old_arg_name="closed", new_arg_name="inclusive") def interval_range( start=None, end=None, periods=None, freq=None, name: Hashable = None, - inclusive: IntervalInclusiveType | None = None, + closed: IntervalClosedType = "right", ) -> IntervalIndex: """ Return a fixed frequency IntervalIndex. @@ -1009,25 +975,17 @@ def interval_range( for numeric and 'D' for datetime-like. name : str, default None Name of the resulting IntervalIndex. - inclusive : {"both", "neither", "left", "right"}, default "both" - Include boundaries; Whether to set each bound as inclusive or not. - - .. versionadded:: 1.5.0 closed : {'left', 'right', 'both', 'neither'}, default 'right' Whether the intervals are closed on the left-side, right-side, both or neither. - .. deprecated:: 1.5.0 - Argument `closed` has been deprecated to standardize boundary inputs. - Use `inclusive` instead, to set each bound as closed or open. - Returns ------- IntervalIndex See Also -------- - IntervalIndex : An Index of intervals that are all inclusive on the same side. + IntervalIndex : An Index of intervals that are all closed on the same side. Notes ----- @@ -1043,14 +1001,14 @@ def interval_range( -------- Numeric ``start`` and ``end`` is supported. - >>> pd.interval_range(start=0, end=5, inclusive="right") + >>> 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'), inclusive="right") + ... 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]') @@ -1059,7 +1017,7 @@ def interval_range( 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, inclusive="right") + >>> 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]') @@ -1067,7 +1025,7 @@ def interval_range( convertible to a DateOffset. >>> pd.interval_range(start=pd.Timestamp('2017-01-01'), - ... periods=3, freq='MS', inclusive="right") + ... 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]') @@ -1075,20 +1033,17 @@ def interval_range( Specify ``start``, ``end``, and ``periods``; the frequency is generated automatically (linearly spaced). - >>> pd.interval_range(start=0, end=6, periods=4, inclusive="right") + >>> 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 ``inclusive`` parameter specifies which endpoints of the individual - intervals within the ``IntervalIndex`` are inclusive. + The ``closed`` parameter specifies which endpoints of the individual + intervals within the ``IntervalIndex`` are closed. - >>> pd.interval_range(end=5, periods=4, inclusive='both') + >>> pd.interval_range(end=5, periods=4, closed='both') IntervalIndex([[1, 2], [2, 3], [3, 4], [4, 5]], dtype='interval[int64, both]') """ - if inclusive is None: - inclusive = "right" - start = maybe_box_datetimelike(start) end = maybe_box_datetimelike(end) endpoint = start if start is not None else end @@ -1161,4 +1116,4 @@ def interval_range( else: breaks = timedelta_range(start=start, end=end, periods=periods, freq=freq) - return IntervalIndex.from_breaks(breaks, name=name, inclusive=inclusive) + return IntervalIndex.from_breaks(breaks, name=name, closed=closed) diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index 3e27cf0b15511..69089cc64e671 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -1978,8 +1978,8 @@ def _catch_deprecated_value_error(err: Exception) -> None: # is enforced, stop catching ValueError here altogether if isinstance(err, IncompatibleFrequency): pass - elif "'value.inclusive' is" in str(err): - # IntervalDtype mismatched 'inclusive' + elif "'value.closed' is" in str(err): + # IntervalDtype mismatched 'closed' pass elif "Timezones don't match" not in str(err): raise diff --git a/pandas/core/reshape/tile.py b/pandas/core/reshape/tile.py index 00b2b30eb3122..94705790e40bd 100644 --- a/pandas/core/reshape/tile.py +++ b/pandas/core/reshape/tile.py @@ -231,7 +231,7 @@ def cut( is to the left of the first bin (which is closed on the right), and 1.5 falls between two bins. - >>> bins = pd.IntervalIndex.from_tuples([(0, 1), (2, 3), (4, 5)], inclusive="right") + >>> bins = pd.IntervalIndex.from_tuples([(0, 1), (2, 3), (4, 5)]) >>> pd.cut([0, 0.5, 1.5, 2.5, 4.5], bins) [NaN, (0.0, 1.0], NaN, (2.0, 3.0], (4.0, 5.0]] Categories (3, interval[int64, right]): [(0, 1] < (2, 3] < (4, 5]] @@ -561,7 +561,7 @@ def _format_labels( bins, precision: int, right: bool = True, include_lowest: bool = False, dtype=None ): """based on the dtype, return our labels""" - inclusive: IntervalLeftRight = "right" if right else "left" + closed: IntervalLeftRight = "right" if right else "left" formatter: Callable[[Any], Timestamp] | Callable[[Any], Timedelta] @@ -584,7 +584,7 @@ def _format_labels( # adjust lhs of first interval by precision to account for being right closed breaks[0] = adjust(breaks[0]) - return IntervalIndex.from_breaks(breaks, inclusive=inclusive) + return IntervalIndex.from_breaks(breaks, closed=closed) def _preprocess_for_cut(x): diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py index 0522e113d6525..8444efb7cb636 100644 --- a/pandas/io/formats/style.py +++ b/pandas/io/formats/style.py @@ -26,7 +26,6 @@ Axis, FilePath, IndexLabel, - IntervalInclusiveType, Level, QuantileInterpolation, Scalar, @@ -3489,7 +3488,7 @@ def highlight_between( axis: Axis | None = 0, left: Scalar | Sequence | None = None, right: Scalar | Sequence | None = None, - inclusive: IntervalInclusiveType = "both", + inclusive: str = "both", props: str | None = None, ) -> Styler: """ @@ -3594,7 +3593,7 @@ def highlight_quantile( q_left: float = 0.0, q_right: float = 1.0, interpolation: QuantileInterpolation = "linear", - inclusive: IntervalInclusiveType = "both", + inclusive: str = "both", props: str | None = None, ) -> Styler: """ @@ -3979,7 +3978,7 @@ def _highlight_between( props: str, left: Scalar | Sequence | np.ndarray | NDFrame | None = None, right: Scalar | Sequence | np.ndarray | NDFrame | None = None, - inclusive: bool | IntervalInclusiveType = True, + inclusive: bool | str = True, ) -> np.ndarray: """ Return an array of css props based on condition of data values within given range. diff --git a/pandas/tests/arithmetic/test_interval.py b/pandas/tests/arithmetic/test_interval.py index 99e1ad1767e07..88e3dca62d9e0 100644 --- a/pandas/tests/arithmetic/test_interval.py +++ b/pandas/tests/arithmetic/test_interval.py @@ -62,16 +62,16 @@ def interval_array(left_right_dtypes): return IntervalArray.from_arrays(left, right) -def create_categorical_intervals(left, right, inclusive="right"): - return Categorical(IntervalIndex.from_arrays(left, right, inclusive)) +def create_categorical_intervals(left, right, closed="right"): + return Categorical(IntervalIndex.from_arrays(left, right, closed)) -def create_series_intervals(left, right, inclusive="right"): - return Series(IntervalArray.from_arrays(left, right, inclusive)) +def create_series_intervals(left, right, closed="right"): + return Series(IntervalArray.from_arrays(left, right, closed)) -def create_series_categorical_intervals(left, right, inclusive="right"): - return Series(Categorical(IntervalIndex.from_arrays(left, right, inclusive))) +def create_series_categorical_intervals(left, right, closed="right"): + return Series(Categorical(IntervalIndex.from_arrays(left, right, closed))) class TestComparison: @@ -126,10 +126,8 @@ def test_compare_scalar_interval(self, op, interval_array): tm.assert_numpy_array_equal(result, expected) def test_compare_scalar_interval_mixed_closed(self, op, closed, other_closed): - interval_array = IntervalArray.from_arrays( - range(2), range(1, 3), inclusive=closed - ) - other = Interval(0, 1, inclusive=other_closed) + interval_array = IntervalArray.from_arrays(range(2), range(1, 3), closed=closed) + other = Interval(0, 1, closed=other_closed) result = op(interval_array, other) expected = self.elementwise_comparison(op, interval_array, other) @@ -209,10 +207,8 @@ def test_compare_list_like_interval(self, op, interval_array, interval_construct def test_compare_list_like_interval_mixed_closed( self, op, interval_constructor, closed, other_closed ): - interval_array = IntervalArray.from_arrays( - range(2), range(1, 3), inclusive=closed - ) - other = interval_constructor(range(2), range(1, 3), inclusive=other_closed) + interval_array = IntervalArray.from_arrays(range(2), range(1, 3), closed=closed) + other = interval_constructor(range(2), range(1, 3), closed=other_closed) result = op(interval_array, other) expected = self.elementwise_comparison(op, interval_array, other) diff --git a/pandas/tests/arrays/interval/test_interval.py b/pandas/tests/arrays/interval/test_interval.py index 48f5c676b66e6..2a6bea3255342 100644 --- a/pandas/tests/arrays/interval/test_interval.py +++ b/pandas/tests/arrays/interval/test_interval.py @@ -55,31 +55,31 @@ def test_is_empty(self, constructor, left, right, closed): # GH27219 tuples = [(left, left), (left, right), np.nan] expected = np.array([closed != "both", False, False]) - result = constructor.from_tuples(tuples, inclusive=closed).is_empty + result = constructor.from_tuples(tuples, closed=closed).is_empty tm.assert_numpy_array_equal(result, expected) class TestMethods: - @pytest.mark.parametrize("new_inclusive", ["left", "right", "both", "neither"]) - def test_set_inclusive(self, closed, new_inclusive): + @pytest.mark.parametrize("new_closed", ["left", "right", "both", "neither"]) + def test_set_closed(self, closed, new_closed): # GH 21670 - array = IntervalArray.from_breaks(range(10), inclusive=closed) - result = array.set_inclusive(new_inclusive) - expected = IntervalArray.from_breaks(range(10), inclusive=new_inclusive) + array = IntervalArray.from_breaks(range(10), closed=closed) + result = array.set_closed(new_closed) + expected = IntervalArray.from_breaks(range(10), closed=new_closed) tm.assert_extension_array_equal(result, expected) @pytest.mark.parametrize( "other", [ - Interval(0, 1, inclusive="right"), - IntervalArray.from_breaks([1, 2, 3, 4], inclusive="right"), + Interval(0, 1, closed="right"), + IntervalArray.from_breaks([1, 2, 3, 4], closed="right"), ], ) def test_where_raises(self, other): # GH#45768 The IntervalArray methods raises; the Series method coerces - ser = pd.Series(IntervalArray.from_breaks([1, 2, 3, 4], inclusive="left")) + ser = pd.Series(IntervalArray.from_breaks([1, 2, 3, 4], closed="left")) mask = np.array([True, False, True]) - match = "'value.inclusive' is 'right', expected 'left'." + match = "'value.closed' is 'right', expected 'left'." with pytest.raises(ValueError, match=match): ser.array._where(mask, other) @@ -89,15 +89,15 @@ def test_where_raises(self, other): def test_shift(self): # https://github.com/pandas-dev/pandas/issues/31495, GH#22428, GH#31502 - a = IntervalArray.from_breaks([1, 2, 3], "right") + a = IntervalArray.from_breaks([1, 2, 3]) result = a.shift() # int -> float - expected = IntervalArray.from_tuples([(np.nan, np.nan), (1.0, 2.0)], "right") + expected = IntervalArray.from_tuples([(np.nan, np.nan), (1.0, 2.0)]) tm.assert_interval_array_equal(result, expected) def test_shift_datetime(self): # GH#31502, GH#31504 - a = IntervalArray.from_breaks(date_range("2000", periods=4), "right") + a = IntervalArray.from_breaks(date_range("2000", periods=4)) result = a.shift(2) expected = a.take([-1, -1, 0], allow_fill=True) tm.assert_interval_array_equal(result, expected) @@ -134,12 +134,12 @@ def test_set_na(self, left_right_dtypes): tm.assert_extension_array_equal(result, expected) - def test_setitem_mismatched_inclusive(self): - arr = IntervalArray.from_breaks(range(4), "right") + def test_setitem_mismatched_closed(self): + arr = IntervalArray.from_breaks(range(4)) orig = arr.copy() - other = arr.set_inclusive("both") + other = arr.set_closed("both") - msg = "'value.inclusive' is 'both', expected 'right'" + msg = "'value.closed' is 'both', expected 'right'" with pytest.raises(ValueError, match=msg): arr[0] = other[0] with pytest.raises(ValueError, match=msg): @@ -156,13 +156,13 @@ def test_setitem_mismatched_inclusive(self): arr[:] = other[::-1].astype("category") # empty list should be no-op - arr[:0] = IntervalArray.from_breaks([], "right") + arr[:0] = [] tm.assert_interval_array_equal(arr, orig) def test_repr(): # GH 25022 - arr = IntervalArray.from_tuples([(0, 1), (1, 2)], "right") + arr = IntervalArray.from_tuples([(0, 1), (1, 2)]) result = repr(arr) expected = ( "\n" @@ -254,7 +254,7 @@ def test_arrow_extension_type(): p2 = ArrowIntervalType(pa.int64(), "left") p3 = ArrowIntervalType(pa.int64(), "right") - assert p1.inclusive == "left" + assert p1.closed == "left" assert p1 == p2 assert not p1 == p3 assert hash(p1) == hash(p2) @@ -271,7 +271,7 @@ def test_arrow_array(): result = pa.array(intervals) assert isinstance(result.type, ArrowIntervalType) - assert result.type.inclusive == intervals.inclusive + assert result.type.closed == intervals.closed assert result.type.subtype == pa.int64() assert result.storage.field("left").equals(pa.array([1, 2, 3, 4], type="int64")) assert result.storage.field("right").equals(pa.array([2, 3, 4, 5], type="int64")) @@ -302,7 +302,7 @@ def test_arrow_array_missing(): result = pa.array(arr) assert isinstance(result.type, ArrowIntervalType) - assert result.type.inclusive == arr.inclusive + assert result.type.closed == arr.closed assert result.type.subtype == pa.float64() # fields have missing values (not NaN) @@ -386,11 +386,11 @@ def test_from_arrow_from_raw_struct_array(): import pyarrow as pa arr = pa.array([{"left": 0, "right": 1}, {"left": 1, "right": 2}]) - dtype = pd.IntervalDtype(np.dtype("int64"), inclusive="neither") + dtype = pd.IntervalDtype(np.dtype("int64"), closed="neither") result = dtype.__from_arrow__(arr) expected = IntervalArray.from_breaks( - np.array([0, 1, 2], dtype="int64"), inclusive="neither" + np.array([0, 1, 2], dtype="int64"), closed="neither" ) tm.assert_extension_array_equal(result, expected) @@ -398,51 +398,6 @@ def test_from_arrow_from_raw_struct_array(): tm.assert_extension_array_equal(result, expected) -def test_interval_error_and_warning(): - # GH 40245 - msg = ( - "Deprecated argument `closed` cannot " - "be passed if argument `inclusive` is not None" - ) - with pytest.raises(ValueError, match=msg): - Interval(0, 1, closed="both", inclusive="both") - - msg = "Argument `closed` is deprecated in favor of `inclusive`" - with tm.assert_produces_warning(FutureWarning, match=msg, check_stacklevel=False): - Interval(0, 1, closed="both") - - -def test_interval_array_error_and_warning(): - # GH 40245 - msg = "Can only specify 'closed' or 'inclusive', not both." - with pytest.raises(TypeError, match=msg): - with tm.assert_produces_warning(FutureWarning): - IntervalArray( - [Interval(0, 1), Interval(1, 5)], closed="both", inclusive="both" - ) - - msg = "the 'closed'' keyword is deprecated, use 'inclusive' instead." - with tm.assert_produces_warning(FutureWarning, match=msg): - IntervalArray([Interval(0, 1), Interval(1, 5)], closed="both") - - -@pyarrow_skip -def test_arrow_interval_type_error_and_warning(): - # GH 40245 - import pyarrow as pa - - from pandas.core.arrays.arrow.extension_types import ArrowIntervalType - - msg = "Can only specify 'closed' or 'inclusive', not both." - with pytest.raises(TypeError, match=msg): - with tm.assert_produces_warning(FutureWarning): - ArrowIntervalType(pa.int64(), closed="both", inclusive="both") - - msg = "the 'closed'' keyword is deprecated, use 'inclusive' instead." - with tm.assert_produces_warning(FutureWarning, match=msg): - ArrowIntervalType(pa.int64(), closed="both") - - @pytest.mark.parametrize("timezone", ["UTC", "US/Pacific", "GMT"]) def test_interval_index_subtype(timezone, inclusive_endpoints_fixture): # GH 46999 @@ -451,45 +406,10 @@ def test_interval_index_subtype(timezone, inclusive_endpoints_fixture): result = IntervalIndex.from_arrays( ["2022-01-01", "2022-01-02"], ["2022-01-02", "2022-01-03"], - inclusive=inclusive_endpoints_fixture, + closed=inclusive_endpoints_fixture, dtype=dtype, ) expected = IntervalIndex.from_arrays( - dates[:-1], dates[1:], inclusive=inclusive_endpoints_fixture + dates[:-1], dates[1:], closed=inclusive_endpoints_fixture ) tm.assert_index_equal(result, expected) - - -def test_from_tuples_deprecation(): - # GH#40245 - with tm.assert_produces_warning(FutureWarning): - IntervalArray.from_tuples([(0, 1), (1, 2)], closed="right") - - -def test_from_tuples_deprecation_error(): - # GH#40245 - msg = "Can only specify 'closed' or 'inclusive', not both." - with pytest.raises(TypeError, match=msg): - with tm.assert_produces_warning(FutureWarning): - IntervalArray.from_tuples( - [(0, 1), (1, 2)], closed="right", inclusive="right" - ) - - -def test_from_breaks_deprecation(): - # GH#40245 - with tm.assert_produces_warning(FutureWarning): - IntervalArray.from_breaks([0, 1, 2, 3], closed="right") - - -def test_from_arrays_deprecation(): - # GH#40245 - with tm.assert_produces_warning(FutureWarning): - IntervalArray.from_arrays([0, 1, 2], [1, 2, 3], closed="right") - - -def test_set_closed_deprecated(): - # GH#40245 - array = IntervalArray.from_breaks(range(10)) - with tm.assert_produces_warning(FutureWarning): - array.set_closed(closed="both") diff --git a/pandas/tests/arrays/test_array.py b/pandas/tests/arrays/test_array.py index 79e73fec706f1..9f8c277f07891 100644 --- a/pandas/tests/arrays/test_array.py +++ b/pandas/tests/arrays/test_array.py @@ -133,9 +133,9 @@ ), # Interval ( - [pd.Interval(1, 2, "right"), pd.Interval(3, 4, "right")], + [pd.Interval(1, 2), pd.Interval(3, 4)], "interval", - IntervalArray.from_tuples([(1, 2), (3, 4)], "right"), + IntervalArray.from_tuples([(1, 2), (3, 4)]), ), # Sparse ([0, 1], "Sparse[int64]", SparseArray([0, 1], dtype="int64")), @@ -206,10 +206,7 @@ def test_array_copy(): period_array(["2000", "2001"], freq="D"), ), # interval - ( - [pd.Interval(0, 1, "right"), pd.Interval(1, 2, "right")], - IntervalArray.from_breaks([0, 1, 2], "right"), - ), + ([pd.Interval(0, 1), pd.Interval(1, 2)], IntervalArray.from_breaks([0, 1, 2])), # datetime ( [pd.Timestamp("2000"), pd.Timestamp("2001")], @@ -298,8 +295,8 @@ def test_array_inference(data, expected): [ # mix of frequencies [pd.Period("2000", "D"), pd.Period("2001", "A")], - # mix of inclusive - [pd.Interval(0, 1, "left"), pd.Interval(1, 2, "right")], + # mix of closed + [pd.Interval(0, 1, closed="left"), pd.Interval(1, 2, closed="right")], # Mix of timezones [pd.Timestamp("2000", tz="CET"), pd.Timestamp("2000", tz="UTC")], # Mix of tz-aware and tz-naive diff --git a/pandas/tests/base/test_conversion.py b/pandas/tests/base/test_conversion.py index 3adaddf89cf30..599aaae4d3527 100644 --- a/pandas/tests/base/test_conversion.py +++ b/pandas/tests/base/test_conversion.py @@ -290,10 +290,8 @@ def test_array_multiindex_raises(): ), (pd.array([0, np.nan], dtype="Int64"), np.array([0, pd.NA], dtype=object)), ( - IntervalArray.from_breaks([0, 1, 2], "right"), - np.array( - [pd.Interval(0, 1, "right"), pd.Interval(1, 2, "right")], dtype=object - ), + IntervalArray.from_breaks([0, 1, 2]), + np.array([pd.Interval(0, 1), pd.Interval(1, 2)], dtype=object), ), (SparseArray([0, 1]), np.array([0, 1], dtype=np.int64)), # tz-naive datetime diff --git a/pandas/tests/base/test_value_counts.py b/pandas/tests/base/test_value_counts.py index 55a6cc48ebfc8..c46f1b036dbee 100644 --- a/pandas/tests/base/test_value_counts.py +++ b/pandas/tests/base/test_value_counts.py @@ -133,10 +133,10 @@ def test_value_counts_bins(index_or_series): s1 = Series([1, 1, 2, 3]) res1 = s1.value_counts(bins=1) - exp1 = Series({Interval(0.997, 3.0, "right"): 4}) + exp1 = Series({Interval(0.997, 3.0): 4}) tm.assert_series_equal(res1, exp1) res1n = s1.value_counts(bins=1, normalize=True) - exp1n = Series({Interval(0.997, 3.0, "right"): 1.0}) + exp1n = Series({Interval(0.997, 3.0): 1.0}) tm.assert_series_equal(res1n, exp1n) if isinstance(s1, Index): @@ -149,12 +149,12 @@ def test_value_counts_bins(index_or_series): # these return the same res4 = s1.value_counts(bins=4, dropna=True) - intervals = IntervalIndex.from_breaks([0.997, 1.5, 2.0, 2.5, 3.0], "right") + intervals = IntervalIndex.from_breaks([0.997, 1.5, 2.0, 2.5, 3.0]) exp4 = Series([2, 1, 1, 0], index=intervals.take([0, 1, 3, 2])) tm.assert_series_equal(res4, exp4) res4 = s1.value_counts(bins=4, dropna=False) - intervals = IntervalIndex.from_breaks([0.997, 1.5, 2.0, 2.5, 3.0], "right") + intervals = IntervalIndex.from_breaks([0.997, 1.5, 2.0, 2.5, 3.0]) exp4 = Series([2, 1, 1, 0], index=intervals.take([0, 1, 3, 2])) tm.assert_series_equal(res4, exp4) diff --git a/pandas/tests/dtypes/test_common.py b/pandas/tests/dtypes/test_common.py index 92b99ba6d1fe2..984655c68d56b 100644 --- a/pandas/tests/dtypes/test_common.py +++ b/pandas/tests/dtypes/test_common.py @@ -269,7 +269,7 @@ def test_is_interval_dtype(): assert com.is_interval_dtype(IntervalDtype()) - interval = pd.Interval(1, 2, inclusive="right") + interval = pd.Interval(1, 2, closed="right") assert not com.is_interval_dtype(interval) assert com.is_interval_dtype(pd.IntervalIndex([interval])) diff --git a/pandas/tests/dtypes/test_dtypes.py b/pandas/tests/dtypes/test_dtypes.py index 64849c4223486..aeae5fec481ec 100644 --- a/pandas/tests/dtypes/test_dtypes.py +++ b/pandas/tests/dtypes/test_dtypes.py @@ -577,30 +577,21 @@ def test_hash_vs_equality(self, dtype): "subtype", ["interval[int64]", "Interval[int64]", "int64", np.dtype("int64")] ) def test_construction(self, subtype): - i = IntervalDtype(subtype, inclusive="right") + i = IntervalDtype(subtype, closed="right") assert i.subtype == np.dtype("int64") assert is_interval_dtype(i) - @pytest.mark.parametrize( - "subtype", ["interval[int64, right]", "Interval[int64, right]"] - ) - def test_construction_string_regex(self, subtype): - i = IntervalDtype(subtype=subtype) - assert i.subtype == np.dtype("int64") - assert i.inclusive == "right" - assert is_interval_dtype(i) - @pytest.mark.parametrize( "subtype", ["interval[int64]", "Interval[int64]", "int64", np.dtype("int64")] ) - def test_construction_allows_inclusive_none(self, subtype): + def test_construction_allows_closed_none(self, subtype): # GH#38394 dtype = IntervalDtype(subtype) - assert dtype.inclusive is None + assert dtype.closed is None - def test_inclusive_mismatch(self): - msg = "'inclusive' keyword does not match value specified in dtype string" + def test_closed_mismatch(self): + msg = "'closed' keyword does not match value specified in dtype string" with pytest.raises(ValueError, match=msg): IntervalDtype("interval[int64, left]", "right") @@ -638,16 +629,16 @@ def test_construction_errors(self, subtype): with pytest.raises(TypeError, match=msg): IntervalDtype(subtype) - def test_inclusive_must_match(self): + def test_closed_must_match(self): # GH#37933 dtype = IntervalDtype(np.float64, "left") - msg = "dtype.inclusive and 'inclusive' do not match" + msg = "dtype.closed and 'closed' do not match" with pytest.raises(ValueError, match=msg): - IntervalDtype(dtype, inclusive="both") + IntervalDtype(dtype, closed="both") - def test_inclusive_invalid(self): - with pytest.raises(ValueError, match="inclusive must be one of"): + def test_closed_invalid(self): + with pytest.raises(ValueError, match="closed must be one of"): IntervalDtype(np.float64, "foo") def test_construction_from_string(self, dtype): @@ -747,8 +738,8 @@ def test_equality(self, dtype): ) def test_equality_generic(self, subtype): # GH 18980 - inclusive = "right" if subtype is not None else None - dtype = IntervalDtype(subtype, inclusive=inclusive) + closed = "right" if subtype is not None else None + dtype = IntervalDtype(subtype, closed=closed) assert is_dtype_equal(dtype, "interval") assert is_dtype_equal(dtype, IntervalDtype()) @@ -766,9 +757,9 @@ def test_equality_generic(self, subtype): ) def test_name_repr(self, subtype): # GH 18980 - inclusive = "right" if subtype is not None else None - dtype = IntervalDtype(subtype, inclusive=inclusive) - expected = f"interval[{subtype}, {inclusive}]" + closed = "right" if subtype is not None else None + dtype = IntervalDtype(subtype, closed=closed) + expected = f"interval[{subtype}, {closed}]" assert str(dtype) == expected assert dtype.name == "interval" @@ -822,29 +813,14 @@ def test_not_string(self): # GH30568: though IntervalDtype has object kind, it cannot be string assert not is_string_dtype(IntervalDtype()) - def test_unpickling_without_inclusive(self): + def test_unpickling_without_closed(self): # GH#38394 dtype = IntervalDtype("interval") - assert dtype._inclusive is None + assert dtype._closed is None tm.round_trip_pickle(dtype) - def test_interval_dtype_error_and_warning(self): - # GH 40245 - msg = ( - "Deprecated argument `closed` cannot " - "be passed if argument `inclusive` is not None" - ) - with pytest.raises(ValueError, match=msg): - IntervalDtype("int64", closed="right", inclusive="right") - - msg = "Argument `closed` is deprecated in favor of `inclusive`" - with tm.assert_produces_warning( - FutureWarning, match=msg, check_stacklevel=False - ): - IntervalDtype("int64", closed="right") - class TestCategoricalDtypeParametrized: @pytest.mark.parametrize( diff --git a/pandas/tests/dtypes/test_inference.py b/pandas/tests/dtypes/test_inference.py index 14f37bca71f82..f08d6b8c9feb8 100644 --- a/pandas/tests/dtypes/test_inference.py +++ b/pandas/tests/dtypes/test_inference.py @@ -966,7 +966,7 @@ def test_mixed_dtypes_remain_object_array(self): @pytest.mark.parametrize( "idx", [ - pd.IntervalIndex.from_breaks(range(5), inclusive="both"), + pd.IntervalIndex.from_breaks(range(5), closed="both"), pd.period_range("2016-01-01", periods=3, freq="D"), ], ) @@ -1659,7 +1659,7 @@ def test_categorical(self): @pytest.mark.parametrize("asobject", [True, False]) def test_interval(self, asobject): - idx = pd.IntervalIndex.from_breaks(range(5), inclusive="both") + idx = pd.IntervalIndex.from_breaks(range(5), closed="both") if asobject: idx = idx.astype(object) @@ -1675,21 +1675,21 @@ def test_interval(self, asobject): @pytest.mark.parametrize("value", [Timestamp(0), Timedelta(0), 0, 0.0]) def test_interval_mismatched_closed(self, value): - first = Interval(value, value, inclusive="left") - second = Interval(value, value, inclusive="right") + first = Interval(value, value, closed="left") + second = Interval(value, value, closed="right") - # if inclusive match, we should infer "interval" + # if closed match, we should infer "interval" arr = np.array([first, first], dtype=object) assert lib.infer_dtype(arr, skipna=False) == "interval" - # if inclusive dont match, we should _not_ get "interval" + # if closed dont match, we should _not_ get "interval" arr2 = np.array([first, second], dtype=object) assert lib.infer_dtype(arr2, skipna=False) == "mixed" def test_interval_mismatched_subtype(self): - first = Interval(0, 1, inclusive="left") - second = Interval(Timestamp(0), Timestamp(1), inclusive="left") - third = Interval(Timedelta(0), Timedelta(1), inclusive="left") + first = Interval(0, 1, closed="left") + second = Interval(Timestamp(0), Timestamp(1), closed="left") + third = Interval(Timedelta(0), Timedelta(1), closed="left") arr = np.array([first, second]) assert lib.infer_dtype(arr, skipna=False) == "mixed" @@ -1701,7 +1701,7 @@ def test_interval_mismatched_subtype(self): assert lib.infer_dtype(arr, skipna=False) == "mixed" # float vs int subdtype are compatible - flt_interval = Interval(1.5, 2.5, inclusive="left") + flt_interval = Interval(1.5, 2.5, closed="left") arr = np.array([first, flt_interval], dtype=object) assert lib.infer_dtype(arr, skipna=False) == "interval" diff --git a/pandas/tests/extension/base/setitem.py b/pandas/tests/extension/base/setitem.py index 04fa3c11a6c40..bb948f2281c64 100644 --- a/pandas/tests/extension/base/setitem.py +++ b/pandas/tests/extension/base/setitem.py @@ -10,7 +10,6 @@ import pandas as pd import pandas._testing as tm -from pandas.core.arrays import IntervalArray from pandas.tests.extension.base.base import BaseExtensionTests @@ -77,17 +76,10 @@ def test_setitem_sequence_mismatched_length_raises(self, data, as_array): self.assert_series_equal(ser, original) def test_setitem_empty_indexer(self, data, box_in_series): - data_dtype = type(data) - if box_in_series: data = pd.Series(data) original = data.copy() - - if data_dtype == IntervalArray: - data[np.array([], dtype=int)] = IntervalArray([], "right") - else: - data[np.array([], dtype=int)] = [] - + data[np.array([], dtype=int)] = [] self.assert_equal(data, original) def test_setitem_sequence_broadcasts(self, data, box_in_series): diff --git a/pandas/tests/extension/test_interval.py b/pandas/tests/extension/test_interval.py index eb307d964d736..0f916cea9d518 100644 --- a/pandas/tests/extension/test_interval.py +++ b/pandas/tests/extension/test_interval.py @@ -30,9 +30,7 @@ def make_data(): N = 100 left_array = np.random.uniform(size=N).cumsum() right_array = left_array + np.random.uniform(size=N) - return [ - Interval(left, right, "right") for left, right in zip(left_array, right_array) - ] + return [Interval(left, right) for left, right in zip(left_array, right_array)] @pytest.fixture @@ -43,7 +41,7 @@ def dtype(): @pytest.fixture def data(): """Length-100 PeriodArray for semantics test.""" - return IntervalArray(make_data(), "right") + return IntervalArray(make_data()) @pytest.fixture diff --git a/pandas/tests/frame/constructors/test_from_records.py b/pandas/tests/frame/constructors/test_from_records.py index 715f69cc03828..c6d54e28ca1c8 100644 --- a/pandas/tests/frame/constructors/test_from_records.py +++ b/pandas/tests/frame/constructors/test_from_records.py @@ -229,11 +229,7 @@ def test_from_records_series_list_dict(self): def test_from_records_series_categorical_index(self): # GH#32805 index = CategoricalIndex( - [ - Interval(-20, -10, "right"), - Interval(-10, 0, "right"), - Interval(0, 10, "right"), - ] + [Interval(-20, -10), Interval(-10, 0), Interval(0, 10)] ) series_of_dicts = Series([{"a": 1}, {"a": 2}, {"b": 3}], index=index) frame = DataFrame.from_records(series_of_dicts, index=index) diff --git a/pandas/tests/frame/indexing/test_setitem.py b/pandas/tests/frame/indexing/test_setitem.py index 6b19738becc8e..53fcfe334b770 100644 --- a/pandas/tests/frame/indexing/test_setitem.py +++ b/pandas/tests/frame/indexing/test_setitem.py @@ -236,10 +236,7 @@ def test_setitem_dict_preserves_dtypes(self): "obj,dtype", [ (Period("2020-01"), PeriodDtype("M")), - ( - Interval(left=0, right=5, inclusive="right"), - IntervalDtype("int64", "right"), - ), + (Interval(left=0, right=5), IntervalDtype("int64", "right")), ( Timestamp("2011-01-01", tz="US/Eastern"), DatetimeTZDtype(tz="US/Eastern"), diff --git a/pandas/tests/frame/methods/test_combine_first.py b/pandas/tests/frame/methods/test_combine_first.py index 6bfe07feb010d..c71b688d390d4 100644 --- a/pandas/tests/frame/methods/test_combine_first.py +++ b/pandas/tests/frame/methods/test_combine_first.py @@ -402,7 +402,7 @@ def test_combine_first_string_dtype_only_na(self, nullable_string_dtype): (datetime(2020, 1, 1), datetime(2020, 1, 2)), (pd.Period("2020-01-01", "D"), pd.Period("2020-01-02", "D")), (pd.Timedelta("89 days"), pd.Timedelta("60 min")), - (pd.Interval(left=0, right=1), pd.Interval(left=2, right=3, inclusive="left")), + (pd.Interval(left=0, right=1), pd.Interval(left=2, right=3, closed="left")), ], ) def test_combine_first_timestamp_bug(scalar1, scalar2, nulls_fixture): diff --git a/pandas/tests/frame/methods/test_reset_index.py b/pandas/tests/frame/methods/test_reset_index.py index bd168e4f14558..37431bc291b76 100644 --- a/pandas/tests/frame/methods/test_reset_index.py +++ b/pandas/tests/frame/methods/test_reset_index.py @@ -751,7 +751,7 @@ def test_reset_index_interval_columns_object_cast(): result = df.reset_index() expected = DataFrame( [[1, 1.0, 0.0], [2, 0.0, 1.0]], - columns=Index(["Year", Interval(0, 1, "right"), Interval(1, 2, "right")]), + columns=Index(["Year", Interval(0, 1), Interval(1, 2)]), ) tm.assert_frame_equal(result, expected) diff --git a/pandas/tests/frame/methods/test_round.py b/pandas/tests/frame/methods/test_round.py index 77cadfff55e2f..dd9206940bcd6 100644 --- a/pandas/tests/frame/methods/test_round.py +++ b/pandas/tests/frame/methods/test_round.py @@ -210,7 +210,7 @@ def test_round_nonunique_categorical(self): def test_round_interval_category_columns(self): # GH#30063 - columns = pd.CategoricalIndex(pd.interval_range(0, 2, inclusive="right")) + columns = pd.CategoricalIndex(pd.interval_range(0, 2)) df = DataFrame([[0.66, 1.1], [0.3, 0.25]], columns=columns) result = df.round() diff --git a/pandas/tests/frame/methods/test_sort_index.py b/pandas/tests/frame/methods/test_sort_index.py index 9cad965e9cb5c..5d1cc3d4ecee5 100644 --- a/pandas/tests/frame/methods/test_sort_index.py +++ b/pandas/tests/frame/methods/test_sort_index.py @@ -384,7 +384,7 @@ def test_sort_index_intervalindex(self): result = model.groupby(["X1", "X2"], observed=True).mean().unstack() expected = IntervalIndex.from_tuples( - [(-3.0, -0.5), (-0.5, 0.0), (0.0, 0.5), (0.5, 3.0)], inclusive="right" + [(-3.0, -0.5), (-0.5, 0.0), (0.0, 0.5), (0.5, 3.0)], closed="right" ) result = result.columns.levels[1].categories tm.assert_index_equal(result, expected) @@ -729,11 +729,7 @@ def test_sort_index_multilevel_repr_8017(self, gen, extra): [ pytest.param(["a", "b", "c"], id="str"), pytest.param( - [ - pd.Interval(0, 1, "right"), - pd.Interval(1, 2, "right"), - pd.Interval(2, 3, "right"), - ], + [pd.Interval(0, 1), pd.Interval(1, 2), pd.Interval(2, 3)], id="pd.Interval", ), ], diff --git a/pandas/tests/frame/methods/test_to_csv.py b/pandas/tests/frame/methods/test_to_csv.py index df7bc04202e39..1933278efb443 100644 --- a/pandas/tests/frame/methods/test_to_csv.py +++ b/pandas/tests/frame/methods/test_to_csv.py @@ -1316,7 +1316,7 @@ def test_to_csv_categorical_and_interval(self): pd.Interval( Timestamp("2020-01-01"), Timestamp("2020-01-02"), - inclusive="both", + closed="both", ) ] } diff --git a/pandas/tests/frame/test_constructors.py b/pandas/tests/frame/test_constructors.py index 4c2e9b8530e81..7d3af7dfa9a42 100644 --- a/pandas/tests/frame/test_constructors.py +++ b/pandas/tests/frame/test_constructors.py @@ -902,10 +902,7 @@ def test_constructor_dict_extension_scalar(self, ea_scalar_and_dtype): "data,dtype", [ (Period("2020-01"), PeriodDtype("M")), - ( - Interval(left=0, right=5, inclusive="right"), - IntervalDtype("int64", "right"), - ), + (Interval(left=0, right=5), IntervalDtype("int64", "right")), ( Timestamp("2011-01-01", tz="US/Eastern"), DatetimeTZDtype(tz="US/Eastern"), @@ -2431,16 +2428,16 @@ def test_constructor_series_nonexact_categoricalindex(self): result = DataFrame({"1": ser1, "2": ser2}) index = CategoricalIndex( [ - Interval(-0.099, 9.9, inclusive="right"), - Interval(9.9, 19.8, inclusive="right"), - Interval(19.8, 29.7, inclusive="right"), - Interval(29.7, 39.6, inclusive="right"), - Interval(39.6, 49.5, inclusive="right"), - Interval(49.5, 59.4, inclusive="right"), - Interval(59.4, 69.3, inclusive="right"), - Interval(69.3, 79.2, inclusive="right"), - Interval(79.2, 89.1, inclusive="right"), - Interval(89.1, 99, inclusive="right"), + Interval(-0.099, 9.9, closed="right"), + Interval(9.9, 19.8, closed="right"), + Interval(19.8, 29.7, closed="right"), + Interval(29.7, 39.6, closed="right"), + Interval(39.6, 49.5, closed="right"), + Interval(49.5, 59.4, closed="right"), + Interval(59.4, 69.3, closed="right"), + Interval(69.3, 79.2, closed="right"), + Interval(79.2, 89.1, closed="right"), + Interval(89.1, 99, closed="right"), ], ordered=True, ) diff --git a/pandas/tests/groupby/aggregate/test_cython.py b/pandas/tests/groupby/aggregate/test_cython.py index 6c5a3ae67c78a..0a3845617b32d 100644 --- a/pandas/tests/groupby/aggregate/test_cython.py +++ b/pandas/tests/groupby/aggregate/test_cython.py @@ -216,7 +216,7 @@ def test_cython_agg_empty_buckets_nanops(observed): result = df.groupby(pd.cut(df["a"], grps), observed=observed)._cython_agg_general( "sum", alt=None, numeric_only=True ) - intervals = pd.interval_range(0, 20, freq=5, inclusive="right") + intervals = pd.interval_range(0, 20, freq=5) expected = DataFrame( {"a": [0, 0, 36, 0]}, index=pd.CategoricalIndex(intervals, name="a", ordered=True), diff --git a/pandas/tests/groupby/test_grouping.py b/pandas/tests/groupby/test_grouping.py index 6da07dafcda74..728575a80f32f 100644 --- a/pandas/tests/groupby/test_grouping.py +++ b/pandas/tests/groupby/test_grouping.py @@ -799,13 +799,13 @@ def test_get_group_empty_bins(self, observed): # TODO: should prob allow a str of Interval work as well # IOW '(0, 5]' - result = g.get_group(pd.Interval(0, 5, "right")) + result = g.get_group(pd.Interval(0, 5)) expected = DataFrame([3, 1], index=[0, 1]) tm.assert_frame_equal(result, expected) - msg = r"Interval\(10, 15, inclusive='right'\)" + msg = r"Interval\(10, 15, closed='right'\)" with pytest.raises(KeyError, match=msg): - g.get_group(pd.Interval(10, 15, "right")) + g.get_group(pd.Interval(10, 15)) def test_get_group_grouped_by_tuple(self): # GH 8121 diff --git a/pandas/tests/indexes/categorical/test_astype.py b/pandas/tests/indexes/categorical/test_astype.py index ec3e3dca92808..854ae8b62db30 100644 --- a/pandas/tests/indexes/categorical/test_astype.py +++ b/pandas/tests/indexes/categorical/test_astype.py @@ -26,9 +26,7 @@ def test_astype(self): assert not isinstance(result, CategoricalIndex) # interval - ii = IntervalIndex.from_arrays( - left=[-0.001, 2.0], right=[2, 4], inclusive="right" - ) + ii = IntervalIndex.from_arrays(left=[-0.001, 2.0], right=[2, 4], closed="right") ci = CategoricalIndex( Categorical.from_codes([0, 1, -1], categories=ii, ordered=True) diff --git a/pandas/tests/indexes/categorical/test_reindex.py b/pandas/tests/indexes/categorical/test_reindex.py index 8764063a1a008..1337eff1f1c2f 100644 --- a/pandas/tests/indexes/categorical/test_reindex.py +++ b/pandas/tests/indexes/categorical/test_reindex.py @@ -69,15 +69,15 @@ def test_reindex_empty_index(self): def test_reindex_categorical_added_category(self): # GH 42424 ci = CategoricalIndex( - [Interval(0, 1, inclusive="right"), Interval(1, 2, inclusive="right")], + [Interval(0, 1, closed="right"), Interval(1, 2, closed="right")], ordered=True, ) ci_add = CategoricalIndex( [ - Interval(0, 1, inclusive="right"), - Interval(1, 2, inclusive="right"), - Interval(2, 3, inclusive="right"), - Interval(3, 4, inclusive="right"), + Interval(0, 1, closed="right"), + Interval(1, 2, closed="right"), + Interval(2, 3, closed="right"), + Interval(3, 4, closed="right"), ], ordered=True, ) diff --git a/pandas/tests/indexes/interval/test_astype.py b/pandas/tests/indexes/interval/test_astype.py index 6751a383699bb..c253a745ef5a2 100644 --- a/pandas/tests/indexes/interval/test_astype.py +++ b/pandas/tests/indexes/interval/test_astype.py @@ -82,7 +82,7 @@ class TestIntSubtype(AstypeTests): indexes = [ IntervalIndex.from_breaks(np.arange(-10, 11, dtype="int64")), - IntervalIndex.from_breaks(np.arange(100, dtype="uint64"), inclusive="left"), + IntervalIndex.from_breaks(np.arange(100, dtype="uint64"), closed="left"), ] @pytest.fixture(params=indexes) @@ -93,12 +93,10 @@ def index(self, request): "subtype", ["float64", "datetime64[ns]", "timedelta64[ns]"] ) def test_subtype_conversion(self, index, subtype): - dtype = IntervalDtype(subtype, index.inclusive) + dtype = IntervalDtype(subtype, index.closed) result = index.astype(dtype) expected = IntervalIndex.from_arrays( - index.left.astype(subtype), - index.right.astype(subtype), - inclusive=index.inclusive, + index.left.astype(subtype), index.right.astype(subtype), closed=index.closed ) tm.assert_index_equal(result, expected) @@ -107,19 +105,19 @@ def test_subtype_conversion(self, index, subtype): ) def test_subtype_integer(self, subtype_start, subtype_end): index = IntervalIndex.from_breaks(np.arange(100, dtype=subtype_start)) - dtype = IntervalDtype(subtype_end, index.inclusive) + dtype = IntervalDtype(subtype_end, index.closed) result = index.astype(dtype) expected = IntervalIndex.from_arrays( index.left.astype(subtype_end), index.right.astype(subtype_end), - inclusive=index.inclusive, + closed=index.closed, ) tm.assert_index_equal(result, expected) @pytest.mark.xfail(reason="GH#15832") def test_subtype_integer_errors(self): # int64 -> uint64 fails with negative values - index = interval_range(-10, 10, inclusive="right") + index = interval_range(-10, 10) dtype = IntervalDtype("uint64", "right") # Until we decide what the exception message _should_ be, we @@ -135,11 +133,9 @@ class TestFloatSubtype(AstypeTests): """Tests specific to IntervalIndex with float subtype""" indexes = [ - interval_range(-10.0, 10.0, inclusive="neither"), + interval_range(-10.0, 10.0, closed="neither"), IntervalIndex.from_arrays( - [-1.5, np.nan, 0.0, 0.0, 1.5], - [-0.5, np.nan, 1.0, 1.0, 3.0], - inclusive="both", + [-1.5, np.nan, 0.0, 0.0, 1.5], [-0.5, np.nan, 1.0, 1.0, 3.0], closed="both" ), ] @@ -153,9 +149,7 @@ def test_subtype_integer(self, subtype): dtype = IntervalDtype(subtype, "right") result = index.astype(dtype) expected = IntervalIndex.from_arrays( - index.left.astype(subtype), - index.right.astype(subtype), - inclusive=index.inclusive, + index.left.astype(subtype), index.right.astype(subtype), closed=index.closed ) tm.assert_index_equal(result, expected) @@ -170,15 +164,13 @@ def test_subtype_integer_with_non_integer_borders(self, subtype): dtype = IntervalDtype(subtype, "right") result = index.astype(dtype) expected = IntervalIndex.from_arrays( - index.left.astype(subtype), - index.right.astype(subtype), - inclusive=index.inclusive, + index.left.astype(subtype), index.right.astype(subtype), closed=index.closed ) tm.assert_index_equal(result, expected) def test_subtype_integer_errors(self): # float64 -> uint64 fails with negative values - index = interval_range(-10.0, 10.0, inclusive="right") + index = interval_range(-10.0, 10.0) dtype = IntervalDtype("uint64", "right") msg = re.escape( "Cannot convert interval[float64, right] to interval[uint64, right]; " @@ -199,10 +191,10 @@ class TestDatetimelikeSubtype(AstypeTests): """Tests specific to IntervalIndex with datetime-like subtype""" indexes = [ - interval_range(Timestamp("2018-01-01"), periods=10, inclusive="neither"), + interval_range(Timestamp("2018-01-01"), periods=10, closed="neither"), interval_range(Timestamp("2018-01-01"), periods=10).insert(2, NaT), interval_range(Timestamp("2018-01-01", tz="US/Eastern"), periods=10), - interval_range(Timedelta("0 days"), periods=10, inclusive="both"), + interval_range(Timedelta("0 days"), periods=10, closed="both"), interval_range(Timedelta("0 days"), periods=10).insert(2, NaT), ] @@ -224,9 +216,7 @@ def test_subtype_integer(self, index, subtype): new_left = index.left.astype(subtype) new_right = index.right.astype(subtype) - expected = IntervalIndex.from_arrays( - new_left, new_right, inclusive=index.inclusive - ) + expected = IntervalIndex.from_arrays(new_left, new_right, closed=index.closed) tm.assert_index_equal(result, expected) def test_subtype_float(self, index): diff --git a/pandas/tests/indexes/interval/test_base.py b/pandas/tests/indexes/interval/test_base.py index 933707bfe8357..c44303aa2c862 100644 --- a/pandas/tests/indexes/interval/test_base.py +++ b/pandas/tests/indexes/interval/test_base.py @@ -16,14 +16,14 @@ class TestBase(Base): @pytest.fixture def simple_index(self) -> IntervalIndex: - return self._index_cls.from_breaks(range(11), inclusive="right") + return self._index_cls.from_breaks(range(11), closed="right") @pytest.fixture def index(self): return tm.makeIntervalIndex(10) - def create_index(self, *, inclusive="right"): - return IntervalIndex.from_breaks(range(11), inclusive=inclusive) + def create_index(self, *, closed="right"): + return IntervalIndex.from_breaks(range(11), closed=closed) def test_repr_max_seq_item_setting(self): # override base test: not a valid repr as we use interval notation @@ -34,13 +34,13 @@ def test_repr_roundtrip(self): pass def test_take(self, closed): - index = self.create_index(inclusive=closed) + index = self.create_index(closed=closed) result = index.take(range(10)) tm.assert_index_equal(result, index) result = index.take([0, 0, 1]) - expected = IntervalIndex.from_arrays([0, 0, 1], [1, 1, 2], inclusive=closed) + expected = IntervalIndex.from_arrays([0, 0, 1], [1, 1, 2], closed=closed) tm.assert_index_equal(result, expected) def test_where(self, simple_index, listlike_box): diff --git a/pandas/tests/indexes/interval/test_constructors.py b/pandas/tests/indexes/interval/test_constructors.py index a23f66d241cd9..a71a8f9e34ea9 100644 --- a/pandas/tests/indexes/interval/test_constructors.py +++ b/pandas/tests/indexes/interval/test_constructors.py @@ -53,24 +53,14 @@ class ConstructorTests: ) def test_constructor(self, constructor, breaks, closed, name): result_kwargs = self.get_kwargs_from_breaks(breaks, closed) - result = constructor(inclusive=closed, name=name, **result_kwargs) + result = constructor(closed=closed, name=name, **result_kwargs) - assert result.inclusive == closed + assert result.closed == closed assert result.name == name assert result.dtype.subtype == getattr(breaks, "dtype", "int64") tm.assert_index_equal(result.left, Index(breaks[:-1])) tm.assert_index_equal(result.right, Index(breaks[1:])) - def test_constructor_inclusive_default(self, constructor, name): - result_kwargs = self.get_kwargs_from_breaks([3, 14, 15, 92, 653]) - inclusive_in = result_kwargs.pop("inclusive", None) - result = constructor(name=name, **result_kwargs) - - if inclusive_in is not None: - result_kwargs["inclusive"] = "right" - expected = constructor(name=name, **result_kwargs) - tm.assert_index_equal(result, expected) - @pytest.mark.parametrize( "breaks, subtype", [ @@ -104,8 +94,8 @@ def test_constructor_dtype(self, constructor, breaks, subtype): timedelta_range("1 day", periods=5), ], ) - def test_constructor_pass_inclusive(self, constructor, breaks): - # not passing inclusive to IntervalDtype, but to IntervalArray constructor + def test_constructor_pass_closed(self, constructor, breaks): + # not passing closed to IntervalDtype, but to IntervalArray constructor warn = None if isinstance(constructor, partial) and constructor.func is Index: # passing kwargs to Index is deprecated @@ -118,20 +108,20 @@ def test_constructor_pass_inclusive(self, constructor, breaks): for dtype in (iv_dtype, str(iv_dtype)): with tm.assert_produces_warning(warn): - result = constructor(dtype=dtype, inclusive="left", **result_kwargs) - assert result.dtype.inclusive == "left" + result = constructor(dtype=dtype, closed="left", **result_kwargs) + assert result.dtype.closed == "left" @pytest.mark.filterwarnings("ignore:Passing keywords other:FutureWarning") @pytest.mark.parametrize("breaks", [[np.nan] * 2, [np.nan] * 4, [np.nan] * 50]) def test_constructor_nan(self, constructor, breaks, closed): # GH 18421 result_kwargs = self.get_kwargs_from_breaks(breaks) - result = constructor(inclusive=closed, **result_kwargs) + result = constructor(closed=closed, **result_kwargs) expected_subtype = np.float64 expected_values = np.array(breaks[:-1], dtype=object) - assert result.inclusive == closed + assert result.closed == closed assert result.dtype.subtype == expected_subtype tm.assert_numpy_array_equal(np.array(result), expected_values) @@ -149,13 +139,13 @@ def test_constructor_nan(self, constructor, breaks, closed): def test_constructor_empty(self, constructor, breaks, closed): # GH 18421 result_kwargs = self.get_kwargs_from_breaks(breaks) - result = constructor(inclusive=closed, **result_kwargs) + result = constructor(closed=closed, **result_kwargs) expected_values = np.array([], dtype=object) expected_subtype = getattr(breaks, "dtype", np.int64) assert result.empty - assert result.inclusive == closed + assert result.closed == closed assert result.dtype.subtype == expected_subtype tm.assert_numpy_array_equal(np.array(result), expected_values) @@ -193,10 +183,10 @@ def test_generic_errors(self, constructor): # filler input data to be used when supplying invalid kwargs filler = self.get_kwargs_from_breaks(range(10)) - # invalid inclusive - msg = "inclusive must be one of 'right', 'left', 'both', 'neither'" + # invalid closed + msg = "closed must be one of 'right', 'left', 'both', 'neither'" with pytest.raises(ValueError, match=msg): - constructor(inclusive="invalid", **filler) + constructor(closed="invalid", **filler) # unsupported dtype msg = "dtype must be an IntervalDtype, got int64" @@ -229,7 +219,7 @@ class TestFromArrays(ConstructorTests): def constructor(self): return IntervalIndex.from_arrays - def get_kwargs_from_breaks(self, breaks, inclusive="right"): + def get_kwargs_from_breaks(self, breaks, closed="right"): """ converts intervals in breaks format to a dictionary of kwargs to specific to the format expected by IntervalIndex.from_arrays @@ -278,7 +268,7 @@ class TestFromBreaks(ConstructorTests): def constructor(self): return IntervalIndex.from_breaks - def get_kwargs_from_breaks(self, breaks, inclusive="right"): + def get_kwargs_from_breaks(self, breaks, closed="right"): """ converts intervals in breaks format to a dictionary of kwargs to specific to the format expected by IntervalIndex.from_breaks @@ -316,7 +306,7 @@ class TestFromTuples(ConstructorTests): def constructor(self): return IntervalIndex.from_tuples - def get_kwargs_from_breaks(self, breaks, inclusive="right"): + def get_kwargs_from_breaks(self, breaks, closed="right"): """ converts intervals in breaks format to a dictionary of kwargs to specific to the format expected by IntervalIndex.from_tuples @@ -366,7 +356,7 @@ class TestClassConstructors(ConstructorTests): def constructor(self, request): return request.param - def get_kwargs_from_breaks(self, breaks, inclusive="right"): + def get_kwargs_from_breaks(self, breaks, closed="right"): """ converts intervals in breaks format to a dictionary of kwargs to specific to the format expected by the IntervalIndex/Index constructors @@ -375,7 +365,7 @@ def get_kwargs_from_breaks(self, breaks, inclusive="right"): return {"data": breaks} ivs = [ - Interval(left, right, inclusive) if notna(left) else left + Interval(left, right, closed) if notna(left) else left for left, right in zip(breaks[:-1], breaks[1:]) ] @@ -399,9 +389,9 @@ def test_constructor_string(self): pass def test_constructor_errors(self, constructor): - # mismatched inclusive within intervals with no constructor override - ivs = [Interval(0, 1, inclusive="right"), Interval(2, 3, inclusive="left")] - msg = "intervals must all be inclusive on the same side" + # mismatched closed within intervals with no constructor override + ivs = [Interval(0, 1, closed="right"), Interval(2, 3, closed="left")] + msg = "intervals must all be closed on the same side" with pytest.raises(ValueError, match=msg): constructor(ivs) @@ -420,32 +410,29 @@ def test_constructor_errors(self, constructor): @pytest.mark.filterwarnings("ignore:Passing keywords other:FutureWarning") @pytest.mark.parametrize( - "data, inclusive", + "data, closed", [ ([], "both"), ([np.nan, np.nan], "neither"), ( - [ - Interval(0, 3, inclusive="neither"), - Interval(2, 5, inclusive="neither"), - ], + [Interval(0, 3, closed="neither"), Interval(2, 5, closed="neither")], "left", ), ( - [Interval(0, 3, inclusive="left"), Interval(2, 5, inclusive="right")], + [Interval(0, 3, closed="left"), Interval(2, 5, closed="right")], "neither", ), - (IntervalIndex.from_breaks(range(5), inclusive="both"), "right"), + (IntervalIndex.from_breaks(range(5), closed="both"), "right"), ], ) - def test_override_inferred_inclusive(self, constructor, data, inclusive): + def test_override_inferred_closed(self, constructor, data, closed): # GH 19370 if isinstance(data, IntervalIndex): tuples = data.to_tuples() else: tuples = [(iv.left, iv.right) if notna(iv) else iv for iv in data] - expected = IntervalIndex.from_tuples(tuples, inclusive=inclusive) - result = constructor(data, inclusive=inclusive) + expected = IntervalIndex.from_tuples(tuples, closed=closed) + result = constructor(data, closed=closed) tm.assert_index_equal(result, expected) @pytest.mark.parametrize( @@ -460,27 +447,27 @@ def test_index_object_dtype(self, values_constructor): assert type(result) is Index tm.assert_numpy_array_equal(result.values, np.array(values)) - def test_index_mixed_inclusive(self): + def test_index_mixed_closed(self): # GH27172 intervals = [ - Interval(0, 1, inclusive="left"), - Interval(1, 2, inclusive="right"), - Interval(2, 3, inclusive="neither"), - Interval(3, 4, inclusive="both"), + Interval(0, 1, closed="left"), + Interval(1, 2, closed="right"), + Interval(2, 3, closed="neither"), + Interval(3, 4, closed="both"), ] result = Index(intervals) expected = Index(intervals, dtype=object) tm.assert_index_equal(result, expected) -def test_dtype_inclusive_mismatch(): - # GH#38394 +def test_dtype_closed_mismatch(): + # GH#38394 closed specified in both dtype and IntervalIndex constructor dtype = IntervalDtype(np.int64, "left") - msg = "inclusive keyword does not match dtype.inclusive" + msg = "closed keyword does not match dtype.closed" with pytest.raises(ValueError, match=msg): - IntervalIndex([], dtype=dtype, inclusive="neither") + IntervalIndex([], dtype=dtype, closed="neither") with pytest.raises(ValueError, match=msg): - IntervalArray([], dtype=dtype, inclusive="neither") + IntervalArray([], dtype=dtype, closed="neither") diff --git a/pandas/tests/indexes/interval/test_equals.py b/pandas/tests/indexes/interval/test_equals.py index a873116600d6d..87e2348e5fdb3 100644 --- a/pandas/tests/indexes/interval/test_equals.py +++ b/pandas/tests/indexes/interval/test_equals.py @@ -8,7 +8,7 @@ class TestEquals: def test_equals(self, closed): - expected = IntervalIndex.from_breaks(np.arange(5), inclusive=closed) + expected = IntervalIndex.from_breaks(np.arange(5), closed=closed) assert expected.equals(expected) assert expected.equals(expected.copy()) @@ -21,16 +21,16 @@ def test_equals(self, closed): assert not expected.equals(date_range("20130101", periods=2)) expected_name1 = IntervalIndex.from_breaks( - np.arange(5), inclusive=closed, name="foo" + np.arange(5), closed=closed, name="foo" ) expected_name2 = IntervalIndex.from_breaks( - np.arange(5), inclusive=closed, name="bar" + np.arange(5), closed=closed, name="bar" ) assert expected.equals(expected_name1) assert expected_name1.equals(expected_name2) - for other_inclusive in {"left", "right", "both", "neither"} - {closed}: - expected_other_inclusive = IntervalIndex.from_breaks( - np.arange(5), inclusive=other_inclusive + for other_closed in {"left", "right", "both", "neither"} - {closed}: + expected_other_closed = IntervalIndex.from_breaks( + np.arange(5), closed=other_closed ) - assert not expected.equals(expected_other_inclusive) + assert not expected.equals(expected_other_closed) diff --git a/pandas/tests/indexes/interval/test_formats.py b/pandas/tests/indexes/interval/test_formats.py index 2d9b8c83c7ab2..db477003900bc 100644 --- a/pandas/tests/indexes/interval/test_formats.py +++ b/pandas/tests/indexes/interval/test_formats.py @@ -17,8 +17,7 @@ class TestIntervalIndexRendering: def test_frame_repr(self): # https://github.com/pandas-dev/pandas/pull/24134/files df = DataFrame( - {"A": [1, 2, 3, 4]}, - index=IntervalIndex.from_breaks([0, 1, 2, 3, 4], "right"), + {"A": [1, 2, 3, 4]}, index=IntervalIndex.from_breaks([0, 1, 2, 3, 4]) ) result = repr(df) expected = " A\n(0, 1] 1\n(1, 2] 2\n(2, 3] 3\n(3, 4] 4" @@ -41,7 +40,7 @@ def test_frame_repr(self): ) def test_repr_missing(self, constructor, expected): # GH 25984 - index = IntervalIndex.from_tuples([(0, 1), np.nan, (2, 3)], "right") + index = IntervalIndex.from_tuples([(0, 1), np.nan, (2, 3)]) obj = constructor(list("abc"), index=index) result = repr(obj) assert result == expected @@ -58,8 +57,7 @@ def test_repr_floats(self): Float64Index([329.973, 345.137], dtype="float64"), Float64Index([345.137, 360.191], dtype="float64"), ) - ], - "right", + ] ), ) result = str(markers) @@ -67,7 +65,7 @@ def test_repr_floats(self): assert result == expected @pytest.mark.parametrize( - "tuples, inclusive, expected_data", + "tuples, closed, expected_data", [ ([(0, 1), (1, 2), (2, 3)], "left", ["[0, 1)", "[1, 2)", "[2, 3)"]), ( @@ -99,9 +97,9 @@ def test_repr_floats(self): ), ], ) - def test_to_native_types(self, tuples, inclusive, expected_data): + def test_to_native_types(self, tuples, closed, expected_data): # GH 28210 - index = IntervalIndex.from_tuples(tuples, inclusive=inclusive) + index = IntervalIndex.from_tuples(tuples, closed=closed) result = index._format_native_types() expected = np.array(expected_data) tm.assert_numpy_array_equal(result, expected) diff --git a/pandas/tests/indexes/interval/test_indexing.py b/pandas/tests/indexes/interval/test_indexing.py index 74d17b31aff27..9b4afcc9c00b8 100644 --- a/pandas/tests/indexes/interval/test_indexing.py +++ b/pandas/tests/indexes/interval/test_indexing.py @@ -29,23 +29,23 @@ class TestGetLoc: @pytest.mark.parametrize("side", ["right", "left", "both", "neither"]) def test_get_loc_interval(self, closed, side): - idx = IntervalIndex.from_tuples([(0, 1), (2, 3)], inclusive=closed) + idx = IntervalIndex.from_tuples([(0, 1), (2, 3)], closed=closed) for bound in [[0, 1], [1, 2], [2, 3], [3, 4], [0, 2], [2.5, 3], [-1, 4]]: # if get_loc is supplied an interval, it should only search # for exact matches, not overlaps or covers, else KeyError. - msg = re.escape(f"Interval({bound[0]}, {bound[1]}, inclusive='{side}')") + msg = re.escape(f"Interval({bound[0]}, {bound[1]}, closed='{side}')") if closed == side: if bound == [0, 1]: - assert idx.get_loc(Interval(0, 1, inclusive=side)) == 0 + assert idx.get_loc(Interval(0, 1, closed=side)) == 0 elif bound == [2, 3]: - assert idx.get_loc(Interval(2, 3, inclusive=side)) == 1 + assert idx.get_loc(Interval(2, 3, closed=side)) == 1 else: with pytest.raises(KeyError, match=msg): - idx.get_loc(Interval(*bound, inclusive=side)) + idx.get_loc(Interval(*bound, closed=side)) else: with pytest.raises(KeyError, match=msg): - idx.get_loc(Interval(*bound, inclusive=side)) + idx.get_loc(Interval(*bound, closed=side)) @pytest.mark.parametrize("scalar", [-0.5, 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5]) def test_get_loc_scalar(self, closed, scalar): @@ -59,7 +59,7 @@ def test_get_loc_scalar(self, closed, scalar): "neither": {0.5: 0, 2.5: 1}, } - idx = IntervalIndex.from_tuples([(0, 1), (2, 3)], inclusive=closed) + idx = IntervalIndex.from_tuples([(0, 1), (2, 3)], closed=closed) # if get_loc is supplied a scalar, it should return the index of # the interval which contains the scalar, or KeyError. @@ -72,7 +72,7 @@ def test_get_loc_scalar(self, closed, scalar): @pytest.mark.parametrize("scalar", [-1, 0, 0.5, 3, 4.5, 5, 6]) def test_get_loc_length_one_scalar(self, scalar, closed): # GH 20921 - index = IntervalIndex.from_tuples([(0, 5)], inclusive=closed) + index = IntervalIndex.from_tuples([(0, 5)], closed=closed) if scalar in index[0]: result = index.get_loc(scalar) assert result == 0 @@ -80,21 +80,19 @@ def test_get_loc_length_one_scalar(self, scalar, closed): with pytest.raises(KeyError, match=str(scalar)): index.get_loc(scalar) - @pytest.mark.parametrize("other_inclusive", ["left", "right", "both", "neither"]) + @pytest.mark.parametrize("other_closed", ["left", "right", "both", "neither"]) @pytest.mark.parametrize("left, right", [(0, 5), (-1, 4), (-1, 6), (6, 7)]) - def test_get_loc_length_one_interval(self, left, right, closed, other_inclusive): + def test_get_loc_length_one_interval(self, left, right, closed, other_closed): # GH 20921 - index = IntervalIndex.from_tuples([(0, 5)], inclusive=closed) - interval = Interval(left, right, inclusive=other_inclusive) + index = IntervalIndex.from_tuples([(0, 5)], closed=closed) + interval = Interval(left, right, closed=other_closed) if interval == index[0]: result = index.get_loc(interval) assert result == 0 else: with pytest.raises( KeyError, - match=re.escape( - f"Interval({left}, {right}, inclusive='{other_inclusive}')" - ), + match=re.escape(f"Interval({left}, {right}, closed='{other_closed}')"), ): index.get_loc(interval) @@ -198,35 +196,23 @@ class TestGetIndexer: @pytest.mark.parametrize( "query, expected", [ - ([Interval(2, 4, inclusive="right")], [1]), - ([Interval(2, 4, inclusive="left")], [-1]), - ([Interval(2, 4, inclusive="both")], [-1]), - ([Interval(2, 4, inclusive="neither")], [-1]), - ([Interval(1, 4, inclusive="right")], [-1]), - ([Interval(0, 4, inclusive="right")], [-1]), - ([Interval(0.5, 1.5, inclusive="right")], [-1]), - ( - [Interval(2, 4, inclusive="right"), Interval(0, 1, inclusive="right")], - [1, -1], - ), - ( - [Interval(2, 4, inclusive="right"), Interval(2, 4, inclusive="right")], - [1, 1], - ), - ( - [Interval(5, 7, inclusive="right"), Interval(2, 4, inclusive="right")], - [2, 1], - ), - ( - [Interval(2, 4, inclusive="right"), Interval(2, 4, inclusive="left")], - [1, -1], - ), + ([Interval(2, 4, closed="right")], [1]), + ([Interval(2, 4, closed="left")], [-1]), + ([Interval(2, 4, closed="both")], [-1]), + ([Interval(2, 4, closed="neither")], [-1]), + ([Interval(1, 4, closed="right")], [-1]), + ([Interval(0, 4, closed="right")], [-1]), + ([Interval(0.5, 1.5, closed="right")], [-1]), + ([Interval(2, 4, closed="right"), Interval(0, 1, closed="right")], [1, -1]), + ([Interval(2, 4, closed="right"), Interval(2, 4, closed="right")], [1, 1]), + ([Interval(5, 7, closed="right"), Interval(2, 4, closed="right")], [2, 1]), + ([Interval(2, 4, closed="right"), Interval(2, 4, closed="left")], [1, -1]), ], ) def test_get_indexer_with_interval(self, query, expected): tuples = [(0, 2), (2, 4), (5, 7)] - index = IntervalIndex.from_tuples(tuples, inclusive="right") + index = IntervalIndex.from_tuples(tuples, closed="right") result = index.get_indexer(query) expected = np.array(expected, dtype="intp") @@ -255,7 +241,7 @@ def test_get_indexer_with_interval(self, query, expected): def test_get_indexer_with_int_and_float(self, query, expected): tuples = [(0, 1), (1, 2), (3, 4)] - index = IntervalIndex.from_tuples(tuples, inclusive="right") + index = IntervalIndex.from_tuples(tuples, closed="right") result = index.get_indexer(query) expected = np.array(expected, dtype="intp") @@ -264,7 +250,7 @@ def test_get_indexer_with_int_and_float(self, query, expected): @pytest.mark.parametrize("item", [[3], np.arange(0.5, 5, 0.5)]) def test_get_indexer_length_one(self, item, closed): # GH 17284 - index = IntervalIndex.from_tuples([(0, 5)], inclusive=closed) + index = IntervalIndex.from_tuples([(0, 5)], closed=closed) result = index.get_indexer(item) expected = np.array([0] * len(item), dtype="intp") tm.assert_numpy_array_equal(result, expected) @@ -272,7 +258,7 @@ def test_get_indexer_length_one(self, item, closed): @pytest.mark.parametrize("size", [1, 5]) def test_get_indexer_length_one_interval(self, size, closed): # GH 17284 - index = IntervalIndex.from_tuples([(0, 5)], inclusive=closed) + index = IntervalIndex.from_tuples([(0, 5)], closed=closed) result = index.get_indexer([Interval(0, 5, closed)] * size) expected = np.array([0] * size, dtype="intp") tm.assert_numpy_array_equal(result, expected) @@ -282,14 +268,14 @@ def test_get_indexer_length_one_interval(self, size, closed): [ IntervalIndex.from_tuples([(7, 8), (1, 2), (3, 4), (0, 1)]), IntervalIndex.from_tuples([(0, 1), (1, 2), (3, 4), np.nan]), - IntervalIndex.from_tuples([(0, 1), (1, 2), (3, 4)], inclusive="both"), + IntervalIndex.from_tuples([(0, 1), (1, 2), (3, 4)], closed="both"), [-1, 0, 0.5, 1, 2, 2.5, np.nan], ["foo", "foo", "bar", "baz"], ], ) def test_get_indexer_categorical(self, target, ordered): # GH 30063: categorical and non-categorical results should be consistent - index = IntervalIndex.from_tuples([(0, 1), (1, 2), (3, 4)], inclusive="right") + index = IntervalIndex.from_tuples([(0, 1), (1, 2), (3, 4)]) categorical_target = CategoricalIndex(target, ordered=ordered) result = index.get_indexer(categorical_target) @@ -298,7 +284,7 @@ def test_get_indexer_categorical(self, target, ordered): def test_get_indexer_categorical_with_nans(self): # GH#41934 nans in both index and in target - ii = IntervalIndex.from_breaks(range(5), inclusive="right") + ii = IntervalIndex.from_breaks(range(5)) ii2 = ii.append(IntervalIndex([np.nan])) ci2 = CategoricalIndex(ii2) @@ -317,7 +303,7 @@ def test_get_indexer_categorical_with_nans(self): tm.assert_numpy_array_equal(result, expected) @pytest.mark.parametrize( - "tuples, inclusive", + "tuples, closed", [ ([(0, 2), (1, 3), (3, 4)], "neither"), ([(0, 5), (1, 4), (6, 7)], "left"), @@ -325,9 +311,9 @@ def test_get_indexer_categorical_with_nans(self): ([(0, 1), (2, 3), (3, 4)], "both"), ], ) - def test_get_indexer_errors(self, tuples, inclusive): + def test_get_indexer_errors(self, tuples, closed): # IntervalIndex needs non-overlapping for uniqueness when querying - index = IntervalIndex.from_tuples(tuples, inclusive=inclusive) + index = IntervalIndex.from_tuples(tuples, closed=closed) msg = ( "cannot handle overlapping indices; use " @@ -359,7 +345,7 @@ def test_get_indexer_errors(self, tuples, inclusive): def test_get_indexer_non_unique_with_int_and_float(self, query, expected): tuples = [(0, 2.5), (1, 3), (2, 4)] - index = IntervalIndex.from_tuples(tuples, inclusive="left") + index = IntervalIndex.from_tuples(tuples, closed="left") result_indexer, result_missing = index.get_indexer_non_unique(query) expected_indexer = np.array(expected[0], dtype="intp") @@ -461,45 +447,45 @@ def test_slice_locs_with_interval(self): assert index.slice_locs(start=Interval(2, 4), end=Interval(0, 2)) == (2, 2) # unsorted duplicates - index = IntervalIndex.from_tuples([(0, 2), (2, 4), (0, 2)], "right") + index = IntervalIndex.from_tuples([(0, 2), (2, 4), (0, 2)]) with pytest.raises( KeyError, match=re.escape( '"Cannot get left slice bound for non-unique label: ' - "Interval(0, 2, inclusive='right')\"" + "Interval(0, 2, closed='right')\"" ), ): - index.slice_locs(start=Interval(0, 2, "right"), end=Interval(2, 4, "right")) + index.slice_locs(start=Interval(0, 2), end=Interval(2, 4)) with pytest.raises( KeyError, match=re.escape( '"Cannot get left slice bound for non-unique label: ' - "Interval(0, 2, inclusive='right')\"" + "Interval(0, 2, closed='right')\"" ), ): - index.slice_locs(start=Interval(0, 2, "right")) + index.slice_locs(start=Interval(0, 2)) - assert index.slice_locs(end=Interval(2, 4, "right")) == (0, 2) + assert index.slice_locs(end=Interval(2, 4)) == (0, 2) with pytest.raises( KeyError, match=re.escape( '"Cannot get right slice bound for non-unique label: ' - "Interval(0, 2, inclusive='right')\"" + "Interval(0, 2, closed='right')\"" ), ): - index.slice_locs(end=Interval(0, 2, "right")) + index.slice_locs(end=Interval(0, 2)) with pytest.raises( KeyError, match=re.escape( '"Cannot get right slice bound for non-unique label: ' - "Interval(0, 2, inclusive='right')\"" + "Interval(0, 2, closed='right')\"" ), ): - index.slice_locs(start=Interval(2, 4, "right"), end=Interval(0, 2, "right")) + index.slice_locs(start=Interval(2, 4), end=Interval(0, 2)) # another unsorted duplicates index = IntervalIndex.from_tuples([(0, 2), (0, 2), (2, 4), (1, 3)]) @@ -513,7 +499,7 @@ def test_slice_locs_with_interval(self): def test_slice_locs_with_ints_and_floats_succeeds(self): # increasing non-overlapping - index = IntervalIndex.from_tuples([(0, 1), (1, 2), (3, 4)], inclusive="right") + index = IntervalIndex.from_tuples([(0, 1), (1, 2), (3, 4)]) assert index.slice_locs(0, 1) == (0, 1) assert index.slice_locs(0, 2) == (0, 2) @@ -523,7 +509,7 @@ def test_slice_locs_with_ints_and_floats_succeeds(self): assert index.slice_locs(0, 4) == (0, 3) # decreasing non-overlapping - index = IntervalIndex.from_tuples([(3, 4), (1, 2), (0, 1)], inclusive="right") + index = IntervalIndex.from_tuples([(3, 4), (1, 2), (0, 1)]) assert index.slice_locs(0, 1) == (3, 3) assert index.slice_locs(0, 2) == (3, 2) assert index.slice_locs(0, 3) == (3, 1) @@ -544,7 +530,7 @@ def test_slice_locs_with_ints_and_floats_succeeds(self): ) def test_slice_locs_with_ints_and_floats_errors(self, tuples, query): start, stop = query - index = IntervalIndex.from_tuples(tuples, inclusive="right") + index = IntervalIndex.from_tuples(tuples) with pytest.raises( KeyError, match=( @@ -599,17 +585,17 @@ class TestContains: def test_contains_dunder(self): - index = IntervalIndex.from_arrays([0, 1], [1, 2], inclusive="right") + index = IntervalIndex.from_arrays([0, 1], [1, 2], closed="right") # __contains__ requires perfect matches to intervals. assert 0 not in index assert 1 not in index assert 2 not in index - assert Interval(0, 1, inclusive="right") in index - assert Interval(0, 2, inclusive="right") not in index - assert Interval(0, 0.5, inclusive="right") not in index - assert Interval(3, 5, inclusive="right") not in index - assert Interval(-1, 0, inclusive="left") not in index - assert Interval(0, 1, inclusive="left") not in index - assert Interval(0, 1, inclusive="both") not in index + assert Interval(0, 1, closed="right") in index + assert Interval(0, 2, closed="right") not in index + assert Interval(0, 0.5, closed="right") not in index + assert Interval(3, 5, closed="right") not in index + assert Interval(-1, 0, closed="left") not in index + assert Interval(0, 1, closed="left") not in index + assert Interval(0, 1, closed="both") not in index diff --git a/pandas/tests/indexes/interval/test_interval.py b/pandas/tests/indexes/interval/test_interval.py index 5bf29093152d8..37c13c37d070b 100644 --- a/pandas/tests/indexes/interval/test_interval.py +++ b/pandas/tests/indexes/interval/test_interval.py @@ -28,21 +28,21 @@ def name(request): class TestIntervalIndex: - index = IntervalIndex.from_arrays([0, 1], [1, 2], "right") + index = IntervalIndex.from_arrays([0, 1], [1, 2]) - def create_index(self, inclusive="right"): - return IntervalIndex.from_breaks(range(11), inclusive=inclusive) + def create_index(self, closed="right"): + return IntervalIndex.from_breaks(range(11), closed=closed) - def create_index_with_nan(self, inclusive="right"): + def create_index_with_nan(self, closed="right"): mask = [True, False] + [True] * 8 return IntervalIndex.from_arrays( np.where(mask, np.arange(10), np.nan), np.where(mask, np.arange(1, 11), np.nan), - inclusive=inclusive, + closed=closed, ) def test_properties(self, closed): - index = self.create_index(inclusive=closed) + index = self.create_index(closed=closed) assert len(index) == 10 assert index.size == 10 assert index.shape == (10,) @@ -51,7 +51,7 @@ def test_properties(self, closed): tm.assert_index_equal(index.right, Index(np.arange(1, 11))) tm.assert_index_equal(index.mid, Index(np.arange(0.5, 10.5))) - assert index.inclusive == closed + assert index.closed == closed ivs = [ Interval(left, right, closed) @@ -61,7 +61,7 @@ def test_properties(self, closed): tm.assert_numpy_array_equal(np.asarray(index), expected) # with nans - index = self.create_index_with_nan(inclusive=closed) + index = self.create_index_with_nan(closed=closed) assert len(index) == 10 assert index.size == 10 assert index.shape == (10,) @@ -73,7 +73,7 @@ def test_properties(self, closed): tm.assert_index_equal(index.right, expected_right) tm.assert_index_equal(index.mid, expected_mid) - assert index.inclusive == closed + assert index.closed == closed ivs = [ Interval(left, right, closed) if notna(left) else np.nan @@ -93,7 +93,7 @@ def test_properties(self, closed): ) def test_length(self, closed, breaks): # GH 18789 - index = IntervalIndex.from_breaks(breaks, inclusive=closed) + index = IntervalIndex.from_breaks(breaks, closed=closed) result = index.length expected = Index(iv.length for iv in index) tm.assert_index_equal(result, expected) @@ -105,7 +105,7 @@ def test_length(self, closed, breaks): tm.assert_index_equal(result, expected) def test_with_nans(self, closed): - index = self.create_index(inclusive=closed) + index = self.create_index(closed=closed) assert index.hasnans is False result = index.isna() @@ -116,7 +116,7 @@ def test_with_nans(self, closed): expected = np.ones(len(index), dtype=bool) tm.assert_numpy_array_equal(result, expected) - index = self.create_index_with_nan(inclusive=closed) + index = self.create_index_with_nan(closed=closed) assert index.hasnans is True result = index.isna() @@ -128,7 +128,7 @@ def test_with_nans(self, closed): tm.assert_numpy_array_equal(result, expected) def test_copy(self, closed): - expected = self.create_index(inclusive=closed) + expected = self.create_index(closed=closed) result = expected.copy() assert result.equals(expected) @@ -141,7 +141,7 @@ def test_ensure_copied_data(self, closed): # exercise the copy flag in the constructor # not copying - index = self.create_index(inclusive=closed) + index = self.create_index(closed=closed) result = IntervalIndex(index, copy=False) tm.assert_numpy_array_equal( index.left.values, result.left.values, check_same="same" @@ -160,17 +160,17 @@ def test_ensure_copied_data(self, closed): ) def test_delete(self, closed): - expected = IntervalIndex.from_breaks(np.arange(1, 11), inclusive=closed) - result = self.create_index(inclusive=closed).delete(0) + expected = IntervalIndex.from_breaks(np.arange(1, 11), closed=closed) + result = self.create_index(closed=closed).delete(0) tm.assert_index_equal(result, expected) @pytest.mark.parametrize( "data", [ - interval_range(0, periods=10, inclusive="neither"), - interval_range(1.7, periods=8, freq=2.5, inclusive="both"), - interval_range(Timestamp("20170101"), periods=12, inclusive="left"), - interval_range(Timedelta("1 day"), periods=6, inclusive="right"), + interval_range(0, periods=10, closed="neither"), + interval_range(1.7, periods=8, freq=2.5, closed="both"), + interval_range(Timestamp("20170101"), periods=12, closed="left"), + interval_range(Timedelta("1 day"), periods=6, closed="right"), ], ) def test_insert(self, data): @@ -201,11 +201,11 @@ def test_insert(self, data): with pytest.raises(TypeError, match=msg): data._data.insert(1, "foo") - # invalid inclusive - msg = "'value.inclusive' is 'left', expected 'right'." - for inclusive in {"left", "right", "both", "neither"} - {item.inclusive}: - msg = f"'value.inclusive' is '{inclusive}', expected '{item.inclusive}'." - bad_item = Interval(item.left, item.right, inclusive=inclusive) + # invalid closed + msg = "'value.closed' is 'left', expected 'right'." + for closed in {"left", "right", "both", "neither"} - {item.closed}: + msg = f"'value.closed' is '{closed}', expected '{item.closed}'." + bad_item = Interval(item.left, item.right, closed=closed) res = data.insert(1, bad_item) expected = data.astype(object).insert(1, bad_item) tm.assert_index_equal(res, expected) @@ -213,7 +213,7 @@ def test_insert(self, data): data._data.insert(1, bad_item) # GH 18295 (test missing) - na_idx = IntervalIndex([np.nan], inclusive=data.inclusive) + na_idx = IntervalIndex([np.nan], closed=data.closed) for na in [np.nan, None, pd.NA]: expected = data[:1].append(na_idx).append(data[1:]) result = data.insert(1, na) @@ -235,93 +235,93 @@ def test_is_unique_interval(self, closed): Interval specific tests for is_unique in addition to base class tests """ # unique overlapping - distinct endpoints - idx = IntervalIndex.from_tuples([(0, 1), (0.5, 1.5)], inclusive=closed) + idx = IntervalIndex.from_tuples([(0, 1), (0.5, 1.5)], closed=closed) assert idx.is_unique is True # unique overlapping - shared endpoints - idx = IntervalIndex.from_tuples([(1, 2), (1, 3), (2, 3)], inclusive=closed) + idx = IntervalIndex.from_tuples([(1, 2), (1, 3), (2, 3)], closed=closed) assert idx.is_unique is True # unique nested - idx = IntervalIndex.from_tuples([(-1, 1), (-2, 2)], inclusive=closed) + idx = IntervalIndex.from_tuples([(-1, 1), (-2, 2)], closed=closed) assert idx.is_unique is True # unique NaN - idx = IntervalIndex.from_tuples([(np.NaN, np.NaN)], inclusive=closed) + idx = IntervalIndex.from_tuples([(np.NaN, np.NaN)], closed=closed) assert idx.is_unique is True # non-unique NaN idx = IntervalIndex.from_tuples( - [(np.NaN, np.NaN), (np.NaN, np.NaN)], inclusive=closed + [(np.NaN, np.NaN), (np.NaN, np.NaN)], closed=closed ) assert idx.is_unique is False def test_monotonic(self, closed): # increasing non-overlapping - idx = IntervalIndex.from_tuples([(0, 1), (2, 3), (4, 5)], inclusive=closed) + idx = IntervalIndex.from_tuples([(0, 1), (2, 3), (4, 5)], closed=closed) assert idx.is_monotonic_increasing is True assert idx._is_strictly_monotonic_increasing is True assert idx.is_monotonic_decreasing is False assert idx._is_strictly_monotonic_decreasing is False # decreasing non-overlapping - idx = IntervalIndex.from_tuples([(4, 5), (2, 3), (1, 2)], inclusive=closed) + idx = IntervalIndex.from_tuples([(4, 5), (2, 3), (1, 2)], closed=closed) assert idx.is_monotonic_increasing is False assert idx._is_strictly_monotonic_increasing is False assert idx.is_monotonic_decreasing is True assert idx._is_strictly_monotonic_decreasing is True # unordered non-overlapping - idx = IntervalIndex.from_tuples([(0, 1), (4, 5), (2, 3)], inclusive=closed) + idx = IntervalIndex.from_tuples([(0, 1), (4, 5), (2, 3)], closed=closed) assert idx.is_monotonic_increasing is False assert idx._is_strictly_monotonic_increasing is False assert idx.is_monotonic_decreasing is False assert idx._is_strictly_monotonic_decreasing is False # increasing overlapping - idx = IntervalIndex.from_tuples([(0, 2), (0.5, 2.5), (1, 3)], inclusive=closed) + idx = IntervalIndex.from_tuples([(0, 2), (0.5, 2.5), (1, 3)], closed=closed) assert idx.is_monotonic_increasing is True assert idx._is_strictly_monotonic_increasing is True assert idx.is_monotonic_decreasing is False assert idx._is_strictly_monotonic_decreasing is False # decreasing overlapping - idx = IntervalIndex.from_tuples([(1, 3), (0.5, 2.5), (0, 2)], inclusive=closed) + idx = IntervalIndex.from_tuples([(1, 3), (0.5, 2.5), (0, 2)], closed=closed) assert idx.is_monotonic_increasing is False assert idx._is_strictly_monotonic_increasing is False assert idx.is_monotonic_decreasing is True assert idx._is_strictly_monotonic_decreasing is True # unordered overlapping - idx = IntervalIndex.from_tuples([(0.5, 2.5), (0, 2), (1, 3)], inclusive=closed) + idx = IntervalIndex.from_tuples([(0.5, 2.5), (0, 2), (1, 3)], closed=closed) assert idx.is_monotonic_increasing is False assert idx._is_strictly_monotonic_increasing is False assert idx.is_monotonic_decreasing is False assert idx._is_strictly_monotonic_decreasing is False # increasing overlapping shared endpoints - idx = IntervalIndex.from_tuples([(1, 2), (1, 3), (2, 3)], inclusive=closed) + idx = IntervalIndex.from_tuples([(1, 2), (1, 3), (2, 3)], closed=closed) assert idx.is_monotonic_increasing is True assert idx._is_strictly_monotonic_increasing is True assert idx.is_monotonic_decreasing is False assert idx._is_strictly_monotonic_decreasing is False # decreasing overlapping shared endpoints - idx = IntervalIndex.from_tuples([(2, 3), (1, 3), (1, 2)], inclusive=closed) + idx = IntervalIndex.from_tuples([(2, 3), (1, 3), (1, 2)], closed=closed) assert idx.is_monotonic_increasing is False assert idx._is_strictly_monotonic_increasing is False assert idx.is_monotonic_decreasing is True assert idx._is_strictly_monotonic_decreasing is True # stationary - idx = IntervalIndex.from_tuples([(0, 1), (0, 1)], inclusive=closed) + idx = IntervalIndex.from_tuples([(0, 1), (0, 1)], closed=closed) assert idx.is_monotonic_increasing is True assert idx._is_strictly_monotonic_increasing is False assert idx.is_monotonic_decreasing is True assert idx._is_strictly_monotonic_decreasing is False # empty - idx = IntervalIndex([], inclusive=closed) + idx = IntervalIndex([], closed=closed) assert idx.is_monotonic_increasing is True assert idx._is_strictly_monotonic_increasing is True assert idx.is_monotonic_decreasing is True @@ -338,22 +338,22 @@ def test_is_monotonic_with_nans(self): assert not index.is_monotonic_decreasing def test_get_item(self, closed): - i = IntervalIndex.from_arrays((0, 1, np.nan), (1, 2, np.nan), inclusive=closed) - assert i[0] == Interval(0.0, 1.0, inclusive=closed) - assert i[1] == Interval(1.0, 2.0, inclusive=closed) + i = IntervalIndex.from_arrays((0, 1, np.nan), (1, 2, np.nan), closed=closed) + assert i[0] == Interval(0.0, 1.0, closed=closed) + assert i[1] == Interval(1.0, 2.0, closed=closed) assert isna(i[2]) result = i[0:1] - expected = IntervalIndex.from_arrays((0.0,), (1.0,), inclusive=closed) + expected = IntervalIndex.from_arrays((0.0,), (1.0,), closed=closed) tm.assert_index_equal(result, expected) result = i[0:2] - expected = IntervalIndex.from_arrays((0.0, 1), (1.0, 2.0), inclusive=closed) + expected = IntervalIndex.from_arrays((0.0, 1), (1.0, 2.0), closed=closed) tm.assert_index_equal(result, expected) result = i[1:3] expected = IntervalIndex.from_arrays( - (1.0, np.nan), (2.0, np.nan), inclusive=closed + (1.0, np.nan), (2.0, np.nan), closed=closed ) tm.assert_index_equal(result, expected) @@ -477,7 +477,7 @@ def test_maybe_convert_i8_errors(self, breaks1, breaks2, make_key): def test_contains_method(self): # can select values that are IN the range of a value - i = IntervalIndex.from_arrays([0, 1], [1, 2], "right") + i = IntervalIndex.from_arrays([0, 1], [1, 2]) expected = np.array([False, False], dtype="bool") actual = i.contains(0) @@ -500,18 +500,18 @@ def test_contains_method(self): def test_dropna(self, closed): - expected = IntervalIndex.from_tuples([(0.0, 1.0), (1.0, 2.0)], inclusive=closed) + expected = IntervalIndex.from_tuples([(0.0, 1.0), (1.0, 2.0)], closed=closed) - ii = IntervalIndex.from_tuples([(0, 1), (1, 2), np.nan], inclusive=closed) + ii = IntervalIndex.from_tuples([(0, 1), (1, 2), np.nan], closed=closed) result = ii.dropna() tm.assert_index_equal(result, expected) - ii = IntervalIndex.from_arrays([0, 1, np.nan], [1, 2, np.nan], inclusive=closed) + ii = IntervalIndex.from_arrays([0, 1, np.nan], [1, 2, np.nan], closed=closed) result = ii.dropna() tm.assert_index_equal(result, expected) def test_non_contiguous(self, closed): - index = IntervalIndex.from_tuples([(0, 1), (2, 3)], inclusive=closed) + index = IntervalIndex.from_tuples([(0, 1), (2, 3)], closed=closed) target = [0.5, 1.5, 2.5] actual = index.get_indexer(target) expected = np.array([0, -1, 1], dtype="intp") @@ -520,7 +520,7 @@ def test_non_contiguous(self, closed): assert 1.5 not in index def test_isin(self, closed): - index = self.create_index(inclusive=closed) + index = self.create_index(closed=closed) expected = np.array([True] + [False] * (len(index) - 1)) result = index.isin(index[:1]) @@ -529,7 +529,7 @@ def test_isin(self, closed): result = index.isin([index[0]]) tm.assert_numpy_array_equal(result, expected) - other = IntervalIndex.from_breaks(np.arange(-2, 10), inclusive=closed) + other = IntervalIndex.from_breaks(np.arange(-2, 10), closed=closed) expected = np.array([True] * (len(index) - 1) + [False]) result = index.isin(other) tm.assert_numpy_array_equal(result, expected) @@ -537,9 +537,9 @@ def test_isin(self, closed): result = index.isin(other.tolist()) tm.assert_numpy_array_equal(result, expected) - for other_inclusive in {"right", "left", "both", "neither"}: - other = self.create_index(inclusive=other_inclusive) - expected = np.repeat(closed == other_inclusive, len(index)) + for other_closed in {"right", "left", "both", "neither"}: + other = self.create_index(closed=other_closed) + expected = np.repeat(closed == other_closed, len(index)) result = index.isin(other) tm.assert_numpy_array_equal(result, expected) @@ -547,14 +547,14 @@ def test_isin(self, closed): tm.assert_numpy_array_equal(result, expected) def test_comparison(self): - actual = Interval(0, 1, "right") < self.index + actual = Interval(0, 1) < self.index expected = np.array([False, True]) tm.assert_numpy_array_equal(actual, expected) - actual = Interval(0.5, 1.5, "right") < self.index + actual = Interval(0.5, 1.5) < self.index expected = np.array([False, True]) tm.assert_numpy_array_equal(actual, expected) - actual = self.index > Interval(0.5, 1.5, "right") + actual = self.index > Interval(0.5, 1.5) tm.assert_numpy_array_equal(actual, expected) actual = self.index == self.index @@ -612,11 +612,9 @@ def test_comparison(self): def test_missing_values(self, closed): idx = Index( - [np.nan, Interval(0, 1, inclusive=closed), Interval(1, 2, inclusive=closed)] - ) - idx2 = IntervalIndex.from_arrays( - [np.nan, 0, 1], [np.nan, 1, 2], inclusive=closed + [np.nan, Interval(0, 1, closed=closed), Interval(1, 2, closed=closed)] ) + idx2 = IntervalIndex.from_arrays([np.nan, 0, 1], [np.nan, 1, 2], closed=closed) assert idx.equals(idx2) msg = ( @@ -625,13 +623,13 @@ def test_missing_values(self, closed): ) with pytest.raises(ValueError, match=msg): IntervalIndex.from_arrays( - [np.nan, 0, 1], np.array([0, 1, 2]), inclusive=closed + [np.nan, 0, 1], np.array([0, 1, 2]), closed=closed ) tm.assert_numpy_array_equal(isna(idx), np.array([True, False, False])) def test_sort_values(self, closed): - index = self.create_index(inclusive=closed) + index = self.create_index(closed=closed) result = index.sort_values() tm.assert_index_equal(result, index) @@ -654,7 +652,7 @@ def test_sort_values(self, closed): def test_datetime(self, tz): start = Timestamp("2000-01-01", tz=tz) dates = date_range(start=start, periods=10) - index = IntervalIndex.from_breaks(dates, "right") + index = IntervalIndex.from_breaks(dates) # test mid start = Timestamp("2000-01-01T12:00", tz=tz) @@ -666,10 +664,10 @@ def test_datetime(self, tz): assert Timestamp("2000-01-01T12", tz=tz) not in index assert Timestamp("2000-01-02", tz=tz) not in index iv_true = Interval( - Timestamp("2000-01-02", tz=tz), Timestamp("2000-01-03", tz=tz), "right" + Timestamp("2000-01-02", tz=tz), Timestamp("2000-01-03", tz=tz) ) iv_false = Interval( - Timestamp("1999-12-31", tz=tz), Timestamp("2000-01-01", tz=tz), "right" + Timestamp("1999-12-31", tz=tz), Timestamp("2000-01-01", tz=tz) ) assert iv_true in index assert iv_false not in index @@ -694,62 +692,58 @@ def test_datetime(self, tz): def test_append(self, closed): - index1 = IntervalIndex.from_arrays([0, 1], [1, 2], inclusive=closed) - index2 = IntervalIndex.from_arrays([1, 2], [2, 3], inclusive=closed) + index1 = IntervalIndex.from_arrays([0, 1], [1, 2], closed=closed) + index2 = IntervalIndex.from_arrays([1, 2], [2, 3], closed=closed) result = index1.append(index2) - expected = IntervalIndex.from_arrays( - [0, 1, 1, 2], [1, 2, 2, 3], inclusive=closed - ) + expected = IntervalIndex.from_arrays([0, 1, 1, 2], [1, 2, 2, 3], closed=closed) tm.assert_index_equal(result, expected) result = index1.append([index1, index2]) expected = IntervalIndex.from_arrays( - [0, 1, 0, 1, 1, 2], [1, 2, 1, 2, 2, 3], inclusive=closed + [0, 1, 0, 1, 1, 2], [1, 2, 1, 2, 2, 3], closed=closed ) tm.assert_index_equal(result, expected) - for other_inclusive in {"left", "right", "both", "neither"} - {closed}: - index_other_inclusive = IntervalIndex.from_arrays( - [0, 1], [1, 2], inclusive=other_inclusive - ) - result = index1.append(index_other_inclusive) - expected = index1.astype(object).append( - index_other_inclusive.astype(object) + for other_closed in {"left", "right", "both", "neither"} - {closed}: + index_other_closed = IntervalIndex.from_arrays( + [0, 1], [1, 2], closed=other_closed ) + result = index1.append(index_other_closed) + expected = index1.astype(object).append(index_other_closed.astype(object)) tm.assert_index_equal(result, expected) def test_is_non_overlapping_monotonic(self, closed): # Should be True in all cases tpls = [(0, 1), (2, 3), (4, 5), (6, 7)] - idx = IntervalIndex.from_tuples(tpls, inclusive=closed) + idx = IntervalIndex.from_tuples(tpls, closed=closed) assert idx.is_non_overlapping_monotonic is True - idx = IntervalIndex.from_tuples(tpls[::-1], inclusive=closed) + idx = IntervalIndex.from_tuples(tpls[::-1], closed=closed) assert idx.is_non_overlapping_monotonic is True # Should be False in all cases (overlapping) tpls = [(0, 2), (1, 3), (4, 5), (6, 7)] - idx = IntervalIndex.from_tuples(tpls, inclusive=closed) + idx = IntervalIndex.from_tuples(tpls, closed=closed) assert idx.is_non_overlapping_monotonic is False - idx = IntervalIndex.from_tuples(tpls[::-1], inclusive=closed) + idx = IntervalIndex.from_tuples(tpls[::-1], closed=closed) assert idx.is_non_overlapping_monotonic is False # Should be False in all cases (non-monotonic) tpls = [(0, 1), (2, 3), (6, 7), (4, 5)] - idx = IntervalIndex.from_tuples(tpls, inclusive=closed) + idx = IntervalIndex.from_tuples(tpls, closed=closed) assert idx.is_non_overlapping_monotonic is False - idx = IntervalIndex.from_tuples(tpls[::-1], inclusive=closed) + idx = IntervalIndex.from_tuples(tpls[::-1], closed=closed) assert idx.is_non_overlapping_monotonic is False - # Should be False for inclusive='both', otherwise True (GH16560) + # Should be False for closed='both', otherwise True (GH16560) if closed == "both": - idx = IntervalIndex.from_breaks(range(4), inclusive=closed) + idx = IntervalIndex.from_breaks(range(4), closed=closed) assert idx.is_non_overlapping_monotonic is False else: - idx = IntervalIndex.from_breaks(range(4), inclusive=closed) + idx = IntervalIndex.from_breaks(range(4), closed=closed) assert idx.is_non_overlapping_monotonic is True @pytest.mark.parametrize( @@ -766,34 +760,34 @@ def test_is_overlapping(self, start, shift, na_value, closed): # non-overlapping tuples = [(start + n * shift, start + (n + 1) * shift) for n in (0, 2, 4)] - index = IntervalIndex.from_tuples(tuples, inclusive=closed) + index = IntervalIndex.from_tuples(tuples, closed=closed) assert index.is_overlapping is False # non-overlapping with NA tuples = [(na_value, na_value)] + tuples + [(na_value, na_value)] - index = IntervalIndex.from_tuples(tuples, inclusive=closed) + index = IntervalIndex.from_tuples(tuples, closed=closed) assert index.is_overlapping is False # overlapping tuples = [(start + n * shift, start + (n + 2) * shift) for n in range(3)] - index = IntervalIndex.from_tuples(tuples, inclusive=closed) + index = IntervalIndex.from_tuples(tuples, closed=closed) assert index.is_overlapping is True # overlapping with NA tuples = [(na_value, na_value)] + tuples + [(na_value, na_value)] - index = IntervalIndex.from_tuples(tuples, inclusive=closed) + index = IntervalIndex.from_tuples(tuples, closed=closed) assert index.is_overlapping is True # common endpoints tuples = [(start + n * shift, start + (n + 1) * shift) for n in range(3)] - index = IntervalIndex.from_tuples(tuples, inclusive=closed) + index = IntervalIndex.from_tuples(tuples, closed=closed) result = index.is_overlapping expected = closed == "both" assert result is expected # common endpoints with NA tuples = [(na_value, na_value)] + tuples + [(na_value, na_value)] - index = IntervalIndex.from_tuples(tuples, inclusive=closed) + index = IntervalIndex.from_tuples(tuples, closed=closed) result = index.is_overlapping assert result is expected @@ -871,21 +865,21 @@ def test_nbytes(self): expected = 64 # 4 * 8 * 2 assert result == expected - @pytest.mark.parametrize("new_inclusive", ["left", "right", "both", "neither"]) - def test_set_inclusive(self, name, closed, new_inclusive): + @pytest.mark.parametrize("new_closed", ["left", "right", "both", "neither"]) + def test_set_closed(self, name, closed, new_closed): # GH 21670 - index = interval_range(0, 5, inclusive=closed, name=name) - result = index.set_inclusive(new_inclusive) - expected = interval_range(0, 5, inclusive=new_inclusive, name=name) + index = interval_range(0, 5, closed=closed, name=name) + result = index.set_closed(new_closed) + expected = interval_range(0, 5, closed=new_closed, name=name) tm.assert_index_equal(result, expected) - @pytest.mark.parametrize("bad_inclusive", ["foo", 10, "LEFT", True, False]) - def test_set_inclusive_errors(self, bad_inclusive): + @pytest.mark.parametrize("bad_closed", ["foo", 10, "LEFT", True, False]) + def test_set_closed_errors(self, bad_closed): # GH 21670 index = interval_range(0, 5) - msg = f"invalid option for 'inclusive': {bad_inclusive}" + msg = f"invalid option for 'closed': {bad_closed}" with pytest.raises(ValueError, match=msg): - index.set_inclusive(bad_inclusive) + index.set_closed(bad_closed) def test_is_all_dates(self): # GH 23576 @@ -895,35 +889,6 @@ def test_is_all_dates(self): year_2017_index = IntervalIndex([year_2017]) assert not year_2017_index._is_all_dates - def test_interval_index_error_and_warning(self): - # GH 40245 - msg = "Can only specify 'closed' or 'inclusive', not both." - msg_warn = "the 'closed'' keyword is deprecated, use 'inclusive' instead." - with pytest.raises(TypeError, match=msg): - with tm.assert_produces_warning(FutureWarning, match=msg_warn): - IntervalIndex.from_breaks(range(11), closed="both", inclusive="both") - - with pytest.raises(TypeError, match=msg): - with tm.assert_produces_warning(FutureWarning, match=msg_warn): - IntervalIndex.from_arrays( - [0, 1], [1, 2], closed="both", inclusive="both" - ) - - with pytest.raises(TypeError, match=msg): - with tm.assert_produces_warning(FutureWarning, match=msg_warn): - IntervalIndex.from_tuples( - [(0, 1), (0.5, 1.5)], closed="both", inclusive="both" - ) - - with tm.assert_produces_warning(FutureWarning, match=msg_warn): - IntervalIndex.from_breaks(range(11), closed="both") - - with tm.assert_produces_warning(FutureWarning, match=msg_warn): - IntervalIndex.from_arrays([0, 1], [1, 2], closed="both") - - with tm.assert_produces_warning(FutureWarning, match=msg_warn): - IntervalIndex.from_tuples([(0, 1), (0.5, 1.5)], closed="both") - def test_dir(): # GH#27571 dir(interval_index) should not raise @@ -951,9 +916,3 @@ def test_searchsorted_invalid_argument(arg): msg = "'<' not supported between instances of 'pandas._libs.interval.Interval' and " with pytest.raises(TypeError, match=msg): values.searchsorted(arg) - - -def test_interval_range_deprecated_closed(): - # GH#40245 - with tm.assert_produces_warning(FutureWarning): - interval_range(start=0, end=5, closed="right") diff --git a/pandas/tests/indexes/interval/test_interval_range.py b/pandas/tests/indexes/interval/test_interval_range.py index 3bde2f51178dc..2f28c33a3bbc6 100644 --- a/pandas/tests/indexes/interval/test_interval_range.py +++ b/pandas/tests/indexes/interval/test_interval_range.py @@ -30,29 +30,29 @@ class TestIntervalRange: def test_constructor_numeric(self, closed, name, freq, periods): start, end = 0, 100 breaks = np.arange(101, step=freq) - expected = IntervalIndex.from_breaks(breaks, name=name, inclusive=closed) + expected = IntervalIndex.from_breaks(breaks, name=name, closed=closed) # defined from start/end/freq result = interval_range( - start=start, end=end, freq=freq, name=name, inclusive=closed + start=start, end=end, freq=freq, name=name, closed=closed ) tm.assert_index_equal(result, expected) # defined from start/periods/freq result = interval_range( - start=start, periods=periods, freq=freq, name=name, inclusive=closed + start=start, periods=periods, freq=freq, name=name, closed=closed ) tm.assert_index_equal(result, expected) # defined from end/periods/freq result = interval_range( - end=end, periods=periods, freq=freq, name=name, inclusive=closed + end=end, periods=periods, freq=freq, name=name, closed=closed ) tm.assert_index_equal(result, expected) # GH 20976: linspace behavior defined from start/end/periods result = interval_range( - start=start, end=end, periods=periods, name=name, inclusive=closed + start=start, end=end, periods=periods, name=name, closed=closed ) tm.assert_index_equal(result, expected) @@ -63,23 +63,23 @@ def test_constructor_numeric(self, closed, name, freq, periods): def test_constructor_timestamp(self, closed, name, freq, periods, tz): start, end = Timestamp("20180101", tz=tz), Timestamp("20181231", tz=tz) breaks = date_range(start=start, end=end, freq=freq) - expected = IntervalIndex.from_breaks(breaks, name=name, inclusive=closed) + expected = IntervalIndex.from_breaks(breaks, name=name, closed=closed) # defined from start/end/freq result = interval_range( - start=start, end=end, freq=freq, name=name, inclusive=closed + start=start, end=end, freq=freq, name=name, closed=closed ) tm.assert_index_equal(result, expected) # defined from start/periods/freq result = interval_range( - start=start, periods=periods, freq=freq, name=name, inclusive=closed + start=start, periods=periods, freq=freq, name=name, closed=closed ) tm.assert_index_equal(result, expected) # defined from end/periods/freq result = interval_range( - end=end, periods=periods, freq=freq, name=name, inclusive=closed + end=end, periods=periods, freq=freq, name=name, closed=closed ) tm.assert_index_equal(result, expected) @@ -88,7 +88,7 @@ def test_constructor_timestamp(self, closed, name, freq, periods, tz): # matches expected only for non-anchored offsets and tz naive # (anchored/DST transitions cause unequal spacing in expected) result = interval_range( - start=start, end=end, periods=periods, name=name, inclusive=closed + start=start, end=end, periods=periods, name=name, closed=closed ) tm.assert_index_equal(result, expected) @@ -98,29 +98,29 @@ def test_constructor_timestamp(self, closed, name, freq, periods, tz): def test_constructor_timedelta(self, closed, name, freq, periods): start, end = Timedelta("0 days"), Timedelta("100 days") breaks = timedelta_range(start=start, end=end, freq=freq) - expected = IntervalIndex.from_breaks(breaks, name=name, inclusive=closed) + expected = IntervalIndex.from_breaks(breaks, name=name, closed=closed) # defined from start/end/freq result = interval_range( - start=start, end=end, freq=freq, name=name, inclusive=closed + start=start, end=end, freq=freq, name=name, closed=closed ) tm.assert_index_equal(result, expected) # defined from start/periods/freq result = interval_range( - start=start, periods=periods, freq=freq, name=name, inclusive=closed + start=start, periods=periods, freq=freq, name=name, closed=closed ) tm.assert_index_equal(result, expected) # defined from end/periods/freq result = interval_range( - end=end, periods=periods, freq=freq, name=name, inclusive=closed + end=end, periods=periods, freq=freq, name=name, closed=closed ) tm.assert_index_equal(result, expected) # GH 20976: linspace behavior defined from start/end/periods result = interval_range( - start=start, end=end, periods=periods, name=name, inclusive=closed + start=start, end=end, periods=periods, name=name, closed=closed ) tm.assert_index_equal(result, expected) @@ -161,11 +161,9 @@ def test_no_invalid_float_truncation(self, start, end, freq): breaks = [0.5, 1.5, 2.5, 3.5, 4.5] else: breaks = [0.5, 2.0, 3.5, 5.0, 6.5] - expected = IntervalIndex.from_breaks(breaks, "right") + expected = IntervalIndex.from_breaks(breaks) - result = interval_range( - start=start, end=end, periods=4, freq=freq, inclusive="right" - ) + result = interval_range(start=start, end=end, periods=4, freq=freq) tm.assert_index_equal(result, expected) @pytest.mark.parametrize( @@ -186,9 +184,8 @@ def test_no_invalid_float_truncation(self, start, end, freq): def test_linspace_dst_transition(self, start, mid, end): # GH 20976: linspace behavior defined from start/end/periods # accounts for the hour gained/lost during DST transition - result = interval_range(start=start, end=end, periods=2, inclusive="right") - expected = IntervalIndex.from_breaks([start, mid, end], "right") - + result = interval_range(start=start, end=end, periods=2) + expected = IntervalIndex.from_breaks([start, mid, end]) tm.assert_index_equal(result, expected) @pytest.mark.parametrize("freq", [2, 2.0]) @@ -337,7 +334,7 @@ def test_errors(self): # invalid end msg = r"end must be numeric or datetime-like, got \(0, 1\]" with pytest.raises(ValueError, match=msg): - interval_range(end=Interval(0, 1, "right"), periods=10) + interval_range(end=Interval(0, 1), periods=10) # invalid freq for datetime-like msg = "freq must be numeric or convertible to DateOffset, got foo" @@ -356,17 +353,3 @@ def test_errors(self): msg = "Start and end cannot both be tz-aware with different timezones" with pytest.raises(TypeError, match=msg): interval_range(start=start, end=end) - - def test_interval_range_error_and_warning(self): - # GH 40245 - - msg = "Can only specify 'closed' or 'inclusive', not both." - msg_warn = "the 'closed'' keyword is deprecated, use 'inclusive' instead." - - with pytest.raises(TypeError, match=msg): - with tm.assert_produces_warning(FutureWarning, match=msg_warn): - interval_range(end=5, periods=4, closed="both", inclusive="both") - - msg = "the 'closed'' keyword is deprecated, use 'inclusive' instead." - with tm.assert_produces_warning(FutureWarning, match=msg_warn): - interval_range(end=5, periods=4, closed="right") diff --git a/pandas/tests/indexes/interval/test_interval_tree.py b/pandas/tests/indexes/interval/test_interval_tree.py index 6c30d16e61582..3b9de8d9e45d9 100644 --- a/pandas/tests/indexes/interval/test_interval_tree.py +++ b/pandas/tests/indexes/interval/test_interval_tree.py @@ -42,7 +42,7 @@ def leaf_size(request): ) def tree(request, leaf_size): left = request.param - return IntervalTree(left, left + 2, leaf_size=leaf_size, inclusive="right") + return IntervalTree(left, left + 2, leaf_size=leaf_size) class TestIntervalTree: @@ -129,7 +129,7 @@ def test_get_indexer_closed(self, closed, leaf_size): found = x.astype("intp") not_found = (-1 * np.ones(1000)).astype("intp") - tree = IntervalTree(x, x + 0.5, inclusive=closed, leaf_size=leaf_size) + tree = IntervalTree(x, x + 0.5, closed=closed, leaf_size=leaf_size) tm.assert_numpy_array_equal(found, tree.get_indexer(x + 0.25)) expected = found if tree.closed_left else not_found @@ -151,7 +151,7 @@ def test_get_indexer_closed(self, closed, leaf_size): @pytest.mark.parametrize("order", (list(x) for x in permutations(range(3)))) def test_is_overlapping(self, closed, order, left, right, expected): # GH 23309 - tree = IntervalTree(left[order], right[order], inclusive=closed) + tree = IntervalTree(left[order], right[order], closed=closed) result = tree.is_overlapping assert result is expected @@ -160,7 +160,7 @@ def test_is_overlapping_endpoints(self, closed, order): """shared endpoints are marked as overlapping""" # GH 23309 left, right = np.arange(3, dtype="int64"), np.arange(1, 4) - tree = IntervalTree(left[order], right[order], inclusive=closed) + tree = IntervalTree(left[order], right[order], closed=closed) result = tree.is_overlapping expected = closed == "both" assert result is expected @@ -176,7 +176,7 @@ def test_is_overlapping_endpoints(self, closed, order): ) def test_is_overlapping_trivial(self, closed, left, right): # GH 23309 - tree = IntervalTree(left, right, inclusive=closed) + tree = IntervalTree(left, right, closed=closed) assert tree.is_overlapping is False @pytest.mark.skipif(not IS64, reason="GH 23440") diff --git a/pandas/tests/indexes/interval/test_pickle.py b/pandas/tests/indexes/interval/test_pickle.py index ef6db9c8a0513..308a90e72eab5 100644 --- a/pandas/tests/indexes/interval/test_pickle.py +++ b/pandas/tests/indexes/interval/test_pickle.py @@ -1,10 +1,13 @@ +import pytest + from pandas import IntervalIndex import pandas._testing as tm class TestPickle: - def test_pickle_round_trip_inclusive(self, closed): + @pytest.mark.parametrize("closed", ["left", "right", "both"]) + def test_pickle_round_trip_closed(self, closed): # https://github.com/pandas-dev/pandas/issues/35658 - idx = IntervalIndex.from_tuples([(1, 2), (2, 3)], inclusive=closed) + idx = IntervalIndex.from_tuples([(1, 2), (2, 3)], closed=closed) result = tm.round_trip_pickle(idx) tm.assert_index_equal(result, idx) diff --git a/pandas/tests/indexes/interval/test_setops.py b/pandas/tests/indexes/interval/test_setops.py index 2e1f6f7925374..059b0b75f4190 100644 --- a/pandas/tests/indexes/interval/test_setops.py +++ b/pandas/tests/indexes/interval/test_setops.py @@ -10,22 +10,20 @@ import pandas._testing as tm -def monotonic_index(start, end, dtype="int64", inclusive="right"): - return IntervalIndex.from_breaks( - np.arange(start, end, dtype=dtype), inclusive=inclusive - ) +def monotonic_index(start, end, dtype="int64", closed="right"): + return IntervalIndex.from_breaks(np.arange(start, end, dtype=dtype), closed=closed) -def empty_index(dtype="int64", inclusive="right"): - return IntervalIndex(np.array([], dtype=dtype), inclusive=inclusive) +def empty_index(dtype="int64", closed="right"): + return IntervalIndex(np.array([], dtype=dtype), closed=closed) class TestIntervalIndex: def test_union(self, closed, sort): - index = monotonic_index(0, 11, inclusive=closed) - other = monotonic_index(5, 13, inclusive=closed) + index = monotonic_index(0, 11, closed=closed) + other = monotonic_index(5, 13, closed=closed) - expected = monotonic_index(0, 13, inclusive=closed) + expected = monotonic_index(0, 13, closed=closed) result = index[::-1].union(other, sort=sort) if sort is None: tm.assert_index_equal(result, expected) @@ -41,12 +39,12 @@ def test_union(self, closed, sort): def test_union_empty_result(self, closed, sort): # GH 19101: empty result, same dtype - index = empty_index(dtype="int64", inclusive=closed) + index = empty_index(dtype="int64", closed=closed) result = index.union(index, sort=sort) tm.assert_index_equal(result, index) # GH 19101: empty result, different numeric dtypes -> common dtype is f8 - other = empty_index(dtype="float64", inclusive=closed) + other = empty_index(dtype="float64", closed=closed) result = index.union(other, sort=sort) expected = other tm.assert_index_equal(result, expected) @@ -54,7 +52,7 @@ def test_union_empty_result(self, closed, sort): other = index.union(index, sort=sort) tm.assert_index_equal(result, expected) - other = empty_index(dtype="uint64", inclusive=closed) + other = empty_index(dtype="uint64", closed=closed) result = index.union(other, sort=sort) tm.assert_index_equal(result, expected) @@ -62,10 +60,10 @@ def test_union_empty_result(self, closed, sort): tm.assert_index_equal(result, expected) def test_intersection(self, closed, sort): - index = monotonic_index(0, 11, inclusive=closed) - other = monotonic_index(5, 13, inclusive=closed) + index = monotonic_index(0, 11, closed=closed) + other = monotonic_index(5, 13, closed=closed) - expected = monotonic_index(5, 11, inclusive=closed) + expected = monotonic_index(5, 11, closed=closed) result = index[::-1].intersection(other, sort=sort) if sort is None: tm.assert_index_equal(result, expected) @@ -100,21 +98,21 @@ def test_intersection(self, closed, sort): tm.assert_index_equal(result, expected) def test_intersection_empty_result(self, closed, sort): - index = monotonic_index(0, 11, inclusive=closed) + index = monotonic_index(0, 11, closed=closed) # GH 19101: empty result, same dtype - other = monotonic_index(300, 314, inclusive=closed) - expected = empty_index(dtype="int64", inclusive=closed) + other = monotonic_index(300, 314, closed=closed) + expected = empty_index(dtype="int64", closed=closed) result = index.intersection(other, sort=sort) tm.assert_index_equal(result, expected) # GH 19101: empty result, different numeric dtypes -> common dtype is float64 - other = monotonic_index(300, 314, dtype="float64", inclusive=closed) + other = monotonic_index(300, 314, dtype="float64", closed=closed) result = index.intersection(other, sort=sort) expected = other[:0] tm.assert_index_equal(result, expected) - other = monotonic_index(300, 314, dtype="uint64", inclusive=closed) + other = monotonic_index(300, 314, dtype="uint64", closed=closed) result = index.intersection(other, sort=sort) tm.assert_index_equal(result, expected) @@ -127,7 +125,7 @@ def test_intersection_duplicates(self): tm.assert_index_equal(result, expected) def test_difference(self, closed, sort): - index = IntervalIndex.from_arrays([1, 0, 3, 2], [1, 2, 3, 4], inclusive=closed) + index = IntervalIndex.from_arrays([1, 0, 3, 2], [1, 2, 3, 4], closed=closed) result = index.difference(index[:1], sort=sort) expected = index[1:] if sort is None: @@ -136,18 +134,18 @@ def test_difference(self, closed, sort): # GH 19101: empty result, same dtype result = index.difference(index, sort=sort) - expected = empty_index(dtype="int64", inclusive=closed) + expected = empty_index(dtype="int64", closed=closed) tm.assert_index_equal(result, expected) # GH 19101: empty result, different dtypes other = IntervalIndex.from_arrays( - index.left.astype("float64"), index.right, inclusive=closed + index.left.astype("float64"), index.right, closed=closed ) result = index.difference(other, sort=sort) tm.assert_index_equal(result, expected) def test_symmetric_difference(self, closed, sort): - index = monotonic_index(0, 11, inclusive=closed) + index = monotonic_index(0, 11, closed=closed) result = index[1:].symmetric_difference(index[:-1], sort=sort) expected = IntervalIndex([index[0], index[-1]]) if sort is None: @@ -156,17 +154,17 @@ def test_symmetric_difference(self, closed, sort): # GH 19101: empty result, same dtype result = index.symmetric_difference(index, sort=sort) - expected = empty_index(dtype="int64", inclusive=closed) + expected = empty_index(dtype="int64", closed=closed) if sort is None: tm.assert_index_equal(result, expected) assert tm.equalContents(result, expected) # GH 19101: empty result, different dtypes other = IntervalIndex.from_arrays( - index.left.astype("float64"), index.right, inclusive=closed + index.left.astype("float64"), index.right, closed=closed ) result = index.symmetric_difference(other, sort=sort) - expected = empty_index(dtype="float64", inclusive=closed) + expected = empty_index(dtype="float64", closed=closed) tm.assert_index_equal(result, expected) @pytest.mark.filterwarnings("ignore:'<' not supported between:RuntimeWarning") @@ -174,7 +172,7 @@ def test_symmetric_difference(self, closed, sort): "op_name", ["union", "intersection", "difference", "symmetric_difference"] ) def test_set_incompatible_types(self, closed, op_name, sort): - index = monotonic_index(0, 11, inclusive=closed) + index = monotonic_index(0, 11, closed=closed) set_op = getattr(index, op_name) # TODO: standardize return type of non-union setops type(self vs other) @@ -187,8 +185,8 @@ def test_set_incompatible_types(self, closed, op_name, sort): tm.assert_index_equal(result, expected) # mixed closed -> cast to object - for other_inclusive in {"right", "left", "both", "neither"} - {closed}: - other = monotonic_index(0, 11, inclusive=other_inclusive) + for other_closed in {"right", "left", "both", "neither"} - {closed}: + other = monotonic_index(0, 11, closed=other_closed) expected = getattr(index.astype(object), op_name)(other, sort=sort) if op_name == "difference": expected = index @@ -196,7 +194,7 @@ def test_set_incompatible_types(self, closed, op_name, sort): tm.assert_index_equal(result, expected) # GH 19016: incompatible dtypes -> cast to object - other = interval_range(Timestamp("20180101"), periods=9, inclusive=closed) + other = interval_range(Timestamp("20180101"), periods=9, closed=closed) expected = getattr(index.astype(object), op_name)(other, sort=sort) if op_name == "difference": expected = index diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 5d7fc23feb5a8..43b893b084672 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -1432,10 +1432,10 @@ def test_ensure_index_from_sequences(self, data, names, expected): def test_ensure_index_mixed_closed_intervals(self): # GH27172 intervals = [ - pd.Interval(0, 1, inclusive="left"), - pd.Interval(1, 2, inclusive="right"), - pd.Interval(2, 3, inclusive="neither"), - pd.Interval(3, 4, inclusive="both"), + pd.Interval(0, 1, closed="left"), + pd.Interval(1, 2, closed="right"), + pd.Interval(2, 3, closed="neither"), + pd.Interval(3, 4, closed="both"), ] result = ensure_index(intervals) expected = Index(intervals, dtype=object) diff --git a/pandas/tests/indexing/interval/test_interval.py b/pandas/tests/indexing/interval/test_interval.py index 7d1f1ef09fc5d..db3a569d3925b 100644 --- a/pandas/tests/indexing/interval/test_interval.py +++ b/pandas/tests/indexing/interval/test_interval.py @@ -13,7 +13,7 @@ class TestIntervalIndex: @pytest.fixture def series_with_interval_index(self): - return Series(np.arange(5), IntervalIndex.from_breaks(np.arange(6), "right")) + return Series(np.arange(5), IntervalIndex.from_breaks(np.arange(6))) def test_getitem_with_scalar(self, series_with_interval_index, indexer_sl): @@ -40,7 +40,7 @@ def test_getitem_nonoverlapping_monotonic(self, direction, closed, indexer_sl): if direction == "decreasing": tpls = tpls[::-1] - idx = IntervalIndex.from_tuples(tpls, inclusive=closed) + idx = IntervalIndex.from_tuples(tpls, closed=closed) ser = Series(list("abc"), idx) for key, expected in zip(idx.left, ser): diff --git a/pandas/tests/indexing/interval/test_interval_new.py b/pandas/tests/indexing/interval/test_interval_new.py index 602f45d637afb..4b89232f9fb12 100644 --- a/pandas/tests/indexing/interval/test_interval_new.py +++ b/pandas/tests/indexing/interval/test_interval_new.py @@ -17,9 +17,7 @@ class TestIntervalIndex: @pytest.fixture def series_with_interval_index(self): - return Series( - np.arange(5), IntervalIndex.from_breaks(np.arange(6), inclusive="right") - ) + return Series(np.arange(5), IntervalIndex.from_breaks(np.arange(6))) def test_loc_with_interval(self, series_with_interval_index, indexer_sl): @@ -30,33 +28,27 @@ def test_loc_with_interval(self, series_with_interval_index, indexer_sl): ser = series_with_interval_index.copy() expected = 0 - result = indexer_sl(ser)[Interval(0, 1, "right")] + result = indexer_sl(ser)[Interval(0, 1)] assert result == expected expected = ser.iloc[3:5] - result = indexer_sl(ser)[[Interval(3, 4, "right"), Interval(4, 5, "right")]] + result = indexer_sl(ser)[[Interval(3, 4), Interval(4, 5)]] tm.assert_series_equal(expected, result) # missing or not exact - with pytest.raises( - KeyError, match=re.escape("Interval(3, 5, inclusive='left')") - ): - indexer_sl(ser)[Interval(3, 5, inclusive="left")] + with pytest.raises(KeyError, match=re.escape("Interval(3, 5, closed='left')")): + indexer_sl(ser)[Interval(3, 5, closed="left")] - with pytest.raises( - KeyError, match=re.escape("Interval(3, 5, inclusive='right')") - ): - indexer_sl(ser)[Interval(3, 5, "right")] + with pytest.raises(KeyError, match=re.escape("Interval(3, 5, closed='right')")): + indexer_sl(ser)[Interval(3, 5)] with pytest.raises( - KeyError, match=re.escape("Interval(-2, 0, inclusive='right')") + KeyError, match=re.escape("Interval(-2, 0, closed='right')") ): - indexer_sl(ser)[Interval(-2, 0, "right")] + indexer_sl(ser)[Interval(-2, 0)] - with pytest.raises( - KeyError, match=re.escape("Interval(5, 6, inclusive='right')") - ): - indexer_sl(ser)[Interval(5, 6, "right")] + with pytest.raises(KeyError, match=re.escape("Interval(5, 6, closed='right')")): + indexer_sl(ser)[Interval(5, 6)] def test_loc_with_scalar(self, series_with_interval_index, indexer_sl): @@ -95,11 +87,11 @@ def test_loc_with_slices(self, series_with_interval_index, indexer_sl): # slice of interval expected = ser.iloc[:3] - result = indexer_sl(ser)[Interval(0, 1, "right") : Interval(2, 3, "right")] + result = indexer_sl(ser)[Interval(0, 1) : Interval(2, 3)] tm.assert_series_equal(expected, result) expected = ser.iloc[3:] - result = indexer_sl(ser)[Interval(3, 4, "right") :] + result = indexer_sl(ser)[Interval(3, 4) :] tm.assert_series_equal(expected, result) msg = "Interval objects are not currently supported" @@ -107,7 +99,7 @@ def test_loc_with_slices(self, series_with_interval_index, indexer_sl): indexer_sl(ser)[Interval(3, 6) :] with pytest.raises(NotImplementedError, match=msg): - indexer_sl(ser)[Interval(3, 4, inclusive="left") :] + indexer_sl(ser)[Interval(3, 4, closed="left") :] def test_slice_step_ne1(self, series_with_interval_index): # GH#31658 slice of scalar with step != 1 @@ -138,7 +130,7 @@ def test_slice_interval_step(self, series_with_interval_index): def test_loc_with_overlap(self, indexer_sl): - idx = IntervalIndex.from_tuples([(1, 5), (3, 7)], inclusive="right") + idx = IntervalIndex.from_tuples([(1, 5), (3, 7)]) ser = Series(range(len(idx)), index=idx) # scalar @@ -151,25 +143,23 @@ def test_loc_with_overlap(self, indexer_sl): # interval expected = 0 - result = indexer_sl(ser)[Interval(1, 5, "right")] + result = indexer_sl(ser)[Interval(1, 5)] result == expected expected = ser - result = indexer_sl(ser)[[Interval(1, 5, "right"), Interval(3, 7, "right")]] + result = indexer_sl(ser)[[Interval(1, 5), Interval(3, 7)]] tm.assert_series_equal(expected, result) - with pytest.raises( - KeyError, match=re.escape("Interval(3, 5, inclusive='right')") - ): - indexer_sl(ser)[Interval(3, 5, "right")] + with pytest.raises(KeyError, match=re.escape("Interval(3, 5, closed='right')")): + indexer_sl(ser)[Interval(3, 5)] - msg = r"None of \[\[Interval\(3, 5, inclusive='right'\)\]\]" + msg = r"None of \[\[Interval\(3, 5, closed='right'\)\]\]" with pytest.raises(KeyError, match=msg): - indexer_sl(ser)[[Interval(3, 5, "right")]] + indexer_sl(ser)[[Interval(3, 5)]] # slices with interval (only exact matches) expected = ser - result = indexer_sl(ser)[Interval(1, 5, "right") : Interval(3, 7, "right")] + result = indexer_sl(ser)[Interval(1, 5) : Interval(3, 7)] tm.assert_series_equal(expected, result) msg = "'can only get slices from an IntervalIndex if bounds are" diff --git a/pandas/tests/indexing/test_categorical.py b/pandas/tests/indexing/test_categorical.py index 21a14ef8523f1..b94323e975cd7 100644 --- a/pandas/tests/indexing/test_categorical.py +++ b/pandas/tests/indexing/test_categorical.py @@ -114,7 +114,7 @@ def test_slicing(self): df = DataFrame({"value": (np.arange(100) + 1).astype("int64")}) df["D"] = pd.cut(df.value, bins=[0, 25, 50, 75, 100]) - expected = Series([11, Interval(0, 25, "right")], index=["value", "D"], name=10) + expected = Series([11, Interval(0, 25)], index=["value", "D"], name=10) result = df.iloc[10] tm.assert_series_equal(result, expected) @@ -126,7 +126,7 @@ def test_slicing(self): result = df.iloc[10:20] tm.assert_frame_equal(result, expected) - expected = Series([9, Interval(0, 25, "right")], index=["value", "D"], name=8) + expected = Series([9, Interval(0, 25)], index=["value", "D"], name=8) result = df.loc[8] tm.assert_series_equal(result, expected) @@ -495,13 +495,13 @@ def test_loc_and_at_with_categorical_index(self): # numpy object np.array([1, "b", 3.5], dtype=object), # pandas scalars - [Interval(1, 4, "right"), Interval(4, 6, "right"), Interval(6, 9, "right")], + [Interval(1, 4), Interval(4, 6), Interval(6, 9)], [Timestamp(2019, 1, 1), Timestamp(2019, 2, 1), Timestamp(2019, 3, 1)], [Timedelta(1, "d"), Timedelta(2, "d"), Timedelta(3, "D")], # pandas Integer arrays *(pd.array([1, 2, 3], dtype=dtype) for dtype in tm.ALL_INT_EA_DTYPES), # other pandas arrays - pd.IntervalIndex.from_breaks([1, 4, 6, 9], "right").array, + pd.IntervalIndex.from_breaks([1, 4, 6, 9]).array, pd.date_range("2019-01-01", periods=3).array, pd.timedelta_range(start="1d", periods=3).array, ], diff --git a/pandas/tests/indexing/test_coercion.py b/pandas/tests/indexing/test_coercion.py index be8fcfb4d8348..2d54a9ba370ca 100644 --- a/pandas/tests/indexing/test_coercion.py +++ b/pandas/tests/indexing/test_coercion.py @@ -701,7 +701,7 @@ def test_fillna_datetime64tz(self, index_or_series, fill_val, fill_dtype): 1.1, 1 + 1j, True, - pd.Interval(1, 2, inclusive="left"), + pd.Interval(1, 2, closed="left"), pd.Timestamp("2012-01-01", tz="US/Eastern"), pd.Timestamp("2012-01-01"), pd.Timedelta(days=1), @@ -709,7 +709,7 @@ def test_fillna_datetime64tz(self, index_or_series, fill_val, fill_dtype): ], ) def test_fillna_interval(self, index_or_series, fill_val): - ii = pd.interval_range(1.0, 5.0, inclusive="right").insert(1, np.nan) + ii = pd.interval_range(1.0, 5.0, closed="right").insert(1, np.nan) assert isinstance(ii.dtype, pd.IntervalDtype) obj = index_or_series(ii) @@ -745,7 +745,7 @@ def test_fillna_series_timedelta64(self): 1.1, 1 + 1j, True, - pd.Interval(1, 2, inclusive="left"), + pd.Interval(1, 2, closed="left"), pd.Timestamp("2012-01-01", tz="US/Eastern"), pd.Timestamp("2012-01-01"), pd.Timedelta(days=1), diff --git a/pandas/tests/indexing/test_loc.py b/pandas/tests/indexing/test_loc.py index a7c03c672be58..b1eaf43f0b368 100644 --- a/pandas/tests/indexing/test_loc.py +++ b/pandas/tests/indexing/test_loc.py @@ -1539,12 +1539,12 @@ def test_loc_getitem_interval_index(self): def test_loc_getitem_interval_index2(self): # GH#19977 - index = pd.interval_range(start=0, periods=3, inclusive="both") + index = pd.interval_range(start=0, periods=3, closed="both") df = DataFrame( [[1, 2, 3], [4, 5, 6], [7, 8, 9]], index=index, columns=["A", "B", "C"] ) - index_exp = pd.interval_range(start=0, periods=2, freq=1, inclusive="both") + index_exp = pd.interval_range(start=0, periods=2, freq=1, closed="both") expected = Series([1, 4], index=index_exp, name="A") result = df.loc[1, "A"] tm.assert_series_equal(result, expected) diff --git a/pandas/tests/internals/test_internals.py b/pandas/tests/internals/test_internals.py index 2f3b569c899e1..3c90eee5be999 100644 --- a/pandas/tests/internals/test_internals.py +++ b/pandas/tests/internals/test_internals.py @@ -1271,7 +1271,7 @@ def test_interval_can_hold_element(self, dtype, element): # Careful: to get the expected Series-inplace behavior we need # `elem` to not have the same length as `arr` - ii2 = IntervalIndex.from_breaks(arr[:-1], inclusive="neither") + ii2 = IntervalIndex.from_breaks(arr[:-1], closed="neither") elem = element(ii2) self.check_series_setitem(elem, ii, False) assert not blk._can_hold_element(elem) diff --git a/pandas/tests/io/excel/test_writers.py b/pandas/tests/io/excel/test_writers.py index ba6366b71d854..f9836810afbe0 100644 --- a/pandas/tests/io/excel/test_writers.py +++ b/pandas/tests/io/excel/test_writers.py @@ -281,10 +281,7 @@ def test_multiindex_interval_datetimes(self, ext): [ range(4), pd.interval_range( - start=pd.Timestamp("2020-01-01"), - periods=4, - freq="6M", - inclusive="right", + start=pd.Timestamp("2020-01-01"), periods=4, freq="6M" ), ] ) diff --git a/pandas/tests/plotting/test_misc.py b/pandas/tests/plotting/test_misc.py index 7dd9b78bab1cd..93da7f71f51f9 100644 --- a/pandas/tests/plotting/test_misc.py +++ b/pandas/tests/plotting/test_misc.py @@ -607,7 +607,7 @@ def test_bar_plt_xaxis_intervalrange(self): expected = [Text(0, 0, "([0, 1],)"), Text(1, 0, "([1, 2],)")] s = Series( [1, 2], - index=[interval_range(0, 2, inclusive="both")], + index=[interval_range(0, 2, closed="both")], ) _check_plot_works(s.plot.bar) assert all( diff --git a/pandas/tests/reshape/concat/test_append.py b/pandas/tests/reshape/concat/test_append.py index 7e4371100b5ad..0b1d1c4a3d346 100644 --- a/pandas/tests/reshape/concat/test_append.py +++ b/pandas/tests/reshape/concat/test_append.py @@ -172,7 +172,7 @@ def test_append_preserve_index_name(self): Index(list("abc")), pd.CategoricalIndex("A B C".split()), pd.CategoricalIndex("D E F".split(), ordered=True), - pd.IntervalIndex.from_breaks([7, 8, 9, 10], inclusive="right"), + pd.IntervalIndex.from_breaks([7, 8, 9, 10]), pd.DatetimeIndex( [ dt.datetime(2013, 1, 3, 0, 0), diff --git a/pandas/tests/reshape/test_cut.py b/pandas/tests/reshape/test_cut.py index 815890f319396..1425686f027e4 100644 --- a/pandas/tests/reshape/test_cut.py +++ b/pandas/tests/reshape/test_cut.py @@ -37,7 +37,7 @@ def test_bins(func): data = func([0.2, 1.4, 2.5, 6.2, 9.7, 2.1]) result, bins = cut(data, 3, retbins=True) - intervals = IntervalIndex.from_breaks(bins.round(3), "right") + intervals = IntervalIndex.from_breaks(bins.round(3)) intervals = intervals.take([0, 0, 0, 1, 2, 0]) expected = Categorical(intervals, ordered=True) @@ -49,7 +49,7 @@ def test_right(): data = np.array([0.2, 1.4, 2.5, 6.2, 9.7, 2.1, 2.575]) result, bins = cut(data, 4, right=True, retbins=True) - intervals = IntervalIndex.from_breaks(bins.round(3), "right") + intervals = IntervalIndex.from_breaks(bins.round(3)) expected = Categorical(intervals, ordered=True) expected = expected.take([0, 0, 0, 2, 3, 0, 0]) @@ -61,7 +61,7 @@ def test_no_right(): data = np.array([0.2, 1.4, 2.5, 6.2, 9.7, 2.1, 2.575]) result, bins = cut(data, 4, right=False, retbins=True) - intervals = IntervalIndex.from_breaks(bins.round(3), inclusive="left") + intervals = IntervalIndex.from_breaks(bins.round(3), closed="left") intervals = intervals.take([0, 0, 0, 2, 3, 0, 1]) expected = Categorical(intervals, ordered=True) @@ -86,7 +86,7 @@ def test_bins_from_interval_index_doc_example(): # Make sure we preserve the bins. ages = np.array([10, 15, 13, 12, 23, 25, 28, 59, 60]) c = cut(ages, bins=[0, 18, 35, 70]) - expected = IntervalIndex.from_tuples([(0, 18), (18, 35), (35, 70)], "right") + expected = IntervalIndex.from_tuples([(0, 18), (18, 35), (35, 70)]) tm.assert_index_equal(c.categories, expected) result = cut([25, 20, 50], bins=c.categories) @@ -121,8 +121,7 @@ def test_bins_not_monotonic(): [ (Timestamp.min, Timestamp("2018-01-01")), (Timestamp("2018-01-01"), Timestamp.max), - ], - "right", + ] ), ), ( @@ -131,7 +130,7 @@ def test_bins_not_monotonic(): [np.iinfo(np.int64).min, 0, np.iinfo(np.int64).max], dtype="int64" ), IntervalIndex.from_tuples( - [(np.iinfo(np.int64).min, 0), (0, np.iinfo(np.int64).max)], "right" + [(np.iinfo(np.int64).min, 0), (0, np.iinfo(np.int64).max)] ), ), ( @@ -157,8 +156,7 @@ def test_bins_not_monotonic(): np.timedelta64(0, "ns"), np.timedelta64(np.iinfo(np.int64).max, "ns"), ), - ], - "right", + ] ), ), ], @@ -234,7 +232,7 @@ def test_labels(right, breaks, closed): arr = np.tile(np.arange(0, 1.01, 0.1), 4) result, bins = cut(arr, 4, retbins=True, right=right) - ex_levels = IntervalIndex.from_breaks(breaks, inclusive=closed) + ex_levels = IntervalIndex.from_breaks(breaks, closed=closed) tm.assert_index_equal(result.categories, ex_levels) @@ -250,7 +248,7 @@ def test_label_precision(): arr = np.arange(0, 0.73, 0.01) result = cut(arr, 4, precision=2) - ex_levels = IntervalIndex.from_breaks([-0.00072, 0.18, 0.36, 0.54, 0.72], "right") + ex_levels = IntervalIndex.from_breaks([-0.00072, 0.18, 0.36, 0.54, 0.72]) tm.assert_index_equal(result.categories, ex_levels) @@ -274,13 +272,13 @@ def test_inf_handling(): result = cut(data, bins) result_ser = cut(data_ser, bins) - ex_uniques = IntervalIndex.from_breaks(bins, "right") + ex_uniques = IntervalIndex.from_breaks(bins) tm.assert_index_equal(result.categories, ex_uniques) - assert result[5] == Interval(4, np.inf, "right") - assert result[0] == Interval(-np.inf, 2, "right") - assert result_ser[5] == Interval(4, np.inf, "right") - assert result_ser[0] == Interval(-np.inf, 2, "right") + assert result[5] == Interval(4, np.inf) + assert result[0] == Interval(-np.inf, 2) + assert result_ser[5] == Interval(4, np.inf) + assert result_ser[0] == Interval(-np.inf, 2) def test_cut_out_of_bounds(): @@ -357,7 +355,7 @@ def test_cut_return_intervals(): exp_bins[0] -= 0.008 expected = Series( - IntervalIndex.from_breaks(exp_bins, inclusive="right").take( + IntervalIndex.from_breaks(exp_bins, closed="right").take( [0, 0, 0, 1, 1, 1, 2, 2, 2] ) ).astype(CDT(ordered=True)) @@ -370,7 +368,7 @@ def test_series_ret_bins(): result, bins = cut(ser, 2, retbins=True) expected = Series( - IntervalIndex.from_breaks([-0.003, 1.5, 3], inclusive="right").repeat(2) + IntervalIndex.from_breaks([-0.003, 1.5, 3], closed="right").repeat(2) ).astype(CDT(ordered=True)) tm.assert_series_equal(result, expected) @@ -444,8 +442,7 @@ def test_datetime_bin(conv): [ Interval(Timestamp(bin_data[0]), Timestamp(bin_data[1])), Interval(Timestamp(bin_data[1]), Timestamp(bin_data[2])), - ], - "right", + ] ) ).astype(CDT(ordered=True)) @@ -491,8 +488,7 @@ def test_datetime_cut(data): Interval( Timestamp("2013-01-02 08:00:00"), Timestamp("2013-01-03 00:00:00") ), - ], - "right", + ] ) ).astype(CDT(ordered=True)) tm.assert_series_equal(Series(result), expected) @@ -535,8 +531,7 @@ def test_datetime_tz_cut(bins, box): Timestamp("2013-01-02 08:00:00", tz=tz), Timestamp("2013-01-03 00:00:00", tz=tz), ), - ], - "right", + ] ) ).astype(CDT(ordered=True)) tm.assert_series_equal(result, expected) @@ -690,8 +685,8 @@ def test_cut_no_warnings(): def test_cut_with_duplicated_index_lowest_included(): # GH 42185 expected = Series( - [Interval(-0.001, 2, inclusive="right")] * 3 - + [Interval(2, 4, inclusive="right"), Interval(-0.001, 2, inclusive="right")], + [Interval(-0.001, 2, closed="right")] * 3 + + [Interval(2, 4, closed="right"), Interval(-0.001, 2, closed="right")], index=[0, 1, 2, 3, 0], dtype="category", ).cat.as_ordered() @@ -711,16 +706,16 @@ def test_cut_with_nonexact_categorical_indices(): index = pd.CategoricalIndex( [ - Interval(-0.099, 9.9, inclusive="right"), - Interval(9.9, 19.8, inclusive="right"), - Interval(19.8, 29.7, inclusive="right"), - Interval(29.7, 39.6, inclusive="right"), - Interval(39.6, 49.5, inclusive="right"), - Interval(49.5, 59.4, inclusive="right"), - Interval(59.4, 69.3, inclusive="right"), - Interval(69.3, 79.2, inclusive="right"), - Interval(79.2, 89.1, inclusive="right"), - Interval(89.1, 99, inclusive="right"), + Interval(-0.099, 9.9, closed="right"), + Interval(9.9, 19.8, closed="right"), + Interval(19.8, 29.7, closed="right"), + Interval(29.7, 39.6, closed="right"), + Interval(39.6, 49.5, closed="right"), + Interval(49.5, 59.4, closed="right"), + Interval(59.4, 69.3, closed="right"), + Interval(69.3, 79.2, closed="right"), + Interval(79.2, 89.1, closed="right"), + Interval(89.1, 99, closed="right"), ], ordered=True, ) diff --git a/pandas/tests/reshape/test_pivot.py b/pandas/tests/reshape/test_pivot.py index e2eed4358aaaa..0322ed161c83c 100644 --- a/pandas/tests/reshape/test_pivot.py +++ b/pandas/tests/reshape/test_pivot.py @@ -301,7 +301,7 @@ def test_pivot_with_interval_index(self, interval_values, dropna): def test_pivot_with_interval_index_margins(self): # GH 25815 - ordered_cat = pd.IntervalIndex.from_arrays([0, 0, 1, 1], [1, 1, 2, 2], "right") + ordered_cat = pd.IntervalIndex.from_arrays([0, 0, 1, 1], [1, 1, 2, 2]) df = DataFrame( { "A": np.arange(4, 0, -1, dtype=np.intp), @@ -319,10 +319,7 @@ def test_pivot_with_interval_index_margins(self): result = pivot_tab["All"] expected = Series( [3, 7, 10], - index=Index( - [pd.Interval(0, 1, "right"), pd.Interval(1, 2, "right"), "All"], - name="C", - ), + index=Index([pd.Interval(0, 1), pd.Interval(1, 2), "All"], name="C"), name="All", dtype=np.intp, ) diff --git a/pandas/tests/reshape/test_qcut.py b/pandas/tests/reshape/test_qcut.py index 0f82bb736c069..f7c7204d02a49 100644 --- a/pandas/tests/reshape/test_qcut.py +++ b/pandas/tests/reshape/test_qcut.py @@ -76,8 +76,7 @@ def test_qcut_include_lowest(): Interval(2.25, 4.5), Interval(4.5, 6.75), Interval(6.75, 9), - ], - "right", + ] ) tm.assert_index_equal(ii.categories, ex_levels) @@ -92,7 +91,7 @@ def test_qcut_nas(): def test_qcut_index(): result = qcut([0, 2], 2) - intervals = [Interval(-0.001, 1, "right"), Interval(1, 2, "right")] + intervals = [Interval(-0.001, 1), Interval(1, 2)] expected = Categorical(intervals, ordered=True) tm.assert_categorical_equal(result, expected) @@ -128,11 +127,7 @@ def test_qcut_return_intervals(): res = qcut(ser, [0, 0.333, 0.666, 1]) exp_levels = np.array( - [ - Interval(-0.001, 2.664, "right"), - Interval(2.664, 5.328, "right"), - Interval(5.328, 8, "right"), - ] + [Interval(-0.001, 2.664), Interval(2.664, 5.328), Interval(5.328, 8)] ) exp = Series(exp_levels.take([0, 0, 0, 1, 1, 1, 2, 2, 2])).astype(CDT(ordered=True)) tm.assert_series_equal(res, exp) @@ -188,7 +183,7 @@ def test_qcut_duplicates_bin(kwargs, msg): qcut(values, 3, **kwargs) else: result = qcut(values, 3, **kwargs) - expected = IntervalIndex([Interval(-0.001, 1), Interval(1, 3)], "right") + expected = IntervalIndex([Interval(-0.001, 1), Interval(1, 3)]) tm.assert_index_equal(result.categories, expected) @@ -203,7 +198,7 @@ def test_single_quantile(data, start, end, length, labels): result = qcut(ser, 1, labels=labels) if labels is None: - intervals = IntervalIndex([Interval(start, end)] * length, inclusive="right") + intervals = IntervalIndex([Interval(start, end)] * length, closed="right") expected = Series(intervals).astype(CDT(ordered=True)) else: expected = Series([0] * length, dtype=np.intp) @@ -222,7 +217,7 @@ def test_single_quantile(data, start, end, length, labels): def test_qcut_nat(ser): # see gh-19768 intervals = IntervalIndex.from_tuples( - [(ser[0] - Nano(), ser[2] - Day()), np.nan, (ser[2] - Day(), ser[2])], "right" + [(ser[0] - Nano(), ser[2] - Day()), np.nan, (ser[2] - Day(), ser[2])] ) expected = Series(Categorical(intervals, ordered=True)) @@ -252,8 +247,7 @@ def test_datetime_tz_qcut(bins): Timestamp("2013-01-02 08:00:00", tz=tz), Timestamp("2013-01-03 00:00:00", tz=tz), ), - ], - "right", + ] ) ).astype(CDT(ordered=True)) tm.assert_series_equal(result, expected) diff --git a/pandas/tests/scalar/interval/test_interval.py b/pandas/tests/scalar/interval/test_interval.py index c5644b2f36ead..192aaacbac2b5 100644 --- a/pandas/tests/scalar/interval/test_interval.py +++ b/pandas/tests/scalar/interval/test_interval.py @@ -13,22 +13,22 @@ @pytest.fixture def interval(): - return Interval(0, 1, "right") + return Interval(0, 1) class TestInterval: def test_properties(self, interval): - assert interval.inclusive == "right" + assert interval.closed == "right" assert interval.left == 0 assert interval.right == 1 assert interval.mid == 0.5 def test_repr(self, interval): - assert repr(interval) == "Interval(0, 1, inclusive='right')" + assert repr(interval) == "Interval(0, 1, closed='right')" assert str(interval) == "(0, 1]" - interval_left = Interval(0, 1, "left") - assert repr(interval_left) == "Interval(0, 1, inclusive='left')" + interval_left = Interval(0, 1, closed="left") + assert repr(interval_left) == "Interval(0, 1, closed='left')" assert str(interval_left) == "[0, 1)" def test_contains(self, interval): @@ -40,14 +40,14 @@ def test_contains(self, interval): assert 0 in interval_both assert 1 in interval_both - interval_neither = Interval(0, 1, "neither") + interval_neither = Interval(0, 1, closed="neither") assert 0 not in interval_neither assert 0.5 in interval_neither assert 1 not in interval_neither def test_equal(self): - assert Interval(0, 1, "right") == Interval(0, 1, "right") - assert Interval(0, 1, "right") != Interval(0, 1, "left") + assert Interval(0, 1) == Interval(0, 1, closed="right") + assert Interval(0, 1) != Interval(0, 1, closed="left") assert Interval(0, 1) != 0 def test_comparison(self): @@ -125,7 +125,7 @@ def test_is_empty(self, left, right, closed): iv = Interval(left, right, closed) assert iv.is_empty is False - # same endpoint is empty except when inclusive='both' (contains one point) + # same endpoint is empty except when closed='both' (contains one point) iv = Interval(left, left, closed) result = iv.is_empty expected = closed != "both" @@ -148,8 +148,8 @@ def test_construct_errors(self, left, right): Interval(left, right) def test_math_add(self, closed): - interval = Interval(0, 1, closed) - expected = Interval(1, 2, closed) + interval = Interval(0, 1, closed=closed) + expected = Interval(1, 2, closed=closed) result = interval + 1 assert result == expected @@ -169,8 +169,8 @@ def test_math_add(self, closed): interval + "foo" def test_math_sub(self, closed): - interval = Interval(0, 1, closed) - expected = Interval(-1, 0, closed) + interval = Interval(0, 1, closed=closed) + expected = Interval(-1, 0, closed=closed) result = interval - 1 assert result == expected @@ -187,8 +187,8 @@ def test_math_sub(self, closed): interval - "foo" def test_math_mult(self, closed): - interval = Interval(0, 1, closed) - expected = Interval(0, 2, closed) + interval = Interval(0, 1, closed=closed) + expected = Interval(0, 2, closed=closed) result = interval * 2 assert result == expected @@ -209,8 +209,8 @@ def test_math_mult(self, closed): interval * "foo" def test_math_div(self, closed): - interval = Interval(0, 1, closed) - expected = Interval(0, 0.5, closed) + interval = Interval(0, 1, closed=closed) + expected = Interval(0, 0.5, closed=closed) result = interval / 2.0 assert result == expected @@ -227,8 +227,8 @@ def test_math_div(self, closed): interval / "foo" def test_math_floordiv(self, closed): - interval = Interval(1, 2, closed) - expected = Interval(0, 1, closed) + interval = Interval(1, 2, closed=closed) + expected = Interval(0, 1, closed=closed) result = interval // 2 assert result == expected @@ -245,9 +245,9 @@ def test_math_floordiv(self, closed): interval // "foo" def test_constructor_errors(self): - msg = "invalid option for 'inclusive': foo" + msg = "invalid option for 'closed': foo" with pytest.raises(ValueError, match=msg): - Interval(0, 1, "foo") + Interval(0, 1, closed="foo") msg = "left side of interval must be <= right side" with pytest.raises(ValueError, match=msg): diff --git a/pandas/tests/series/indexing/test_setitem.py b/pandas/tests/series/indexing/test_setitem.py index 244ad9884b82a..90051405c6935 100644 --- a/pandas/tests/series/indexing/test_setitem.py +++ b/pandas/tests/series/indexing/test_setitem.py @@ -807,14 +807,9 @@ def test_index_putmask(self, obj, key, expected, val): pytest.param( # GH#45568 setting a valid NA value into IntervalDtype[int] should # cast to IntervalDtype[float] - Series(interval_range(1, 5, inclusive="right")), + Series(interval_range(1, 5)), Series( - [ - Interval(1, 2, "right"), - np.nan, - Interval(3, 4, "right"), - Interval(4, 5, "right"), - ], + [Interval(1, 2), np.nan, Interval(3, 4), Interval(4, 5)], dtype="interval[float64]", ), 1, @@ -1085,9 +1080,9 @@ class TestSetitemFloatIntervalWithIntIntervalValues(SetitemCastingEquivalents): def test_setitem_example(self): # Just a case here to make obvious what this test class is aimed at - idx = IntervalIndex.from_breaks(range(4), inclusive="right") + idx = IntervalIndex.from_breaks(range(4)) obj = Series(idx) - val = Interval(0.5, 1.5, "right") + val = Interval(0.5, 1.5) obj[0] = val assert obj.dtype == "Interval[float64, right]" @@ -1381,7 +1376,7 @@ def obj(self): @pytest.mark.parametrize( - "val", ["foo", Period("2016", freq="Y"), Interval(1, 2, inclusive="both")] + "val", ["foo", Period("2016", freq="Y"), Interval(1, 2, closed="both")] ) @pytest.mark.parametrize("exp_dtype", [object]) class TestPeriodIntervalCoercion(CoercionTest): @@ -1389,7 +1384,7 @@ class TestPeriodIntervalCoercion(CoercionTest): @pytest.fixture( params=[ period_range("2016-01-01", periods=3, freq="D"), - interval_range(1, 5, inclusive="right"), + interval_range(1, 5), ] ) def obj(self, request): @@ -1580,7 +1575,7 @@ def test_setitem_int_as_positional_fallback_deprecation(): # Once the deprecation is enforced, we will have # expected = Series([1, 2, 3, 4, 5], index=[1.1, 2.1, 3.0, 4.1, 5.0]) - ii = IntervalIndex.from_breaks(range(10), inclusive="right")[::2] + ii = IntervalIndex.from_breaks(range(10))[::2] ser2 = Series(range(len(ii)), index=ii) expected2 = ser2.copy() expected2.iloc[-1] = 9 diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py index 01fe6a529a86f..f79714ae6455c 100644 --- a/pandas/tests/series/test_constructors.py +++ b/pandas/tests/series/test_constructors.py @@ -1182,7 +1182,7 @@ def test_constructor_datetime64_bigendian(self): @pytest.mark.parametrize("interval_constructor", [IntervalIndex, IntervalArray]) def test_construction_interval(self, interval_constructor): # construction from interval & array of intervals - intervals = interval_constructor.from_breaks(np.arange(3), inclusive="right") + intervals = interval_constructor.from_breaks(np.arange(3), closed="right") result = Series(intervals) assert result.dtype == "interval[int64, right]" tm.assert_index_equal(Index(result.values), Index(intervals)) @@ -1192,7 +1192,7 @@ def test_construction_interval(self, interval_constructor): ) def test_constructor_infer_interval(self, data_constructor): # GH 23563: consistent closed results in interval dtype - data = [Interval(0, 1, "right"), Interval(0, 2, "right"), None] + data = [Interval(0, 1), Interval(0, 2), None] result = Series(data_constructor(data)) expected = Series(IntervalArray(data)) assert result.dtype == "interval[float64, right]" @@ -1201,9 +1201,9 @@ def test_constructor_infer_interval(self, data_constructor): @pytest.mark.parametrize( "data_constructor", [list, np.array], ids=["list", "ndarray[object]"] ) - def test_constructor_interval_mixed_inclusive(self, data_constructor): - # GH 23563: mixed inclusive results in object dtype (not interval dtype) - data = [Interval(0, 1, inclusive="both"), Interval(0, 2, inclusive="neither")] + def test_constructor_interval_mixed_closed(self, data_constructor): + # GH 23563: mixed closed results in object dtype (not interval dtype) + data = [Interval(0, 1, closed="both"), Interval(0, 2, closed="neither")] result = Series(data_constructor(data)) assert result.dtype == object assert result.tolist() == data diff --git a/pandas/tests/test_algos.py b/pandas/tests/test_algos.py index fd617dcb4565e..b3e9a0f6498b3 100644 --- a/pandas/tests/test_algos.py +++ b/pandas/tests/test_algos.py @@ -1134,26 +1134,19 @@ def test_value_counts(self): # assert isinstance(factor, n) result = algos.value_counts(factor) breaks = [-1.194, -0.535, 0.121, 0.777, 1.433] - index = IntervalIndex.from_breaks(breaks, inclusive="right").astype( - CDT(ordered=True) - ) + index = IntervalIndex.from_breaks(breaks).astype(CDT(ordered=True)) expected = Series([1, 1, 1, 1], index=index) tm.assert_series_equal(result.sort_index(), expected.sort_index()) def test_value_counts_bins(self): s = [1, 2, 3, 4] result = algos.value_counts(s, bins=1) - expected = Series( - [4], index=IntervalIndex.from_tuples([(0.996, 4.0)], inclusive="right") - ) + expected = Series([4], index=IntervalIndex.from_tuples([(0.996, 4.0)])) tm.assert_series_equal(result, expected) result = algos.value_counts(s, bins=2, sort=False) expected = Series( - [2, 2], - index=IntervalIndex.from_tuples( - [(0.996, 2.5), (2.5, 4.0)], inclusive="right" - ), + [2, 2], index=IntervalIndex.from_tuples([(0.996, 2.5), (2.5, 4.0)]) ) tm.assert_series_equal(result, expected) diff --git a/pandas/tests/util/test_assert_frame_equal.py b/pandas/tests/util/test_assert_frame_equal.py index c3c5f2fdc9d29..6ff1a1c17b179 100644 --- a/pandas/tests/util/test_assert_frame_equal.py +++ b/pandas/tests/util/test_assert_frame_equal.py @@ -247,7 +247,7 @@ def test_assert_frame_equal_extension_dtype_mismatch(): def test_assert_frame_equal_interval_dtype_mismatch(): # https://github.com/pandas-dev/pandas/issues/32747 - left = DataFrame({"a": [pd.Interval(0, 1, "right")]}, dtype="interval") + left = DataFrame({"a": [pd.Interval(0, 1)]}, dtype="interval") right = left.astype(object) msg = ( diff --git a/pandas/tests/util/test_assert_interval_array_equal.py b/pandas/tests/util/test_assert_interval_array_equal.py index 29ebc00b2e69a..8cc4ade3d7e95 100644 --- a/pandas/tests/util/test_assert_interval_array_equal.py +++ b/pandas/tests/util/test_assert_interval_array_equal.py @@ -9,7 +9,7 @@ [ {"start": 0, "periods": 4}, {"start": 1, "periods": 5}, - {"start": 5, "end": 10, "inclusive": "left"}, + {"start": 5, "end": 10, "closed": "left"}, ], ) def test_interval_array_equal(kwargs): @@ -19,13 +19,13 @@ def test_interval_array_equal(kwargs): def test_interval_array_equal_closed_mismatch(): kwargs = {"start": 0, "periods": 5} - arr1 = interval_range(inclusive="left", **kwargs).values - arr2 = interval_range(inclusive="right", **kwargs).values + arr1 = interval_range(closed="left", **kwargs).values + arr2 = interval_range(closed="right", **kwargs).values msg = """\ IntervalArray are different -Attribute "inclusive" are different +Attribute "closed" are different \\[left\\]: left \\[right\\]: right""" diff --git a/pandas/tests/util/test_assert_series_equal.py b/pandas/tests/util/test_assert_series_equal.py index f8600956aa2f9..25f5b31eb4664 100644 --- a/pandas/tests/util/test_assert_series_equal.py +++ b/pandas/tests/util/test_assert_series_equal.py @@ -290,7 +290,7 @@ def test_assert_series_equal_extension_dtype_mismatch(): def test_assert_series_equal_interval_dtype_mismatch(): # https://github.com/pandas-dev/pandas/issues/32747 - left = Series([pd.Interval(0, 1, "right")], dtype="interval") + left = Series([pd.Interval(0, 1)], dtype="interval") right = left.astype(object) msg = """Attributes of Series are different diff --git a/pandas/util/_validators.py b/pandas/util/_validators.py index 7e938e4648e97..2b5be2d48f382 100644 --- a/pandas/util/_validators.py +++ b/pandas/util/_validators.py @@ -16,7 +16,6 @@ import numpy as np -from pandas._typing import IntervalInclusiveType from pandas.util._exceptions import find_stack_level from pandas.core.dtypes.common import ( @@ -494,7 +493,7 @@ def validate_endpoints(closed: str | None) -> tuple[bool, bool]: return left_closed, right_closed -def validate_inclusive(inclusive: IntervalInclusiveType | None) -> tuple[bool, bool]: +def validate_inclusive(inclusive: str | None) -> tuple[bool, bool]: """ Check that the `inclusive` argument is among {"both", "neither", "left", "right"}.