0
votes

I need to compute x in the following way (legacy code):

x = numpy.where(b == 0, a, 1/b) 

I suppose it worked in (as it was in a code), but it does not work in (if b = 0 it returns an error).

How do I make it work in python-3.x?

EDIT: error message (Python 3.6.3):

ZeroDivisionError: division by zero
5
Why is it working in python 2? You are dividing 1/0 which is illegal. Did you use b=0 in python 2? - whackamadoodle3000
What error do you get? - jpp
I guess (I might be wrong) that in Python 2x the 3rd expression was not evaluated if the first one is true. - user5805065
I just tested it in both 2.7 and 3.6. Same: RuntimeWarning: divide by zero encountered in divide - roganjosh
@henry - we need to know what a and b are here. With them both as arrays, this doesn't cause a ZeroDivisionError for me in either version, but both give the RuntimeWarning (if any element of b is zero). With b as an int, I get the ZeroDivisionError. - Nathan Vērzemnieks

5 Answers

1
votes

The numpy.where documentation states:

If x and y are given and input arrays are 1-D, where is equivalent to::

    [xv if c else yv for (c,xv,yv) in zip(condition,x,y)]

So why do you see the error? Take this trivial example:

c = 0
result = (1 if c==0 else 1/c)
# 1

So far so good. if c==0 is checked first and the result is 1. The code does not attempt to evaluate 1/c. This is because the Python interpreter processes a lazy ternary operator and so only evaluates the appropriate expression.

Now let's translate this into numpy.where approach:

c = 0
result = (xv if c else yv for (c, xv, yv) in zip([c==0], [1], [1/c]))
# ZeroDivisionError

The error occurs in evaluating zip([c==0], [1], [1/c]) before even the logic is applied. The generator expression itself can't be evaluated. As a function, numpy.where does not, and indeed cannot, replicate the lazy computation of Python's ternary expression.

11
votes

numpy.where is not conditional execution; it is conditional selection. Python function parameters are always completely evaluated before a function call, so there is no way for a function to conditionally or partially evaluate its parameters.

Your code:

x = numpy.where(b == 0, a, 1/b)

tells Python to invert every element of b and then select elements from a or 1/b based on elements of b == 0. Python never even reaches the point of selecting elements, because computing 1/b fails.

You can avoid this problem by only inverting the nonzero parts of b. Assuming a and b have the same shape, it could look like this:

x = numpy.empty_like(b)
mask = (b == 0)
x[mask] = a[mask]
x[~mask] = 1/b[~mask]
6
votes

A old trick for handling 0 elements in an array division is to add a conditional value:

In [63]: 1/(b+(b==0))
Out[63]: array([1.        , 1.        , 0.5       , 0.33333333])

(I used this years ago in apl).


x = numpy.where(b == 0, a, 1/b) is evaluated in the same way as any other Python function. Each function argument is evaluated, and the value passed to the where function. There's no 'short-circuiting' or other method of bypassing bad values of 1/b.

So if 1/b returns a error you need to either change b so it doesn't do that, calculate it in context that traps traps the ZeroDivisionError, or skips the 1/b.

In [53]: 1/0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-53-9e1622b385b6> in <module>()
----> 1 1/0

ZeroDivisionError: division by zero
In [54]: 1.0/0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-54-99b9b9983fe8> in <module>()
----> 1 1.0/0

ZeroDivisionError: float division by zero
In [55]: 1/np.array(0)
/usr/local/bin/ipython3:1: RuntimeWarning: divide by zero encountered in true_divide
  #!/usr/bin/python3
Out[55]: inf

What are a and b? Scalars, arrays of some size?


where makes most sense if b (and maybe a) is an array:

In [59]: b = np.array([0,1,2,3])

The bare division gives me a warning, and an inf element:

In [60]: 1/b
/usr/local/bin/ipython3:1: RuntimeWarning: divide by zero encountered in true_divide
  #!/usr/bin/python3
Out[60]: array([       inf, 1.        , 0.5       , 0.33333333])

I could use where to replace that inf with something else, for example a nan:

In [61]: np.where(b==0, np.nan, 1/b)
/usr/local/bin/ipython3:1: RuntimeWarning: divide by zero encountered in true_divide
  #!/usr/bin/python3
Out[61]: array([       nan, 1.        , 0.5       , 0.33333333])

The warning can be silenced as @donkopotamus shows.

An alternative to seterr is errstate in a with context:

In [64]: with np.errstate(divide='ignore'):
    ...:     x = np.where(b==0, np.nan, 1/b)
    ...:     
In [65]: x
Out[65]: array([       nan, 1.        , 0.5       , 0.33333333])

How to suppress the error message when dividing 0 by 0 using np.divide (alongside other floats)?

1
votes

If you wish to disable warnings in numpy while you divide by zero, then do something like:

>>> existing = numpy.seterr(divide="ignore")
>>> # now divide by zero in numpy raises no sort of exception
>>> 1 / numpy.zeros( (2, 2) )
array([[ inf,  inf],
       [ inf,  inf]])
>>> numpy.seterr(*existing)

Of course this only governs division by zero in an array. It will not prevent an error when doing a simple 1 / 0.

In your particular case, if we wish to ensure that we work whether b is a scalar or a numpy type, do as follows:

# ignore division by zero in numpy
existing = numpy.seterr(divide="ignore")

# upcast `1.0` to be a numpy type so that numpy division will always occur
x = numpy.where(b == 0, a, numpy.float64(1.0) / b) 

# restore old error settings for numpy
numpy.seterr(*existing) 
1
votes

I solved it using this:

x = (1/(np.where(b == 0, np.nan, b))).fillna(a)