123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- import operator
- import numpy as np
- import pytest
- import pandas as pd
- import pandas._testing as tm
- from pandas.arrays import BooleanArray
- from pandas.core.ops.mask_ops import (
- kleene_and,
- kleene_or,
- kleene_xor,
- )
- from pandas.tests.extension.base import BaseOpsUtil
- class TestLogicalOps(BaseOpsUtil):
- def test_numpy_scalars_ok(self, all_logical_operators):
- a = pd.array([True, False, None], dtype="boolean")
- op = getattr(a, all_logical_operators)
- tm.assert_extension_array_equal(op(True), op(np.bool_(True)))
- tm.assert_extension_array_equal(op(False), op(np.bool_(False)))
- def get_op_from_name(self, op_name):
- short_opname = op_name.strip("_")
- short_opname = short_opname if "xor" in short_opname else short_opname + "_"
- try:
- op = getattr(operator, short_opname)
- except AttributeError:
- # Assume it is the reverse operator
- rop = getattr(operator, short_opname[1:])
- op = lambda x, y: rop(y, x)
- return op
- def test_empty_ok(self, all_logical_operators):
- a = pd.array([], dtype="boolean")
- op_name = all_logical_operators
- result = getattr(a, op_name)(True)
- tm.assert_extension_array_equal(a, result)
- result = getattr(a, op_name)(False)
- tm.assert_extension_array_equal(a, result)
- result = getattr(a, op_name)(pd.NA)
- tm.assert_extension_array_equal(a, result)
- @pytest.mark.parametrize(
- "other", ["a", pd.Timestamp(2017, 1, 1, 12), np.timedelta64(4)]
- )
- def test_eq_mismatched_type(self, other):
- # GH-44499
- arr = pd.array([True, False])
- result = arr == other
- expected = pd.array([False, False])
- tm.assert_extension_array_equal(result, expected)
- result = arr != other
- expected = pd.array([True, True])
- tm.assert_extension_array_equal(result, expected)
- def test_logical_length_mismatch_raises(self, all_logical_operators):
- op_name = all_logical_operators
- a = pd.array([True, False, None], dtype="boolean")
- msg = "Lengths must match"
- with pytest.raises(ValueError, match=msg):
- getattr(a, op_name)([True, False])
- with pytest.raises(ValueError, match=msg):
- getattr(a, op_name)(np.array([True, False]))
- with pytest.raises(ValueError, match=msg):
- getattr(a, op_name)(pd.array([True, False], dtype="boolean"))
- def test_logical_nan_raises(self, all_logical_operators):
- op_name = all_logical_operators
- a = pd.array([True, False, None], dtype="boolean")
- msg = "Got float instead"
- with pytest.raises(TypeError, match=msg):
- getattr(a, op_name)(np.nan)
- @pytest.mark.parametrize("other", ["a", 1])
- def test_non_bool_or_na_other_raises(self, other, all_logical_operators):
- a = pd.array([True, False], dtype="boolean")
- with pytest.raises(TypeError, match=str(type(other).__name__)):
- getattr(a, all_logical_operators)(other)
- def test_kleene_or(self):
- # A clear test of behavior.
- a = pd.array([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean")
- b = pd.array([True, False, None] * 3, dtype="boolean")
- result = a | b
- expected = pd.array(
- [True, True, True, True, False, None, True, None, None], dtype="boolean"
- )
- tm.assert_extension_array_equal(result, expected)
- result = b | a
- tm.assert_extension_array_equal(result, expected)
- # ensure we haven't mutated anything inplace
- tm.assert_extension_array_equal(
- a, pd.array([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean")
- )
- tm.assert_extension_array_equal(
- b, pd.array([True, False, None] * 3, dtype="boolean")
- )
- @pytest.mark.parametrize(
- "other, expected",
- [
- (pd.NA, [True, None, None]),
- (True, [True, True, True]),
- (np.bool_(True), [True, True, True]),
- (False, [True, False, None]),
- (np.bool_(False), [True, False, None]),
- ],
- )
- def test_kleene_or_scalar(self, other, expected):
- # TODO: test True & False
- a = pd.array([True, False, None], dtype="boolean")
- result = a | other
- expected = pd.array(expected, dtype="boolean")
- tm.assert_extension_array_equal(result, expected)
- result = other | a
- tm.assert_extension_array_equal(result, expected)
- # ensure we haven't mutated anything inplace
- tm.assert_extension_array_equal(
- a, pd.array([True, False, None], dtype="boolean")
- )
- def test_kleene_and(self):
- # A clear test of behavior.
- a = pd.array([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean")
- b = pd.array([True, False, None] * 3, dtype="boolean")
- result = a & b
- expected = pd.array(
- [True, False, None, False, False, False, None, False, None], dtype="boolean"
- )
- tm.assert_extension_array_equal(result, expected)
- result = b & a
- tm.assert_extension_array_equal(result, expected)
- # ensure we haven't mutated anything inplace
- tm.assert_extension_array_equal(
- a, pd.array([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean")
- )
- tm.assert_extension_array_equal(
- b, pd.array([True, False, None] * 3, dtype="boolean")
- )
- @pytest.mark.parametrize(
- "other, expected",
- [
- (pd.NA, [None, False, None]),
- (True, [True, False, None]),
- (False, [False, False, False]),
- (np.bool_(True), [True, False, None]),
- (np.bool_(False), [False, False, False]),
- ],
- )
- def test_kleene_and_scalar(self, other, expected):
- a = pd.array([True, False, None], dtype="boolean")
- result = a & other
- expected = pd.array(expected, dtype="boolean")
- tm.assert_extension_array_equal(result, expected)
- result = other & a
- tm.assert_extension_array_equal(result, expected)
- # ensure we haven't mutated anything inplace
- tm.assert_extension_array_equal(
- a, pd.array([True, False, None], dtype="boolean")
- )
- def test_kleene_xor(self):
- a = pd.array([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean")
- b = pd.array([True, False, None] * 3, dtype="boolean")
- result = a ^ b
- expected = pd.array(
- [False, True, None, True, False, None, None, None, None], dtype="boolean"
- )
- tm.assert_extension_array_equal(result, expected)
- result = b ^ a
- tm.assert_extension_array_equal(result, expected)
- # ensure we haven't mutated anything inplace
- tm.assert_extension_array_equal(
- a, pd.array([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean")
- )
- tm.assert_extension_array_equal(
- b, pd.array([True, False, None] * 3, dtype="boolean")
- )
- @pytest.mark.parametrize(
- "other, expected",
- [
- (pd.NA, [None, None, None]),
- (True, [False, True, None]),
- (np.bool_(True), [False, True, None]),
- (np.bool_(False), [True, False, None]),
- ],
- )
- def test_kleene_xor_scalar(self, other, expected):
- a = pd.array([True, False, None], dtype="boolean")
- result = a ^ other
- expected = pd.array(expected, dtype="boolean")
- tm.assert_extension_array_equal(result, expected)
- result = other ^ a
- tm.assert_extension_array_equal(result, expected)
- # ensure we haven't mutated anything inplace
- tm.assert_extension_array_equal(
- a, pd.array([True, False, None], dtype="boolean")
- )
- @pytest.mark.parametrize("other", [True, False, pd.NA, [True, False, None] * 3])
- def test_no_masked_assumptions(self, other, all_logical_operators):
- # The logical operations should not assume that masked values are False!
- a = pd.arrays.BooleanArray(
- np.array([True, True, True, False, False, False, True, False, True]),
- np.array([False] * 6 + [True, True, True]),
- )
- b = pd.array([True] * 3 + [False] * 3 + [None] * 3, dtype="boolean")
- if isinstance(other, list):
- other = pd.array(other, dtype="boolean")
- result = getattr(a, all_logical_operators)(other)
- expected = getattr(b, all_logical_operators)(other)
- tm.assert_extension_array_equal(result, expected)
- if isinstance(other, BooleanArray):
- other._data[other._mask] = True
- a._data[a._mask] = False
- result = getattr(a, all_logical_operators)(other)
- expected = getattr(b, all_logical_operators)(other)
- tm.assert_extension_array_equal(result, expected)
- @pytest.mark.parametrize("operation", [kleene_or, kleene_xor, kleene_and])
- def test_error_both_scalar(operation):
- msg = r"Either `left` or `right` need to be a np\.ndarray."
- with pytest.raises(TypeError, match=msg):
- # masks need to be non-None, otherwise it ends up in an infinite recursion
- operation(True, True, np.zeros(1), np.zeros(1))
|