Logical operations on Boolean arrays#

np.logical_and, np.logical_or:

Sometimes we want to combine Boolean values using logical operators like AND, OR, NOT. This is straightforward for Python Booleans:

# Logical AND - True only if both are True
print(True and True)
print(True and False)
True
False
# Logical OR - True if either or both are True
print(True or True)
print(True or False)
print(False or False)
True
True
False
# Logical NOT - inverts truth value
print(not True)
print(not False)
False
True

We have to do a little more work for arrays of Booleans, because the Python and, or, not operators only return a single Boolean values, and so do not operate as we expect on arrays:

import numpy as np
bool1 = np.array([True, True, False, False])
bool2 = np.array([False, True, False, True])
bool1 and bool2
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In [6], line 1
----> 1 bool1 and bool2

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
bool1 or bool2
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In [7], line 1
----> 1 bool1 or bool2

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

To do elementwise AND, OR, NOT, we can use np.logical_and, np.logical_or, np.logical_not:

# "logical_and" True where both of bool1 and bool2 are True
np.logical_and(bool1, bool2)
array([False,  True, False, False])
# "logical_or" True where either of bool1 and bool2 are True
np.logical_or(bool1, bool2)
array([ True,  True, False,  True])
# "logical_not" True where input array is False
np.logical_not(bool1)
array([False, False,  True,  True])

Using the bitwise operators#

Equivalently, the &, | and ~ operators are applied elementwise.

These are called bitwise operators, for reasons we do not need to go into here. Iff applied to Boolean values then:

  • & gives the same result as np.logical_and

  • | gives the same result as np.logical_or

  • ~ gives the same result as np.logical_not

bool1 & bool2
array([False,  True, False, False])
bool1 | bool2
array([ True,  True, False,  True])
~bool1
array([False, False,  True,  True])

Bitwise, brackets#

Be careful when using the bitwise operators. The bitwise operators have relatively high operator precedence, meaning that Python will prefer to apply the bitwise operator before other operators, such as comparison operators.

For example, consider these arrays, and the Boolean arrays from their comparison:

first = np.array([1, 0, 1])
first == 1
array([ True, False,  True])
second = np.array([0, 1, 1])
second == 1
array([False,  True,  True])
# This will give an error.  Why?
first == 1 & second == 1
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In [16], line 2
      1 # This will give an error.  Why?
----> 2 first == 1 & second == 1

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

The problem is that Numpy registers & as having higher operator preference than ==, so it does the & operation before the ==, meaning that the code above is equivalent to:

first == (1 & second) == 1
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In [17], line 1
----> 1 first == (1 & second) == 1

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Therefore, you get the error like this:

# Python is doing this under the hood in the statement above.
res = 1 & second
# Python next does this, generating the error.
first == res == second
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In [19], line 2
      1 # Python next does this, generating the error.
----> 2 first == res == second

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

The exact reason this last statement gives an error is a little advanced. It is because Python internally translates the statement above to:

# Python internally translates "first == res == second) to:
(first == res) and (res == second)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In [20], line 2
      1 # Python internally translates "first == res == second) to:
----> 2 (first == res) and (res == second)

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

The problem with this internal translation is that and does not work when comparing arrays:

# "and" does not work on arrays.
np.array([True, False]) and np.array([True, False])
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In [21], line 2
      1 # "and" does not work on arrays.
----> 2 np.array([True, False]) and np.array([True, False])

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

The fix is to specify that you want the == operation done before the & operation, using parentheses:

# Guarantee the order of operations with parentheses.
(first == 1) & (second == 1)
array([False, False,  True])

To avoid worrying about this problem, you might prefer to use np.logical_and etc:

# The same operation using logical_and
np.logical_and(first == 1, second == 1)
array([False, False,  True])