|
34 | 34 |
|
35 | 35 | from pandas.core.dtypes.base import ExtensionDtype
|
36 | 36 | from pandas.core.dtypes.common import (
|
| 37 | + is_bool_dtype, |
37 | 38 | is_dtype_equal,
|
| 39 | + is_float_dtype, |
38 | 40 | is_integer,
|
| 41 | + is_integer_dtype, |
39 | 42 | is_object_dtype,
|
40 | 43 | is_scalar,
|
41 | 44 | is_string_dtype,
|
|
50 | 53 | from pandas.core import (
|
51 | 54 | missing,
|
52 | 55 | nanops,
|
| 56 | + ops, |
53 | 57 | )
|
54 | 58 | from pandas.core.algorithms import (
|
55 | 59 | factorize_array,
|
|
64 | 68 | if TYPE_CHECKING:
|
65 | 69 | from pandas import Series
|
66 | 70 | from pandas.core.arrays import BooleanArray
|
| 71 | + |
67 | 72 | from pandas.compat.numpy import function as nv
|
68 | 73 |
|
69 | 74 | BaseMaskedArrayT = TypeVar("BaseMaskedArrayT", bound="BaseMaskedArray")
|
@@ -360,6 +365,65 @@ def __array__(self, dtype: NpDtype | None = None) -> np.ndarray:
|
360 | 365 | """
|
361 | 366 | return self.to_numpy(dtype=dtype)
|
362 | 367 |
|
| 368 | + _HANDLED_TYPES: tuple[type, ...] |
| 369 | + |
| 370 | + def __array_ufunc__(self, ufunc: np.ufunc, method: str, *inputs, **kwargs): |
| 371 | + # For MaskedArray inputs, we apply the ufunc to ._data |
| 372 | + # and mask the result. |
| 373 | + if method == "reduce": |
| 374 | + # Not clear how to handle missing values in reductions. Raise. |
| 375 | + raise NotImplementedError("The 'reduce' method is not supported.") |
| 376 | + |
| 377 | + out = kwargs.get("out", ()) |
| 378 | + |
| 379 | + for x in inputs + out: |
| 380 | + if not isinstance(x, self._HANDLED_TYPES + (BaseMaskedArray,)): |
| 381 | + return NotImplemented |
| 382 | + |
| 383 | + # for binary ops, use our custom dunder methods |
| 384 | + result = ops.maybe_dispatch_ufunc_to_dunder_op( |
| 385 | + self, ufunc, method, *inputs, **kwargs |
| 386 | + ) |
| 387 | + if result is not NotImplemented: |
| 388 | + return result |
| 389 | + |
| 390 | + mask = np.zeros(len(self), dtype=bool) |
| 391 | + inputs2 = [] |
| 392 | + for x in inputs: |
| 393 | + if isinstance(x, BaseMaskedArray): |
| 394 | + mask |= x._mask |
| 395 | + inputs2.append(x._data) |
| 396 | + else: |
| 397 | + inputs2.append(x) |
| 398 | + |
| 399 | + def reconstruct(x): |
| 400 | + # we don't worry about scalar `x` here, since we |
| 401 | + # raise for reduce up above. |
| 402 | + from pandas.core.arrays import ( |
| 403 | + BooleanArray, |
| 404 | + FloatingArray, |
| 405 | + IntegerArray, |
| 406 | + ) |
| 407 | + |
| 408 | + if is_bool_dtype(x.dtype): |
| 409 | + m = mask.copy() |
| 410 | + return BooleanArray(x, m) |
| 411 | + elif is_integer_dtype(x.dtype): |
| 412 | + m = mask.copy() |
| 413 | + return IntegerArray(x, m) |
| 414 | + elif is_float_dtype(x.dtype): |
| 415 | + m = mask.copy() |
| 416 | + return FloatingArray(x, m) |
| 417 | + else: |
| 418 | + x[mask] = np.nan |
| 419 | + return x |
| 420 | + |
| 421 | + result = getattr(ufunc, method)(*inputs2, **kwargs) |
| 422 | + if isinstance(result, tuple): |
| 423 | + return tuple(reconstruct(x) for x in result) |
| 424 | + else: |
| 425 | + return reconstruct(result) |
| 426 | + |
363 | 427 | def __arrow_array__(self, type=None):
|
364 | 428 | """
|
365 | 429 | Convert myself into a pyarrow Array.
|
|
0 commit comments