test_logical.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. import operator
  2. import numpy as np
  3. import pytest
  4. import pandas as pd
  5. import pandas._testing as tm
  6. from pandas.arrays import BooleanArray
  7. from pandas.core.ops.mask_ops import (
  8. kleene_and,
  9. kleene_or,
  10. kleene_xor,
  11. )
  12. from pandas.tests.extension.base import BaseOpsUtil
  13. class TestLogicalOps(BaseOpsUtil):
  14. def test_numpy_scalars_ok(self, all_logical_operators):
  15. a = pd.array([True, False, None], dtype="boolean")
  16. op = getattr(a, all_logical_operators)
  17. tm.assert_extension_array_equal(op(True), op(np.bool_(True)))
  18. tm.assert_extension_array_equal(op(False), op(np.bool_(False)))
  19. def get_op_from_name(self, op_name):
  20. short_opname = op_name.strip("_")
  21. short_opname = short_opname if "xor" in short_opname else short_opname + "_"
  22. try:
  23. op = getattr(operator, short_opname)
  24. except AttributeError:
  25. # Assume it is the reverse operator
  26. rop = getattr(operator, short_opname[1:])
  27. op = lambda x, y: rop(y, x)
  28. return op
  29. def test_empty_ok(self, all_logical_operators):
  30. a = pd.array([], dtype="boolean")
  31. op_name = all_logical_operators
  32. result = getattr(a, op_name)(True)
  33. tm.assert_extension_array_equal(a, result)
  34. result = getattr(a, op_name)(False)
  35. tm.assert_extension_array_equal(a, result)
  36. result = getattr(a, op_name)(pd.NA)
  37. tm.assert_extension_array_equal(a, result)
  38. @pytest.mark.parametrize(
  39. "other", ["a", pd.Timestamp(2017, 1, 1, 12), np.timedelta64(4)]
  40. )
  41. def test_eq_mismatched_type(self, other):
  42. # GH-44499
  43. arr = pd.array([True, False])
  44. result = arr == other
  45. expected = pd.array([False, False])
  46. tm.assert_extension_array_equal(result, expected)
  47. result = arr != other
  48. expected = pd.array([True, True])
  49. tm.assert_extension_array_equal(result, expected)
  50. def test_logical_length_mismatch_raises(self, all_logical_operators):
  51. op_name = all_logical_operators
  52. a = pd.array([True, False, None], dtype="boolean")
  53. msg = "Lengths must match"
  54. with pytest.raises(ValueError, match=msg):
  55. getattr(a, op_name)([True, False])
  56. with pytest.raises(ValueError, match=msg):
  57. getattr(a, op_name)(np.array([True, False]))
  58. with pytest.raises(ValueError, match=msg):
  59. getattr(a, op_name)(pd.array([True, False], dtype="boolean"))
  60. def test_logical_nan_raises(self, all_logical_operators):
  61. op_name = all_logical_operators
  62. a = pd.array([True, False, None], dtype="boolean")
  63. msg = "Got float instead"
  64. with pytest.raises(TypeError, match=msg):
  65. getattr(a, op_name)(np.nan)
  66. @pytest.mark.parametrize("other", ["a", 1])
  67. def test_non_bool_or_na_other_raises(self, other, all_logical_operators):
  68. a = pd.array([True, False], dtype="boolean")
  69. with pytest.raises(TypeError, match=str(type(other).__name__)):
  70. getattr(a, all_logical_operators)(other)
  71. def test_kleene_or(self):
  72. # A clear test of behavior.
  73. a = pd.array([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean")
  74. b = pd.array([True, False, None] * 3, dtype="boolean")
  75. result = a | b
  76. expected = pd.array(
  77. [True, True, True, True, False, None, True, None, None], dtype="boolean"
  78. )
  79. tm.assert_extension_array_equal(result, expected)
  80. result = b | a
  81. tm.assert_extension_array_equal(result, expected)
  82. # ensure we haven't mutated anything inplace
  83. tm.assert_extension_array_equal(
  84. a, pd.array([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean")
  85. )
  86. tm.assert_extension_array_equal(
  87. b, pd.array([True, False, None] * 3, dtype="boolean")
  88. )
  89. @pytest.mark.parametrize(
  90. "other, expected",
  91. [
  92. (pd.NA, [True, None, None]),
  93. (True, [True, True, True]),
  94. (np.bool_(True), [True, True, True]),
  95. (False, [True, False, None]),
  96. (np.bool_(False), [True, False, None]),
  97. ],
  98. )
  99. def test_kleene_or_scalar(self, other, expected):
  100. # TODO: test True & False
  101. a = pd.array([True, False, None], dtype="boolean")
  102. result = a | other
  103. expected = pd.array(expected, dtype="boolean")
  104. tm.assert_extension_array_equal(result, expected)
  105. result = other | a
  106. tm.assert_extension_array_equal(result, expected)
  107. # ensure we haven't mutated anything inplace
  108. tm.assert_extension_array_equal(
  109. a, pd.array([True, False, None], dtype="boolean")
  110. )
  111. def test_kleene_and(self):
  112. # A clear test of behavior.
  113. a = pd.array([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean")
  114. b = pd.array([True, False, None] * 3, dtype="boolean")
  115. result = a & b
  116. expected = pd.array(
  117. [True, False, None, False, False, False, None, False, None], dtype="boolean"
  118. )
  119. tm.assert_extension_array_equal(result, expected)
  120. result = b & a
  121. tm.assert_extension_array_equal(result, expected)
  122. # ensure we haven't mutated anything inplace
  123. tm.assert_extension_array_equal(
  124. a, pd.array([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean")
  125. )
  126. tm.assert_extension_array_equal(
  127. b, pd.array([True, False, None] * 3, dtype="boolean")
  128. )
  129. @pytest.mark.parametrize(
  130. "other, expected",
  131. [
  132. (pd.NA, [None, False, None]),
  133. (True, [True, False, None]),
  134. (False, [False, False, False]),
  135. (np.bool_(True), [True, False, None]),
  136. (np.bool_(False), [False, False, False]),
  137. ],
  138. )
  139. def test_kleene_and_scalar(self, other, expected):
  140. a = pd.array([True, False, None], dtype="boolean")
  141. result = a & other
  142. expected = pd.array(expected, dtype="boolean")
  143. tm.assert_extension_array_equal(result, expected)
  144. result = other & a
  145. tm.assert_extension_array_equal(result, expected)
  146. # ensure we haven't mutated anything inplace
  147. tm.assert_extension_array_equal(
  148. a, pd.array([True, False, None], dtype="boolean")
  149. )
  150. def test_kleene_xor(self):
  151. a = pd.array([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean")
  152. b = pd.array([True, False, None] * 3, dtype="boolean")
  153. result = a ^ b
  154. expected = pd.array(
  155. [False, True, None, True, False, None, None, None, None], dtype="boolean"
  156. )
  157. tm.assert_extension_array_equal(result, expected)
  158. result = b ^ a
  159. tm.assert_extension_array_equal(result, expected)
  160. # ensure we haven't mutated anything inplace
  161. tm.assert_extension_array_equal(
  162. a, pd.array([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean")
  163. )
  164. tm.assert_extension_array_equal(
  165. b, pd.array([True, False, None] * 3, dtype="boolean")
  166. )
  167. @pytest.mark.parametrize(
  168. "other, expected",
  169. [
  170. (pd.NA, [None, None, None]),
  171. (True, [False, True, None]),
  172. (np.bool_(True), [False, True, None]),
  173. (np.bool_(False), [True, False, None]),
  174. ],
  175. )
  176. def test_kleene_xor_scalar(self, other, expected):
  177. a = pd.array([True, False, None], dtype="boolean")
  178. result = a ^ other
  179. expected = pd.array(expected, dtype="boolean")
  180. tm.assert_extension_array_equal(result, expected)
  181. result = other ^ a
  182. tm.assert_extension_array_equal(result, expected)
  183. # ensure we haven't mutated anything inplace
  184. tm.assert_extension_array_equal(
  185. a, pd.array([True, False, None], dtype="boolean")
  186. )
  187. @pytest.mark.parametrize("other", [True, False, pd.NA, [True, False, None] * 3])
  188. def test_no_masked_assumptions(self, other, all_logical_operators):
  189. # The logical operations should not assume that masked values are False!
  190. a = pd.arrays.BooleanArray(
  191. np.array([True, True, True, False, False, False, True, False, True]),
  192. np.array([False] * 6 + [True, True, True]),
  193. )
  194. b = pd.array([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean")
  195. if isinstance(other, list):
  196. other = pd.array(other, dtype="boolean")
  197. result = getattr(a, all_logical_operators)(other)
  198. expected = getattr(b, all_logical_operators)(other)
  199. tm.assert_extension_array_equal(result, expected)
  200. if isinstance(other, BooleanArray):
  201. other._data[other._mask] = True
  202. a._data[a._mask] = False
  203. result = getattr(a, all_logical_operators)(other)
  204. expected = getattr(b, all_logical_operators)(other)
  205. tm.assert_extension_array_equal(result, expected)
  206. @pytest.mark.parametrize("operation", [kleene_or, kleene_xor, kleene_and])
  207. def test_error_both_scalar(operation):
  208. msg = r"Either `left` or `right` need to be a np\.ndarray."
  209. with pytest.raises(TypeError, match=msg):
  210. # masks need to be non-None, otherwise it ends up in an infinite recursion
  211. operation(True, True, np.zeros(1), np.zeros(1))