Numpy equivalent of if/else without loop
Question:
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]])
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)
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
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.
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)
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.
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]])
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)
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
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.
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)
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.