1
1
import numbers
2
- from typing import TYPE_CHECKING , Type
2
+ from typing import TYPE_CHECKING , Optional , Type , Union
3
3
import warnings
4
4
5
5
import numpy as np
@@ -565,6 +565,7 @@ def logical_method(self, other):
565
565
assert op .__name__ in {"or_" , "ror_" , "and_" , "rand_" , "xor" , "rxor" }
566
566
other = lib .item_from_zerodim (other )
567
567
other_is_booleanarray = isinstance (other , BooleanArray )
568
+ other_is_scalar = lib .is_scalar (other )
568
569
mask = None
569
570
570
571
if other_is_booleanarray :
@@ -577,7 +578,7 @@ def logical_method(self, other):
577
578
)
578
579
other , mask = coerce_to_array (other , copy = False )
579
580
580
- if not lib . is_scalar ( other ) and len (self ) != len (other ):
581
+ if not other_is_scalar and len (self ) != len (other ):
581
582
raise ValueError ("Lengths must match to compare" )
582
583
583
584
if op .__name__ in {"or_" , "ror_" }:
@@ -741,13 +742,38 @@ def boolean_arithmetic_method(self, other):
741
742
return set_function_name (boolean_arithmetic_method , name , cls )
742
743
743
744
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
745
772
if left_mask is None :
746
773
return kleene_or (right , left , right_mask , left_mask )
747
774
748
775
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
751
777
752
778
mask = left_mask
753
779
@@ -757,35 +783,59 @@ def kleene_or(left, right, left_mask, right_mask):
757
783
mask = mask .copy ()
758
784
759
785
# 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
761
787
result = left .copy ()
762
788
mask = left_mask .copy ()
763
789
mask [~ result ] = True
764
790
return result , mask
765
791
766
- # XXX: this implicitly relies on masked values being False!
792
+ # XXX: verify that this doesn't assume masked values are False!
767
793
result = left | right
768
794
mask [result ] = False
769
795
770
796
# update
771
797
return result , mask
772
798
773
799
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
+ """
775
826
if left_mask is None :
776
827
return kleene_xor (right , left , right_mask , left_mask )
777
828
829
+ # Re-use or, and update with adustments.
778
830
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
784
831
832
+ # TODO(pd.NA): change to pd.NA
785
833
if lib .is_scalar (right ) and right is np .nan :
834
+ # True | NA == True
835
+ # True ^ NA == NA
786
836
mask [result ] = True
787
837
else :
788
- # assumes masked values are False
838
+ # XXX: verify that this doesn't assume masked values are False!
789
839
result [left & right ] = False
790
840
mask [right & left_mask ] = True
791
841
if right_mask is not None :
@@ -795,7 +845,29 @@ def kleene_xor(left, right, left_mask, right_mask):
795
845
return result , mask
796
846
797
847
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
+ """
799
871
if left_mask is None :
800
872
return kleene_and (right , left , right_mask , left_mask )
801
873
0 commit comments