26

Suppose you have a numpy array and a list:

>>> a = np.array([1,2,2,1]).reshape(2,2)
>>> a
array([[1, 2],
       [2, 1]])
>>> b = [0, 10]

I'd like to replace values in an array, so that 1 is replaced by 0, and 2 by 10.

I found a similar problem here - http://mail.python.org/pipermail//tutor/2011-September/085392.html

But using this solution:

for x in np.nditer(a):
    if x==1:
        x[...]=x=0
    elif x==2:
        x[...]=x=10

Throws me an error:

ValueError: assignment destination is read-only

I guess that's because I can't really write into a numpy array.

P.S. The actual size of the numpy array is 514 by 504 and of the list is 8.

6 Answers 6

34

Well, I suppose what you need is

a[a==2] = 10 #replace all 2's with 10's
Sign up to request clarification or add additional context in comments.

3 Comments

When I do this, I get "assignment destination is read-only", do you know why this is?
This is significantly simpler than the other solutions, thank you
What would we do, if we want to change the elements at indexes which are multiple of given n, simultaneously. Like simultaneously change a[2],a[4],a[6].... for n = 2., what should be done?
28

Instead of replacing the values one by one, it is possible to remap the entire array like this:

import numpy as np
a = np.array([1,2,2,1]).reshape(2,2)
# palette must be given in sorted order
palette = [1, 2]
# key gives the new values you wish palette to be mapped to.
key = np.array([0, 10])
index = np.digitize(a.ravel(), palette, right=True)
print(key[index].reshape(a.shape))

yields

[[ 0 10]
 [10  0]]

Credit for the above idea goes to @JoshAdel. It is significantly faster than my original answer:

import numpy as np
import random
palette = np.arange(8)
key = palette**2
a = np.array([random.choice(palette) for i in range(514*504)]).reshape(514,504)

def using_unique():
    palette, index = np.unique(a, return_inverse=True)
    return key[index].reshape(a.shape)

def using_digitize():
    index = np.digitize(a.ravel(), palette, right=True)
    return key[index].reshape(a.shape)

if __name__ == '__main__':
    assert np.allclose(using_unique(), using_digitize())

I benchmarked the two versions this way:

In [107]: %timeit using_unique()
10 loops, best of 3: 35.6 ms per loop
In [112]: %timeit using_digitize()
100 loops, best of 3: 5.14 ms per loop

4 Comments

Thanks unutbu! I'll accept your answer as it's more versatile. Cheers.
"index = np.digitize(a.reshape(-1,), palette)-1" could be replaced with "index = np.digitize(a.reshape(-1,), palette, right=True)", right? (=True?)
@PietroBattiston: Since every value in a is in palette, yes I think right=True returns the same result. Thanks for the improvement!
What would we do, if we wanted to change values at indexes which are multiple of given n, like a[2],a[4],a[6],a[8]..... for n=2?
25

Read-only array in numpy can be made writable:

nArray.flags.writeable = True

This will then allow assignment operations like this one:

nArray[nArray == 10] = 9999 # replace all 10's with 9999's

The real problem was not assignment itself but the writable flag.

Comments

3

I found another solution with the numpy function place. (Documentation here)

Using it on your example:

>>> a = np.array([1,2,2,1]).reshape(2,2)
>>> a
array([[1, 2],
   [2, 1]])
>>> np.place(a, a==1, 0)
>>> np.place(a, a==2, 10)
>>> a
array([[ 0, 10],
       [10,  0]])

Comments

1

You can also use np.choose(idx, vals), where idx is an array of indices that indicate which value of vals should be put in their place. The indices must be 0-based, though. Also make sure that idx has an integer datatype. So you would only need to do:

np.choose(a.astype(np.int32) - 1, b)

Comments

1

I was unable to set the flags, or use a mask to modify the value. In the end I just made a copy of the array.

a2 = np.copy(a)

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.