4

I have three numpy arrays:

row = np.array([1,2,3,4,5])

# a is a subset of row:

a = np.array([1, 5])

# b is an array that I use to change some elements in the first row array:

b = np.array([10, 550])

What I need to do is to change in one shot the elements of the row array that are present in a with the correspondent b elements.

i.e.:

>> modified_row
array([10, 2, 3, 4, 500])

Doing this in a naive way would be:

for i in range(len(a)):
    row[np.where(row==a[i])]= b[i]

I would like a solution like;

row[np.where(row==a)] = b

But that doesn't work...

Thanks in advance!

2
  • Could you clarify your expected behaviour for repeated elements? Commented Dec 18, 2013 at 11:37
  • @wim, the fact is that for my problem, all elements are unique and sorting is not a problem, since those values are indexes by themselves. Commented Dec 19, 2013 at 14:37

5 Answers 5

5

If you don't have guarantees on the sorting of your arrays, you could have a reasonably efficient implementation using np.searchsorted:

def find_and_replace(array, find, replace):
    sort_idx = np.argsort(array)
    where_ = np.take(sort_idx, 
                     np.searchsorted(array, find, sorter=sort_idx))
    if not np.all(array[where_] == find):
        raise ValueError('All items in find must be in array')
    row[where_] = b

The only thing that this can't handle is repeated entries in array, but other than that it works like a charm:

>>> row = np.array([5,4,3,2,1])
>>> a = np.array([5, 1])
>>> b = np.array([10, 550])
>>> find_and_replace(row, a, b)
>>> row
array([ 10,   4,   3,   2, 550])

>>> row = np.array([5,4,3,2,1])
>>> a = np.array([1, 5])
>>> b = np.array([10, 550])
>>> find_and_replace(row, a, b)
>>> row
array([550,   4,   3,   2,  10])

>>> row = np.array([4, 5, 1, 3, 2])
>>> find_and_replace(row, a, b)
>>> row
array([  4, 550,  10,   3,   2])
Sign up to request clarification or add additional context in comments.

1 Comment

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?
4

Please note @Jaime's answer is better - this was marked as correct despite it relying on the ordering of the elements. Here is a working version that does not modify row in-place but otherwise will work in the general case. At the end of this post is my original answer.

import numpy as np

def replaced(row, a, b):
    row_order = row.argsort()
    a_order = a.argsort()

    sorted_row = row[row_order]
    sorted_a = a[a_order]
    sorted_b = b[a_order]

    sorted_row[np.in1d(sorted_row, sorted_a)] = sorted_b

    # return results in original order
    return sorted_row[row_order.argsort()]

a = np.array([1, 5])
b = np.array([10, 550])

row = np.array([1,2,3,4,5])
print replaced(row, a, b)

row = np.array([5,4,3,2,1])
print replaced(row, a, b)

row = np.array([4, 5, 1, 3, 2])
print replaced(row, a, b)

results:

>>> row = np.array([1,2,3,4,5])
>>> print replaced(row, a, b)
[ 10   2   3   4 550]
>>> 
>>> row = np.array([5,4,3,2,1])
>>> print replaced(row, a, b)
[550   4   3   2  10]
>>> 
>>> row = np.array([4, 5, 1, 3, 2])
>>> print replaced(row, a, b)
[  4 550  10   3   2]

ORIGINAL INCORRECT ANSWER

One way to do this is with the in1d function, which will generate a boolean array that you can use to index row as shown below.

Note that you may have problems with this (and other methods) if the elements of row are not unique or if you have repeated elements in a

>>> import numpy as np
>>> row = np.array([1,2,3,4,5])
>>> a = np.array([1, 5])
>>> b = np.array([10, 550])
>>> np.in1d(row, a)
array([ True, False, False, False,  True], dtype=bool)
>>> row[np.in1d(row, a)] = b
>>> row
array([ 10,   2,   3,   4, 550])

You can normally use whatever index/boolean array you originally used to extract a for this purpose too.

3 Comments

You are assuming that all arrays are sorted, your solution wouldn't work properly with a = [5, 1], as it would put the values from b in the wrong positions.
@Jaime you are right - OP you should remove the accepted flag from this post.
np.in1d would work with some careful argsorting, but I don't have time to change it right this minute
3

Another possibility:

>>> row = np.array([1,2,3,4,5])
>>> row[np.any(row.reshape(-1, 1) == a, axis=1)] = b
>>> row
array([ 10,   2,   3,   4, 550])

The way this works is:

>>> row.reshape(-1, 1) == a
array([[ True, False],
       [False, False],
       [False, False],
       [False, False],
       [False,  True]], dtype=bool)
>>> np.any(row.reshape(-1, 1) == a, axis=1)
array([ True, False, False, False,  True], dtype=bool)

And this boolean mask corresponds to the entries you want to replace.

The time and space complexity of this solution are pretty bad: Θ(nm) to replace m entries in an array of size n due to the large boolean mask. I don't recommend it over in1d for your specific use case, but it shows a detour that is useful in related cases.

3 Comments

yes these intermediate results can definitely be useful. I would suggest writing it as np.any(row[..., None] == a, axis=1) though. It always takes me longer to understand the reshape operations vs the explicit broadcasting.
@MrE: I guess it's a matter of taste; I'm used to the scikit-learn codebase, where reshape(-1, 1) is the common way to transpose a 1-d array. But np.any is a good idea, I should have thought of that myself.
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?
2

An interesting alternative solution is to use numpy.put as documented here. In this case it is also important to think carefully about what will happen if there are duplicates in row. By default, put will cycle through the elements in b if there are more than two matches in this case.

import numpy as np
row = np.array([1,2,3,4,5])
a = np.array([1, 5])
b = np.array([10, 550])
index_list = [np.where(row == element) for element in a]
np.put(row,index_list,b)
row
array([ 10,   2,   3,   4, 550]) #output

Edit: additional example to deal with index-based assignment query in comments:

>>> import numpy as np
>>> target_array = np.arange(50)
>>> n = 2 
>>> index_array = np.arange(0,len(target_array),n)
>>> b = np.array([10, 550])
>>> np.put(target_array, index_array, b)
>>> target_array #note that np.put cycles through the substitutions in b
array([ 10,   1, 550,   3,  10,   5, 550,   7,  10,   9, 550,  11,  10,
        13, 550,  15,  10,  17, 550,  19,  10,  21, 550,  23,  10,  25,
       550,  27,  10,  29, 550,  31,  10,  33, 550,  35,  10,  37, 550,
        39,  10,  41, 550,  43,  10,  45, 550,  47,  10,  49])

2 Comments

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?
I've added an example for that as well.
2

You can now use array.put

>>> a = np.arange(5)
>>> np.put(a, [0, 2], [-44, -55])
>>> a
array([-44,   1, -55,   3,   4])

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.