40

I found this post: Python: finding an element in an array

and it's about returning the index of an array through matching the values.

On the other hand, what I am thinking of doing is similar but different. I would like to find the nearest value for the target value. For example I am looking for 4.2 but I know in the array there is no 4.2 but I want to return the index of the value 4.1 instead of 4.4.

What would be the fastest way of doing it?

I am thinking of doing it the old way like how I used to do it with Matlab, which is using the array A where I want to get the index from to minus the target value and take the absolute of it, then select the min. Something like this:-

[~,idx] = min(abs(A - target))

That is Matlab code but I am newbie in Python so I am thinking, is there a fast way of doing it in Python?

Thank you so much for your help!

Community
  • 1
  • 1
Harry MacDowel
  • 843
  • 1
  • 10
  • 17
  • http://stackoverflow.com/questions/2566412/find-nearest-value-in-numpy-array and http://stackoverflow.com/questions/6065697/python-numpy-quickly-find-the-index-in-an-array-closest-to-some-value might be useful. – DSM Jan 18 '12 at 17:31

6 Answers6

36

This is similar to using bisect_left, but it'll allow you to pass in an array of targets

def find_closest(A, target):
    #A must be sorted
    idx = A.searchsorted(target)
    idx = np.clip(idx, 1, len(A)-1)
    left = A[idx-1]
    right = A[idx]
    idx -= target - left < right - target
    return idx

Some explanation:

First the general case: idx = A.searchsorted(target) returns an index for each target such that target is between A[index - 1] and A[index]. I call these left and right so we know that left < target <= right. target - left < right - target is True (or 1) when target is closer to left and False (or 0) when target is closer to right.

Now the special case: when target is less than all the elements of A, idx = 0. idx = np.clip(idx, 1, len(A)-1) replaces all values of idx < 1 with 1, so idx=1. In this case left = A[0], right = A[1] and we know that target <= left <= right. Therefor we know that target - left <= 0 and right - target >= 0 so target - left < right - target is True unless target == left == right and idx - True = 0.

There is another special case if target is greater than all the elements of A, In that case idx = A.searchsorted(target) and np.clip(idx, 1, len(A)-1) replaces len(A) with len(A) - 1 so idx=len(A) -1 and target - left < right - target ends up False so idx returns len(A) -1. I'll let you work though the logic on your own.

For example:

In [163]: A = np.arange(0, 20.)

In [164]: target = np.array([-2, 100., 2., 2.4, 2.5, 2.6])

In [165]: find_closest(A, target)
Out[165]: array([ 0, 19,  2,  2,  3,  3])
Ajean
  • 5,528
  • 14
  • 46
  • 69
Bi Rico
  • 25,283
  • 3
  • 52
  • 75
  • Thank you so much @Bago! I am trying to understand the codes and I am having problems with the `idx -= target - left < right - target` part. I am new to Python so I tested the `target - left < right - target` first and they return a range of `True` and `False`. I use the idx to minus the results and I get a slightly distorted array. For example the original index of 0 turns to -1. Am I missing something here? – Harry MacDowel Jan 20 '12 at 09:40
34

The corresponding Numpy code is almost the same, except you use numpy.argmin to find the minimum index.

idx = numpy.argmin(numpy.abs(A - target))
kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • 3
    `numpy.searchsorted` is also handy (and more efficient) if the input array is in sorted order. – Joe Kington Jan 18 '12 at 17:40
  • OP didn't specify it specifically but I thought I'd point out that if `A = [4.1, 4.4, 5, 4.1]` and `target = 4.2`. This code will only return `idx = 0` not `idx = [0, 3]`. Is the only recourse to loop back through `A` comparing each value to the value at `idx = 0`, to determine if there are others? – sgallen Jan 18 '12 at 17:49
  • @sgallen: The Matlab code OP posted gives `idx = 1` (Matlab index is 1-based), so I guess the others aren't needed. – kennytm Jan 18 '12 at 17:52
  • 1
    as @JoeKington has already mentioned `searchsorted` is faster if A is sorted, but also it can take an array of targets as it's argument so it is especially useful if you need to repeat this with more than one target. If that would be useful for you let me know and I'll write it up as an answer. – Bi Rico Jan 18 '12 at 20:54
  • I use this function to search for a sorted array to be honest. I am would really appreciate it if you can guide me on that, @Bago. Thank you so much! You guys are all really helpful. – Harry MacDowel Jan 19 '12 at 13:48
9

Well, more than 2 years have gone by and I have found a very simple implementation from this URL in fact: Find nearest value in numpy array

The implementation is:

def getnearpos(array,value):
    idx = (np.abs(array-value)).argmin()
    return idx   

Cheers!!

Community
  • 1
  • 1
Harry MacDowel
  • 843
  • 1
  • 10
  • 17
5

Tested and timed two solutions:

idx = np.searchsorted(sw, sCut)

and

idx = np.argmin(np.abs(sw - sCut))

for computation in a time expensive method. timing was 113s for computation with the second solution, and 132s for computation with the first one.

kiriloff
  • 25,609
  • 37
  • 148
  • 229
  • 1
    Note that these return different results. `np.argmin(np.abs(np.array([1, 2, 3, 4]) - 2.1))` returns 1, where `np.searchsorted([1, 2, 3, 4], 2.1)` returns 2... `np.searchsorted` returns the insertion location to preserve order, thus it returns the index of the first value after the target, which may not be the nearest value. Using any method that minimizes `abs(value-target)` is actually finding the nearest value which may be above or below the target. Either may be appropriate depending on your needs, but they aren't equivalent. – flutefreak7 Aug 06 '19 at 17:26
2

Possible solution:

>>> a = [1.0, 3.2, -2.5, -3.1]
>>> i = -1.5
>>> diff = [(abs(i - x),idx) for (idx,x) in enumerate(a)]
>>> diff
[(2.5, 0), (4.7, 1), (1.0, 2), (1.6, 3)]
>>> diff.sort()
>>> diff
[(1.0, 2), (1.6, 3), (2.5, 0), (4.7, 1)]

You'll have the index of nearest value in diff[0][1]

Maciek
  • 3,174
  • 1
  • 22
  • 26
0
def finder(myList, target)
    diff = ''
    index = None
    for i,num in enumerate(myList):
        if abs(target - num) < diff:
            diff = abs(target - num)
            index = i
    return index

Hope this helps

EDIT:

If you'd like a one-liner, then you might like this better:

min(L, key=lambda x: abs(target-x))
inspectorG4dget
  • 110,290
  • 27
  • 149
  • 241