4

I need to find the index of the first element in an array, that is close to a float within a given tolerance.

I can do this with a for block:

import numpy as np

# Define array of floats
a = np.random.uniform(0., 10., 10.)

# Float to search, and tolerance.
val, tol = 6.87, 0.1

for i, _ in enumerate(a):
    if np.isclose(_, val, tol):
        print('Index is: {}'.format(i))
        break

but I was wondering if there might be a one-liner solution using some numpy function.

Notice that I'm interested in the index of the first element that is close to val, no matter that there might be closer elements further down the a array. The solutions I've found are interested in the index of the nearest value, no matter where it is located within the array.

4 Answers 4

8

Here's a one-liner:

Index = next(i for i, _ in enumerate(a) if np.isclose(_, val, tol))

What is this?

The code in parentheses is a generator expression and next returns (you guessed it!) the next (in this case, the first) value the generator will produce. If there'll be no next value, a StopIteration exception will be raised.

Advantages

  1. It isn't memory-consuming as it doesn't need to compute all the possible values and store them.
  2. It's fast as it doesn't continue looping through the array if the needed value is already found.
  3. It will raise an exception if no value could be found.
  4. It could be easily turned into a one-liner function:

    FirstIndex = lambda a, val, tol: next(i for i, _ in enumerate(a) if np.isclose(_, val, tol))
    
    i = FirstIndex(a, val, tol) # call it
    
Sign up to request clarification or add additional context in comments.

5 Comments

This is indeed a much more efficient solution than those of José Sánchez and Divakar.
Looks promising! I tried it on the sample posted in my solution and I got 0 as the index. I was expecting 1.
@Divakar, NumPy 1.8.0 on Pyton 3.5 doesn't show any result when running the OP's code, but your solution returns 0 given the exact same array as in your answer...
@Divakar, this is because numpy.isclose(a, val, rtol, atol) uses a different formula, namely absolute(a - val) <= (atol + rtol * absolute(val)).
Hmm I guess it will work then, thanks for the clarifications!
5

You could use numpy.where:

np.where(np.isclose(a, val, tol))

And the just get the lowest index (i.e. the first result) that is returned by where.

Hope this helps.

Comments

4

Here's a vectorized one-liner -

(np.abs(a - val) <= tol).argmax()

Sample step-by-step run -

In [57]: a
Out[57]: array([5, 3, 9, 6, 8, 3, 5, 1])

In [58]: val = 2

In [59]: tol = 2

In [60]: (np.abs(a - val) < tol) # Many might satisfy
Out[60]: array([False,True,False,False,False,True,False,True], dtype=bool)

In [61]: (np.abs(a - val) <= tol).argmax() # Choose the first one with argmax
Out[61]: 1

1 Comment

Fails if there is no closest value, since it will return 0, which is wrong! You should return 'None' if there is no element closest to 'val'.
1

Come on folks! None of the above are robust. Even Divakar's answer fails if there is no closest value, you'd get Index=0 which is an error. It should probably return 'None' in that case. So at a minimum a one-liner is out of the question. The original question is not well-posed. You need to consider, "...and what if there is no such close value within tolerance?" and then give specifications for what to do in that case.

i_array = np.where(np.isclose(a, val, tol))

is fair enough, but then an empty result still has to be dealt with. So, I'd say a one-liner is going to be clunky, and at minimum a two-liner is preferable:

i_array = np.where(np.isclose(a, val, tol))
my_i = None if (len(i_array[0])==0) else int(i_array[0])

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.