Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pandas/core/ops/mask_ops.py : 13%

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
6import numpy as np
8from pandas._libs import lib, missing as libmissing
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.
20 Values are NA where we have ``NA | NA`` or ``NA | False``.
21 ``NA | True`` is considered True.
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.
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)
42 assert isinstance(left, np.ndarray)
44 raise_for_nan(right, method="or")
46 if right is libmissing.NA:
47 result = left.copy()
48 else:
49 result = left | right
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()
69 return result, mask
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.
81 This is the same as ``or``, with the following adjustments
83 * True, True -> False
84 * True, NA -> NA
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.
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)
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
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
116 return result, mask
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.
128 Values are ``NA`` for ``NA & NA`` or ``True & NA``.
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.
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)
149 assert isinstance(left, np.ndarray)
150 raise_for_nan(right, method="and")
152 if right is libmissing.NA:
153 result = np.zeros_like(left)
154 else:
155 result = left & right
157 if right_mask is None:
158 # Scalar `right`
159 if right is libmissing.NA:
160 mask = (left & ~left_mask) | left_mask
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)
173 return result, mask
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")