4

I have a numpy array and I have a list of elements I want to insert at specific locations (not contiguous) into that array. The indices are in another numpy array.

target answer: [1,2,3,4,5]
original array: [1,3,5]
elements to insert: [2,4]
indices: [1,3]

numpy.insert(arr,[1,3],[2,4]) dosen't give the desired result. It gives [1,2,3,5,4]. Any pointers?

4
  • 2
    You probably shouldn't be doing this. It's more efficient to work with Python lists if they're changing size Commented Sep 15, 2018 at 15:46
  • The complication is with your choice of indices - are they relative to the original array or the target. insert put the 2 after original[1], and 4 after original[3]. You in contrast want them to be at the result[1] and result[3] slots. Commented Sep 15, 2018 at 15:56
  • @roganjosh, normally multiple insertions into a list require starting with the last, so that growth of the list doesn't mess up the indexing. In this case, that iteration can start from the first, because indices already factors in the growth. Commented Sep 15, 2018 at 16:06
  • @hpaulj I think you're right. I've been trying to benchmark in chat and it's not as horrendous as I believed for arrays Commented Sep 15, 2018 at 16:42

2 Answers 2

4

Use range-offsetted indices with np.insert -

np.insert(a, add_idx - np.arange(len(add_idx)), add_val)

Sample run -

In [20]: a
Out[20]: array([1, 3, 5])

In [21]: add_idx
Out[21]: [1, 3]

In [22]: add_val
Out[22]: [2, 4]

In [23]: np.insert(a, add_idx - np.arange(len(add_idx)), add_val)
Out[23]: array([1, 2, 3, 4, 5])
Sign up to request clarification or add additional context in comments.

1 Comment

This is perfect. I tried to trace this on the paper but I am not quite intuitively clear how this works and how you came up with this. Could you explain? Thanks.
0

The list approach is:

In [122]: alist=[1,3,5]
In [123]: for i,j in zip([1,3],[2,4]):
     ...:     alist[i:i] = [j]
     ...:     
In [124]: alist
Out[124]: [1, 2, 3, 4, 5]

np.insert is a complex function taking different approaches depending on the indices input. But in a case like this is uses a boolean mask approach:

In [126]: arr = np.array([1,3,5])
In [127]: res = np.zeros(5, int) # 5 is len(arr)+len([2,4])
In [128]: mask = res.astype(bool)
In [129]: mask[[1,3]] = True
In [130]: mask
Out[130]: array([False,  True, False,  True, False])
In [131]: res[mask] = [2,4]
In [132]: res[~mask] = arr
In [133]: res
Out[133]: array([1, 2, 3, 4, 5])

mask defines where the new values are supposed to go, and ~mask where the originals go.

insert assumes the indices are given relative to the source array, not the target. To do this same thing, it has to be given [1,2], in other words after arr[1] and arr[2]. insert would adjust [1,2] to [1,3] to use the above masking approach. @Divakar's answer takes your [1,3] and converts it to the [1,2] that insert expects. He in effect is compensating for the offset that insert normally adds.


If we used [1,2] (indices relative to the source list), the list iteration that I used above is wrong. It doesn't account for the fact that the list grows after inserting the 2:

In [134]: alist=[1,3,5]
In [135]: for i,j in zip([1,2],[2,4]):
     ...:     alist[i:i] = [j]   
In [136]: alist
Out[136]: [1, 2, 4, 3, 5]

To compensate for that, a common trick is to insert in reverse order:

In [137]: alist=[1,3,5]
In [138]: for i,j in zip([1,2][::-1],[2,4][::-1]):
     ...:     alist[i:i] = [j]
In [139]: alist
Out[139]: [1, 2, 3, 4, 5]

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.