Skip to content

Commit 373aaab

Browse files
committed
updates
1 parent 2e9d547 commit 373aaab

File tree

1 file changed

+87
-15
lines changed

1 file changed

+87
-15
lines changed

pandas/core/arrays/boolean.py

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import numbers
2-
from typing import TYPE_CHECKING, Type
2+
from typing import TYPE_CHECKING, Optional, Type, Union
33
import warnings
44

55
import numpy as np
@@ -565,6 +565,7 @@ def logical_method(self, other):
565565
assert op.__name__ in {"or_", "ror_", "and_", "rand_", "xor", "rxor"}
566566
other = lib.item_from_zerodim(other)
567567
other_is_booleanarray = isinstance(other, BooleanArray)
568+
other_is_scalar = lib.is_scalar(other)
568569
mask = None
569570

570571
if other_is_booleanarray:
@@ -577,7 +578,7 @@ def logical_method(self, other):
577578
)
578579
other, mask = coerce_to_array(other, copy=False)
579580

580-
if not lib.is_scalar(other) and len(self) != len(other):
581+
if not other_is_scalar and len(self) != len(other):
581582
raise ValueError("Lengths must match to compare")
582583

583584
if op.__name__ in {"or_", "ror_"}:
@@ -741,13 +742,38 @@ def boolean_arithmetic_method(self, other):
741742
return set_function_name(boolean_arithmetic_method, name, cls)
742743

743744

744-
def kleene_or(left, right, left_mask, right_mask):
745+
def kleene_or(
746+
left: Union[bool, np.nan, np.ndarray],
747+
right: Union[bool, np.nan, np.ndarary],
748+
left_mask: Optional[np.ndarary],
749+
right_mask: Optional[np.ndarray],
750+
):
751+
"""
752+
Boolean ``or`` using Kleene logic.
753+
754+
Values are NA where we have ``NA | NA`` or ``NA | False``.
755+
``NA | True`` is considered True.
756+
757+
Parameters
758+
----------
759+
left, right : ndarray, NA, or bool
760+
The values of the array.
761+
left_mask, right_mask : ndarray, optional
762+
The masks. When
763+
764+
Returns
765+
-------
766+
result, mask: ndarray[bool]
767+
The result of the logical or, and the new mask.
768+
"""
769+
# To reduce the number of cases, we ensure that `left` & `left_mask`
770+
# always come from an array, not a scalar. This is safe, since because
771+
# A | B == B | A
745772
if left_mask is None:
746773
return kleene_or(right, left, right_mask, left_mask)
747774

748775
assert left_mask is not None
749-
assert isinstance(left, np.ndarray)
750-
assert isinstance(left_mask, np.ndarray)
776+
right_is_scalar = right_mask is None
751777

752778
mask = left_mask
753779

@@ -757,35 +783,59 @@ def kleene_or(left, right, left_mask, right_mask):
757783
mask = mask.copy()
758784

759785
# handle scalars:
760-
if lib.is_scalar(right) and np.isnan(right):
786+
if right_is_scalar and np.isnan(right): # TODO(pd.NA): change to NA
761787
result = left.copy()
762788
mask = left_mask.copy()
763789
mask[~result] = True
764790
return result, mask
765791

766-
# XXX: this implicitly relies on masked values being False!
792+
# XXX: verify that this doesn't assume masked values are False!
767793
result = left | right
768794
mask[result] = False
769795

770796
# update
771797
return result, mask
772798

773799

774-
def kleene_xor(left, right, left_mask, right_mask):
800+
def kleene_xor(
801+
left: Union[bool, np.nan, np.ndarray],
802+
right: Union[bool, np.nan, np.ndarary],
803+
left_mask: Optional[np.ndarary],
804+
right_mask: Optional[np.ndarray],
805+
):
806+
"""
807+
Boolean ``xor`` using Kleene logic.
808+
809+
This is the same as ``or``, with the following adjustments
810+
811+
* True, True -> False
812+
* True, NA -> NA
813+
814+
Parameters
815+
----------
816+
left, right : ndarray, NA, or bool
817+
The values of the array.
818+
left_mask, right_mask : ndarray, optional
819+
The masks. When
820+
821+
Returns
822+
-------
823+
result, mask: ndarray[bool]
824+
The result of the logical xor, and the new mask.
825+
"""
775826
if left_mask is None:
776827
return kleene_xor(right, left, right_mask, left_mask)
777828

829+
# Re-use or, and update with adustments.
778830
result, mask = kleene_or(left, right, left_mask, right_mask)
779-
#
780-
# if lib.is_scalar(right):
781-
# if right is True:
782-
# result[result] = False
783-
# result[left & right] = False
784831

832+
# TODO(pd.NA): change to pd.NA
785833
if lib.is_scalar(right) and right is np.nan:
834+
# True | NA == True
835+
# True ^ NA == NA
786836
mask[result] = True
787837
else:
788-
# assumes masked values are False
838+
# XXX: verify that this doesn't assume masked values are False!
789839
result[left & right] = False
790840
mask[right & left_mask] = True
791841
if right_mask is not None:
@@ -795,7 +845,29 @@ def kleene_xor(left, right, left_mask, right_mask):
795845
return result, mask
796846

797847

798-
def kleene_and(left, right, left_mask, right_mask):
848+
def kleene_and(
849+
left: Union[bool, np.nan, np.ndarray],
850+
right: Union[bool, np.nan, np.ndarary],
851+
left_mask: Optional[np.ndarary],
852+
right_mask: Optional[np.ndarray],
853+
):
854+
"""
855+
Boolean ``and`` using Kleene logic.
856+
857+
Values are ``NA`` for ``NA & NA`` or ``True & NA``.
858+
859+
Parameters
860+
----------
861+
left, right : ndarray, NA, or bool
862+
The values of the array.
863+
left_mask, right_mask : ndarray, optional
864+
The masks. When
865+
866+
Returns
867+
-------
868+
result, mask: ndarray[bool]
869+
The result of the logical xor, and the new mask.
870+
"""
799871
if left_mask is None:
800872
return kleene_and(right, left, right_mask, left_mask)
801873

0 commit comments

Comments
 (0)