Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pandas/core/ops/mask_ops.py: 12%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

59 statements  

1""" 

2Ops for masked arrays. 

3""" 

4from __future__ import annotations 

5 

6import numpy as np 

7 

8from pandas._libs import ( 

9 lib, 

10 missing as libmissing, 

11) 

12 

13 

14def kleene_or( 

15 left: bool | np.ndarray | libmissing.NAType, 

16 right: bool | np.ndarray | libmissing.NAType, 

17 left_mask: np.ndarray | None, 

18 right_mask: np.ndarray | None, 

19): 

20 """ 

21 Boolean ``or`` using Kleene logic. 

22 

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

24 ``NA | True`` is considered True. 

25 

26 Parameters 

27 ---------- 

28 left, right : ndarray, NA, or bool 

29 The values of the array. 

30 left_mask, right_mask : ndarray, optional 

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

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

33 

34 Returns 

35 ------- 

36 result, mask: ndarray[bool] 

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

38 """ 

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

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

41 # A | B == B | A 

42 if left_mask is None: 

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

44 

45 if not isinstance(left, np.ndarray): 

46 raise TypeError("Either `left` or `right` need to be a np.ndarray.") 

47 

48 raise_for_nan(right, method="or") 

49 

50 if right is libmissing.NA: 

51 result = left.copy() 

52 else: 

53 result = left | right 

54 

55 if right_mask is not None: 

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

57 left_false = ~(left | left_mask) 

58 right_false = ~(right | right_mask) 

59 mask = ( 

60 (left_false & right_mask) 

61 | (right_false & left_mask) 

62 | (left_mask & right_mask) 

63 ) 

64 else: 

65 if right is True: 

66 mask = np.zeros_like(left_mask) 

67 elif right is libmissing.NA: 

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

69 else: 

70 # False 

71 mask = left_mask.copy() 

72 

73 return result, mask 

74 

75 

76def kleene_xor( 

77 left: bool | np.ndarray | libmissing.NAType, 

78 right: bool | np.ndarray | libmissing.NAType, 

79 left_mask: np.ndarray | None, 

80 right_mask: np.ndarray | None, 

81): 

82 """ 

83 Boolean ``xor`` using Kleene logic. 

84 

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

86 

87 * True, True -> False 

88 * True, NA -> NA 

89 

90 Parameters 

91 ---------- 

92 left, right : ndarray, NA, or bool 

93 The values of the array. 

94 left_mask, right_mask : ndarray, optional 

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

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

97 

98 Returns 

99 ------- 

100 result, mask: ndarray[bool] 

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

102 """ 

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

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

105 # A ^ B == B ^ A 

106 if left_mask is None: 

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

108 

109 if not isinstance(left, np.ndarray): 

110 raise TypeError("Either `left` or `right` need to be a np.ndarray.") 

111 

112 raise_for_nan(right, method="xor") 

113 if right is libmissing.NA: 

114 result = np.zeros_like(left) 

115 else: 

116 result = left ^ right 

117 

118 if right_mask is None: 

119 if right is libmissing.NA: 

120 mask = np.ones_like(left_mask) 

121 else: 

122 mask = left_mask.copy() 

123 else: 

124 mask = left_mask | right_mask 

125 

126 return result, mask 

127 

128 

129def kleene_and( 

130 left: bool | libmissing.NAType | np.ndarray, 

131 right: bool | libmissing.NAType | np.ndarray, 

132 left_mask: np.ndarray | None, 

133 right_mask: np.ndarray | None, 

134): 

135 """ 

136 Boolean ``and`` using Kleene logic. 

137 

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

139 

140 Parameters 

141 ---------- 

142 left, right : ndarray, NA, or bool 

143 The values of the array. 

144 left_mask, right_mask : ndarray, optional 

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

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

147 

148 Returns 

149 ------- 

150 result, mask: ndarray[bool] 

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

152 """ 

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

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

155 # A & B == B & A 

156 if left_mask is None: 

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

158 

159 if not isinstance(left, np.ndarray): 

160 raise TypeError("Either `left` or `right` need to be a np.ndarray.") 

161 raise_for_nan(right, method="and") 

162 

163 if right is libmissing.NA: 

164 result = np.zeros_like(left) 

165 else: 

166 result = left & right 

167 

168 if right_mask is None: 

169 # Scalar `right` 

170 if right is libmissing.NA: 

171 mask = (left & ~left_mask) | left_mask 

172 

173 else: 

174 mask = left_mask.copy() 

175 if right is False: 

176 # unmask everything 

177 mask[:] = False 

178 else: 

179 # unmask where either left or right is False 

180 left_false = ~(left | left_mask) 

181 right_false = ~(right | right_mask) 

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

183 

184 return result, mask 

185 

186 

187def raise_for_nan(value, method: str) -> None: 

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

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