@@ -562,13 +562,13 @@ def logical_method(self, other):
562
562
# Rely on pandas to unbox and dispatch to us.
563
563
return NotImplemented
564
564
565
+ assert op .__name__ in {"or_" , "ror_" , "and_" , "rand_" , "xor" , "rxor" }
565
566
other = lib .item_from_zerodim (other )
566
- omask = mask = None
567
567
other_is_booleanarray = isinstance (other , BooleanArray )
568
+ mask = None
568
569
569
570
if other_is_booleanarray :
570
- other , omask = other ._data , other ._mask
571
- mask = omask
571
+ other , mask = other ._data , other ._mask
572
572
elif is_list_like (other ):
573
573
other = np .asarray (other , dtype = "bool" )
574
574
if other .ndim > 1 :
@@ -579,41 +579,15 @@ def logical_method(self, other):
579
579
raise ValueError ("Lengths must match to compare" )
580
580
other , mask = coerce_to_array (other , copy = False )
581
581
582
- # numpy will show a DeprecationWarning on invalid elementwise
583
- # comparisons, this will raise in the future
584
- if lib .is_scalar (other ) and np .isnan (
585
- other
586
- ): # TODO(NA): change to libmissing.NA:
587
- result = self ._data
588
- mask = True
589
- else :
590
- with warnings .catch_warnings ():
591
- warnings .filterwarnings ("ignore" , "elementwise" , FutureWarning )
592
- with np .errstate (all = "ignore" ):
593
- result = op (self ._data , other )
594
-
595
- # nans propagate
596
- if mask is None :
597
- mask = self ._mask
598
- else :
599
- mask = self ._mask | mask
600
-
601
- # Kleene-logic adjustments to the mask.
602
582
if op .__name__ in {"or_" , "ror_" }:
603
- mask [result ] = False
583
+ result , mask = kleene_or (self ._data , other , self ._mask , mask )
584
+ return BooleanArray (result , mask )
604
585
elif op .__name__ in {"and_" , "rand_" }:
605
- mask [~ self ._data & ~ self ._mask ] = False
606
- if other_is_booleanarray :
607
- mask [~ other & ~ omask ] = False
608
- elif lib .is_scalar (other ) and np .isnan (other ): # TODO(NA): change to NA
609
- mask [:] = True
610
- # Do we ever assume that masked values are False?
611
- result [mask ] = False
586
+ result , mask = kleene_and (self ._data , other , self ._mask , mask )
587
+ return BooleanArray (result , mask )
612
588
elif op .__name__ in {"xor" , "rxor" }:
613
- # Do we ever assume that masked values are False?
614
- result [mask ] = False
615
-
616
- return BooleanArray (result , mask )
589
+ result , mask = kleene_xor (self ._data , other , self ._mask , mask )
590
+ return BooleanArray (result , mask )
617
591
618
592
name = "__{name}__" .format (name = op .__name__ )
619
593
return set_function_name (logical_method , name , cls )
@@ -766,6 +740,91 @@ def boolean_arithmetic_method(self, other):
766
740
return set_function_name (boolean_arithmetic_method , name , cls )
767
741
768
742
743
+ def kleene_or (left , right , left_mask , right_mask ):
744
+ if left_mask is None :
745
+ return kleene_or (right , left , right_mask , left_mask )
746
+
747
+ assert left_mask is not None
748
+ assert isinstance (left , np .ndarray )
749
+ assert isinstance (left_mask , np .ndarray )
750
+
751
+ mask = left_mask
752
+
753
+ if right_mask is not None :
754
+ mask = mask | right_mask
755
+ else :
756
+ mask = mask .copy ()
757
+
758
+ # handle scalars:
759
+ if lib .is_scalar (right ) and np .isnan (right ):
760
+ result = left .copy ()
761
+ mask = left_mask .copy ()
762
+ mask [~ result ] = True
763
+ return result , mask
764
+
765
+ # XXX: this implicitly relies on masked values being False!
766
+ result = left | right
767
+ mask [result ] = False
768
+
769
+ # update
770
+ return result , mask
771
+
772
+
773
+ def kleene_xor (left , right , left_mask , right_mask ):
774
+ if left_mask is None :
775
+ return kleene_xor (right , left , right_mask , left_mask )
776
+
777
+ result , mask = kleene_or (left , right , left_mask , right_mask )
778
+ #
779
+ # if lib.is_scalar(right):
780
+ # if right is True:
781
+ # result[result] = False
782
+ # result[left & right] = False
783
+
784
+ if lib .is_scalar (right ) and right is np .nan :
785
+ mask [result ] = True
786
+ else :
787
+ # assumes masked values are False
788
+ result [left & right ] = False
789
+ mask [right & left_mask ] = True
790
+ if right_mask is not None :
791
+ mask [left & right_mask ] = True
792
+
793
+ result [mask ] = False
794
+ return result , mask
795
+
796
+
797
+ def kleene_and (left , right , left_mask , right_mask ):
798
+ if left_mask is None :
799
+ return kleene_and (right , left , right_mask , left_mask )
800
+
801
+ mask = left_mask
802
+
803
+ if right_mask is not None :
804
+ mask = mask | right_mask
805
+ else :
806
+ mask = mask .copy ()
807
+
808
+ if lib .is_scalar (right ):
809
+ result = left .copy ()
810
+ mask = left_mask .copy ()
811
+ if np .isnan (right ):
812
+ mask [result ] = True
813
+ else :
814
+ result = result & right # already copied.
815
+ if right is False :
816
+ # unmask everything
817
+ mask [:] = False
818
+ else :
819
+ result = left & right
820
+ # unmask where either left or right is False
821
+ mask [~ left & ~ left_mask ] = False
822
+ mask [~ right & ~ right_mask ] = False
823
+
824
+ result [mask ] = False
825
+ return result , mask
826
+
827
+
769
828
BooleanArray ._add_logical_ops ()
770
829
BooleanArray ._add_comparison_ops ()
771
830
BooleanArray ._add_arithmetic_ops ()
0 commit comments