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.

jns
  • 1,250
  • 1
  • 13
  • 29
abudis
  • 2,841
  • 6
  • 32
  • 41

6 Answers6

34

Well, I suppose what you need is

a[a==2] = 10 #replace all 2's with 10's
alex_jordan
  • 877
  • 1
  • 6
  • 11
  • 7
    When I do this, I get "assignment destination is read-only", do you know why this is? – wolfsatthedoor May 30 '14 at 03:19
  • 1
    This is significantly simpler than the other solutions, thank you – reabow Apr 17 '15 at 13:07
  • 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? – lavee_singh Oct 07 '15 at 18:57
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
Community
  • 1
  • 1
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • Thanks unutbu! I'll accept your answer as it's more versatile. Cheers. – abudis Nov 26 '12 at 20:40
  • "index = np.digitize(a.reshape(-1,), palette)-1" could be replaced with "index = np.digitize(a.reshape(-1,), palette, right=True)", right? (=True?) – Pietro Battiston Mar 11 '15 at 13:06
  • @PietroBattiston: Since every value in `a` is in `palette`, yes I think `right=True` returns the same result. Thanks for the improvement! – unutbu Mar 11 '15 at 13:30
  • 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? – lavee_singh Oct 07 '15 at 19:01
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.

DorinPopescu
  • 715
  • 6
  • 10
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]])
Linda
  • 1,003
  • 1
  • 11
  • 25
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)
jns
  • 1,250
  • 1
  • 13
  • 29
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)
Paul Bendevis
  • 2,381
  • 2
  • 31
  • 42