Numpy Equivalent of If/Else Without Loop

Numpy Equivalent of If/Else Without Loop

is there any pythonic way to remove for loop and if/else in the code below.

this code iterating over a NumPy array and check a condition and according to the condition change the value.

>>> import numpy as np
>>> x=np.random.randint(100, size=(10,5))
>>> x
array([[79, 50, 18, 55, 35],
       [46, 71, 46, 95, 52],
       [97, 37, 71,  2, 79],
       [80, 96, 60, 85, 72],
       [ 6, 52, 63, 86, 38],
       [35, 50, 13, 93, 54],
       [69, 21,  4, 40, 53],
       [83,  7, 30, 16, 78],
       [18, 34, 91, 67, 89],
       [82, 16, 16, 24, 80]])

>>> for i in range(x.shape[0]):
    for j in range(x.shape[1]):
        if x[i,j]>50:
            x[i,j]=0
        elif x[i,j]<50:
            x[i,j]=1


>>> x
array([[ 0, 50,  1,  0,  1],
       [ 1,  0,  1,  0,  0],
       [ 0,  1,  0,  1,  0],
       [ 0,  0,  0,  0,  0],
       [ 1,  0,  0,  0,  1],
       [ 1, 50,  1,  0,  0],
       [ 0,  1,  1,  1,  0],
       [ 0,  1,  1,  1,  0],
       [ 1,  1,  0,  0,  0],
       [ 0,  1,  1,  1,  0]])

I want to do same thing without loops and if statement. something like below dose not work, because of changes on the array:

>>> import numpy as np
>>> x=np.random.randint(100, size=(10,5))
>>> x
array([[ 2, 88, 27, 67, 29],
       [62, 44, 62, 87, 32],
       [80, 95, 31, 30, 33],
       [14, 41, 40, 95, 27],
       [53, 30, 35, 22, 98],
       [90, 39, 74, 28, 73],
       [10, 71,  0, 11, 37],
       [28, 25, 83, 24, 93],
       [30, 70, 15,  5, 79],
       [69, 43, 85, 68, 53]])
>>> x[x>50]=0
>>> x[x<50]=1
>>> x
array([[1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1]])

UPDATE and what happend if there are more conditions like :

   >>> import numpy as np
    >>> x=np.random.randint(100, size=(10,5))
    >>> x
    array([[87, 99, 70, 32, 28],
           [38, 76, 89, 17, 34],
           [28,  1, 40, 34, 67],
           [45, 47, 69, 78, 89],
           [14, 81, 46, 71, 97],
           [39, 45, 36, 36, 25],
           [87, 28,  1, 46, 99],
           [27, 98, 37, 36, 84],
           [55,  2, 23, 29,  9],
           [34, 79, 49, 76, 48]])
    >>> for i in range(x.shape[0]):
        for j in range(x.shape[1]):
            if x[i,j]>90:
                x[i,j]=9
            elif x[i,j]>80:
                x[i,j]=8
            elif x[i,j]>70:
                x[i,j]=7
            elif x[i,j]>60:
                x[i,j]=6
            elif x[i,j]>50:
                x[i,j]=5
            elif x[i,j]>40:
                x[i,j]=4
            else:
                x[i,j]=0


    >>> x
    array([[8, 9, 6, 0, 0],
           [0, 7, 8, 0, 0],
           [0, 0, 0, 0, 6],
           [4, 4, 6, 7, 8],
           [0, 8, 4, 7, 9],
           [0, 4, 0, 0, 0],
           [8, 0, 0, 4, 9],
           [0, 9, 0, 0, 8],
           [5, 0, 0, 0, 0],
           [0, 7, 4, 7, 4]])

5 Answers

One IF-ELIF

Approach #1 One approach -

keep_mask = x==50
out = np.where(x>50,0,1)
out[keep_mask] = 50

Approach #2 Alternatively, for in-situ edit -

replace_mask = x!=50
x[replace_mask] = np.where(x>50,0,1)[replace_mask]
# Or (x<=50).astype(int) in place of np.where(x>50,0,1)

Code-golf? If you actually want to play code-golf/one-liner -

(x<=50)+(x==50)*49

Multiple IF-ELIFs

Approach #1

For a bit more generic case involving more if-elif parts, we could make use of np.searchsorted -

out_x = np.where(x<=40,0, np.searchsorted([40,50,60,70,80,90], x)+3)
4
np.where(x < 50, 0, 1)

This should be enough. You don't need to keep a mask value for 50 since 50 is neither less than nor greater than 50. Hope this helps.

0

A one-liner that does everything your loops does:

x[x != 50] = x[x != 50] < 50

EDIT:

For your extended question, you'd want something like:

bins = [40, 50, 60, 70, 80, 90, 100]
out = np.digitize(x, bins, right = 1)
out[out.astype(bool)] += 3
3

Sorry for being late to the party, just wanted to share another approach to the problem.

One-Line Solution:

x = np.where(x>=50, 50, 1) + np.where(x>50, -50, 0)

Rationale:

We can sum over the following two numpy.where-matrices:

  • For matrix A: if x[i,j] >= 50, then set value 50, otherwise 1 because we want x[i,j]<50 to be equal to 1.
  • For matrix B: if x[i,j] > 50, then set value -50, thus for x[i,j]>50 the sum over both matrices will yield value 0 for the corresponding elements.

By calculating A+B, the values set for conditions x>50 (i.e. -50) and x>=50 (i.e. 50) yield the wanted values (0 and 50) and do not interfere with values set for x<50.

Update for UPDATE

x = np.where(x>40, 4, 0) + np.where(x>50, 1, 0) + np.where(x>60, 1, 0) + np.where(x>70, 1, 0) + np.where(x>80, 1, 0) + np.where(x>90, 1, 0)

Or shorter, if we can rely on the fact that values are always smaller than 100 (change dtype if you want integers):

x = np.where(x>40, np.floor(x/10), 0)

For me this code is quite readable, but I may not be representative.

np.where(x < 50, 0, 1)

This should be enough. You don't need to keep a mask value for 50 since 50 is neither less than nor greater than 50. Hope this helps.

Update

#update
np.where(x < 40, 0, x)
np.where(x > (x - (x % 10)), x // 10, x)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Alexander Ross
Author

Alexander Ross

Alexander Ross has covered the video game industry for a decade, writing deep dives on game design, esports tournaments, VR developments, and gaming culture.