-
- Notifications
You must be signed in to change notification settings - Fork 19.4k
ENH: Implement Kleene logic for BooleanArray #29842
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
bb904cb 13c7ea3 fff786f 4067e7f 708c553 c56894e 2e9d547 373aaab 7f78a64 36b171b 747e046 d0a8cca fe061b0 9f9e44c 0a34257 2ba0034 2d1129a a24fc22 77dd1fc 7b9002c c18046b 1237caa 2ecf9b8 87aeb09 969b6dc 1c9ba49 8eec954 cb47b6a 2a946b9 efb6f8b 004238e 5a2c81c 7032318 bbb7f9b ce763b4 5bc5328 457bd08 31c2bc6 File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -562,13 +562,13 @@ def logical_method(self, other): | |
| # Rely on pandas to unbox and dispatch to us. | ||
| return NotImplemented | ||
| | ||
| assert op.__name__ in {"or_", "ror_", "and_", "rand_", "xor", "rxor"} | ||
| other = lib.item_from_zerodim(other) | ||
| omask = mask = None | ||
| other_is_booleanarray = isinstance(other, BooleanArray) | ||
| mask = None | ||
| | ||
| if other_is_booleanarray: | ||
| other, omask = other._data, other._mask | ||
| mask = omask | ||
| other, mask = other._data, other._mask | ||
| elif is_list_like(other): | ||
| other = np.asarray(other, dtype="bool") | ||
| if other.ndim > 1: | ||
| | @@ -579,41 +579,15 @@ def logical_method(self, other): | |
| raise ValueError("Lengths must match to compare") | ||
| other, mask = coerce_to_array(other, copy=False) | ||
| | ||
| # numpy will show a DeprecationWarning on invalid elementwise | ||
| # comparisons, this will raise in the future | ||
| if lib.is_scalar(other) and np.isnan( | ||
| other | ||
| ): # TODO(NA): change to libmissing.NA: | ||
| result = self._data | ||
| mask = True | ||
| else: | ||
| with warnings.catch_warnings(): | ||
| warnings.filterwarnings("ignore", "elementwise", FutureWarning) | ||
| with np.errstate(all="ignore"): | ||
| result = op(self._data, other) | ||
| | ||
| # nans propagate | ||
| if mask is None: | ||
| mask = self._mask | ||
| else: | ||
| mask = self._mask | mask | ||
| | ||
| # Kleene-logic adjustments to the mask. | ||
| if op.__name__ in {"or_", "ror_"}: | ||
| mask[result] = False | ||
| result, mask = kleene_or(self._data, other, self._mask, mask) | ||
| return BooleanArray(result, mask) | ||
TomAugspurger marked this conversation as resolved. Outdated Show resolved Hide resolved | ||
| elif op.__name__ in {"and_", "rand_"}: | ||
| mask[~self._data & ~self._mask] = False | ||
| if other_is_booleanarray: | ||
| mask[~other & ~omask] = False | ||
| elif lib.is_scalar(other) and np.isnan(other): # TODO(NA): change to NA | ||
| mask[:] = True | ||
| # Do we ever assume that masked values are False? | ||
| result[mask] = False | ||
| result, mask = kleene_and(self._data, other, self._mask, mask) | ||
| return BooleanArray(result, mask) | ||
| elif op.__name__ in {"xor", "rxor"}: | ||
| # Do we ever assume that masked values are False? | ||
| result[mask] = False | ||
| | ||
| return BooleanArray(result, mask) | ||
| result, mask = kleene_xor(self._data, other, self._mask, mask) | ||
| return BooleanArray(result, mask) | ||
| | ||
| name = "__{name}__".format(name=op.__name__) | ||
| return set_function_name(logical_method, name, cls) | ||
| | @@ -766,6 +740,91 @@ def boolean_arithmetic_method(self, other): | |
| return set_function_name(boolean_arithmetic_method, name, cls) | ||
| | ||
| | ||
| def kleene_or(left, right, left_mask, right_mask): | ||
| if left_mask is None: | ||
| return kleene_or(right, left, right_mask, left_mask) | ||
| | ||
| assert left_mask is not None | ||
| assert isinstance(left, np.ndarray) | ||
| assert isinstance(left_mask, np.ndarray) | ||
| | ||
| mask = left_mask | ||
| | ||
| if right_mask is not None: | ||
| mask = mask | right_mask | ||
| ||
| else: | ||
| mask = mask.copy() | ||
| | ||
| # handle scalars: | ||
| if lib.is_scalar(right) and np.isnan(right): | ||
| result = left.copy() | ||
| mask = left_mask.copy() | ||
| mask[~result] = True | ||
| return result, mask | ||
| | ||
| # XXX: this implicitly relies on masked values being False! | ||
| result = left | right | ||
| mask[result] = False | ||
TomAugspurger marked this conversation as resolved. Outdated Show resolved Hide resolved | ||
| | ||
| # update | ||
TomAugspurger marked this conversation as resolved. Outdated Show resolved Hide resolved | ||
| return result, mask | ||
| | ||
| | ||
| def kleene_xor(left, right, left_mask, right_mask): | ||
| if left_mask is None: | ||
| return kleene_xor(right, left, right_mask, left_mask) | ||
| | ||
| result, mask = kleene_or(left, right, left_mask, right_mask) | ||
| # | ||
| # if lib.is_scalar(right): | ||
| # if right is True: | ||
| # result[result] = False | ||
| # result[left & right] = False | ||
| | ||
| if lib.is_scalar(right) and right is np.nan: | ||
| mask[result] = True | ||
| else: | ||
| # assumes masked values are False | ||
| result[left & right] = False | ||
| mask[right & left_mask] = True | ||
| if right_mask is not None: | ||
| mask[left & right_mask] = True | ||
| | ||
| result[mask] = False | ||
| return result, mask | ||
| | ||
| | ||
| def kleene_and(left, right, left_mask, right_mask): | ||
| if left_mask is None: | ||
| return kleene_and(right, left, right_mask, left_mask) | ||
| | ||
| mask = left_mask | ||
| | ||
| if right_mask is not None: | ||
| mask = mask | right_mask | ||
| else: | ||
| mask = mask.copy() | ||
| | ||
| if lib.is_scalar(right): | ||
| result = left.copy() | ||
| mask = left_mask.copy() | ||
| if np.isnan(right): | ||
| mask[result] = True | ||
| else: | ||
| result = result & right # already copied. | ||
| if right is False: | ||
| # unmask everything | ||
| mask[:] = False | ||
| else: | ||
| result = left & right | ||
| # unmask where either left or right is False | ||
| mask[~left & ~left_mask] = False | ||
| mask[~right & ~right_mask] = False | ||
| ||
| | ||
| result[mask] = False | ||
| ||
| return result, mask | ||
| | ||
| | ||
| BooleanArray._add_logical_ops() | ||
| BooleanArray._add_comparison_ops() | ||
| BooleanArray._add_arithmetic_ops() | ||
Uh oh!
There was an error while loading. Please reload this page.