1"""
2An interface for extending pandas with custom arrays.
3
4.. warning::
5
6 This is an experimental API and subject to breaking changes
7 without warning.
8"""
9
10from __future__ import annotations
11
12import operator
13from typing import (
14 TYPE_CHECKING,
15 Any,
16 ClassVar,
17 Literal,
18 Self,
19 cast,
20 overload,
21)
22import warnings
23
24import numpy as np
25
26from pandas._libs import (
27 algos as libalgos,
28 lib,
29)
30from pandas.compat import set_function_name
31from pandas.compat.numpy import function as nv
32from pandas.errors import AbstractMethodError
33from pandas.util._decorators import (
34 cache_readonly,
35 set_module,
36)
37from pandas.util._exceptions import find_stack_level
38from pandas.util._validators import (
39 validate_bool_kwarg,
40 validate_insert_loc,
41)
42
43from pandas.core.dtypes.astype import astype_is_view
44from pandas.core.dtypes.common import (
45 is_integer,
46 is_list_like,
47 is_scalar,
48 pandas_dtype,
49)
50from pandas.core.dtypes.dtypes import ExtensionDtype
51from pandas.core.dtypes.generic import (
52 ABCDataFrame,
53 ABCIndex,
54 ABCSeries,
55)
56from pandas.core.dtypes.missing import isna
57
58from pandas.core import (
59 arraylike,
60 missing,
61 roperator,
62)
63from pandas.core.algorithms import (
64 duplicated,
65 factorize_array,
66 isin,
67 map_array,
68 mode,
69 rank,
70 unique,
71)
72from pandas.core.array_algos.quantile import quantile_with_mask
73from pandas.core.missing import _fill_limit_area_1d
74from pandas.core.sorting import (
75 nargminmax,
76 nargsort,
77)
78
79if TYPE_CHECKING:
80 from collections.abc import (
81 Callable,
82 Iterator,
83 Sequence,
84 )
85
86 from pandas._libs.missing import NAType
87 from pandas._typing import (
88 ArrayLike,
89 AstypeArg,
90 AxisInt,
91 Dtype,
92 DtypeObj,
93 FillnaOptions,
94 InterpolateOptions,
95 NumpySorter,
96 NumpyValueArrayLike,
97 PositionalIndexer,
98 ScalarIndexer,
99 SequenceIndexer,
100 Shape,
101 SortKind,
102 TakeIndexer,
103 npt,
104 )
105
106 from pandas import (
107 Index,
108 Series,
109 )
110
111_extension_array_shared_docs: dict[str, str] = {}
112
113
114@set_module("pandas.api.extensions")
115class ExtensionArray:
116 """
117 Abstract base class for custom 1-D array types.
118
119 pandas will recognize instances of this class as proper arrays
120 with a custom type and will not attempt to coerce them to objects. They
121 may be stored directly inside a :class:`DataFrame` or :class:`Series`.
122
123 Attributes
124 ----------
125 dtype
126 nbytes
127 ndim
128 shape
129
130 Methods
131 -------
132 argsort
133 astype
134 copy
135 dropna
136 duplicated
137 factorize
138 fillna
139 equals
140 insert
141 interpolate
142 isin
143 isna
144 item
145 ravel
146 repeat
147 searchsorted
148 shift
149 take
150 tolist
151 unique
152 view
153 _accumulate
154 _concat_same_type
155 _explode
156 _formatter
157 _from_factorized
158 _from_sequence
159 _from_sequence_of_strings
160 _hash_pandas_object
161 _pad_or_backfill
162 _reduce
163 _values_for_argsort
164 _values_for_factorize
165
166 See Also
167 --------
168 api.extensions.ExtensionDtype : A custom data type, to be paired with an
169 ExtensionArray.
170 api.extensions.ExtensionArray.dtype : An instance of ExtensionDtype.
171
172 Notes
173 -----
174 The interface includes the following abstract methods that must be
175 implemented by subclasses:
176
177 * _from_sequence
178 * _from_factorized
179 * __getitem__
180 * __len__
181 * __eq__
182 * dtype
183 * nbytes
184 * isna
185 * take
186 * copy
187 * _concat_same_type
188 * interpolate
189
190 A default repr displaying the type, (truncated) data, length,
191 and dtype is provided. It can be customized or replaced by
192 by overriding:
193
194 * __repr__ : A default repr for the ExtensionArray.
195 * _formatter : Print scalars inside a Series or DataFrame.
196
197 Some methods require casting the ExtensionArray to an ndarray of Python
198 objects with ``self.astype(object)``, which may be expensive. When
199 performance is a concern, we highly recommend overriding the following
200 methods:
201
202 * fillna
203 * _pad_or_backfill
204 * dropna
205 * unique
206 * factorize / _values_for_factorize
207 * argsort, argmax, argmin / _values_for_argsort
208 * searchsorted
209 * map
210
211 The remaining methods implemented on this class should be performant,
212 as they only compose abstract methods. Still, a more efficient
213 implementation may be available, and these methods can be overridden.
214
215 One can implement methods to handle array accumulations or reductions.
216
217 * _accumulate
218 * _reduce
219
220 One can implement methods to handle parsing from strings that will be used
221 in methods such as ``pandas.io.parsers.read_csv``.
222
223 * _from_sequence_of_strings
224
225 This class does not inherit from 'abc.ABCMeta' for performance reasons.
226 Methods and properties required by the interface raise
227 ``pandas.errors.AbstractMethodError`` and no ``register`` method is
228 provided for registering virtual subclasses.
229
230 ExtensionArrays are limited to 1 dimension.
231
232 They may be backed by none, one, or many NumPy arrays. For example,
233 ``pandas.Categorical`` is an extension array backed by two arrays,
234 one for codes and one for categories. An array of IPv6 address may
235 be backed by a NumPy structured array with two fields, one for the
236 lower 64 bits and one for the upper 64 bits. Or they may be backed
237 by some other storage type, like Python lists. Pandas makes no
238 assumptions on how the data are stored, just that it can be converted
239 to a NumPy array.
240 The ExtensionArray interface does not impose any rules on how this data
241 is stored. However, currently, the backing data cannot be stored in
242 attributes called ``.values`` or ``._values`` to ensure full compatibility
243 with pandas internals. But other names as ``.data``, ``._data``,
244 ``._items``, ... can be freely used.
245
246 If implementing NumPy's ``__array_ufunc__`` interface, pandas expects
247 that
248
249 1. You defer by returning ``NotImplemented`` when any Series are present
250 in `inputs`. Pandas will extract the arrays and call the ufunc again.
251 2. You define a ``_HANDLED_TYPES`` tuple as an attribute on the class.
252 Pandas inspect this to determine whether the ufunc is valid for the
253 types present.
254
255 See :ref:`extending.extension.ufunc` for more.
256
257 By default, ExtensionArrays are not hashable. Immutable subclasses may
258 override this behavior.
259
260 Examples
261 --------
262 Please see the following:
263
264 https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/list/array.py
265 """
266
267 # '_typ' is for pandas.core.dtypes.generic.ABCExtensionArray.
268 # Don't override this.
269 _typ = "extension"
270
271 # similar to __array_priority__, positions ExtensionArray after Index,
272 # Series, and DataFrame. EA subclasses may override to choose which EA
273 # subclass takes priority. If overriding, the value should always be
274 # strictly less than 2000 to be below Index.__pandas_priority__.
275 __pandas_priority__ = 1000
276
277 _readonly = False
278
279 # ------------------------------------------------------------------------
280 # Constructors
281 # ------------------------------------------------------------------------
282
283 @classmethod
284 def _from_sequence(
285 cls, scalars, *, dtype: Dtype | None = None, copy: bool = False
286 ) -> Self:
287 """
288 Construct a new ExtensionArray from a sequence of scalars.
289
290 Parameters
291 ----------
292 scalars : Sequence
293 Each element will be an instance of the scalar type for this
294 array, ``cls.dtype.type`` or be converted into this type in this method.
295 dtype : dtype, optional
296 Construct for this particular dtype. This should be a Dtype
297 compatible with the ExtensionArray.
298 copy : bool, default False
299 If True, copy the underlying data.
300
301 Returns
302 -------
303 ExtensionArray
304
305 See Also
306 --------
307 api.extensions.ExtensionArray._from_sequence_of_strings : Construct a new
308 ExtensionArray from a sequence of strings.
309 api.extensions.ExtensionArray._hash_pandas_object : Hook for
310 hash_pandas_object.
311
312 Examples
313 --------
314 >>> pd.arrays.IntegerArray._from_sequence([4, 5])
315 <IntegerArray>
316 [4, 5]
317 Length: 2, dtype: Int64
318 """
319 raise AbstractMethodError(cls)
320
321 @classmethod
322 def _from_sequence_of_strings(
323 cls, strings, *, dtype: ExtensionDtype, copy: bool = False
324 ) -> Self:
325 """
326 Construct a new ExtensionArray from a sequence of strings.
327
328 Parameters
329 ----------
330 strings : Sequence
331 Each element will be an instance of the scalar type for this
332 array, ``cls.dtype.type``.
333 dtype : ExtensionDtype
334 Construct for this particular dtype. This should be a Dtype
335 compatible with the ExtensionArray.
336 copy : bool, default False
337 If True, copy the underlying data.
338
339 Returns
340 -------
341 ExtensionArray
342
343 See Also
344 --------
345 api.extensions.ExtensionArray._from_sequence : Construct a new ExtensionArray
346 from a sequence of scalars.
347 api.extensions.ExtensionArray._from_factorized : Reconstruct an ExtensionArray
348 after factorization.
349
350 Examples
351 --------
352 >>> pd.arrays.IntegerArray._from_sequence_of_strings(
353 ... ["1", "2", "3"], dtype=pd.Int64Dtype()
354 ... )
355 <IntegerArray>
356 [1, 2, 3]
357 Length: 3, dtype: Int64
358 """
359 raise AbstractMethodError(cls)
360
361 @classmethod
362 def _from_factorized(cls, values, original):
363 """
364 Reconstruct an ExtensionArray after factorization.
365
366 Parameters
367 ----------
368 values : ndarray
369 An integer ndarray with the factorized values.
370 original : ExtensionArray
371 The original ExtensionArray that factorize was called on.
372
373 See Also
374 --------
375 factorize : Top-level factorize method that dispatches here.
376 ExtensionArray.factorize : Encode the extension array as an enumerated type.
377
378 Examples
379 --------
380 >>> interv_arr = pd.arrays.IntervalArray(
381 ... [pd.Interval(0, 1), pd.Interval(1, 5), pd.Interval(1, 5)]
382 ... )
383 >>> codes, uniques = pd.factorize(interv_arr)
384 >>> pd.arrays.IntervalArray._from_factorized(uniques, interv_arr)
385 <IntervalArray>
386 [(0, 1], (1, 5]]
387 Length: 2, dtype: interval[int64, right]
388 """
389 raise AbstractMethodError(cls)
390
391 @classmethod
392 def _from_scalars(cls, scalars, *, dtype: DtypeObj) -> Self:
393 """
394 Strict analogue to _from_sequence, allowing only sequences of scalars
395 that should be specifically inferred to the given dtype.
396
397 Parameters
398 ----------
399 scalars : sequence
400 dtype : ExtensionDtype
401
402 Raises
403 ------
404 TypeError or ValueError
405
406 Notes
407 -----
408 This is called in a try/except block when casting the result of a
409 pointwise operation in ExtensionArray._cast_pointwise_result.
410 """
411 try:
412 return cls._from_sequence(scalars, dtype=dtype, copy=False)
413 except (ValueError, TypeError):
414 raise
415 except Exception:
416 warnings.warn(
417 "_from_scalars should only raise ValueError or TypeError. "
418 "Consider overriding _from_scalars where appropriate.",
419 stacklevel=find_stack_level(),
420 )
421 raise
422
423 def _cast_pointwise_result(self, values) -> ArrayLike:
424 """
425 Construct an ExtensionArray after a pointwise operation.
426
427 Cast the result of a pointwise operation (e.g. Series.map) to an
428 array. This is not required to return an ExtensionArray of the same
429 type as self or of the same dtype. It can also return another
430 ExtensionArray of the same "family" if you implement multiple
431 ExtensionArrays/Dtypes that are interoperable (e.g. if you have float
432 array with units, this method can return an int array with units).
433
434 If converting to your own ExtensionArray is not possible, this method
435 falls back to returning an array with the default type inference.
436 If you only need to cast to `self.dtype`, it is recommended to override
437 `_from_scalars` instead of this method.
438
439 Parameters
440 ----------
441 values : sequence
442
443 Returns
444 -------
445 ExtensionArray or ndarray
446 """
447 try:
448 return type(self)._from_scalars(values, dtype=self.dtype)
449 except (ValueError, TypeError):
450 values = np.asarray(values, dtype=object)
451 return lib.maybe_convert_objects(values, convert_non_numeric=True)
452
453 # ------------------------------------------------------------------------
454 # Must be a Sequence
455 # ------------------------------------------------------------------------
456 @overload
457 def __getitem__(self, item: ScalarIndexer) -> Any: ...
458
459 @overload
460 def __getitem__(self, item: SequenceIndexer) -> Self: ...
461
462 def __getitem__(self, item: PositionalIndexer) -> Self | Any:
463 """
464 Select a subset of self.
465
466 Parameters
467 ----------
468 item : int, slice, or ndarray
469 * int: The position in 'self' to get.
470
471 * slice: A slice object, where 'start', 'stop', and 'step' are
472 integers or None
473
474 * ndarray: A 1-d boolean NumPy ndarray the same length as 'self'
475
476 * list[int]: A list of int
477
478 Returns
479 -------
480 item : scalar or ExtensionArray
481
482 Notes
483 -----
484 For scalar ``item``, return a scalar value suitable for the array's
485 type. This should be an instance of ``self.dtype.type``.
486
487 For slice ``key``, return an instance of ``ExtensionArray``, even
488 if the slice is length 0 or 1.
489
490 For a boolean mask, return an instance of ``ExtensionArray``, filtered
491 to the values where ``item`` is True.
492 """
493 raise AbstractMethodError(self)
494
495 def __setitem__(self, key, value) -> None:
496 """
497 Set one or more values inplace.
498
499 This method is not required to satisfy the pandas extension array
500 interface.
501
502 Parameters
503 ----------
504 key : int, ndarray, or slice
505 When called from, e.g. ``Series.__setitem__``, ``key`` will be
506 one of
507
508 * scalar int
509 * ndarray of integers.
510 * boolean ndarray
511 * slice object
512
513 value : ExtensionDtype.type, Sequence[ExtensionDtype.type], or object
514 value or values to be set of ``key``.
515
516 Returns
517 -------
518 None
519
520 Raises
521 ------
522 ValueError
523 If the array is readonly and modification is attempted.
524 """
525 # Some notes to the ExtensionArray implementer who may have ended up
526 # here. While this method is not required for the interface, if you
527 # *do* choose to implement __setitem__, then some semantics should be
528 # observed:
529 #
530 # * Setting multiple values : ExtensionArrays should support setting
531 # multiple values at once, 'key' will be a sequence of integers and
532 # 'value' will be a same-length sequence.
533 #
534 # * Broadcasting : For a sequence 'key' and a scalar 'value',
535 # each position in 'key' should be set to 'value'.
536 #
537 # * Coercion : Most users will expect basic coercion to work. For
538 # example, a string like '2018-01-01' is coerced to a datetime
539 # when setting on a datetime64ns array. In general, if the
540 # __init__ method coerces that value, then so should __setitem__
541 # Note, also, that Series/DataFrame.where internally use __setitem__
542 # on a copy of the data.
543 # Check if the array is readonly
544 if self._readonly:
545 raise ValueError("Cannot modify read-only array")
546
547 raise NotImplementedError(f"{type(self)} does not implement __setitem__.")
548
549 def __len__(self) -> int:
550 """
551 Length of this array
552
553 Returns
554 -------
555 length : int
556 """
557 raise AbstractMethodError(self)
558
559 def __iter__(self) -> Iterator[Any]:
560 """
561 Iterate over elements of the array.
562 """
563 # This needs to be implemented so that pandas recognizes extension
564 # arrays as list-like. The default implementation makes successive
565 # calls to ``__getitem__``, which may be slower than necessary.
566 for i in range(len(self)):
567 yield self[i]
568
569 def __contains__(self, item: object) -> bool | np.bool_:
570 """
571 Return for `item in self`.
572 """
573 # GH37867
574 # comparisons of any item to pd.NA always return pd.NA, so e.g. "a" in [pd.NA]
575 # would raise a TypeError. The implementation below works around that.
576 if is_scalar(item) and isna(item):
577 if not self._can_hold_na:
578 return False
579 elif item is self.dtype.na_value or isinstance(item, self.dtype.type):
580 return self._hasna
581 else:
582 return False
583 else:
584 # error: Item "ExtensionArray" of "Union[ExtensionArray, ndarray]" has no
585 # attribute "any"
586 return (item == self).any() # type: ignore[union-attr]
587
588 # error: Signature of "__eq__" incompatible with supertype "object"
589 def __eq__(self, other: object) -> ArrayLike: # type: ignore[override]
590 """
591 Return for `self == other` (element-wise equality).
592 """
593 # Implementer note: this should return a boolean numpy ndarray or
594 # a boolean ExtensionArray.
595 # When `other` is one of Series, Index, or DataFrame, this method should
596 # return NotImplemented (to ensure that those objects are responsible for
597 # first unpacking the arrays, and then dispatch the operation to the
598 # underlying arrays)
599 raise AbstractMethodError(self)
600
601 # error: Signature of "__ne__" incompatible with supertype "object"
602 def __ne__(self, other: object) -> ArrayLike: # type: ignore[override]
603 """
604 Return for `self != other` (element-wise in-equality).
605 """
606 # error: Unsupported operand type for ~ ("ExtensionArray")
607 return ~(self == other) # type: ignore[operator]
608
609 def item(self, index: int | None = None):
610 """
611 Return the array element at the specified position as a Python scalar.
612
613 Parameters
614 ----------
615 index : int, optional
616 Position of the element. If not provided, the array must contain
617 exactly one element.
618
619 Returns
620 -------
621 scalar
622 The element at the specified position.
623
624 Raises
625 ------
626 ValueError
627 If no index is provided and the array does not have exactly
628 one element.
629 IndexError
630 If the specified position is out of bounds.
631
632 See Also
633 --------
634 numpy.ndarray.item : Return the item of an array as a scalar.
635
636 Examples
637 --------
638 >>> arr = pd.array([1], dtype="Int64")
639 >>> arr.item()
640 np.int64(1)
641
642 >>> arr = pd.array([1, 2, 3], dtype="Int64")
643 >>> arr.item(0)
644 np.int64(1)
645 >>> arr.item(2)
646 np.int64(3)
647 """
648 if index is None:
649 if len(self) != 1:
650 raise ValueError(
651 "can only convert an array of size 1 to a Python scalar"
652 )
653 return self[0]
654 else:
655 if not is_integer(index):
656 raise TypeError(f"index must be an integer, got {type(index)}")
657 return self[index]
658
659 def to_numpy(
660 self,
661 dtype: npt.DTypeLike | None = None,
662 copy: bool = False,
663 na_value: object = lib.no_default,
664 ) -> np.ndarray:
665 """
666 Convert to a NumPy ndarray.
667
668 This is similar to :meth:`numpy.asarray`, but may provide additional control
669 over how the conversion is done.
670
671 Parameters
672 ----------
673 dtype : str or numpy.dtype, optional
674 The dtype to pass to :meth:`numpy.asarray`.
675 copy : bool, default False
676 Whether to ensure that the returned value is a not a view on
677 another array. Note that ``copy=False`` does not *ensure* that
678 ``to_numpy()`` is no-copy. Rather, ``copy=True`` ensure that
679 a copy is made, even if not strictly necessary.
680 na_value : Any, optional
681 The value to use for missing values. The default value depends
682 on `dtype` and the type of the array.
683
684 Returns
685 -------
686 numpy.ndarray
687 """
688 result = np.asarray(self, dtype=dtype)
689 if copy or na_value is not lib.no_default:
690 result = result.copy()
691 elif self._readonly and astype_is_view(self.dtype, result.dtype):
692 # If the ExtensionArray is readonly, make the numpy array readonly too
693 result = result.view()
694 result.flags.writeable = False
695
696 if na_value is not lib.no_default:
697 result[self.isna()] = na_value # type: ignore[index]
698
699 return result
700
701 # ------------------------------------------------------------------------
702 # Required attributes
703 # ------------------------------------------------------------------------
704
705 @property
706 def dtype(self) -> ExtensionDtype:
707 """
708 An instance of ExtensionDtype.
709
710 See Also
711 --------
712 api.extensions.ExtensionDtype : Base class for extension dtypes.
713 api.extensions.ExtensionArray : Base class for extension array types.
714 api.extensions.ExtensionArray.dtype : The dtype of an ExtensionArray.
715 Series.dtype : The dtype of a Series.
716 DataFrame.dtype : The dtype of a DataFrame.
717
718 Examples
719 --------
720 >>> pd.array([1, 2, 3]).dtype
721 Int64Dtype()
722 """
723 raise AbstractMethodError(self)
724
725 @property
726 def shape(self) -> Shape:
727 """
728 Return a tuple of the array dimensions.
729
730 See Also
731 --------
732 numpy.ndarray.shape : Similar attribute which returns the shape of an array.
733 DataFrame.shape : Return a tuple representing the dimensionality of the
734 DataFrame.
735 Series.shape : Return a tuple representing the dimensionality of the Series.
736
737 Examples
738 --------
739 >>> arr = pd.array([1, 2, 3])
740 >>> arr.shape
741 (3,)
742 """
743 return (len(self),)
744
745 @property
746 def size(self) -> int:
747 """
748 The number of elements in the array.
749 """
750 # error: Incompatible return value type (got "signedinteger[_64Bit]",
751 # expected "int") [return-value]
752 return np.prod(self.shape) # type: ignore[return-value]
753
754 @property
755 def ndim(self) -> int:
756 """
757 Extension Arrays are only allowed to be 1-dimensional.
758
759 See Also
760 --------
761 ExtensionArray.shape: Return a tuple of the array dimensions.
762 ExtensionArray.size: The number of elements in the array.
763
764 Examples
765 --------
766 >>> arr = pd.array([1, 2, 3])
767 >>> arr.ndim
768 1
769 """
770 return 1
771
772 @property
773 def nbytes(self) -> int:
774 """
775 The number of bytes needed to store this object in memory.
776
777 See Also
778 --------
779 ExtensionArray.shape: Return a tuple of the array dimensions.
780 ExtensionArray.size: The number of elements in the array.
781
782 Examples
783 --------
784 >>> pd.array([1, 2, 3]).nbytes
785 27
786 """
787 # If this is expensive to compute, return an approximate lower bound
788 # on the number of bytes needed.
789 raise AbstractMethodError(self)
790
791 # ------------------------------------------------------------------------
792 # Additional Methods
793 # ------------------------------------------------------------------------
794
795 @overload
796 def astype(self, dtype: npt.DTypeLike, copy: bool = ...) -> np.ndarray: ...
797
798 @overload
799 def astype(self, dtype: ExtensionDtype, copy: bool = ...) -> ExtensionArray: ...
800
801 @overload
802 def astype(self, dtype: AstypeArg, copy: bool = ...) -> ArrayLike: ...
803
804 def astype(self, dtype: AstypeArg, copy: bool = True) -> ArrayLike:
805 """
806 Cast to a NumPy array or ExtensionArray with 'dtype'.
807
808 Parameters
809 ----------
810 dtype : str or dtype
811 Typecode or data-type to which the array is cast.
812 copy : bool, default True
813 Whether to copy the data, even if not necessary. If False,
814 a copy is made only if the old dtype does not match the
815 new dtype.
816
817 Returns
818 -------
819 np.ndarray or pandas.api.extensions.ExtensionArray
820 An ``ExtensionArray`` if ``dtype`` is ``ExtensionDtype``,
821 otherwise a Numpy ndarray with ``dtype`` for its dtype.
822
823 See Also
824 --------
825 Series.astype : Cast a Series to a different dtype.
826 DataFrame.astype : Cast a DataFrame to a different dtype.
827 api.extensions.ExtensionArray : Base class for ExtensionArray objects.
828 core.arrays.DatetimeArray._from_sequence : Create a DatetimeArray from a
829 sequence.
830 core.arrays.TimedeltaArray._from_sequence : Create a TimedeltaArray from
831 a sequence.
832
833 Examples
834 --------
835 >>> arr = pd.array([1, 2, 3])
836 >>> arr
837 <IntegerArray>
838 [1, 2, 3]
839 Length: 3, dtype: Int64
840
841 Casting to another ``ExtensionDtype`` returns an ``ExtensionArray``:
842
843 >>> arr1 = arr.astype("Float64")
844 >>> arr1
845 <FloatingArray>
846 [1.0, 2.0, 3.0]
847 Length: 3, dtype: Float64
848 >>> arr1.dtype
849 Float64Dtype()
850
851 Otherwise, we will get a Numpy ndarray:
852
853 >>> arr2 = arr.astype("float64")
854 >>> arr2
855 array([1., 2., 3.])
856 >>> arr2.dtype
857 dtype('float64')
858 """
859 dtype = pandas_dtype(dtype)
860 if dtype == self.dtype:
861 if not copy:
862 return self
863 else:
864 return self.copy()
865
866 if isinstance(dtype, ExtensionDtype):
867 cls = dtype.construct_array_type()
868 return cls._from_sequence(self, dtype=dtype, copy=copy)
869
870 elif lib.is_np_dtype(dtype, "M"):
871 from pandas.core.arrays import DatetimeArray
872
873 return DatetimeArray._from_sequence(self, dtype=dtype, copy=copy)
874
875 elif lib.is_np_dtype(dtype, "m"):
876 from pandas.core.arrays import TimedeltaArray
877
878 return TimedeltaArray._from_sequence(self, dtype=dtype, copy=copy)
879
880 if not copy:
881 return np.asarray(self, dtype=dtype)
882 else:
883 return np.array(self, dtype=dtype, copy=copy)
884
885 def isna(self) -> np.ndarray | ExtensionArraySupportsAnyAll:
886 """
887 A 1-D array indicating if each value is missing.
888
889 Returns
890 -------
891 numpy.ndarray or pandas.api.extensions.ExtensionArray
892 In most cases, this should return a NumPy ndarray. For
893 exceptional cases like ``SparseArray``, where returning
894 an ndarray would be expensive, an ExtensionArray may be
895 returned.
896
897 See Also
898 --------
899 ExtensionArray.dropna: Return ExtensionArray without NA values.
900 ExtensionArray.fillna: Fill NA/NaN values using the specified method.
901
902 Notes
903 -----
904 If returning an ExtensionArray, then
905
906 * ``na_values._is_boolean`` should be True
907 * ``na_values`` should implement :func:`ExtensionArray._reduce`
908 * ``na_values`` should implement :func:`ExtensionArray._accumulate`
909 * ``na_values.any`` and ``na_values.all`` should be implemented
910
911 Examples
912 --------
913 >>> arr = pd.array([1, 2, np.nan, np.nan])
914 >>> arr.isna()
915 array([False, False, True, True])
916 """
917 raise AbstractMethodError(self)
918
919 @property
920 def _hasna(self) -> bool:
921 # GH#22680
922 """
923 Equivalent to `self.isna().any()`.
924
925 Some ExtensionArray subclasses may be able to optimize this check.
926 """
927 return bool(self.isna().any())
928
929 def _values_for_argsort(self) -> np.ndarray:
930 """
931 Return values for sorting.
932
933 Returns
934 -------
935 ndarray
936 The transformed values should maintain the ordering between values
937 within the array.
938
939 See Also
940 --------
941 ExtensionArray.argsort : Return the indices that would sort this array.
942
943 Notes
944 -----
945 The caller is responsible for *not* modifying these values in-place, so
946 it is safe for implementers to give views on ``self``.
947
948 Functions that use this (e.g. ``ExtensionArray.argsort``) should ignore
949 entries with missing values in the original array (according to
950 ``self.isna()``). This means that the corresponding entries in the returned
951 array don't need to be modified to sort correctly.
952
953 Examples
954 --------
955 In most cases, this is the underlying Numpy array of the ``ExtensionArray``:
956
957 >>> arr = pd.array([1, 2, 3])
958 >>> arr._values_for_argsort()
959 array([1, 2, 3])
960 """
961 # Note: this is used in `ExtensionArray.argsort/argmin/argmax`.
962 return np.array(self)
963
964 def argsort(
965 self,
966 *,
967 ascending: bool = True,
968 kind: SortKind = "quicksort",
969 na_position: str = "last",
970 **kwargs,
971 ) -> np.ndarray:
972 """
973 Return the indices that would sort this array.
974
975 Parameters
976 ----------
977 ascending : bool, default True
978 Whether the indices should result in an ascending
979 or descending sort.
980 kind : {'quicksort', 'mergesort', 'heapsort', 'stable'}, optional
981 Sorting algorithm.
982 na_position : {'first', 'last'}, default 'last'
983 If ``'first'``, put ``NaN`` values at the beginning.
984 If ``'last'``, put ``NaN`` values at the end.
985 **kwargs
986 Passed through to :func:`numpy.argsort`.
987
988 Returns
989 -------
990 np.ndarray[np.intp]
991 Array of indices that sort ``self``. If NaN values are contained,
992 NaN values are placed at the end.
993
994 See Also
995 --------
996 numpy.argsort : Sorting implementation used internally.
997
998 Examples
999 --------
1000 >>> arr = pd.array([3, 1, 2, 5, 4])
1001 >>> arr.argsort()
1002 array([1, 2, 0, 4, 3])
1003 """
1004 # Implementer note: You have two places to override the behavior of
1005 # argsort.
1006 # 1. _values_for_argsort : construct the values passed to np.argsort
1007 # 2. argsort : total control over sorting. In case of overriding this,
1008 # it is recommended to also override argmax/argmin
1009 ascending = nv.validate_argsort_with_ascending(ascending, (), kwargs)
1010
1011 values = self._values_for_argsort()
1012 return nargsort(
1013 values,
1014 kind=kind,
1015 ascending=ascending,
1016 na_position=na_position,
1017 mask=np.asarray(self.isna()),
1018 )
1019
1020 def argmin(self, skipna: bool = True) -> int:
1021 """
1022 Return the index of minimum value.
1023
1024 In case of multiple occurrences of the minimum value, the index
1025 corresponding to the first occurrence is returned.
1026
1027 Parameters
1028 ----------
1029 skipna : bool, default True
1030
1031 Returns
1032 -------
1033 int
1034
1035 See Also
1036 --------
1037 ExtensionArray.argmax : Return the index of the maximum value.
1038
1039 Examples
1040 --------
1041 >>> arr = pd.array([3, 1, 2, 5, 4])
1042 >>> arr.argmin()
1043 np.int64(1)
1044 """
1045 # Implementer note: You have two places to override the behavior of
1046 # argmin.
1047 # 1. _values_for_argsort : construct the values used in nargminmax
1048 # 2. argmin itself : total control over sorting.
1049 validate_bool_kwarg(skipna, "skipna")
1050 if not skipna and self._hasna:
1051 raise ValueError("Encountered an NA value with skipna=False")
1052 return nargminmax(self, "argmin")
1053
1054 def argmax(self, skipna: bool = True) -> int:
1055 """
1056 Return the index of maximum value.
1057
1058 In case of multiple occurrences of the maximum value, the index
1059 corresponding to the first occurrence is returned.
1060
1061 Parameters
1062 ----------
1063 skipna : bool, default True
1064
1065 Returns
1066 -------
1067 int
1068
1069 See Also
1070 --------
1071 ExtensionArray.argmin : Return the index of the minimum value.
1072
1073 Examples
1074 --------
1075 >>> arr = pd.array([3, 1, 2, 5, 4])
1076 >>> arr.argmax()
1077 np.int64(3)
1078 """
1079 # Implementer note: You have two places to override the behavior of
1080 # argmax.
1081 # 1. _values_for_argsort : construct the values used in nargminmax
1082 # 2. argmax itself : total control over sorting.
1083 validate_bool_kwarg(skipna, "skipna")
1084 if not skipna and self._hasna:
1085 raise ValueError("Encountered an NA value with skipna=False")
1086 return nargminmax(self, "argmax")
1087
1088 def interpolate(
1089 self,
1090 *,
1091 method: InterpolateOptions,
1092 axis: int,
1093 index: Index,
1094 limit,
1095 limit_direction,
1096 limit_area,
1097 copy: bool,
1098 **kwargs,
1099 ) -> Self:
1100 """
1101 Fill NaN values using an interpolation method.
1102
1103 Parameters
1104 ----------
1105 method : str, default 'linear'
1106 Interpolation technique to use. One of:
1107 * 'linear': Ignore the index and treat the values as equally spaced.
1108 This is the only method supported on MultiIndexes.
1109 * 'time': Works on daily and higher resolution data to interpolate
1110 given length of interval.
1111 * 'index', 'values': use the actual numerical values of the index.
1112 * 'nearest', 'zero', 'slinear', 'quadratic', 'cubic', 'barycentric',
1113 'polynomial': Passed to scipy.interpolate.interp1d, whereas 'spline'
1114 is passed to scipy.interpolate.UnivariateSpline. These methods use
1115 the numerical values of the index.
1116 Both 'polynomial' and 'spline' require that you also specify an
1117 order (int), e.g. arr.interpolate(method='polynomial', order=5).
1118 * 'krogh', 'piecewise_polynomial', 'spline', 'pchip', 'akima',
1119 'cubicspline': Wrappers around the SciPy interpolation methods
1120 of similar names. See Notes.
1121 * 'from_derivatives': Refers to scipy.interpolate.BPoly.from_derivatives.
1122 axis : int
1123 Axis to interpolate along. For 1-dimensional data, use 0.
1124 index : Index
1125 Index to use for interpolation.
1126 limit : int or None
1127 Maximum number of consecutive NaNs to fill. Must be greater than 0.
1128 limit_direction : {'forward', 'backward', 'both'}
1129 Consecutive NaNs will be filled in this direction.
1130 limit_area : {'inside', 'outside'} or None
1131 If limit is specified, consecutive NaNs will be filled with this
1132 restriction.
1133 * None: No fill restriction.
1134 * 'inside': Only fill NaNs surrounded by valid values (interpolate).
1135 * 'outside': Only fill NaNs outside valid values (extrapolate).
1136 copy : bool
1137 If True, a copy of the object is returned with interpolated values.
1138 **kwargs : optional
1139 Keyword arguments to pass on to the interpolating function.
1140
1141 Returns
1142 -------
1143 ExtensionArray
1144 An ExtensionArray with interpolated values.
1145
1146 See Also
1147 --------
1148 Series.interpolate : Interpolate values in a Series.
1149 DataFrame.interpolate : Interpolate values in a DataFrame.
1150
1151 Notes
1152 -----
1153 - All parameters must be specified as keyword arguments.
1154 - The 'krogh', 'piecewise_polynomial', 'spline', 'pchip' and 'akima'
1155 methods are wrappers around the respective SciPy implementations of
1156 similar names. These use the actual numerical values of the index.
1157
1158 Examples
1159 --------
1160 Interpolating values in a NumPy array:
1161
1162 >>> arr = pd.arrays.NumpyExtensionArray(np.array([0, 1, np.nan, 3]))
1163 >>> arr.interpolate(
1164 ... method="linear",
1165 ... limit=3,
1166 ... limit_direction="forward",
1167 ... index=pd.Index(range(len(arr))),
1168 ... fill_value=1,
1169 ... copy=False,
1170 ... axis=0,
1171 ... limit_area="inside",
1172 ... )
1173 <NumpyExtensionArray>
1174 [0.0, 1.0, 2.0, 3.0]
1175 Length: 4, dtype: float64
1176
1177 Interpolating values in a FloatingArray:
1178
1179 >>> arr = pd.array([1.0, pd.NA, 3.0, 4.0, pd.NA, 6.0], dtype="Float64")
1180 >>> arr.interpolate(
1181 ... method="linear",
1182 ... axis=0,
1183 ... index=pd.Index(range(len(arr))),
1184 ... limit=None,
1185 ... limit_direction="both",
1186 ... limit_area=None,
1187 ... copy=True,
1188 ... )
1189 <FloatingArray>
1190 [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
1191 Length: 6, dtype: Float64
1192 """
1193 # NB: we return type(self) even if copy=False
1194 raise NotImplementedError(
1195 f"{type(self).__name__} does not implement interpolate"
1196 )
1197
1198 def _pad_or_backfill(
1199 self,
1200 *,
1201 method: FillnaOptions,
1202 limit: int | None = None,
1203 limit_area: Literal["inside", "outside"] | None = None,
1204 copy: bool = True,
1205 ) -> Self:
1206 """
1207 Pad or backfill values, used by Series/DataFrame ffill and bfill.
1208
1209 Parameters
1210 ----------
1211 method : {'backfill', 'bfill', 'pad', 'ffill'}
1212 Method to use for filling holes in reindexed Series:
1213
1214 * pad / ffill: propagate last valid observation forward to next valid.
1215 * backfill / bfill: use NEXT valid observation to fill gap.
1216
1217 limit : int, default None
1218 This is the maximum number of consecutive
1219 NaN values to forward/backward fill. In other words, if there is
1220 a gap with more than this number of consecutive NaNs, it will only
1221 be partially filled. If method is not specified, this is the
1222 maximum number of entries along the entire axis where NaNs will be
1223 filled.
1224
1225 limit_area : {'inside', 'outside'} or None, default None
1226 Specifies which area to limit filling.
1227 - 'inside': Limit the filling to the area within the gaps.
1228 - 'outside': Limit the filling to the area outside the gaps.
1229 If `None`, no limitation is applied.
1230
1231 copy : bool, default True
1232 Whether to make a copy of the data before filling. If False, then
1233 the original should be modified and no new memory should be allocated.
1234 For ExtensionArray subclasses that cannot do this, it is at the
1235 author's discretion whether to ignore "copy=False" or to raise.
1236 The base class implementation ignores the keyword if any NAs are
1237 present.
1238
1239 Returns
1240 -------
1241 Same type as self
1242 The filled array with the same type as the original.
1243
1244 See Also
1245 --------
1246 Series.ffill : Forward fill missing values.
1247 Series.bfill : Backward fill missing values.
1248 DataFrame.ffill : Forward fill missing values in DataFrame.
1249 DataFrame.bfill : Backward fill missing values in DataFrame.
1250 api.types.isna : Check for missing values.
1251 api.types.isnull : Check for missing values.
1252
1253 Examples
1254 --------
1255 >>> arr = pd.array([np.nan, np.nan, 2, 3, np.nan, np.nan])
1256 >>> arr._pad_or_backfill(method="backfill", limit=1)
1257 <IntegerArray>
1258 [<NA>, 2, 2, 3, <NA>, <NA>]
1259 Length: 6, dtype: Int64
1260 """
1261 mask = self.isna()
1262
1263 if mask.any():
1264 # NB: the base class does not respect the "copy" keyword
1265 meth = missing.clean_fill_method(method)
1266
1267 npmask = np.asarray(mask)
1268 if limit_area is not None and not npmask.all():
1269 _fill_limit_area_1d(npmask, limit_area)
1270 if meth == "pad":
1271 indexer = libalgos.get_fill_indexer(npmask, limit=limit)
1272 return self.take(indexer, allow_fill=True)
1273 else:
1274 # i.e. meth == "backfill"
1275 indexer = libalgos.get_fill_indexer(npmask[::-1], limit=limit)[::-1]
1276 return self[::-1].take(indexer, allow_fill=True)
1277
1278 else:
1279 if not copy:
1280 return self
1281 new_values = self.copy()
1282 return new_values
1283
1284 def fillna(
1285 self,
1286 value: object | ArrayLike,
1287 limit: int | None = None,
1288 copy: bool = True,
1289 ) -> Self:
1290 """
1291 Fill NA/NaN values using the specified method.
1292
1293 Parameters
1294 ----------
1295 value : scalar, array-like
1296 If a scalar value is passed it is used to fill all missing values.
1297 Alternatively, an array-like "value" can be given. It's expected
1298 that the array-like have the same length as 'self'.
1299 limit : int, default None
1300 The maximum number of entries where NA values will be filled.
1301 copy : bool, default True
1302 Whether to make a copy of the data before filling. If False, then
1303 the original should be modified and no new memory should be allocated.
1304 For ExtensionArray subclasses that cannot do this, it is at the
1305 author's discretion whether to ignore "copy=False" or to raise.
1306
1307 Returns
1308 -------
1309 ExtensionArray
1310 With NA/NaN filled.
1311
1312 See Also
1313 --------
1314 api.extensions.ExtensionArray.dropna : Return ExtensionArray without
1315 NA values.
1316 api.extensions.ExtensionArray.isna : A 1-D array indicating if
1317 each value is missing.
1318
1319 Examples
1320 --------
1321 >>> arr = pd.array([np.nan, np.nan, 2, 3, np.nan, np.nan])
1322 >>> arr.fillna(0)
1323 <IntegerArray>
1324 [0, 0, 2, 3, 0, 0]
1325 Length: 6, dtype: Int64
1326 """
1327 mask = self.isna()
1328 if limit is not None and limit < len(self):
1329 # isna can return an ExtensionArray, we're assuming that comparisons
1330 # are implemented.
1331 # mypy doesn't like that mask can be an EA which need not have `cumsum`
1332 modify = mask.cumsum() > limit # type: ignore[union-attr]
1333 if modify.any():
1334 # Only copy mask if necessary
1335 mask = mask.copy()
1336 mask[modify] = False
1337 # error: Argument 2 to "check_value_size" has incompatible type
1338 # "ExtensionArray"; expected "ndarray"
1339 value = missing.check_value_size(
1340 value,
1341 mask, # type: ignore[arg-type]
1342 len(self),
1343 )
1344
1345 if mask.any():
1346 # fill with value
1347 if not copy:
1348 new_values = self[:]
1349 else:
1350 new_values = self.copy()
1351 new_values[mask] = value
1352 elif not copy:
1353 new_values = self[:]
1354 else:
1355 new_values = self.copy()
1356 return new_values
1357
1358 def dropna(self) -> Self:
1359 """
1360 Return ExtensionArray without NA values.
1361
1362 Returns
1363 -------
1364 Self
1365 An ExtensionArray of the same type as the original but with all
1366 NA values removed.
1367
1368 See Also
1369 --------
1370 Series.dropna : Remove missing values from a Series.
1371 DataFrame.dropna : Remove missing values from a DataFrame.
1372 api.extensions.ExtensionArray.isna : Check for missing values in
1373 an ExtensionArray.
1374
1375 Examples
1376 --------
1377 >>> pd.array([1, 2, np.nan]).dropna()
1378 <IntegerArray>
1379 [1, 2]
1380 Length: 2, dtype: Int64
1381 """
1382 # error: Unsupported operand type for ~ ("ExtensionArray")
1383 return self[~self.isna()] # type: ignore[operator]
1384
1385 def duplicated(
1386 self, keep: Literal["first", "last", False] = "first"
1387 ) -> npt.NDArray[np.bool_]:
1388 """
1389 Return boolean ndarray denoting duplicate values.
1390
1391 Parameters
1392 ----------
1393 keep : {'first', 'last', False}, default 'first'
1394 - ``first`` : Mark duplicates as ``True`` except for the first occurrence.
1395 - ``last`` : Mark duplicates as ``True`` except for the last occurrence.
1396 - False : Mark all duplicates as ``True``.
1397
1398 Returns
1399 -------
1400 ndarray[bool]
1401 With true in indices where elements are duplicated and false otherwise.
1402
1403 See Also
1404 --------
1405 DataFrame.duplicated : Return boolean Series denoting
1406 duplicate rows.
1407 Series.duplicated : Indicate duplicate Series values.
1408 api.extensions.ExtensionArray.unique : Compute the ExtensionArray
1409 of unique values.
1410
1411 Examples
1412 --------
1413 >>> pd.array([1, 1, 2, 3, 3], dtype="Int64").duplicated()
1414 array([False, True, False, False, True])
1415 """
1416 mask = self.isna().astype(np.bool_, copy=False)
1417 return duplicated(values=self, keep=keep, mask=mask)
1418
1419 def shift(self, periods: int = 1, fill_value: object = None) -> ExtensionArray:
1420 """
1421 Shift values by desired number.
1422
1423 Newly introduced missing values are filled with
1424 ``self.dtype.na_value``.
1425
1426 Parameters
1427 ----------
1428 periods : int, default 1
1429 The number of periods to shift. Negative values are allowed
1430 for shifting backwards.
1431
1432 fill_value : object, optional
1433 The scalar value to use for newly introduced missing values.
1434 The default is ``self.dtype.na_value``.
1435
1436 Returns
1437 -------
1438 ExtensionArray
1439 Shifted.
1440
1441 See Also
1442 --------
1443 api.extensions.ExtensionArray.transpose : Return a transposed view on
1444 this array.
1445 api.extensions.ExtensionArray.factorize : Encode the extension array as an
1446 enumerated type.
1447
1448 Notes
1449 -----
1450 If ``self`` is empty or ``periods`` is 0, a copy of ``self`` is
1451 returned.
1452
1453 If ``periods > len(self)``, then an array of size
1454 len(self) is returned, with all values filled with
1455 ``self.dtype.na_value``.
1456
1457 For 2-dimensional ExtensionArrays, we are always shifting along axis=0.
1458
1459 Examples
1460 --------
1461 >>> arr = pd.array([1, 2, 3])
1462 >>> arr.shift(2)
1463 <IntegerArray>
1464 [<NA>, <NA>, 1]
1465 Length: 3, dtype: Int64
1466 """
1467 # Note: this implementation assumes that `self.dtype.na_value` can be
1468 # stored in an instance of your ExtensionArray with `self.dtype`.
1469 if not len(self) or periods == 0:
1470 return self.copy()
1471
1472 if isna(fill_value):
1473 fill_value = self.dtype.na_value
1474
1475 empty = self._from_sequence(
1476 [fill_value] * min(abs(periods), len(self)), dtype=self.dtype
1477 )
1478 if periods > 0:
1479 a = empty
1480 b = self[:-periods]
1481 else:
1482 a = self[abs(periods) :]
1483 b = empty
1484 return self._concat_same_type([a, b])
1485
1486 def unique(self) -> Self:
1487 """
1488 Compute the ExtensionArray of unique values.
1489
1490 Returns
1491 -------
1492 pandas.api.extensions.ExtensionArray
1493 With unique values from the input array.
1494
1495 See Also
1496 --------
1497 Index.unique: Return unique values in the index.
1498 Series.unique: Return unique values of Series object.
1499 unique: Return unique values based on a hash table.
1500
1501 Examples
1502 --------
1503 >>> arr = pd.array([1, 2, 3, 1, 2, 3])
1504 >>> arr.unique()
1505 <IntegerArray>
1506 [1, 2, 3]
1507 Length: 3, dtype: Int64
1508 """
1509 uniques = unique(self.astype(object))
1510 return self._from_sequence(uniques, dtype=self.dtype)
1511
1512 def searchsorted(
1513 self,
1514 value: NumpyValueArrayLike | ExtensionArray,
1515 side: Literal["left", "right"] = "left",
1516 sorter: NumpySorter | None = None,
1517 ) -> npt.NDArray[np.intp] | np.intp:
1518 """
1519 Find indices where elements should be inserted to maintain order.
1520
1521 Find the indices into a sorted array `self` (a) such that, if the
1522 corresponding elements in `value` were inserted before the indices,
1523 the order of `self` would be preserved.
1524
1525 Assuming that `self` is sorted:
1526
1527 ====== ================================
1528 `side` returned index `i` satisfies
1529 ====== ================================
1530 left ``self[i-1] < value <= self[i]``
1531 right ``self[i-1] <= value < self[i]``
1532 ====== ================================
1533
1534 Parameters
1535 ----------
1536 value : array-like, list or scalar
1537 Value(s) to insert into `self`.
1538 side : {'left', 'right'}, optional
1539 If 'left', the index of the first suitable location found is given.
1540 If 'right', return the last such index. If there is no suitable
1541 index, return either 0 or N (where N is the length of `self`).
1542 sorter : 1-D array-like, optional
1543 Optional array of integer indices that sort array a into ascending
1544 order. They are typically the result of argsort.
1545
1546 Returns
1547 -------
1548 array of ints or int
1549 If value is array-like, array of insertion points.
1550 If value is scalar, a single integer.
1551
1552 See Also
1553 --------
1554 numpy.searchsorted : Similar method from NumPy.
1555
1556 Examples
1557 --------
1558 >>> arr = pd.array([1, 2, 3, 5])
1559 >>> arr.searchsorted([4])
1560 array([3])
1561 """
1562 # Note: the base tests provided by pandas only test the basics.
1563 # We do not test
1564 # 1. Values outside the range of the `data_for_sorting` fixture
1565 # 2. Values between the values in the `data_for_sorting` fixture
1566 # 3. Missing values.
1567 arr = self.astype(object)
1568 if isinstance(value, ExtensionArray):
1569 value = value.astype(object)
1570 return arr.searchsorted(value, side=side, sorter=sorter)
1571
1572 def equals(self, other: object) -> bool:
1573 """
1574 Return if another array is equivalent to this array.
1575
1576 Equivalent means that both arrays have the same shape and dtype, and
1577 all values compare equal. Missing values in the same location are
1578 considered equal (in contrast with normal equality).
1579
1580 Parameters
1581 ----------
1582 other : ExtensionArray
1583 Array to compare to this Array.
1584
1585 Returns
1586 -------
1587 boolean
1588 Whether the arrays are equivalent.
1589
1590 See Also
1591 --------
1592 numpy.array_equal : Equivalent method for numpy array.
1593 Series.equals : Equivalent method for Series.
1594 DataFrame.equals : Equivalent method for DataFrame.
1595
1596 Examples
1597 --------
1598 >>> arr1 = pd.array([1, 2, np.nan])
1599 >>> arr2 = pd.array([1, 2, np.nan])
1600 >>> arr1.equals(arr2)
1601 True
1602
1603 >>> arr1 = pd.array([1, 3, np.nan])
1604 >>> arr2 = pd.array([1, 2, np.nan])
1605 >>> arr1.equals(arr2)
1606 False
1607 """
1608 if type(self) != type(other):
1609 return False
1610 other = cast(ExtensionArray, other)
1611 if self.dtype != other.dtype:
1612 return False
1613 elif len(self) != len(other):
1614 return False
1615 else:
1616 equal_values = self == other
1617 if isinstance(equal_values, ExtensionArray):
1618 # boolean array with NA -> fill with False
1619 equal_values = equal_values.fillna(False)
1620 # error: Unsupported left operand type for & ("ExtensionArray")
1621 equal_na = self.isna() & other.isna() # type: ignore[operator]
1622 return bool((equal_values | equal_na).all())
1623
1624 def isin(self, values: ArrayLike) -> npt.NDArray[np.bool_]:
1625 """
1626 Pointwise comparison for set containment in the given values.
1627
1628 Roughly equivalent to `np.array([x in values for x in self])`
1629
1630 Parameters
1631 ----------
1632 values : np.ndarray or ExtensionArray
1633 Values to compare every element in the array against.
1634
1635 Returns
1636 -------
1637 np.ndarray[bool]
1638 With true at indices where value is in `values`.
1639
1640 See Also
1641 --------
1642 DataFrame.isin: Whether each element in the DataFrame is contained in values.
1643 Index.isin: Return a boolean array where the index values are in values.
1644 Series.isin: Whether elements in Series are contained in values.
1645
1646 Examples
1647 --------
1648 >>> arr = pd.array([1, 2, 3])
1649 >>> arr.isin([1])
1650 <BooleanArray>
1651 [True, False, False]
1652 Length: 3, dtype: boolean
1653 """
1654 return isin(np.asarray(self), values)
1655
1656 def _values_for_factorize(self) -> tuple[np.ndarray, Any]:
1657 """
1658 Return an array and missing value suitable for factorization.
1659
1660 Returns
1661 -------
1662 values : ndarray
1663 An array suitable for factorization. This should maintain order
1664 and be a supported dtype (Float64, Int64, UInt64, String, Object).
1665 By default, the extension array is cast to object dtype.
1666 na_value : object
1667 The value in `values` to consider missing. This will be treated
1668 as NA in the factorization routines, so it will be coded as
1669 `-1` and not included in `uniques`. By default,
1670 ``np.nan`` is used.
1671
1672 See Also
1673 --------
1674 util.hash_pandas_object : Hash the pandas object.
1675
1676 Notes
1677 -----
1678 The values returned by this method are also used in
1679 :func:`pandas.util.hash_pandas_object`. If needed, this can be
1680 overridden in the ``self._hash_pandas_object()`` method.
1681
1682 Examples
1683 --------
1684 >>> pd.array([1, 2, 3])._values_for_factorize()
1685 (array([1, 2, 3], dtype=object), nan)
1686 """
1687 return self.astype(object), np.nan
1688
1689 def factorize(
1690 self,
1691 use_na_sentinel: bool = True,
1692 ) -> tuple[np.ndarray, ExtensionArray]:
1693 """
1694 Encode the extension array as an enumerated type.
1695
1696 Parameters
1697 ----------
1698 use_na_sentinel : bool, default True
1699 If True, the sentinel -1 will be used for NaN values. If False,
1700 NaN values will be encoded as non-negative integers and will not drop the
1701 NaN from the uniques of the values.
1702
1703 Returns
1704 -------
1705 codes : ndarray
1706 An integer NumPy array that's an indexer into the original
1707 ExtensionArray.
1708 uniques : ExtensionArray
1709 An ExtensionArray containing the unique values of `self`.
1710
1711 .. note::
1712
1713 uniques will *not* contain an entry for the NA value of
1714 the ExtensionArray if there are any missing values present
1715 in `self`.
1716
1717 See Also
1718 --------
1719 factorize : Top-level factorize method that dispatches here.
1720
1721 Notes
1722 -----
1723 :meth:`pandas.factorize` offers a `sort` keyword as well.
1724
1725 Examples
1726 --------
1727 >>> idx1 = pd.PeriodIndex(
1728 ... ["2014-01", "2014-01", "2014-02", "2014-02", "2014-03", "2014-03"],
1729 ... freq="M",
1730 ... )
1731 >>> arr, idx = idx1.factorize()
1732 >>> arr
1733 array([0, 0, 1, 1, 2, 2])
1734 >>> idx
1735 PeriodIndex(['2014-01', '2014-02', '2014-03'], dtype='period[M]')
1736 """
1737 # Implementer note: There are two ways to override the behavior of
1738 # pandas.factorize
1739 # 1. _values_for_factorize and _from_factorize.
1740 # Specify the values passed to pandas' internal factorization
1741 # routines, and how to convert from those values back to the
1742 # original ExtensionArray.
1743 # 2. ExtensionArray.factorize.
1744 # Complete control over factorization.
1745 arr, na_value = self._values_for_factorize()
1746
1747 codes, uniques = factorize_array(
1748 arr, use_na_sentinel=use_na_sentinel, na_value=na_value
1749 )
1750
1751 uniques_ea = self._from_factorized(uniques, self)
1752 return codes, uniques_ea
1753
1754 _extension_array_shared_docs["repeat"] = """
1755 Repeat elements of a %(klass)s.
1756
1757 Returns a new %(klass)s where each element of the current %(klass)s
1758 is repeated consecutively a given number of times.
1759
1760 Parameters
1761 ----------
1762 repeats : int or array of ints
1763 The number of repetitions for each element. This should be a
1764 non-negative integer. Repeating 0 times will return an empty
1765 %(klass)s.
1766 axis : None
1767 Must be ``None``. Has no effect but is accepted for compatibility
1768 with numpy.
1769
1770 Returns
1771 -------
1772 %(klass)s
1773 Newly created %(klass)s with repeated elements.
1774
1775 See Also
1776 --------
1777 Series.repeat : Equivalent function for Series.
1778 Index.repeat : Equivalent function for Index.
1779 numpy.repeat : Similar method for :class:`numpy.ndarray`.
1780 ExtensionArray.take : Take arbitrary positions.
1781
1782 Examples
1783 --------
1784 >>> cat = pd.Categorical(['a', 'b', 'c'])
1785 >>> cat
1786 ['a', 'b', 'c']
1787 Categories (3, str): ['a', 'b', 'c']
1788 >>> cat.repeat(2)
1789 ['a', 'a', 'b', 'b', 'c', 'c']
1790 Categories (3, str): ['a', 'b', 'c']
1791 >>> cat.repeat([1, 2, 3])
1792 ['a', 'b', 'b', 'c', 'c', 'c']
1793 Categories (3, str): ['a', 'b', 'c']
1794 """
1795
1796 def repeat(self, repeats: int | Sequence[int], axis: AxisInt | None = None) -> Self:
1797 """
1798 Repeat elements of an ExtensionArray.
1799
1800 Returns a new ExtensionArray where each element of the current ExtensionArray
1801 is repeated consecutively a given number of times.
1802
1803 Parameters
1804 ----------
1805 repeats : int or array of ints
1806 The number of repetitions for each element. This should be a
1807 non-negative integer. Repeating 0 times will return an empty
1808 ExtensionArray.
1809 axis : None
1810 Must be ``None``. Has no effect but is accepted for compatibility
1811 with numpy.
1812
1813 Returns
1814 -------
1815 ExtensionArray
1816 Newly created ExtensionArray with repeated elements.
1817
1818 See Also
1819 --------
1820 Series.repeat : Equivalent function for Series.
1821 Index.repeat : Equivalent function for Index.
1822 numpy.repeat : Similar method for :class:`numpy.ndarray`.
1823 ExtensionArray.take : Take arbitrary positions.
1824
1825 Examples
1826 --------
1827 >>> cat = pd.Categorical(["a", "b", "c"])
1828 >>> cat
1829 ['a', 'b', 'c']
1830 Categories (3, str): ['a', 'b', 'c']
1831 >>> cat.repeat(2)
1832 ['a', 'a', 'b', 'b', 'c', 'c']
1833 Categories (3, str): ['a', 'b', 'c']
1834 >>> cat.repeat([1, 2, 3])
1835 ['a', 'b', 'b', 'c', 'c', 'c']
1836 Categories (3, str): ['a', 'b', 'c']
1837 """
1838 nv.validate_repeat((), {"axis": axis})
1839 ind = np.arange(len(self)).repeat(repeats)
1840 return self.take(ind)
1841
1842 def value_counts(self, dropna: bool = True) -> Series:
1843 """
1844 Return a Series containing counts of unique values.
1845
1846 This method returns a Series with unique values as the index and their
1847 counts as the values.
1848
1849 Parameters
1850 ----------
1851 dropna : bool, default True
1852 Don't include counts of NA values.
1853
1854 Returns
1855 -------
1856 Series
1857 A Series with unique values as index and counts as values.
1858
1859 See Also
1860 --------
1861 Series.value_counts : Equivalent method on Series.
1862 DataFrame.value_counts : Equivalent method on DataFrame.
1863 Index.value_counts : Equivalent method on Index.
1864
1865 Examples
1866 --------
1867 >>> from pandas.core.arrays import IntegerArray
1868 >>> arr = IntegerArray._from_sequence([3, 3, 3, 1, 2, 2])
1869 >>> arr.value_counts()
1870 3 3
1871 1 1
1872 2 2
1873 Name: count, dtype: Int64
1874 """
1875 from pandas.core.algorithms import value_counts_internal as value_counts
1876
1877 result = value_counts(self.to_numpy(copy=False), sort=False, dropna=dropna)
1878 result.index = result.index.astype(self.dtype)
1879 return result
1880
1881 # ------------------------------------------------------------------------
1882 # Indexing methods
1883 # ------------------------------------------------------------------------
1884
1885 def take(
1886 self,
1887 indices: TakeIndexer,
1888 *,
1889 allow_fill: bool = False,
1890 fill_value: Any = None,
1891 ) -> Self:
1892 """
1893 Take elements from an array.
1894
1895 Parameters
1896 ----------
1897 indices : sequence of int or one-dimensional np.ndarray of int
1898 Indices to be taken.
1899 allow_fill : bool, default False
1900 How to handle negative values in `indices`.
1901
1902 * False: negative values in `indices` indicate positional indices
1903 from the right (the default). This is similar to
1904 :func:`numpy.take`.
1905
1906 * True: negative values in `indices` indicate
1907 missing values. These values are set to `fill_value`. Any other
1908 other negative values raise a ``ValueError``.
1909
1910 fill_value : any, optional
1911 Fill value to use for NA-indices when `allow_fill` is True.
1912 This may be ``None``, in which case the default NA value for
1913 the type, ``self.dtype.na_value``, is used.
1914
1915 For many ExtensionArrays, there will be two representations of
1916 `fill_value`: a user-facing "boxed" scalar, and a low-level
1917 physical NA value. `fill_value` should be the user-facing version,
1918 and the implementation should handle translating that to the
1919 physical version for processing the take if necessary.
1920
1921 Returns
1922 -------
1923 ExtensionArray
1924 An array formed with selected `indices`.
1925
1926 Raises
1927 ------
1928 IndexError
1929 When the indices are out of bounds for the array.
1930 ValueError
1931 When `indices` contains negative values other than ``-1``
1932 and `allow_fill` is True.
1933
1934 See Also
1935 --------
1936 numpy.take : Take elements from an array along an axis.
1937 api.extensions.take : Take elements from an array.
1938
1939 Notes
1940 -----
1941 ExtensionArray.take is called by ``Series.__getitem__``, ``.loc``,
1942 ``iloc``, when `indices` is a sequence of values. Additionally,
1943 it's called by :meth:`Series.reindex`, or any other method
1944 that causes realignment, with a `fill_value`.
1945
1946 Examples
1947 --------
1948 Here's an example implementation, which relies on casting the
1949 extension array to object dtype. This uses the helper method
1950 :func:`pandas.api.extensions.take`.
1951
1952 .. code-block:: python
1953
1954 def take(self, indices, allow_fill=False, fill_value=None):
1955 from pandas.api.extensions import take
1956
1957 # If the ExtensionArray is backed by an ndarray, then
1958 # just pass that here instead of coercing to object.
1959 data = self.astype(object)
1960
1961 if allow_fill and fill_value is None:
1962 fill_value = self.dtype.na_value
1963
1964 # fill value should always be translated from the scalar
1965 # type for the array, to the physical storage type for
1966 # the data, before passing to take.
1967
1968 result = take(
1969 data, indices, fill_value=fill_value, allow_fill=allow_fill
1970 )
1971 return self._from_sequence(result, dtype=self.dtype)
1972 """
1973 # Implementer note: The `fill_value` parameter should be a user-facing
1974 # value, an instance of self.dtype.type. When passed `fill_value=None`,
1975 # the default of `self.dtype.na_value` should be used.
1976 # This may differ from the physical storage type your ExtensionArray
1977 # uses. In this case, your implementation is responsible for casting
1978 # the user-facing type to the storage type, before using
1979 # pandas.api.extensions.take
1980 raise AbstractMethodError(self)
1981
1982 def copy(self) -> Self:
1983 """
1984 Return a copy of the array.
1985
1986 This method creates a copy of the `ExtensionArray` where modifying the
1987 data in the copy will not affect the original array. This is useful when
1988 you want to manipulate data without altering the original dataset.
1989
1990 Returns
1991 -------
1992 ExtensionArray
1993 A new `ExtensionArray` object that is a copy of the current instance.
1994
1995 See Also
1996 --------
1997 DataFrame.copy : Return a copy of the DataFrame.
1998 Series.copy : Return a copy of the Series.
1999
2000 Examples
2001 --------
2002 >>> arr = pd.array([1, 2, 3])
2003 >>> arr2 = arr.copy()
2004 >>> arr[0] = 2
2005 >>> arr2
2006 <IntegerArray>
2007 [1, 2, 3]
2008 Length: 3, dtype: Int64
2009 """
2010 raise AbstractMethodError(self)
2011
2012 @overload
2013 def view(self) -> Self: ...
2014
2015 @overload
2016 def view(self, dtype: Dtype | None = ...) -> ArrayLike: ...
2017
2018 def view(self, dtype: Dtype | None = None) -> ArrayLike:
2019 """
2020 Return a view on the array.
2021
2022 Parameters
2023 ----------
2024 dtype : str, np.dtype, or ExtensionDtype, optional
2025 Default None.
2026
2027 Returns
2028 -------
2029 ExtensionArray or np.ndarray
2030 A view on the :class:`ExtensionArray`'s data.
2031
2032 See Also
2033 --------
2034 api.extensions.ExtensionArray.ravel: Return a flattened view on input array.
2035 Index.view: Equivalent function for Index.
2036 ndarray.view: New view of array with the same data.
2037
2038 Examples
2039 --------
2040 This gives view on the underlying data of an ``ExtensionArray`` and is not a
2041 copy. Modifications on either the view or the original ``ExtensionArray``
2042 will be reflected on the underlying data:
2043
2044 >>> arr = pd.array([1, 2, 3])
2045 >>> arr2 = arr.view()
2046 >>> arr[0] = 2
2047 >>> arr2
2048 <IntegerArray>
2049 [2, 2, 3]
2050 Length: 3, dtype: Int64
2051 """
2052 # NB:
2053 # - This must return a *new* object referencing the same data, not self.
2054 # - The only case that *must* be implemented is with dtype=None,
2055 # giving a view with the same dtype as self.
2056 if dtype is not None:
2057 raise NotImplementedError(dtype)
2058 return self[:]
2059
2060 # ------------------------------------------------------------------------
2061 # Printing
2062 # ------------------------------------------------------------------------
2063
2064 def __repr__(self) -> str:
2065 if self.ndim > 1:
2066 return self._repr_2d()
2067
2068 from pandas.io.formats.printing import format_object_summary
2069
2070 # the short repr has no trailing newline, while the truncated
2071 # repr does. So we include a newline in our template, and strip
2072 # any trailing newlines from format_object_summary
2073 data = format_object_summary(
2074 self, self._formatter(), indent_for_name=False
2075 ).rstrip(", \n")
2076 class_name = f"<{type(self).__name__}>\n"
2077 footer = self._get_repr_footer()
2078 return f"{class_name}{data}\n{footer}"
2079
2080 def _get_repr_footer(self) -> str:
2081 # GH#24278
2082 if self.ndim > 1:
2083 return f"Shape: {self.shape}, dtype: {self.dtype}"
2084 return f"Length: {len(self)}, dtype: {self.dtype}"
2085
2086 def _repr_2d(self) -> str:
2087 from pandas.io.formats.printing import format_object_summary
2088
2089 # the short repr has no trailing newline, while the truncated
2090 # repr does. So we include a newline in our template, and strip
2091 # any trailing newlines from format_object_summary
2092 lines = [
2093 format_object_summary(x, self._formatter(), indent_for_name=False).rstrip(
2094 ", \n"
2095 )
2096 for x in self
2097 ]
2098 data = ",\n".join(lines)
2099 class_name = f"<{type(self).__name__}>"
2100 footer = self._get_repr_footer()
2101 return f"{class_name}\n[\n{data}\n]\n{footer}"
2102
2103 def _formatter(self, boxed: bool = False) -> Callable[[Any], str | None]:
2104 """
2105 Formatting function for scalar values.
2106
2107 This is used in the default '__repr__'. The returned formatting
2108 function receives instances of your scalar type.
2109
2110 Parameters
2111 ----------
2112 boxed : bool, default False
2113 An indicated for whether or not your array is being printed
2114 within a Series, DataFrame, or Index (True), or just by
2115 itself (False). This may be useful if you want scalar values
2116 to appear differently within a Series versus on its own (e.g.
2117 quoted or not).
2118
2119 Returns
2120 -------
2121 Callable[[Any], str]
2122 A callable that gets instances of the scalar type and
2123 returns a string. By default, :func:`repr` is used
2124 when ``boxed=False`` and :func:`str` is used when
2125 ``boxed=True``.
2126
2127 See Also
2128 --------
2129 api.extensions.ExtensionArray._concat_same_type : Concatenate multiple
2130 array of this dtype.
2131 api.extensions.ExtensionArray._explode : Transform each element of
2132 list-like to a row.
2133 api.extensions.ExtensionArray._from_factorized : Reconstruct an
2134 ExtensionArray after factorization.
2135 api.extensions.ExtensionArray._from_sequence : Construct a new
2136 ExtensionArray from a sequence of scalars.
2137
2138 Examples
2139 --------
2140 >>> class MyExtensionArray(pd.arrays.NumpyExtensionArray):
2141 ... def _formatter(self, boxed=False):
2142 ... return lambda x: "*" + str(x) + "*"
2143 >>> MyExtensionArray(np.array([1, 2, 3, 4]))
2144 <MyExtensionArray>
2145 [*1*, *2*, *3*, *4*]
2146 Length: 4, dtype: int64
2147 """
2148 if boxed:
2149 return str
2150 return repr
2151
2152 # ------------------------------------------------------------------------
2153 # Reshaping
2154 # ------------------------------------------------------------------------
2155
2156 def transpose(self, *axes: int) -> Self:
2157 """
2158 Return a transposed view on this array.
2159
2160 Because ExtensionArrays are always 1D, this is a no-op. It is included
2161 for compatibility with np.ndarray.
2162
2163 Returns
2164 -------
2165 ExtensionArray
2166
2167 Examples
2168 --------
2169 >>> pd.array([1, 2, 3]).transpose()
2170 <IntegerArray>
2171 [1, 2, 3]
2172 Length: 3, dtype: Int64
2173 """
2174 return self[:]
2175
2176 @property
2177 def T(self) -> Self:
2178 return self.transpose()
2179
2180 def ravel(self, order: Literal["C", "F", "A", "K"] | None = "C") -> Self:
2181 """
2182 Return a flattened view on this array.
2183
2184 Parameters
2185 ----------
2186 order : {None, 'C', 'F', 'A', 'K'}, default 'C'
2187
2188 Returns
2189 -------
2190 ExtensionArray
2191 A flattened view on the array.
2192
2193 See Also
2194 --------
2195 ExtensionArray.tolist: Return a list of the values.
2196
2197 Notes
2198 -----
2199 - Because ExtensionArrays are 1D-only, this is a no-op.
2200 - The "order" argument is ignored, is for compatibility with NumPy.
2201
2202 Examples
2203 --------
2204 >>> pd.array([1, 2, 3]).ravel()
2205 <IntegerArray>
2206 [1, 2, 3]
2207 Length: 3, dtype: Int64
2208 """
2209 return self
2210
2211 @classmethod
2212 def _concat_same_type(cls, to_concat: Sequence[Self]) -> Self:
2213 """
2214 Concatenate multiple array of this dtype.
2215
2216 Parameters
2217 ----------
2218 to_concat : sequence of this type
2219 An array of the same dtype to concatenate.
2220
2221 Returns
2222 -------
2223 ExtensionArray
2224
2225 See Also
2226 --------
2227 api.extensions.ExtensionArray._explode : Transform each element of
2228 list-like to a row.
2229 api.extensions.ExtensionArray._formatter : Formatting function for
2230 scalar values.
2231 api.extensions.ExtensionArray._from_factorized : Reconstruct an
2232 ExtensionArray after factorization.
2233
2234 Examples
2235 --------
2236 >>> arr1 = pd.array([1, 2, 3])
2237 >>> arr2 = pd.array([4, 5, 6])
2238 >>> pd.arrays.IntegerArray._concat_same_type([arr1, arr2])
2239 <IntegerArray>
2240 [1, 2, 3, 4, 5, 6]
2241 Length: 6, dtype: Int64
2242 """
2243 # Implementer note: this method will only be called with a sequence of
2244 # ExtensionArrays of this class and with the same dtype as self. This
2245 # should allow "easy" concatenation (no upcasting needed), and result
2246 # in a new ExtensionArray of the same dtype.
2247 # Note: this strict behaviour is only guaranteed starting with pandas 1.1
2248 raise AbstractMethodError(cls)
2249
2250 # The _can_hold_na attribute is set to True so that pandas internals
2251 # will use the ExtensionDtype.na_value as the NA value in operations
2252 # such as take(), reindex(), shift(), etc. In addition, those results
2253 # will then be of the ExtensionArray subclass rather than an array
2254 # of objects
2255 @cache_readonly
2256 def _can_hold_na(self) -> bool:
2257 return self.dtype._can_hold_na
2258
2259 def _accumulate(
2260 self, name: str, *, skipna: bool = True, **kwargs
2261 ) -> ExtensionArray:
2262 """
2263 Return an ExtensionArray performing an accumulation operation.
2264
2265 The underlying data type might change.
2266
2267 Parameters
2268 ----------
2269 name : str
2270 Name of the function, supported values are:
2271 - cummin
2272 - cummax
2273 - cumsum
2274 - cumprod
2275 skipna : bool, default True
2276 If True, skip NA values.
2277 **kwargs
2278 Additional keyword arguments passed to the accumulation function.
2279 Currently, there is no supported kwarg.
2280
2281 Returns
2282 -------
2283 array
2284 An array performing the accumulation operation.
2285
2286 Raises
2287 ------
2288 NotImplementedError : subclass does not define accumulations
2289
2290 See Also
2291 --------
2292 api.extensions.ExtensionArray._concat_same_type : Concatenate multiple
2293 array of this dtype.
2294 api.extensions.ExtensionArray.view : Return a view on the array.
2295 api.extensions.ExtensionArray._explode : Transform each element of
2296 list-like to a row.
2297
2298 Examples
2299 --------
2300 >>> arr = pd.array([1, 2, 3])
2301 >>> arr._accumulate(name="cumsum")
2302 <IntegerArray>
2303 [1, 3, 6]
2304 Length: 3, dtype: Int64
2305 """
2306 raise NotImplementedError(f"cannot perform {name} with type {self.dtype}")
2307
2308 def _reduce(
2309 self, name: str, *, skipna: bool = True, keepdims: bool = False, **kwargs
2310 ):
2311 """
2312 Return a scalar result of performing the reduction operation.
2313
2314 Parameters
2315 ----------
2316 name : str
2317 Name of the function, supported values are:
2318 { any, all, min, max, sum, mean, median, prod,
2319 std, var, sem, kurt, skew }.
2320 skipna : bool, default True
2321 If True, skip NaN values.
2322 keepdims : bool, default False
2323 If False, a scalar is returned.
2324 If True, the result has dimension with size one along the reduced axis.
2325 **kwargs
2326 Additional keyword arguments passed to the reduction function.
2327 Currently, `ddof` is the only supported kwarg.
2328
2329 Returns
2330 -------
2331 scalar or ndarray:
2332 The result of the reduction operation. The type of the result
2333 depends on `keepdims`:
2334 - If `keepdims` is `False`, a scalar value is returned.
2335 - If `keepdims` is `True`, the result is wrapped in a numpy array with
2336 a single element.
2337
2338 Raises
2339 ------
2340 TypeError : subclass does not define operations
2341
2342 See Also
2343 --------
2344 Series.min : Return the minimum value.
2345 Series.max : Return the maximum value.
2346 Series.sum : Return the sum of values.
2347 Series.mean : Return the mean of values.
2348 Series.median : Return the median of values.
2349 Series.std : Return the standard deviation.
2350 Series.var : Return the variance.
2351 Series.prod : Return the product of values.
2352 Series.sem : Return the standard error of the mean.
2353 Series.kurt : Return the kurtosis.
2354 Series.skew : Return the skewness.
2355
2356 Examples
2357 --------
2358 >>> pd.array([1, 2, 3])._reduce("min")
2359 np.int64(1)
2360 >>> pd.array([1, 2, 3])._reduce("max")
2361 np.int64(3)
2362 >>> pd.array([1, 2, 3])._reduce("sum")
2363 np.int64(6)
2364 >>> pd.array([1, 2, 3])._reduce("mean")
2365 np.float64(2.0)
2366 >>> pd.array([1, 2, 3])._reduce("median")
2367 np.float64(2.0)
2368 """
2369 meth = getattr(self, name, None)
2370 if meth is None:
2371 raise TypeError(
2372 f"'{type(self).__name__}' with dtype {self.dtype} "
2373 f"does not support operation '{name}'"
2374 )
2375 result = meth(skipna=skipna, **kwargs)
2376 if keepdims:
2377 if name in ["min", "max"]:
2378 result = self._from_sequence([result], dtype=self.dtype)
2379 else:
2380 result = np.array([result])
2381
2382 return result
2383
2384 # https://github.com/python/typeshed/issues/2148#issuecomment-520783318
2385 # Incompatible types in assignment (expression has type "None", base class
2386 # "object" defined the type as "Callable[[object], int]")
2387 __hash__: ClassVar[None] # type: ignore[assignment]
2388
2389 # ------------------------------------------------------------------------
2390 # Non-Optimized Default Methods; in the case of the private methods here,
2391 # these are not guaranteed to be stable across pandas versions.
2392
2393 def _values_for_json(self) -> np.ndarray:
2394 """
2395 Specify how to render our entries in to_json.
2396
2397 Notes
2398 -----
2399 The dtype on the returned ndarray is not restricted, but for non-native
2400 types that are not specifically handled in objToJSON.c, to_json is
2401 liable to raise. In these cases, it may be safer to return an ndarray
2402 of strings.
2403 """
2404 return np.asarray(self)
2405
2406 def _hash_pandas_object(
2407 self, *, encoding: str, hash_key: str, categorize: bool
2408 ) -> npt.NDArray[np.uint64]:
2409 """
2410 Hook for hash_pandas_object.
2411
2412 Default is to use the values returned by _values_for_factorize.
2413
2414 Parameters
2415 ----------
2416 encoding : str
2417 Encoding for data & key when strings.
2418 hash_key : str
2419 Hash_key for string key to encode.
2420 categorize : bool
2421 Whether to first categorize object arrays before hashing. This is more
2422 efficient when the array contains duplicate values.
2423
2424 Returns
2425 -------
2426 np.ndarray[uint64]
2427 An array of hashed values.
2428
2429 See Also
2430 --------
2431 api.extensions.ExtensionArray._values_for_factorize : Return an array and
2432 missing value suitable for factorization.
2433 util.hash_array : Given a 1d array, return an array of hashed values.
2434
2435 Examples
2436 --------
2437 >>> pd.array([1, 2])._hash_pandas_object(
2438 ... encoding="utf-8", hash_key="1000000000000000", categorize=False
2439 ... )
2440 array([ 6238072747940578789, 15839785061582574730], dtype=uint64)
2441 """
2442 from pandas.core.util.hashing import hash_array
2443
2444 values, _ = self._values_for_factorize()
2445 return hash_array(
2446 values, encoding=encoding, hash_key=hash_key, categorize=categorize
2447 )
2448
2449 def _explode(self) -> tuple[Self, npt.NDArray[np.uint64]]:
2450 """
2451 Transform each element of list-like to a row.
2452
2453 For arrays that do not contain list-like elements the default
2454 implementation of this method just returns a copy and an array
2455 of ones (unchanged index).
2456
2457 Returns
2458 -------
2459 ExtensionArray
2460 Array with the exploded values.
2461 np.ndarray[uint64]
2462 The original lengths of each list-like for determining the
2463 resulting index.
2464
2465 See Also
2466 --------
2467 Series.explode : The method on the ``Series`` object that this
2468 extension array method is meant to support.
2469
2470 Examples
2471 --------
2472 >>> import pyarrow as pa
2473 >>> a = pd.array(
2474 ... [[1, 2, 3], [4], [5, 6]], dtype=pd.ArrowDtype(pa.list_(pa.int64()))
2475 ... )
2476 >>> a._explode()
2477 (<ArrowExtensionArray>
2478 [1, 2, 3, 4, 5, 6]
2479 Length: 6, dtype: int64[pyarrow], array([3, 1, 2], dtype=int32))
2480 """
2481 values = self.copy()
2482 counts = np.ones(shape=(len(self),), dtype=np.uint64)
2483 return values, counts
2484
2485 def tolist(self) -> list:
2486 """
2487 Return a list of the values.
2488
2489 These are each a scalar type, which is a Python scalar
2490 (for str, int, float) or a pandas scalar
2491 (for Timestamp/Timedelta/Interval/Period)
2492
2493 Returns
2494 -------
2495 list
2496 Python list of values in array.
2497
2498 See Also
2499 --------
2500 Index.to_list: Return a list of the values in the Index.
2501 Series.to_list: Return a list of the values in the Series.
2502
2503 Examples
2504 --------
2505 >>> arr = pd.array([1, 2, 3])
2506 >>> arr.tolist()
2507 [1, 2, 3]
2508 """
2509 if self.ndim > 1:
2510 return [x.tolist() for x in self]
2511 return list(self)
2512
2513 def delete(self, loc: PositionalIndexer) -> Self:
2514 indexer = np.delete(np.arange(len(self)), loc)
2515 return self.take(indexer)
2516
2517 def insert(self, loc: int, item) -> Self:
2518 """
2519 Insert an item at the given position.
2520
2521 Parameters
2522 ----------
2523 loc : int
2524 Index where the `item` needs to be inserted.
2525 item : scalar-like
2526 Value to be inserted.
2527
2528 Returns
2529 -------
2530 ExtensionArray
2531 With `item` inserted at `loc`.
2532
2533 See Also
2534 --------
2535 Index.insert: Make new Index inserting new item at location.
2536
2537 Notes
2538 -----
2539 This method should be both type and dtype-preserving. If the item
2540 cannot be held in an array of this type/dtype, either ValueError or
2541 TypeError should be raised.
2542
2543 The default implementation relies on _from_sequence to raise on invalid
2544 items.
2545
2546 Examples
2547 --------
2548 >>> arr = pd.array([1, 2, 3])
2549 >>> arr.insert(2, -1)
2550 <IntegerArray>
2551 [1, 2, -1, 3]
2552 Length: 4, dtype: Int64
2553 """
2554 loc = validate_insert_loc(loc, len(self))
2555
2556 item_arr = type(self)._from_sequence([item], dtype=self.dtype)
2557
2558 return type(self)._concat_same_type([self[:loc], item_arr, self[loc:]])
2559
2560 def _putmask(self, mask: npt.NDArray[np.bool_], value) -> None:
2561 """
2562 Analogue to np.putmask(self, mask, value)
2563
2564 Parameters
2565 ----------
2566 mask : np.ndarray[bool]
2567 value : scalar or listlike
2568 If listlike, must be arraylike with same length as self.
2569
2570 Returns
2571 -------
2572 None
2573
2574 Notes
2575 -----
2576 Unlike np.putmask, we do not repeat listlike values with mismatched length.
2577 'value' should either be a scalar or an arraylike with the same length
2578 as self.
2579 """
2580 if is_list_like(value):
2581 val = value[mask]
2582 else:
2583 val = value
2584
2585 self[mask] = val
2586
2587 def _where(self, mask: npt.NDArray[np.bool_], value) -> Self:
2588 """
2589 Analogue to np.where(mask, self, value)
2590
2591 Parameters
2592 ----------
2593 mask : np.ndarray[bool]
2594 value : scalar or listlike
2595
2596 Returns
2597 -------
2598 same type as self
2599 """
2600 result = self.copy()
2601
2602 if is_list_like(value):
2603 val = value[~mask]
2604 else:
2605 val = value
2606
2607 result[~mask] = val
2608 return result
2609
2610 def _rank(
2611 self,
2612 *,
2613 axis: AxisInt = 0,
2614 method: str = "average",
2615 na_option: str = "keep",
2616 ascending: bool = True,
2617 pct: bool = False,
2618 ):
2619 """
2620 See Series.rank.__doc__.
2621 """
2622 if axis != 0:
2623 raise NotImplementedError
2624
2625 return rank(
2626 self._values_for_argsort(),
2627 axis=axis,
2628 method=method,
2629 na_option=na_option,
2630 ascending=ascending,
2631 pct=pct,
2632 mask=np.asarray(self.isna(), dtype="bool") if self._hasna else None,
2633 )
2634
2635 @classmethod
2636 def _empty(cls, shape: Shape, dtype: ExtensionDtype):
2637 """
2638 Create an ExtensionArray with the given shape and dtype.
2639
2640 See also
2641 --------
2642 ExtensionDtype.empty
2643 ExtensionDtype.empty is the 'official' public version of this API.
2644 """
2645 # Implementer note: while ExtensionDtype.empty is the public way to
2646 # call this method, it is still required to implement this `_empty`
2647 # method as well (it is called internally in pandas)
2648 obj = cls._from_sequence([], dtype=dtype)
2649
2650 taker = np.broadcast_to(np.intp(-1), shape)
2651 result = obj.take(taker, allow_fill=True)
2652 if not isinstance(result, cls) or dtype != result.dtype:
2653 raise NotImplementedError(
2654 f"Default 'empty' implementation is invalid for dtype='{dtype}'"
2655 )
2656 return result
2657
2658 def _quantile(self, qs: npt.NDArray[np.float64], interpolation: str) -> Self:
2659 """
2660 Compute the quantiles of self for each quantile in `qs`.
2661
2662 Parameters
2663 ----------
2664 qs : np.ndarray[float64]
2665 interpolation: str
2666
2667 Returns
2668 -------
2669 same type as self
2670 """
2671 mask = np.asarray(self.isna())
2672 arr = np.asarray(self)
2673 fill_value = np.nan
2674
2675 res_values = quantile_with_mask(arr, mask, fill_value, qs, interpolation)
2676 return type(self)._from_sequence(res_values)
2677
2678 def _mode(self, dropna: bool = True) -> Self:
2679 """
2680 Returns the mode(s) of the ExtensionArray.
2681
2682 Always returns `ExtensionArray` even if only one value.
2683
2684 Parameters
2685 ----------
2686 dropna : bool, default True
2687 Don't consider counts of NA values.
2688
2689 Returns
2690 -------
2691 same type as self
2692 Sorted, if possible.
2693 """
2694 # error: Incompatible return value type (got "Union[ExtensionArray,
2695 # Tuple[np.ndarray, npt.NDArray[np.bool_]]", expected "Self")
2696 result, _ = mode(self, dropna=dropna)
2697 return result # type: ignore[return-value]
2698
2699 def __array_ufunc__(self, ufunc: np.ufunc, method: str, *inputs, **kwargs):
2700 if any(
2701 isinstance(other, (ABCSeries, ABCIndex, ABCDataFrame)) for other in inputs
2702 ):
2703 return NotImplemented
2704
2705 result = arraylike.maybe_dispatch_ufunc_to_dunder_op(
2706 self, ufunc, method, *inputs, **kwargs
2707 )
2708 if result is not NotImplemented:
2709 return result
2710
2711 if "out" in kwargs:
2712 return arraylike.dispatch_ufunc_with_out(
2713 self, ufunc, method, *inputs, **kwargs
2714 )
2715
2716 if method == "reduce":
2717 result = arraylike.dispatch_reduction_ufunc(
2718 self, ufunc, method, *inputs, **kwargs
2719 )
2720 if result is not NotImplemented:
2721 return result
2722
2723 return arraylike.default_array_ufunc(self, ufunc, method, *inputs, **kwargs)
2724
2725 def map(self, mapper, na_action: Literal["ignore"] | None = None):
2726 """
2727 Map values using an input mapping or function.
2728
2729 Parameters
2730 ----------
2731 mapper : function, dict, or Series
2732 Mapping correspondence.
2733 na_action : {None, 'ignore'}, default None
2734 If 'ignore', propagate NA values, without passing them to the
2735 mapping correspondence. If 'ignore' is not supported, a
2736 ``NotImplementedError`` should be raised.
2737
2738 Returns
2739 -------
2740 Union[ndarray, Index, ExtensionArray]
2741 The output of the mapping function applied to the array.
2742 If the function returns a tuple with more than one element
2743 a MultiIndex will be returned.
2744 """
2745 return map_array(self, mapper, na_action=na_action)
2746
2747 # ------------------------------------------------------------------------
2748 # GroupBy Methods
2749
2750 def _groupby_op(
2751 self,
2752 *,
2753 how: str,
2754 has_dropped_na: bool,
2755 min_count: int,
2756 ngroups: int,
2757 ids: npt.NDArray[np.intp],
2758 **kwargs,
2759 ) -> ArrayLike:
2760 """
2761 Dispatch GroupBy reduction or transformation operation.
2762
2763 This is an *experimental* API to allow ExtensionArray authors to implement
2764 reductions and transformations. The API is subject to change.
2765
2766 Parameters
2767 ----------
2768 how : {'any', 'all', 'sum', 'prod', 'min', 'max', 'mean', 'median',
2769 'median', 'var', 'std', 'sem', 'nth', 'last', 'ohlc',
2770 'cumprod', 'cumsum', 'cummin', 'cummax', 'rank'}
2771 has_dropped_na : bool
2772 min_count : int
2773 ngroups : int
2774 ids : np.ndarray[np.intp]
2775 ids[i] gives the integer label for the group that self[i] belongs to.
2776 **kwargs : operation-specific
2777 'any', 'all' -> ['skipna']
2778 'var', 'std', 'sem' -> ['ddof']
2779 'cumprod', 'cumsum', 'cummin', 'cummax' -> ['skipna']
2780 'rank' -> ['ties_method', 'ascending', 'na_option', 'pct']
2781
2782 Returns
2783 -------
2784 np.ndarray or ExtensionArray
2785 """
2786 from pandas.core.arrays.string_ import StringDtype
2787 from pandas.core.groupby.ops import WrappedCythonOp
2788
2789 kind = WrappedCythonOp.get_kind_from_how(how)
2790 op = WrappedCythonOp(how=how, kind=kind, has_dropped_na=has_dropped_na)
2791
2792 initial: Any = 0
2793 # GH#43682
2794 if isinstance(self.dtype, StringDtype):
2795 # StringArray
2796 if op.how in [
2797 "prod",
2798 "mean",
2799 "median",
2800 "cumsum",
2801 "cumprod",
2802 "std",
2803 "sem",
2804 "var",
2805 "skew",
2806 "kurt",
2807 ]:
2808 raise TypeError(
2809 f"dtype '{self.dtype}' does not support operation '{how}'"
2810 )
2811 if op.how not in ["any", "all"]:
2812 # Fail early to avoid conversion to object
2813 op._get_cython_function(op.kind, op.how, np.dtype(object), False)
2814
2815 arr = self
2816 if op.how == "sum":
2817 initial = ""
2818 # https://github.com/pandas-dev/pandas/issues/60229
2819 # All NA should result in the empty string.
2820 assert "skipna" in kwargs
2821 if kwargs["skipna"] and min_count == 0:
2822 arr = arr.fillna("")
2823 npvalues = arr.to_numpy(object, na_value=np.nan)
2824 else:
2825 raise NotImplementedError(
2826 f"function is not implemented for this dtype: {self.dtype}"
2827 )
2828
2829 res_values = op._cython_op_ndim_compat(
2830 npvalues,
2831 min_count=min_count,
2832 ngroups=ngroups,
2833 comp_ids=ids,
2834 mask=None,
2835 initial=initial,
2836 **kwargs,
2837 )
2838
2839 if op.how in op.cast_blocklist:
2840 # i.e. how in ["rank"], since other cast_blocklist methods don't go
2841 # through cython_operation
2842 return res_values
2843
2844 if isinstance(self.dtype, StringDtype):
2845 dtype = self.dtype
2846 string_array_cls = dtype.construct_array_type()
2847 return string_array_cls._from_sequence(res_values, dtype=dtype)
2848
2849 else:
2850 raise NotImplementedError
2851
2852
2853class ExtensionArraySupportsAnyAll(ExtensionArray):
2854 @overload
2855 def any(self, *, skipna: Literal[True] = ...) -> bool: ...
2856
2857 @overload
2858 def any(self, *, skipna: bool) -> bool | NAType: ...
2859
2860 def any(self, *, skipna: bool = True) -> bool | NAType:
2861 raise AbstractMethodError(self)
2862
2863 @overload
2864 def all(self, *, skipna: Literal[True] = ...) -> bool: ...
2865
2866 @overload
2867 def all(self, *, skipna: bool) -> bool | NAType: ...
2868
2869 def all(self, *, skipna: bool = True) -> bool | NAType:
2870 raise AbstractMethodError(self)
2871
2872
2873class ExtensionOpsMixin:
2874 """
2875 A base class for linking the operators to their dunder names.
2876
2877 .. note::
2878
2879 You may want to set ``__array_priority__`` if you want your
2880 implementation to be called when involved in binary operations
2881 with NumPy arrays.
2882 """
2883
2884 @classmethod
2885 def _create_arithmetic_method(cls, op):
2886 raise AbstractMethodError(cls)
2887
2888 @classmethod
2889 def _add_arithmetic_ops(cls) -> None:
2890 setattr(cls, "__add__", cls._create_arithmetic_method(operator.add))
2891 setattr(cls, "__radd__", cls._create_arithmetic_method(roperator.radd))
2892 setattr(cls, "__sub__", cls._create_arithmetic_method(operator.sub))
2893 setattr(cls, "__rsub__", cls._create_arithmetic_method(roperator.rsub))
2894 setattr(cls, "__mul__", cls._create_arithmetic_method(operator.mul))
2895 setattr(cls, "__rmul__", cls._create_arithmetic_method(roperator.rmul))
2896 setattr(cls, "__pow__", cls._create_arithmetic_method(operator.pow))
2897 setattr(cls, "__rpow__", cls._create_arithmetic_method(roperator.rpow))
2898 setattr(cls, "__mod__", cls._create_arithmetic_method(operator.mod))
2899 setattr(cls, "__rmod__", cls._create_arithmetic_method(roperator.rmod))
2900 setattr(cls, "__floordiv__", cls._create_arithmetic_method(operator.floordiv))
2901 setattr(
2902 cls, "__rfloordiv__", cls._create_arithmetic_method(roperator.rfloordiv)
2903 )
2904 setattr(cls, "__truediv__", cls._create_arithmetic_method(operator.truediv))
2905 setattr(cls, "__rtruediv__", cls._create_arithmetic_method(roperator.rtruediv))
2906 setattr(cls, "__divmod__", cls._create_arithmetic_method(divmod))
2907 setattr(cls, "__rdivmod__", cls._create_arithmetic_method(roperator.rdivmod))
2908
2909 @classmethod
2910 def _create_comparison_method(cls, op):
2911 raise AbstractMethodError(cls)
2912
2913 @classmethod
2914 def _add_comparison_ops(cls) -> None:
2915 setattr(cls, "__eq__", cls._create_comparison_method(operator.eq))
2916 setattr(cls, "__ne__", cls._create_comparison_method(operator.ne))
2917 setattr(cls, "__lt__", cls._create_comparison_method(operator.lt))
2918 setattr(cls, "__gt__", cls._create_comparison_method(operator.gt))
2919 setattr(cls, "__le__", cls._create_comparison_method(operator.le))
2920 setattr(cls, "__ge__", cls._create_comparison_method(operator.ge))
2921
2922 @classmethod
2923 def _create_logical_method(cls, op):
2924 raise AbstractMethodError(cls)
2925
2926 @classmethod
2927 def _add_logical_ops(cls) -> None:
2928 setattr(cls, "__and__", cls._create_logical_method(operator.and_))
2929 setattr(cls, "__rand__", cls._create_logical_method(roperator.rand_))
2930 setattr(cls, "__or__", cls._create_logical_method(operator.or_))
2931 setattr(cls, "__ror__", cls._create_logical_method(roperator.ror_))
2932 setattr(cls, "__xor__", cls._create_logical_method(operator.xor))
2933 setattr(cls, "__rxor__", cls._create_logical_method(roperator.rxor))
2934
2935
2936@set_module("pandas.api.extensions")
2937class ExtensionScalarOpsMixin(ExtensionOpsMixin):
2938 """
2939 A mixin for defining ops on an ExtensionArray.
2940
2941 It is assumed that the underlying scalar objects have the operators
2942 already defined.
2943
2944 Notes
2945 -----
2946 If you have defined a subclass MyExtensionArray(ExtensionArray), then
2947 use MyExtensionArray(ExtensionArray, ExtensionScalarOpsMixin) to
2948 get the arithmetic operators. After the definition of MyExtensionArray,
2949 insert the lines
2950
2951 MyExtensionArray._add_arithmetic_ops()
2952 MyExtensionArray._add_comparison_ops()
2953
2954 to link the operators to your class.
2955
2956 .. note::
2957
2958 You may want to set ``__array_priority__`` if you want your
2959 implementation to be called when involved in binary operations
2960 with NumPy arrays.
2961 """
2962
2963 @classmethod
2964 def _create_method(cls, op, coerce_to_dtype: bool = True, result_dtype=None):
2965 """
2966 A class method that returns a method that will correspond to an
2967 operator for an ExtensionArray subclass, by dispatching to the
2968 relevant operator defined on the individual elements of the
2969 ExtensionArray.
2970
2971 Parameters
2972 ----------
2973 op : function
2974 An operator that takes arguments op(a, b)
2975 coerce_to_dtype : bool, default True
2976 boolean indicating whether to attempt to convert
2977 the result to the underlying ExtensionArray dtype.
2978 If it's not possible to create a new ExtensionArray with the
2979 values, an ndarray is returned instead.
2980
2981 Returns
2982 -------
2983 Callable[[Any, Any], Union[ndarray, ExtensionArray]]
2984 A method that can be bound to a class. When used, the method
2985 receives the two arguments, one of which is the instance of
2986 this class, and should return an ExtensionArray or an ndarray.
2987
2988 Returning an ndarray may be necessary when the result of the
2989 `op` cannot be stored in the ExtensionArray. The dtype of the
2990 ndarray uses NumPy's normal inference rules.
2991
2992 Examples
2993 --------
2994 Given an ExtensionArray subclass called MyExtensionArray, use
2995
2996 __add__ = cls._create_method(operator.add)
2997
2998 in the class definition of MyExtensionArray to create the operator
2999 for addition, that will be based on the operator implementation
3000 of the underlying elements of the ExtensionArray
3001 """
3002
3003 def _binop(self, other):
3004 def convert_values(param):
3005 if isinstance(param, ExtensionArray) or is_list_like(param):
3006 ovalues = param
3007 else: # Assume its an object
3008 ovalues = [param] * len(self)
3009 return ovalues
3010
3011 if isinstance(other, (ABCSeries, ABCIndex, ABCDataFrame)):
3012 # rely on pandas to unbox and dispatch to us
3013 return NotImplemented
3014
3015 lvalues = self
3016 rvalues = convert_values(other)
3017
3018 # If the operator is not defined for the underlying objects,
3019 # a TypeError should be raised
3020 res = [op(a, b) for (a, b) in zip(lvalues, rvalues, strict=True)]
3021
3022 def _maybe_convert(arr):
3023 if coerce_to_dtype:
3024 # https://github.com/pandas-dev/pandas/issues/22850
3025 # We catch all regular exceptions here, and fall back
3026 # to an ndarray.
3027 res = self._cast_pointwise_result(arr)
3028 if not isinstance(res, type(self)):
3029 # exception raised in _from_sequence; ensure we have ndarray
3030 res = np.asarray(arr)
3031 else:
3032 res = np.asarray(arr, dtype=result_dtype)
3033 return res
3034
3035 if op.__name__ in {"divmod", "rdivmod"}:
3036 a, b = zip(*res, strict=True)
3037 return _maybe_convert(a), _maybe_convert(b)
3038
3039 return _maybe_convert(res)
3040
3041 op_name = f"__{op.__name__}__"
3042 return set_function_name(_binop, op_name, cls)
3043
3044 @classmethod
3045 def _create_arithmetic_method(cls, op):
3046 return cls._create_method(op)
3047
3048 @classmethod
3049 def _create_comparison_method(cls, op):
3050 return cls._create_method(op, coerce_to_dtype=False, result_dtype=bool)