Skip to content

Commit 9958b5c

Browse files
jbrockmendelKevin D Smith
authored and
Kevin D Smith
committed
REF: use OpsMixin in EAs (pandas-dev#37049)
1 parent e4f7bc7 commit 9958b5c

File tree

6 files changed

+233
-315
lines changed

6 files changed

+233
-315
lines changed

pandas/core/arrays/boolean.py

+76-99
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from pandas._libs import lib, missing as libmissing
88
from pandas._typing import ArrayLike
9-
from pandas.compat import set_function_name
109
from pandas.compat.numpy import function as nv
1110

1211
from pandas.core.dtypes.common import (
@@ -23,7 +22,6 @@
2322
from pandas.core.dtypes.missing import isna
2423

2524
from pandas.core import ops
26-
from pandas.core.arraylike import OpsMixin
2725

2826
from .masked import BaseMaskedArray, BaseMaskedDtype
2927

@@ -203,7 +201,7 @@ def coerce_to_array(
203201
return values, mask
204202

205203

206-
class BooleanArray(OpsMixin, BaseMaskedArray):
204+
class BooleanArray(BaseMaskedArray):
207205
"""
208206
Array of boolean (True/False) data with missing values.
209207
@@ -561,48 +559,40 @@ def all(self, skipna: bool = True, **kwargs):
561559
else:
562560
return self.dtype.na_value
563561

564-
@classmethod
565-
def _create_logical_method(cls, op):
566-
@ops.unpack_zerodim_and_defer(op.__name__)
567-
def logical_method(self, other):
568-
569-
assert op.__name__ in {"or_", "ror_", "and_", "rand_", "xor", "rxor"}
570-
other_is_booleanarray = isinstance(other, BooleanArray)
571-
other_is_scalar = lib.is_scalar(other)
572-
mask = None
573-
574-
if other_is_booleanarray:
575-
other, mask = other._data, other._mask
576-
elif is_list_like(other):
577-
other = np.asarray(other, dtype="bool")
578-
if other.ndim > 1:
579-
raise NotImplementedError(
580-
"can only perform ops with 1-d structures"
581-
)
582-
other, mask = coerce_to_array(other, copy=False)
583-
elif isinstance(other, np.bool_):
584-
other = other.item()
585-
586-
if other_is_scalar and not (other is libmissing.NA or lib.is_bool(other)):
587-
raise TypeError(
588-
"'other' should be pandas.NA or a bool. "
589-
f"Got {type(other).__name__} instead."
590-
)
591-
592-
if not other_is_scalar and len(self) != len(other):
593-
raise ValueError("Lengths must match to compare")
562+
def _logical_method(self, other, op):
563+
564+
assert op.__name__ in {"or_", "ror_", "and_", "rand_", "xor", "rxor"}
565+
other_is_booleanarray = isinstance(other, BooleanArray)
566+
other_is_scalar = lib.is_scalar(other)
567+
mask = None
568+
569+
if other_is_booleanarray:
570+
other, mask = other._data, other._mask
571+
elif is_list_like(other):
572+
other = np.asarray(other, dtype="bool")
573+
if other.ndim > 1:
574+
raise NotImplementedError("can only perform ops with 1-d structures")
575+
other, mask = coerce_to_array(other, copy=False)
576+
elif isinstance(other, np.bool_):
577+
other = other.item()
594578

595-
if op.__name__ in {"or_", "ror_"}:
596-
result, mask = ops.kleene_or(self._data, other, self._mask, mask)
597-
elif op.__name__ in {"and_", "rand_"}:
598-
result, mask = ops.kleene_and(self._data, other, self._mask, mask)
599-
elif op.__name__ in {"xor", "rxor"}:
600-
result, mask = ops.kleene_xor(self._data, other, self._mask, mask)
579+
if other_is_scalar and not (other is libmissing.NA or lib.is_bool(other)):
580+
raise TypeError(
581+
"'other' should be pandas.NA or a bool. "
582+
f"Got {type(other).__name__} instead."
583+
)
601584

602-
return BooleanArray(result, mask)
585+
if not other_is_scalar and len(self) != len(other):
586+
raise ValueError("Lengths must match to compare")
603587

604-
name = f"__{op.__name__}__"
605-
return set_function_name(logical_method, name, cls)
588+
if op.__name__ in {"or_", "ror_"}:
589+
result, mask = ops.kleene_or(self._data, other, self._mask, mask)
590+
elif op.__name__ in {"and_", "rand_"}:
591+
result, mask = ops.kleene_and(self._data, other, self._mask, mask)
592+
elif op.__name__ in {"xor", "rxor"}:
593+
result, mask = ops.kleene_xor(self._data, other, self._mask, mask)
594+
595+
return BooleanArray(result, mask)
606596

607597
def _cmp_method(self, other, op):
608598
from pandas.arrays import FloatingArray, IntegerArray
@@ -643,6 +633,50 @@ def _cmp_method(self, other, op):
643633

644634
return BooleanArray(result, mask, copy=False)
645635

636+
def _arith_method(self, other, op):
637+
mask = None
638+
op_name = op.__name__
639+
640+
if isinstance(other, BooleanArray):
641+
other, mask = other._data, other._mask
642+
643+
elif is_list_like(other):
644+
other = np.asarray(other)
645+
if other.ndim > 1:
646+
raise NotImplementedError("can only perform ops with 1-d structures")
647+
if len(self) != len(other):
648+
raise ValueError("Lengths must match")
649+
650+
# nans propagate
651+
if mask is None:
652+
mask = self._mask
653+
if other is libmissing.NA:
654+
mask |= True
655+
else:
656+
mask = self._mask | mask
657+
658+
if other is libmissing.NA:
659+
# if other is NA, the result will be all NA and we can't run the
660+
# actual op, so we need to choose the resulting dtype manually
661+
if op_name in {"floordiv", "rfloordiv", "mod", "rmod", "pow", "rpow"}:
662+
dtype = "int8"
663+
else:
664+
dtype = "bool"
665+
result = np.zeros(len(self._data), dtype=dtype)
666+
else:
667+
with np.errstate(all="ignore"):
668+
result = op(self._data, other)
669+
670+
# divmod returns a tuple
671+
if op_name == "divmod":
672+
div, mod = result
673+
return (
674+
self._maybe_mask_result(div, mask, other, "floordiv"),
675+
self._maybe_mask_result(mod, mask, other, "mod"),
676+
)
677+
678+
return self._maybe_mask_result(result, mask, other, op_name)
679+
646680
def _reduce(self, name: str, skipna: bool = True, **kwargs):
647681

648682
if name in {"any", "all"}:
@@ -678,60 +712,3 @@ def _maybe_mask_result(self, result, mask, other, op_name: str):
678712
else:
679713
result[mask] = np.nan
680714
return result
681-
682-
@classmethod
683-
def _create_arithmetic_method(cls, op):
684-
op_name = op.__name__
685-
686-
@ops.unpack_zerodim_and_defer(op_name)
687-
def boolean_arithmetic_method(self, other):
688-
mask = None
689-
690-
if isinstance(other, BooleanArray):
691-
other, mask = other._data, other._mask
692-
693-
elif is_list_like(other):
694-
other = np.asarray(other)
695-
if other.ndim > 1:
696-
raise NotImplementedError(
697-
"can only perform ops with 1-d structures"
698-
)
699-
if len(self) != len(other):
700-
raise ValueError("Lengths must match")
701-
702-
# nans propagate
703-
if mask is None:
704-
mask = self._mask
705-
if other is libmissing.NA:
706-
mask |= True
707-
else:
708-
mask = self._mask | mask
709-
710-
if other is libmissing.NA:
711-
# if other is NA, the result will be all NA and we can't run the
712-
# actual op, so we need to choose the resulting dtype manually
713-
if op_name in {"floordiv", "rfloordiv", "mod", "rmod", "pow", "rpow"}:
714-
dtype = "int8"
715-
else:
716-
dtype = "bool"
717-
result = np.zeros(len(self._data), dtype=dtype)
718-
else:
719-
with np.errstate(all="ignore"):
720-
result = op(self._data, other)
721-
722-
# divmod returns a tuple
723-
if op_name == "divmod":
724-
div, mod = result
725-
return (
726-
self._maybe_mask_result(div, mask, other, "floordiv"),
727-
self._maybe_mask_result(mod, mask, other, "mod"),
728-
)
729-
730-
return self._maybe_mask_result(result, mask, other, op_name)
731-
732-
name = f"__{op_name}__"
733-
return set_function_name(boolean_arithmetic_method, name, cls)
734-
735-
736-
BooleanArray._add_logical_ops()
737-
BooleanArray._add_arithmetic_ops()

pandas/core/arrays/floating.py

+56-72
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from pandas._libs import lib, missing as libmissing
88
from pandas._typing import ArrayLike, DtypeObj
9-
from pandas.compat import set_function_name
109
from pandas.compat.numpy import function as nv
1110
from pandas.util._decorators import cache_readonly
1211

@@ -26,9 +25,7 @@
2625
from pandas.core.dtypes.missing import isna
2726

2827
from pandas.core import ops
29-
from pandas.core.arraylike import OpsMixin
3028
from pandas.core.ops import invalid_comparison
31-
from pandas.core.ops.common import unpack_zerodim_and_defer
3229
from pandas.core.tools.numeric import to_numeric
3330

3431
from .masked import BaseMaskedArray, BaseMaskedDtype
@@ -202,7 +199,7 @@ def coerce_to_array(
202199
return values, mask
203200

204201

205-
class FloatingArray(OpsMixin, BaseMaskedArray):
202+
class FloatingArray(BaseMaskedArray):
206203
"""
207204
Array of floating (optional missing) values.
208205
@@ -479,83 +476,70 @@ def _maybe_mask_result(self, result, mask, other, op_name: str):
479476

480477
return type(self)(result, mask, copy=False)
481478

482-
@classmethod
483-
def _create_arithmetic_method(cls, op):
484-
op_name = op.__name__
485-
486-
@unpack_zerodim_and_defer(op.__name__)
487-
def floating_arithmetic_method(self, other):
488-
from pandas.arrays import IntegerArray
489-
490-
omask = None
479+
def _arith_method(self, other, op):
480+
from pandas.arrays import IntegerArray
491481

492-
if getattr(other, "ndim", 0) > 1:
493-
raise NotImplementedError("can only perform ops with 1-d structures")
482+
omask = None
494483

495-
if isinstance(other, (IntegerArray, FloatingArray)):
496-
other, omask = other._data, other._mask
484+
if getattr(other, "ndim", 0) > 1:
485+
raise NotImplementedError("can only perform ops with 1-d structures")
497486

498-
elif is_list_like(other):
499-
other = np.asarray(other)
500-
if other.ndim > 1:
501-
raise NotImplementedError(
502-
"can only perform ops with 1-d structures"
503-
)
504-
if len(self) != len(other):
505-
raise ValueError("Lengths must match")
506-
if not (is_float_dtype(other) or is_integer_dtype(other)):
507-
raise TypeError("can only perform ops with numeric values")
487+
if isinstance(other, (IntegerArray, FloatingArray)):
488+
other, omask = other._data, other._mask
508489

509-
else:
510-
if not (is_float(other) or is_integer(other) or other is libmissing.NA):
511-
raise TypeError("can only perform ops with numeric values")
490+
elif is_list_like(other):
491+
other = np.asarray(other)
492+
if other.ndim > 1:
493+
raise NotImplementedError("can only perform ops with 1-d structures")
494+
if len(self) != len(other):
495+
raise ValueError("Lengths must match")
496+
if not (is_float_dtype(other) or is_integer_dtype(other)):
497+
raise TypeError("can only perform ops with numeric values")
512498

513-
if omask is None:
514-
mask = self._mask.copy()
515-
if other is libmissing.NA:
516-
mask |= True
517-
else:
518-
mask = self._mask | omask
519-
520-
if op_name == "pow":
521-
# 1 ** x is 1.
522-
mask = np.where((self._data == 1) & ~self._mask, False, mask)
523-
# x ** 0 is 1.
524-
if omask is not None:
525-
mask = np.where((other == 0) & ~omask, False, mask)
526-
elif other is not libmissing.NA:
527-
mask = np.where(other == 0, False, mask)
528-
529-
elif op_name == "rpow":
530-
# 1 ** x is 1.
531-
if omask is not None:
532-
mask = np.where((other == 1) & ~omask, False, mask)
533-
elif other is not libmissing.NA:
534-
mask = np.where(other == 1, False, mask)
535-
# x ** 0 is 1.
536-
mask = np.where((self._data == 0) & ~self._mask, False, mask)
499+
else:
500+
if not (is_float(other) or is_integer(other) or other is libmissing.NA):
501+
raise TypeError("can only perform ops with numeric values")
537502

503+
if omask is None:
504+
mask = self._mask.copy()
538505
if other is libmissing.NA:
539-
result = np.ones_like(self._data)
540-
else:
541-
with np.errstate(all="ignore"):
542-
result = op(self._data, other)
543-
544-
# divmod returns a tuple
545-
if op_name == "divmod":
546-
div, mod = result
547-
return (
548-
self._maybe_mask_result(div, mask, other, "floordiv"),
549-
self._maybe_mask_result(mod, mask, other, "mod"),
550-
)
551-
552-
return self._maybe_mask_result(result, mask, other, op_name)
553-
554-
name = f"__{op.__name__}__"
555-
return set_function_name(floating_arithmetic_method, name, cls)
506+
mask |= True
507+
else:
508+
mask = self._mask | omask
509+
510+
if op.__name__ == "pow":
511+
# 1 ** x is 1.
512+
mask = np.where((self._data == 1) & ~self._mask, False, mask)
513+
# x ** 0 is 1.
514+
if omask is not None:
515+
mask = np.where((other == 0) & ~omask, False, mask)
516+
elif other is not libmissing.NA:
517+
mask = np.where(other == 0, False, mask)
518+
519+
elif op.__name__ == "rpow":
520+
# 1 ** x is 1.
521+
if omask is not None:
522+
mask = np.where((other == 1) & ~omask, False, mask)
523+
elif other is not libmissing.NA:
524+
mask = np.where(other == 1, False, mask)
525+
# x ** 0 is 1.
526+
mask = np.where((self._data == 0) & ~self._mask, False, mask)
556527

528+
if other is libmissing.NA:
529+
result = np.ones_like(self._data)
530+
else:
531+
with np.errstate(all="ignore"):
532+
result = op(self._data, other)
533+
534+
# divmod returns a tuple
535+
if op.__name__ == "divmod":
536+
div, mod = result
537+
return (
538+
self._maybe_mask_result(div, mask, other, "floordiv"),
539+
self._maybe_mask_result(mod, mask, other, "mod"),
540+
)
557541

558-
FloatingArray._add_arithmetic_ops()
542+
return self._maybe_mask_result(result, mask, other, op.__name__)
559543

560544

561545
_dtype_docstring = """

0 commit comments

Comments
 (0)