Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python numpy strange boolean arithmetic behaviour

Why is it, in python/numpy:

from numpy import asarray
bools=asarray([False,True])

print(bools)
[False True]

print(1*bools, 0+bools, 0-bools)    # False, True are valued as 0, 1
[0 1] [0 1] [ 0 -1]

print(-2*bools, -bools*2)           # !? expected same result!  :-/
[0 -2] [2 0] 

print(-bools)                       # this is the reason!
[True False]

I consider it weird that -bools returns logical_not(bools), because in all other cases the behaviour is "arithmetic", not "logical".

One who wants to use an array of booleans as a 0/1 mask (or "characteristic function") is forced to use somehow involute expressions such as (0-bools) or (-1)*bools, and can easily incur into bugs if he forgets about this.

Why is it so, and what would be the best acceptable way to obtain the desired behaviour? (beside commenting of course)

like image 999
lurix66 Avatar asked May 08 '26 16:05

lurix66


1 Answers

Its all about operator order and data types.

>>> import numpy as np
>>> B = np.array([0, 1], dtype=np.bool)
>>> B
array([False,  True], dtype=bool)

With numpy, boolean arrays are treated as that, boolean arrays. Every operation applied to them, will first try to maintain the data type. That is way:

>>> -B
array([ True, False], dtype=bool)

and

>>> ~B
array([ True, False], dtype=bool)

which are equivalent, return the element-wise negation of its elements. Note however that using -B throws a warning, as the function is deprecated.

When you use things like:

>>> B + 1
array([1, 2])

B and 1 are first casted under the hood to the same data type. In data-type promotions, the boolean array is always casted to a numeric array. In the above case, B is casted to int, which is similar as:

>>> B.astype(int) + 1
array([1, 2])

In your example:

>>> -B * 2
array([2, 0])

First the array B is negated by the operator - and then multiplied by 2. The desired behaviour can be adopted either by explicit data conversion, or adding brackets to ensure proper operation order:

>>> -(B * 2)
array([ 0, -2])

or

>>> -B.astype(int) * 2
array([ 0, -2])

Note that B.astype(int) can be replaced without data-copy by B.view(np.int8), as boolean are represented by characters and have thus 8 bits, the data can be viewed as integer with the .view method without needing to convert it.

>>> B.view(np.int8)
array([0, 1], dtype=int8)

So, in short, B.view(np.int8) or B.astype(yourtype) will always ensurs that B is a [0,1] numeric array.

like image 139
Imanol Luengo Avatar answered May 10 '26 05:05

Imanol Luengo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!