2

I have an array like this and need to replace every 1 with 2, every 3 with 4, every 4 with 1. Is there a way to do this just with np and not loops?

import numpy as np
np.random.seed(2)
arr=np.random.randint(1,5,(3,3),int)
arr

array([[1, 4, 2],
       [1, 3, 4],
       [3, 4, 1]])

If I use array mask sequentially, it doesn't give the expected outcome:

array([[2, 1, 2], 
       [2, 4, 1],
       [4, 1, 2]]) 

It is based on a conditional logic and not maths formula

2
  • I would use a separate mask for each replacement. You should be careful about the order of the assignments. If you still need help, please show the code that you tried. Commented Jan 22, 2020 at 16:49
  • Better yet, create the masks from the original array rather than after each modification. Commented Jan 22, 2020 at 16:51

5 Answers 5

2

If the array values don't necessarely range between 1 and 4 you can use np.select:

import numpy as np

a = np.random.randint(1,5, (3,3))


condlist = [np.logical_or(a==1, a==2),  a==3, a==4]
choicelist= [2, 4, 1]
b = np.select(condlist, choicelist) 

which does not care about the order of the conditions

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

Comments

2

Here's one with np.searchsorted for performance efficiency -

def map_values(arr, old_val, new_val):
    sidx = old_val.argsort()
    idx = np.searchsorted(old_val,arr,sorter=sidx)
    return np.where(old_val[idx]==arr, new_val[sidx[idx]], arr)

Sample run -

In [40]: arr
Out[40]: 
array([[1, 4, 2],
       [1, 3, 4],
       [3, 4, 1]])

In [41]: old_val = np.array([1,3,4])
    ...: new_val = np.array([2,4,1])

In [42]: map_values(arr, old_val, new_val)
Out[42]: 
array([[2, 1, 2],
       [2, 4, 1],
       [4, 1, 2]])

Comments

1

Could do this with a lambda function and np.vectorize():

import numpy as np
np.random.seed(2)
arr=np.random.randint(1,5,(3,3),int)

f = lambda x: x%4 + 1 if x in [1,3,4] else x
vfunc = np.vectorize(f)

Usage:

>>> vfunc(arr)
array([[2, 1, 2],
       [2, 4, 1],
       [4, 1, 2]])

Comments

0

You have to be careful about the order of assignments. For example, if you do

arr[arr == 4] = 1
arr[arr == 1] = 2

Now all of the elements that were originally 4 will be 2, not 1 as you intend.

One solution is to carefully craft the order of assignments:

arr[arr == 1] = 2
arr[arr == 4] = 1

However, this is very brittle and will fall apart as you introduce more of them. It would be better to create the masks up front from the original array:

ones = arr == 1
fours = arr == 4
arr[ones] = 2
arr[fours] = 1

Now the order of the assignments won't matter because the masks are determined before modifying the array.

Comments

0

You want arr % 4 + 1, except in the case of 2, which stays the same. So use np.where to find all the 2s. Then do arr % 4 + 1, then reset all the 2s.

import numpy as np

np.random.seed(2)
arr=np.random.randint(1,5,(3,3),int)

twos = np.where(arr == 2)
arr = arr % 4 + 1
arr[twos] = 2
print(arr)

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.