|
6 | 6 |
|
7 | 7 | from pandas._libs import lib, missing as libmissing
|
8 | 8 | from pandas._typing import ArrayLike
|
9 |
| -from pandas.compat import set_function_name |
10 | 9 | from pandas.compat.numpy import function as nv
|
11 | 10 |
|
12 | 11 | from pandas.core.dtypes.common import (
|
|
23 | 22 | from pandas.core.dtypes.missing import isna
|
24 | 23 |
|
25 | 24 | from pandas.core import ops
|
26 |
| -from pandas.core.arraylike import OpsMixin |
27 | 25 |
|
28 | 26 | from .masked import BaseMaskedArray, BaseMaskedDtype
|
29 | 27 |
|
@@ -203,7 +201,7 @@ def coerce_to_array(
|
203 | 201 | return values, mask
|
204 | 202 |
|
205 | 203 |
|
206 |
| -class BooleanArray(OpsMixin, BaseMaskedArray): |
| 204 | +class BooleanArray(BaseMaskedArray): |
207 | 205 | """
|
208 | 206 | Array of boolean (True/False) data with missing values.
|
209 | 207 |
|
@@ -561,48 +559,40 @@ def all(self, skipna: bool = True, **kwargs):
|
561 | 559 | else:
|
562 | 560 | return self.dtype.na_value
|
563 | 561 |
|
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() |
594 | 578 |
|
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 | + ) |
601 | 584 |
|
602 |
| - return BooleanArray(result, mask) |
| 585 | + if not other_is_scalar and len(self) != len(other): |
| 586 | + raise ValueError("Lengths must match to compare") |
603 | 587 |
|
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) |
606 | 596 |
|
607 | 597 | def _cmp_method(self, other, op):
|
608 | 598 | from pandas.arrays import FloatingArray, IntegerArray
|
@@ -643,6 +633,50 @@ def _cmp_method(self, other, op):
|
643 | 633 |
|
644 | 634 | return BooleanArray(result, mask, copy=False)
|
645 | 635 |
|
| 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 | + |
646 | 680 | def _reduce(self, name: str, skipna: bool = True, **kwargs):
|
647 | 681 |
|
648 | 682 | if name in {"any", "all"}:
|
@@ -678,60 +712,3 @@ def _maybe_mask_result(self, result, mask, other, op_name: str):
|
678 | 712 | else:
|
679 | 713 | result[mask] = np.nan
|
680 | 714 | 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() |
0 commit comments