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