3

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)

3
  • 1
    I don't see why someone would use an array of booleans for a mask and not (0,1) which is much more intuitive but i do see a point. What is the desired behavior though when doing -2 * False ?? Commented Jun 8, 2016 at 8:43
  • + in python everything is logical as well. as in "blabla" being True (thus kinda boolean) and a string at the same time. Commented Jun 8, 2016 at 8:45
  • @Ev.Kounis, I stumbled into it when I wanted to write a delayed exponetial decay by means of the following code delExpDecay=lambda t,d,a,tau: a*exp(-(t<d)*(t-d)/tau), which doesn't work. It looks completely weird that it will instead work, if you alter the order of the operations (a product should be commutative, shouldn't it?). Thereafter, I reduced the point to the simplest question. Commented Jun 8, 2016 at 22:54

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for the comprehensive explanation, and especially for noting about the tilde ~. If we have a tilde operator, why to have the unary minus - operate as a duplicate? I'd rather consider it more logical that both the unary plus + and the unary minus - produce an arithmetical conversion, as they do in operations like 0+B or 0-B. (See also my comment to my own question above)
@lurix66 It is just a design decision, and apparently they are regretting it as it is deprecated and will be removed in the future. When using - as a binary operator 0-B or B-0 it works properly (as you expect), the problem is when you use - as a unary operator -B, which, instead of being translated to -1 * B as in numerical arrays, it is translated to ~B (or not B). Again, we cannot do anything until numpy decides to stop supporting - unary operator as a negation operator, so we will have to either wait, or be more careful meanwhile :P
0

Numpy arrays are homogenous—all elements have the same type for a given array, and the array object stores what type that is. When you create an array with True and False, it is an array of type bool and operators behave on the array as such. It's not surprising, then, that you get logical negation happening in situations that would be logical negation for a normal bool. When you use the arrays for integer math, then they are converted to 1's and 0's. Of all your examples, those are the more anomalous cases, that is, it's behavior that shouldn't be relied upon in good code.

As suggested in the comments, if you want to do math with an array of 0's and 1's, it's better to just make an array of 0's and 1's. However, depending on what you want to do with them, you might be better served looking into functions like numpy.where().

1 Comment

(see my comments to previous questions), I consider one liners valuable if they are simple. Using numpy.where() looks less simple to me, than using the ordinarily obvious equivalence that a boolean mask value is 1 where True and 0 where False.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.