diff --git a/pandas/core/arrays/interval.py b/pandas/core/arrays/interval.py index 8d13e76c57e4f..f09b70a359520 100644 --- a/pandas/core/arrays/interval.py +++ b/pandas/core/arrays/interval.py @@ -39,7 +39,6 @@ ) from pandas.compat.numpy import function as nv from pandas.errors import IntCastingNaNError -from pandas.util._decorators import Appender from pandas.core.dtypes.cast import ( LossySetitemError, @@ -79,7 +78,6 @@ from pandas.core.arrays import ArrowExtensionArray from pandas.core.arrays.base import ( ExtensionArray, - _extension_array_shared_docs, ) from pandas.core.arrays.datetimes import DatetimeArray from pandas.core.arrays.timedeltas import TimedeltaArray @@ -178,21 +176,63 @@ """ -@Appender( - _interval_shared_docs["class"] - % { - "klass": "IntervalArray", - "summary": "Pandas array for interval data that are closed on the same side.", - "name": "", - "extra_attributes": "", - "extra_methods": "", - "examples": textwrap.dedent( - """\ +class IntervalArray(IntervalMixin, ExtensionArray): + """ + Pandas array for interval data that are closed on the same side. + + Parameters + ---------- + data : array-like (1-dimensional) + Array-like (ndarray, :class:`DateTimeArray`, :class:`TimeDeltaArray`) containing + Interval objects from which to build the IntervalArray. + 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. + copy : bool, default False + Copy the input data. + verify_integrity : bool, default True + Verify that the IntervalArray is valid. + + Attributes + ---------- + left + right + closed + mid + length + is_empty + is_non_overlapping_monotonic + + Methods + ------- + from_arrays + from_tuples + from_breaks + contains + overlaps + set_closed + to_tuples + + See Also + -------- + Index : The base pandas Index type. + Interval : A bounded slice-like interval; the elements of an IntervalArray. + interval_range : Function to create a fixed frequency IntervalIndex. + cut : Bin values into discrete Intervals. + qcut : Bin values into equal-sized Intervals based on rank or sample quantiles. + + Notes + ----- + See the `user guide + `__ + for more. + Examples -------- A new ``IntervalArray`` can be constructed directly from an array-like of ``Interval`` objects: - >>> pd.arrays.IntervalArray([pd.Interval(0, 1), pd.Interval(1, 5)]) [(0, 1], (1, 5]] @@ -202,10 +242,7 @@ methods: :meth:`IntervalArray.from_arrays`, :meth:`IntervalArray.from_breaks`, and :meth:`IntervalArray.from_tuples`. """ - ), - } -) -class IntervalArray(IntervalMixin, ExtensionArray): + can_hold_na = True _na_value = _fill_value = np.nan @@ -429,23 +466,6 @@ def _from_factorized(cls, values: np.ndarray, original: IntervalArray) -> Self: ) @classmethod - @Appender( - _interval_shared_docs["from_breaks"] - % { - "klass": "IntervalArray", - "name": "", - "examples": textwrap.dedent( - """\ - Examples - -------- - >>> pd.arrays.IntervalArray.from_breaks([0, 1, 2, 3]) - - [(0, 1], (1, 2], (2, 3]] - Length: 3, dtype: interval[int64, right] - """ - ), - } - ) def from_breaks( cls, breaks, @@ -453,6 +473,39 @@ def from_breaks( copy: bool = False, dtype: Dtype | None = None, ) -> Self: + """ + Construct an IntervalArray from an array of splits. + + Parameters + ---------- + breaks : array-like (1-dimensional) + Left and right bounds for each interval. + 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. + dtype : dtype or None, default None + If None, dtype will be inferred. + + Returns + ------- + IntervalArray + + See Also + -------- + interval_range : Function to create a fixed frequency IntervalIndex. + IntervalArray.from_arrays : Construct from a left and right array. + IntervalArray.from_tuples : Construct from a sequence of tuples. + + Examples + -------- + >>> pd.arrays.IntervalArray.from_breaks([0, 1, 2, 3]) + + [(0, 1], (1, 2], (2, 3]] + Length: 3, dtype: interval[int64, right] + """ + breaks = _maybe_convert_platform_interval(breaks) return cls.from_arrays(breaks[:-1], breaks[1:], closed, copy=copy, dtype=dtype) @@ -508,23 +561,6 @@ def from_breaks( ) @classmethod - @Appender( - _interval_shared_docs["from_arrays"] - % { - "klass": "IntervalArray", - "name": "", - "examples": textwrap.dedent( - """\ - Examples - -------- - >>> pd.arrays.IntervalArray.from_arrays([0, 1, 2], [1, 2, 3]) - - [(0, 1], (1, 2], (2, 3]] - Length: 3, dtype: interval[int64, right] - """ - ), - } - ) def from_arrays( cls, left, @@ -533,6 +569,57 @@ def from_arrays( copy: bool = False, dtype: Dtype | None = None, ) -> Self: + """ + Construct from two arrays defining the left and right bounds. + + Parameters + ---------- + left : array-like (1-dimensional) + Left bounds for each interval. + right : array-like (1-dimensional) + Right bounds for each interval. + 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. + dtype : dtype, optional + If None, dtype will be inferred. + + Returns + ------- + IntervalArray + + Raises + ------ + ValueError + When a value is missing in only one of `left` or `right`. + When a value in `left` is greater than the corresponding value + in `right`. + + See Also + -------- + interval_range : Function to create a fixed frequency IntervalIndex. + IntervalArray.from_breaks : Construct an IntervalArray from an array of + splits. + IntervalArray.from_tuples : Construct an IntervalArray from an + array-like of tuples. + + Notes + ----- + Each element of `left` must be less than or equal to the `right` + element at the same position. If an element is missing, it must be + missing in both `left` and `right`. A TypeError is raised when + using an unsupported type for `left` or `right`. At the moment, + 'category', 'object', and 'string' subtypes are not supported. + + Examples + -------- + >>> pd.arrays.IntervalArray.from_arrays([0, 1, 2], [1, 2, 3]) + + [(0, 1], (1, 2], (2, 3]] + Length: 3, dtype: interval[int64, right] + """ left = _maybe_convert_platform_interval(left) right = _maybe_convert_platform_interval(right) @@ -581,23 +668,6 @@ def from_arrays( ) @classmethod - @Appender( - _interval_shared_docs["from_tuples"] - % { - "klass": "IntervalArray", - "name": "", - "examples": textwrap.dedent( - """\ - Examples - -------- - >>> pd.arrays.IntervalArray.from_tuples([(0, 1), (1, 2)]) - - [(0, 1], (1, 2]] - Length: 2, dtype: interval[int64, right] - """ - ), - } - ) def from_tuples( cls, data, @@ -605,6 +675,40 @@ def from_tuples( copy: bool = False, dtype: Dtype | None = None, ) -> Self: + """ + Construct an IntervalArray from an array-like of tuples. + + Parameters + ---------- + data : array-like (1-dimensional) + Array of tuples. + 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. + dtype : dtype or None, default None + If None, dtype will be inferred. + + Returns + ------- + IntervalArray + + See Also + -------- + interval_range : Function to create a fixed frequency IntervalIndex. + IntervalArray.from_arrays : Construct an IntervalArray from a left and + right array. + IntervalArray.from_breaks : Construct an IntervalArray from an array of + splits. + + Examples + -------- + >>> pd.arrays.IntervalArray.from_tuples([(0, 1), (1, 2)]) + + [(0, 1], (1, 2]] + Length: 2, dtype: interval[int64, right] + """ if len(data): left, right = [], [] else: @@ -1381,23 +1485,50 @@ def mid(self) -> Index: """ ) - @Appender( - _interval_shared_docs["overlaps"] - % { - "klass": "IntervalArray", - "examples": textwrap.dedent( - """\ + def overlaps(self, other): + """ + Check elementwise if an Interval overlaps the values in the IntervalArray. + + Two intervals overlap if they share a common point, including closed + endpoints. Intervals that only have an open endpoint in common do not + overlap. + + Parameters + ---------- + other : IntervalArray + Interval to check against for an overlap. + + Returns + ------- + ndarray + Boolean array positionally indicating where an overlap occurs. + + See Also + -------- + Interval.overlaps : Check whether two Interval objects overlap. + + Examples + -------- >>> data = [(0, 1), (1, 3), (2, 4)] >>> intervals = pd.arrays.IntervalArray.from_tuples(data) >>> intervals [(0, 1], (1, 3], (2, 4]] Length: 3, dtype: interval[int64, right] + + >>> intervals.overlaps(pd.Interval(0.5, 1.5)) + array([ True, True, False]) + + Intervals that share closed endpoints overlap: + + >>> 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, closed="right")) + array([False, True, False]) """ - ), - } - ) - def overlaps(self, other): if isinstance(other, (IntervalArray, ABCIntervalIndex)): raise NotImplementedError if not isinstance(other, Interval): @@ -1817,12 +1948,52 @@ def delete(self, loc) -> Self: new_right = self._right.delete(loc) return self._shallow_copy(left=new_left, right=new_right) - @Appender(_extension_array_shared_docs["repeat"] % _shared_docs_kwargs) def repeat( self, repeats: int | Sequence[int], axis: AxisInt | None = None, ) -> Self: + """ + Repeat elements of a IntervalArray. + + Returns a new IntervalArray where each element of the current IntervalArray + is repeated consecutively a given number of times. + + Parameters + ---------- + repeats : int or array of ints + The number of repetitions for each element. This should be a + non-negative integer. Repeating 0 times will return an empty + IntervalArray. + axis : None + Must be ``None``. Has no effect but is accepted for compatibility + with numpy. + + Returns + ------- + IntervalArray + Newly created IntervalArray with repeated elements. + + See Also + -------- + Series.repeat : Equivalent function for Series. + Index.repeat : Equivalent function for Index. + numpy.repeat : Similar method for :class:`numpy.ndarray`. + ExtensionArray.take : Take arbitrary positions. + + Examples + -------- + >>> cat = pd.Categorical(["a", "b", "c"]) + >>> cat + ['a', 'b', 'c'] + Categories (3, str): ['a', 'b', 'c'] + >>> cat.repeat(2) + ['a', 'a', 'b', 'b', 'c', 'c'] + Categories (3, str): ['a', 'b', 'c'] + >>> cat.repeat([1, 2, 3]) + ['a', 'b', 'b', 'c', 'c', 'c'] + Categories (3, str): ['a', 'b', 'c'] + """ nv.validate_repeat((), {"axis": axis}) left_repeat = self.left.repeat(repeats) right_repeat = self.right.repeat(repeats)