Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2Ops for masked arrays. 

3""" 

4from typing import Optional, Union 

5 

6import numpy as np 

7 

8from pandas._libs import lib, missing as libmissing 

9 

10 

11def kleene_or( 

12 left: Union[bool, np.ndarray], 

13 right: Union[bool, np.ndarray], 

14 left_mask: Optional[np.ndarray], 

15 right_mask: Optional[np.ndarray], 

16): 

17 """ 

18 Boolean ``or`` using Kleene logic. 

19 

20 Values are NA where we have ``NA | NA`` or ``NA | False``. 

21 ``NA | True`` is considered True. 

22 

23 Parameters 

24 ---------- 

25 left, right : ndarray, NA, or bool 

26 The values of the array. 

27 left_mask, right_mask : ndarray, optional 

28 The masks. Only one of these may be None, which implies that 

29 the associated `left` or `right` value is a scalar. 

30 

31 Returns 

32 ------- 

33 result, mask: ndarray[bool] 

34 The result of the logical or, and the new mask. 

35 """ 

36 # To reduce the number of cases, we ensure that `left` & `left_mask` 

37 # always come from an array, not a scalar. This is safe, since because 

38 # A | B == B | A 

39 if left_mask is None: 

40 return kleene_or(right, left, right_mask, left_mask) 

41 

42 assert isinstance(left, np.ndarray) 

43 

44 raise_for_nan(right, method="or") 

45 

46 if right is libmissing.NA: 

47 result = left.copy() 

48 else: 

49 result = left | right 

50 

51 if right_mask is not None: 

52 # output is unknown where (False & NA), (NA & False), (NA & NA) 

53 left_false = ~(left | left_mask) 

54 right_false = ~(right | right_mask) 

55 mask = ( 

56 (left_false & right_mask) 

57 | (right_false & left_mask) 

58 | (left_mask & right_mask) 

59 ) 

60 else: 

61 if right is True: 

62 mask = np.zeros_like(left_mask) 

63 elif right is libmissing.NA: 

64 mask = (~left & ~left_mask) | left_mask 

65 else: 

66 # False 

67 mask = left_mask.copy() 

68 

69 return result, mask 

70 

71 

72def kleene_xor( 

73 left: Union[bool, np.ndarray], 

74 right: Union[bool, np.ndarray], 

75 left_mask: Optional[np.ndarray], 

76 right_mask: Optional[np.ndarray], 

77): 

78 """ 

79 Boolean ``xor`` using Kleene logic. 

80 

81 This is the same as ``or``, with the following adjustments 

82 

83 * True, True -> False 

84 * True, NA -> NA 

85 

86 Parameters 

87 ---------- 

88 left, right : ndarray, NA, or bool 

89 The values of the array. 

90 left_mask, right_mask : ndarray, optional 

91 The masks. Only one of these may be None, which implies that 

92 the associated `left` or `right` value is a scalar. 

93 

94 Returns 

95 ------- 

96 result, mask: ndarray[bool] 

97 The result of the logical xor, and the new mask. 

98 """ 

99 if left_mask is None: 

100 return kleene_xor(right, left, right_mask, left_mask) 

101 

102 raise_for_nan(right, method="xor") 

103 if right is libmissing.NA: 

104 result = np.zeros_like(left) 

105 else: 

106 result = left ^ right 

107 

108 if right_mask is None: 

109 if right is libmissing.NA: 

110 mask = np.ones_like(left_mask) 

111 else: 

112 mask = left_mask.copy() 

113 else: 

114 mask = left_mask | right_mask 

115 

116 return result, mask 

117 

118 

119def kleene_and( 

120 left: Union[bool, libmissing.NAType, np.ndarray], 

121 right: Union[bool, libmissing.NAType, np.ndarray], 

122 left_mask: Optional[np.ndarray], 

123 right_mask: Optional[np.ndarray], 

124): 

125 """ 

126 Boolean ``and`` using Kleene logic. 

127 

128 Values are ``NA`` for ``NA & NA`` or ``True & NA``. 

129 

130 Parameters 

131 ---------- 

132 left, right : ndarray, NA, or bool 

133 The values of the array. 

134 left_mask, right_mask : ndarray, optional 

135 The masks. Only one of these may be None, which implies that 

136 the associated `left` or `right` value is a scalar. 

137 

138 Returns 

139 ------- 

140 result, mask: ndarray[bool] 

141 The result of the logical xor, and the new mask. 

142 """ 

143 # To reduce the number of cases, we ensure that `left` & `left_mask` 

144 # always come from an array, not a scalar. This is safe, since because 

145 # A | B == B | A 

146 if left_mask is None: 

147 return kleene_and(right, left, right_mask, left_mask) 

148 

149 assert isinstance(left, np.ndarray) 

150 raise_for_nan(right, method="and") 

151 

152 if right is libmissing.NA: 

153 result = np.zeros_like(left) 

154 else: 

155 result = left & right 

156 

157 if right_mask is None: 

158 # Scalar `right` 

159 if right is libmissing.NA: 

160 mask = (left & ~left_mask) | left_mask 

161 

162 else: 

163 mask = left_mask.copy() 

164 if right is False: 

165 # unmask everything 

166 mask[:] = False 

167 else: 

168 # unmask where either left or right is False 

169 left_false = ~(left | left_mask) 

170 right_false = ~(right | right_mask) 

171 mask = (left_mask & ~right_false) | (right_mask & ~left_false) 

172 

173 return result, mask 

174 

175 

176def raise_for_nan(value, method): 

177 if lib.is_float(value) and np.isnan(value): 

178 raise ValueError(f"Cannot perform logical '{method}' with floating NaN")