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

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

63 statements  

1""" 

2Ops for masked arrays. 

3""" 

4 

5from __future__ import annotations 

6 

7from typing import TYPE_CHECKING 

8 

9import numpy as np 

10 

11from pandas._libs import ( 

12 lib, 

13 missing as libmissing, 

14) 

15 

16if TYPE_CHECKING: 

17 from pandas._typing import npt 

18 

19 

20def kleene_or( 

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

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

23 left_mask: np.ndarray | None, 

24 right_mask: np.ndarray | None, 

25) -> tuple[npt.NDArray[np.bool_], npt.NDArray[np.bool_]]: 

26 """ 

27 Boolean ``or`` using Kleene logic. 

28 

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

30 ``NA | True`` is considered True. 

31 

32 Parameters 

33 ---------- 

34 left, right : ndarray, NA, or bool 

35 The values of the array. 

36 left_mask, right_mask : ndarray, optional 

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

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

39 

40 Returns 

41 ------- 

42 result, mask: ndarray[bool] 

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

44 """ 

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

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

47 # A | B == B | A 

48 if left_mask is None: 

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

50 

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

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

53 

54 raise_for_nan(right, method="or") 

55 

56 if right is libmissing.NA: 

57 result = left.copy() 

58 else: 

59 result = left | right 

60 

61 if right_mask is not None: 

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

63 left_false = ~(left | left_mask) 

64 right_false = ~(right | right_mask) 

65 mask = ( 

66 (left_false & right_mask) 

67 | (right_false & left_mask) 

68 | (left_mask & right_mask) 

69 ) 

70 elif right is True: 

71 mask = np.zeros_like(left_mask) 

72 elif right is libmissing.NA: 

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

74 else: 

75 # False 

76 mask = left_mask.copy() 

77 

78 return result, mask 

79 

80 

81def kleene_xor( 

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

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

84 left_mask: np.ndarray | None, 

85 right_mask: np.ndarray | None, 

86) -> tuple[npt.NDArray[np.bool_], npt.NDArray[np.bool_]]: 

87 """ 

88 Boolean ``xor`` using Kleene logic. 

89 

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

91 

92 * True, True -> False 

93 * True, NA -> NA 

94 

95 Parameters 

96 ---------- 

97 left, right : ndarray, NA, or bool 

98 The values of the array. 

99 left_mask, right_mask : ndarray, optional 

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

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

102 

103 Returns 

104 ------- 

105 result, mask: ndarray[bool] 

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

107 """ 

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

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

110 # A ^ B == B ^ A 

111 if left_mask is None: 

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

113 

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

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

116 

117 raise_for_nan(right, method="xor") 

118 if right is libmissing.NA: 

119 result = np.zeros_like(left) 

120 else: 

121 result = left ^ right 

122 

123 if right_mask is None: 

124 if right is libmissing.NA: 

125 mask = np.ones_like(left_mask) 

126 else: 

127 mask = left_mask.copy() 

128 else: 

129 mask = left_mask | right_mask 

130 

131 return result, mask 

132 

133 

134def kleene_and( 

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

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

137 left_mask: np.ndarray | None, 

138 right_mask: np.ndarray | None, 

139) -> tuple[npt.NDArray[np.bool_], npt.NDArray[np.bool_]]: 

140 """ 

141 Boolean ``and`` using Kleene logic. 

142 

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

144 

145 Parameters 

146 ---------- 

147 left, right : ndarray, NA, or bool 

148 The values of the array. 

149 left_mask, right_mask : ndarray, optional 

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

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

152 

153 Returns 

154 ------- 

155 result, mask: ndarray[bool] 

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

157 """ 

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

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

160 # A & B == B & A 

161 if left_mask is None: 

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

163 

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

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

166 raise_for_nan(right, method="and") 

167 

168 if right is libmissing.NA: 

169 result = np.zeros_like(left) 

170 else: 

171 result = left & right 

172 

173 if right_mask is None: 

174 # Scalar `right` 

175 if right is libmissing.NA: 

176 mask = (left & ~left_mask) | left_mask 

177 

178 else: 

179 mask = left_mask.copy() 

180 if right is False: 

181 # unmask everything 

182 mask[:] = False 

183 else: 

184 # unmask where either left or right is False 

185 left_false = ~(left | left_mask) 

186 right_false = ~(right | right_mask) 

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

188 

189 return result, mask 

190 

191 

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

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

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