---
jupyter:
jupytext:
notebook_metadata_filter: all,-language_info
split_at_heading: true
text_representation:
extension: .Rmd
format_name: rmarkdown
format_version: '1.2'
jupytext_version: 1.13.7
kernelspec:
display_name: Python 3
language: python
name: python3
---
# 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:
```{python}
# Logical AND - True only if both are True
print(True and True)
print(True and False)
```
```{python}
# Logical OR - True if either or both are True
print(True or True)
print(True or False)
print(False or False)
```
```{python}
# Logical NOT - inverts truth value
print(not True)
print(not False)
```
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:
```{python}
import numpy as np
```
```{python}
bool1 = np.array([True, True, False, False])
bool2 = np.array([False, True, False, True])
```
```{python tags=c("raises-exception")}
bool1 and bool2
```
```{python tags=c("raises-exception")}
bool1 or bool2
```
To do elementwise AND, OR, NOT, we can use `np.logical_and, np.logical_or,
np.logical_not`:
```{python}
# "logical_and" True where both of bool1 and bool2 are True
np.logical_and(bool1, bool2)
```
```{python}
# "logical_or" True where either of bool1 and bool2 are True
np.logical_or(bool1, bool2)
```
```{python}
# "logical_not" True where input array is False
np.logical_not(bool1)
```
## 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`
```{python}
bool1 & bool2
```
```{python}
bool1 | bool2
```
```{python}
~bool1
```
## 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:
```{python}
first = np.array([1, 0, 1])
first == 1
```
```{python}
second = np.array([0, 1, 1])
second == 1
```
```{python tags=c("raises-exception")}
# This will give an error. Why?
first == 1 & second == 1
```
The problem is that Numpy registers `&` as having [higher operator
preference](https://docs.python.org/3/reference/expressions.html#operator-precedence)
than `==`, so it does the `&` operation before the `==`, meaning that the code
above is equivalent to:
```{python tags=c("raises-exception")}
first == (1 & second) == 1
```
Therefore, you get the error like this:
```{python}
# Python is doing this under the hood in the statement above.
res = 1 & second
```
```{python tags=c("raises-exception")}
# Python next does this, generating the error.
first == res == second
```
The exact reason this last statement gives an error is [a little
advanced](https://docs.python.org/3/reference/expressions.html#comparisons). It
is because Python *internally* translates the statement above to:
```{python tags=c("raises-exception")}
# Python internally translates "first == res == second) to:
(first == res) and (res == second)
```
The problem with this internal translation is that `and` does not work when
comparing arrays:
```{python tags=c("raises-exception")}
# "and" does not work on arrays.
np.array([True, False]) and np.array([True, False])
```
The fix is to specify that you want the `==` operation done *before* the `&`
operation, using parentheses:
```{python}
# Guarantee the order of operations with parentheses.
(first == 1) & (second == 1)
```
To avoid worrying about this problem, you might prefer to use `np.logical_and`
etc:
```{python}
# The same operation using logical_and
np.logical_and(first == 1, second == 1)
```