37

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 5

47

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)
Sign up to request clarification or add additional context in comments.

4 Comments

then what if there are more if/elif ?
@pdshah Would depend on how those further if/elif is setup. Show us a sample case?
add it to the qustion.
@pdshah Check out Multiple IF-ELIFs section.
9
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.

Comments

5

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 Comments

Added response for your "new" question
Think you need a correction there - right=True with np.digitize. And out[out!=0] += 3.
right, that's what I get for trying to FGITW you >.<.
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.

Comments

0
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)

Comments

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.