11import numbers
2- from typing import TYPE_CHECKING , Type
2+ from typing import TYPE_CHECKING , Optional , Type , Union
33import warnings
44
55import 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