7

I have indices array

a = np.array([
   [0, 0],
   [1, 1],
   [1, 9]
])

And 2D array

b = np.array([
   [0, 1, 2, 3],
   [5, 6, 7, 8]
])

I can do this one

b[a[:, 0], a[:, 1]]

But it'll be an exception 'out of bounds', because 9 is out of range. I need a very fast way to make array slice by indices and it will be ideal if I can set a clip value, e.g.:

np.indexing_with_clipping(array=b, indices=a, clipping_value=0)
> array([0, 6, --> 0 = clipped value <--])

2 Answers 2

2

Here's an approach -

def indexing_with_clipping(arr, indices, clipping_value=0):
    idx = np.where(indices < arr.shape,indices,clipping_value)
    return arr[idx[:, 0], idx[:, 1]]

Sample runs -

In [266]: arr
Out[266]: 
array([[0, 1, 2, 3],
       [5, 6, 7, 8]])

In [267]: indices
Out[267]: 
array([[0, 0],
       [1, 1],
       [1, 9]])

In [268]: indexing_with_clipping(arr,indices,clipping_value=0)
Out[268]: array([0, 6, 5])

In [269]: indexing_with_clipping(arr,indices,clipping_value=1)
Out[269]: array([0, 6, 6])

In [270]: indexing_with_clipping(arr,indices,clipping_value=2)
Out[270]: array([0, 6, 7])

In [271]: indexing_with_clipping(arr,indices,clipping_value=3)
Out[271]: array([0, 6, 8])

With focus on memory and performance efficiency, here's an approach that modifies the indices within the function -

def indexing_with_clipping_v2(arr, indices, clipping_value=0):
    indices[indices >= arr.shape] = clipping_value
    return arr[indices[:, 0], indices[:, 1]]

Sample run -

In [307]: arr
Out[307]: 
array([[0, 1, 2, 3],
       [5, 6, 7, 8]])

In [308]: indices
Out[308]: 
array([[0, 0],
       [1, 1],
       [1, 9]])

In [309]: indexing_with_clipping_v2(arr,indices,clipping_value=2)
Out[309]: array([0, 6, 7])
Sign up to request clarification or add additional context in comments.

7 Comments

How about np.take?
Thank you for the answer, but this one "np.where(indices < arr.shape,indices,clipping_value)" - is very slow operation for my task :((
@hpaulj yes, I know about np.take, but there are two problems: 1) no clipping value, 2) only 1D arrays...
@MaxTkachenko Would you mind if the original indices array a is modified within the function?
@MaxTkachenko Also, what are the typical shapes of a and b in your actual use-case?
|
2

You can use list comprehension:

b[
    [min(x,len(b[0])-1) for x in a[:,0]],
    [min(x,len(b[1])-1) for x in a[:,1]]
]

edit I used last array value as your clipping value, but you can replace the min() function with whatever you want (e.g. trenary operator)

edit2 OK, based on clarification in comments and all python-fu that I could put together, this snipped finally does what you need:

clipping_value = -1
tmp=np.append(b,[[clipping_value],[clipping_value]],axis=1)
tmp[zip(*[((x,y) if (x<b.shape[0] and y<b.shape[1]) else (0,b.shape[1])) for (x,y) in zip(a.transpose()[0],a.transpose()[1])])]

It is the same as above, just creates ndarray tmp, which is a copy of b but contains the clipping_value as its last element and then uses my previous solution to set indices so, that they point to the last element if either of the indices is bigger than dimensions of b.

I learned that there is reverse to the zip function and that the numpy arrays accept lists as indices. It was fun. Thanks.

5 Comments

I think this is not bad idea and np.clip() is more fast solution here. Thanks :)
@MaxTkachenko But .clip() won't let you set clipping_value, right?
@Divakar I mean to use np.clip() instead of [min(x,len(b[0])-1) for x in a[:,0]] to avoid slow "for".
@MaxTkachenko i see your problem now, my approach only lets you select a value from your original array and clip will be indeed faster.
@MaxTkachenko see my latest edit. is this what you need?

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.