-
-
Notifications
You must be signed in to change notification settings - Fork 18.5k
Add __array_ufunc__ to Series / Array #23293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 56 commits
2fffac3
c5a4664
dd332a4
71c058e
a0d11d9
4cfeb9b
607f8a6
c4fcae7
65dea1b
134df14
5239b70
3d91885
429f15c
41f4158
0d6a663
8f46391
44e3c7e
e179913
27208c1
0b1e745
9be1dff
775c2ef
4d7f249
0b359d7
bbbf269
64d8908
d1788b0
ef5d508
fe0ee4e
971e347
95e8aef
7bfd584
06e5739
feee015
3702b9b
a0f84ed
d83fe7a
edad466
e4ae8dc
db60f6c
a9bd6ef
1a8b807
0b0466d
1f67866
d3089bd
6e770e8
15a3fb1
b5e7f45
4f4bd93
5dbff49
b623be2
2237233
5b5c547
10bc2cc
5380b77
9f4d110
6c15ee7
ab48bd8
30fced8
7486d26
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -208,6 +208,28 @@ will | |
2. call ``result = op(values, ExtensionArray)`` | ||
3. re-box the result in a ``Series`` | ||
|
||
.. _extending.extension.ufunc: | ||
|
||
NumPy Universal Functions | ||
^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
:class:`Series` implements ``__array_ufunc__``. As part of the implementation, | ||
pandas unboxes the ``ExtensionArray`` from the :class:`Series`, applies the ufunc, | ||
and re-boxes it if necessary. | ||
|
||
If applicable, we highly recommend that your implement ``__array_ufunc__`` in your | ||
extension array to avoid coercion to an ndarray. See | ||
`the numpy documentation <https://docs.scipy.org/doc/numpy/reference/generated/numpy.lib.mixins.NDArrayOperatorsMixin.html>`__ | ||
for an example. | ||
|
||
As part of your implementation, we require that you | ||
|
||
1. Define a ``_HANDLED_TYPES`` attribute, a tuple, containing the types your | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. are we now recommeding setting |
||
array can handle | ||
2. Defer to the :class:`Series` implementatio by returning ``NotImplemented`` | ||
TomAugspurger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if there are any :class:`Series` in the ``types``. This ensures consistent | ||
metadata handling, and associativity for binary operations. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is in general the case for pandas containers, I think? (at least in the future if we add ufunc protocol to Index and DataFrame). |
||
|
||
.. _extending.extension.testing: | ||
|
||
Testing extension arrays | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -864,6 +864,7 @@ Sparse | |
- Introduce a better error message in :meth:`Series.sparse.from_coo` so it returns a ``TypeError`` for inputs that are not coo matrices (:issue:`26554`) | ||
- Bug in :func:`numpy.modf` on a :class:`SparseArray`. Now a tuple of :class:`SparseArray` is returned (:issue:`26946`). | ||
|
||
TomAugspurger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Build Changes | ||
^^^^^^^^^^^^^ | ||
|
||
|
@@ -874,6 +875,7 @@ ExtensionArray | |
|
||
- Bug in :func:`factorize` when passing an ``ExtensionArray`` with a custom ``na_sentinel`` (:issue:`25696`). | ||
- :meth:`Series.count` miscounts NA values in ExtensionArrays (:issue:`26835`) | ||
- Added ``Series.__array_ufunc__`` to better handle NumPy ufuncs applied to Series backed by extension arrays (:issue:`23293`). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should there be a general item for |
||
- Keyword argument ``deep`` has been removed from :meth:`ExtensionArray.copy` (:issue:`27083`) | ||
|
||
Other | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import numbers | ||
import sys | ||
from typing import Type | ||
import warnings | ||
|
@@ -17,7 +18,7 @@ | |
from pandas.core.dtypes.generic import ABCIndexClass, ABCSeries | ||
from pandas.core.dtypes.missing import isna, notna | ||
|
||
from pandas.core import nanops | ||
from pandas.core import nanops, ops | ||
from pandas.core.arrays import ExtensionArray, ExtensionOpsMixin | ||
from pandas.core.tools.numeric import to_numeric | ||
|
||
|
@@ -344,6 +345,54 @@ def __array__(self, dtype=None): | |
""" | ||
return self._coerce_to_ndarray() | ||
|
||
_HANDLED_TYPES = (np.ndarray, numbers.Number) | ||
|
||
def __array_ufunc__(self, ufunc, method, *inputs, **kwargs): | ||
# For IntegerArray inputs, we apply the ufunc to ._data | ||
# and mask the result. | ||
if method == 'reduce': | ||
# Not clear how to handle missing values in reductions. Raise. | ||
raise NotImplementedError("The 'reduce' method is not supported.") | ||
out = kwargs.get('out', ()) | ||
|
||
for x in inputs + out: | ||
if not isinstance(x, self._HANDLED_TYPES + (IntegerArray,)): | ||
return NotImplemented | ||
|
||
# for binary ops, use our custom dunder methods | ||
result = ops.maybe_dispatch_ufunc_to_dunder_op( | ||
self, ufunc, method, *inputs, **kwargs) | ||
if result is not NotImplemented: | ||
return result | ||
|
||
mask = np.zeros(len(self), dtype=bool) | ||
inputs2 = [] | ||
for x in inputs: | ||
if isinstance(x, IntegerArray): | ||
mask |= x._mask | ||
inputs2.append(x._data) | ||
else: | ||
inputs2.append(x) | ||
|
||
def reconstruct(x): | ||
if np.isscalar(x): | ||
# reductions. | ||
if mask.any(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess so. This affects, e.g. The current implementing calls the ufunc on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In latest numpy there is a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This 'scalar' branch, is that this relevant if we raise above for 'reduce' methods? (I don't think any of the non-reduce ufuncs can return a scalar |
||
return np.nan | ||
return x | ||
if is_integer_dtype(x.dtype): | ||
TomAugspurger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
m = mask.copy() | ||
return IntegerArray(x, m) | ||
else: | ||
x[mask] = np.nan | ||
return x | ||
|
||
result = getattr(ufunc, method)(*inputs2, **kwargs) | ||
if isinstance(result, tuple): | ||
tuple(reconstruct(x) for x in result) | ||
else: | ||
return reconstruct(result) | ||
|
||
def __iter__(self): | ||
for i in range(len(self)): | ||
if self._mask[i]: | ||
|
Uh oh!
There was an error while loading. Please reload this page.