Знайдіть найближче значення в numpy масиві


336

Чи є нуме-тонічний спосіб, наприклад функція, щоб знайти найближче значення в масиві?

Приклад:

np.find_nearest( array, value )

Відповіді:


516
import numpy as np
def find_nearest(array, value):
    array = np.asarray(array)
    idx = (np.abs(array - value)).argmin()
    return array[idx]

array = np.random.random(10)
print(array)
# [ 0.21069679  0.61290182  0.63425412  0.84635244  0.91599191  0.00213826
#   0.17104965  0.56874386  0.57319379  0.28719469]

value = 0.5

print(find_nearest(array, value))
# 0.568743859261

52
@EOL: return np.abs(array-value).min()дає неправильну відповідь. Це дає вам хв абсолютної величини відстані, і нам якось потрібно повернути фактичне значення масиву. Ми могли б додати valueі наблизитись, але абсолютна величина кидає ключ до речей ...
unutbu

9
@ ~ unutbu Ти маєш рацію, моя погана. Я не можу придумати нічого кращого, ніж твоє рішення!
Ерік О Лебігот

24
здається божевільним, немає вбудованого, який це робить.
dbliss

3
@jsmedmar Метод розбиття (див. мою відповідь нижче) - це O (log (n)).
Джош Альберт

4
FutureWarning: 'argmin' is deprecated. Use 'idxmin' instead. The behavior of 'argmin' will be corrected to return the positional minimum in the future. Use 'series.values.argmin' to get the position of the minimum now.Використання idxminзамість цього argminпрацює для мене з рішенням вище. (v3.6.4)
jorijnsmit

78

ЯКЩО ваш масив відсортований і дуже великий, це набагато швидше рішення:

def find_nearest(array,value):
    idx = np.searchsorted(array, value, side="left")
    if idx > 0 and (idx == len(array) or math.fabs(value - array[idx-1]) < math.fabs(value - array[idx])):
        return array[idx-1]
    else:
        return array[idx]

Це масштабується до дуже великих масивів. Ви можете легко змінити вищезазначене для сортування в методі, якщо ви не можете припустити, що масив вже відсортований. Для невеликих масивів це надмірно, але коли вони набудуть великого розміру, це набагато швидше.


Це звучить як найбільш розумне рішення. Цікаво, чому все одно так повільно. np.searchsortedДля мого тестового набору рівнина займає приблизно 2 мкс, вся функція - близько 10 мкс. Використання np.absстає ще гірше. Немає поняття, що пітон там робить.
Майкл

2
@Michael Що стосується окремих значень, математичні процедури Numpy будуть повільнішими, ніж mathпідпрограми, див. Цю відповідь .
Демітрі

3
Це найкраще рішення, якщо у вас є кілька значень, які ви хочете переглянути відразу (з кількома коригуваннями). Ціле if/elseпотрібно замінити наidx = idx - (np.abs(value - array[idx-1]) < np.abs(value - array[idx])); return array[idx]
coderforlife

3
Це чудово, але не спрацьовує, якщо valueбільший за arrayнайбільший елемент '. Я змінив ifзаяву, if idx == len(array) or math.fabs(value - array[idx - 1]) < math.fabs(value - array[idx])щоб змусити мене працювати!
нікоко

3
Це не працює, коли idx дорівнює 0. Якщо слід прочитати:if idx > 0 and (idx == len(array) or math.fabs(value - array[idx-1]) < math.fabs(value - array[idx])):
JPaget

52

З невеликою модифікацією відповідь вище працює з масивами довільного розміру (1d, 2d, 3d, ...):

def find_nearest(a, a0):
    "Element in nd array `a` closest to the scalar value `a0`"
    idx = np.abs(a - a0).argmin()
    return a.flat[idx]

Або записується як один рядок:

a.flat[np.abs(a - a0).argmin()]

6
"Плоский" біт не потрібен. a[np.abs(a-a0).argmin)]працює чудово.
Макс Шрон

2
Насправді це все ще працює лише для одного виміру, оскільки argmin () дає кілька результатів на стовпець / вимір. Також у мене був друкарський помилок. Це працює, по крайней мере , для 2 -х вимірах: a[np.sum(np.square(np.abs(a-a0)),1).argmin()].
Макс Шрон

3
Отже, це не працює для більш високих розмірів, і відповідь слід видалити (або змінити, щоб відобразити це)
Hugues Fontenelle

11
Наведіть приклад, коли запропонована відповідь не працює. Якщо ви знайдете його, я модифікую свою відповідь. Якщо ви не можете його знайти, чи можете ви видалити свої коментарі?
kwgoodman

18

Короткий зміст відповіді : Якщо впорядковано, arrayто бісекційний код (наведений нижче) виконує найшвидше. ~ 100-1000 разів швидше для великих масивів і ~ 2-100 разів швидше для малих масивів. Він також не вимагає нумету. Якщо у вас несортовано, arrayтоді, якщо arrayвін великий, слід спершу використати сортування O (n logn), а потім бісекцію, а якщо arrayмало, то метод 2 здається найшвидшим.

Спочатку слід уточнити, що ви маєте на увазі під найближчим значенням . Часто хочеться, щоб інтервал в абсцисі, наприклад, масив = [0,0.7,2.1], значення = 1,95, відповідь буде idx = 1. Я вважаю, що це вам потрібно (інакше наступне можна легко змінити за допомогою наступного умовного твердження, як тільки ви знайдете інтервал). Зауважу, що оптимальним способом для цього є бісекція (яку я надам по-перше - зауважте, що вона взагалі не потребує нумерування та швидша, ніж використання функцій numpy, оскільки вони виконують зайві операції). Тоді я наведу порівняння часу щодо інших, представлених тут іншими користувачами.

Розріз:

def bisection(array,value):
    '''Given an ``array`` , and given a ``value`` , returns an index j such that ``value`` is between array[j]
    and array[j+1]. ``array`` must be monotonic increasing. j=-1 or j=len(array) is returned
    to indicate that ``value`` is out of range below and above respectively.'''
    n = len(array)
    if (value < array[0]):
        return -1
    elif (value > array[n-1]):
        return n
    jl = 0# Initialize lower
    ju = n-1# and upper limits.
    while (ju-jl > 1):# If we are not yet done,
        jm=(ju+jl) >> 1# compute a midpoint with a bitshift
        if (value >= array[jm]):
            jl=jm# and replace either the lower limit
        else:
            ju=jm# or the upper limit, as appropriate.
        # Repeat until the test condition is satisfied.
    if (value == array[0]):# edge cases at bottom
        return 0
    elif (value == array[n-1]):# and top
        return n-1
    else:
        return jl

Тепер я визначу код з інших відповідей, кожен з них повертає індекс:

import math
import numpy as np

def find_nearest1(array,value):
    idx,val = min(enumerate(array), key=lambda x: abs(x[1]-value))
    return idx

def find_nearest2(array, values):
    indices = np.abs(np.subtract.outer(array, values)).argmin(0)
    return indices

def find_nearest3(array, values):
    values = np.atleast_1d(values)
    indices = np.abs(np.int64(np.subtract.outer(array, values))).argmin(0)
    out = array[indices]
    return indices

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


def find_nearest5(array, value):
    idx_sorted = np.argsort(array)
    sorted_array = np.array(array[idx_sorted])
    idx = np.searchsorted(sorted_array, value, side="left")
    if idx >= len(array):
        idx_nearest = idx_sorted[len(array)-1]
    elif idx == 0:
        idx_nearest = idx_sorted[0]
    else:
        if abs(value - sorted_array[idx-1]) < abs(value - sorted_array[idx]):
            idx_nearest = idx_sorted[idx-1]
        else:
            idx_nearest = idx_sorted[idx]
    return idx_nearest

def find_nearest6(array,value):
    xi = np.argmin(np.abs(np.ceil(array[None].T - value)),axis=0)
    return xi

Тепер я розберу коди: Примітки 1,2,4,5 не дають інтервал правильно. Методи 1,2,4 обходять до найближчої точки масиву (наприклад,> = 1,5 -> 2), а метод 5 завжди округляється (наприклад, 1,45 -> 2). Тільки методи 3 і 6, і звичайно бісекція дають інтервал належним чином.

array = np.arange(100000)
val = array[50000]+0.55
print( bisection(array,val))
%timeit bisection(array,val)
print( find_nearest1(array,val))
%timeit find_nearest1(array,val)
print( find_nearest2(array,val))
%timeit find_nearest2(array,val)
print( find_nearest3(array,val))
%timeit find_nearest3(array,val)
print( find_nearest4(array,val))
%timeit find_nearest4(array,val)
print( find_nearest5(array,val))
%timeit find_nearest5(array,val)
print( find_nearest6(array,val))
%timeit find_nearest6(array,val)

(50000, 50000)
100000 loops, best of 3: 4.4 µs per loop
50001
1 loop, best of 3: 180 ms per loop
50001
1000 loops, best of 3: 267 µs per loop
[50000]
1000 loops, best of 3: 390 µs per loop
50001
1000 loops, best of 3: 259 µs per loop
50001
1000 loops, best of 3: 1.21 ms per loop
[50000]
1000 loops, best of 3: 746 µs per loop

Для великого масиву бісекція дає 4us порівняно з наступним кращим 180us і найдовшим 1,21ms (~ 100 - 1000 разів швидше). Для менших масивів це ~ 2-100 разів швидше.


2
Ви припускаєте, що масив відсортований. Є багато причин, чому хтось не хотів би сортувати масив: наприклад, якщо масив представляв точки даних на лінійному графіку.
користувач1917407

7
Стандартна бібліотека python вже містить алгоритм розділення
Felix

Коли ви сказали, "якщо arrayмало, то метод 2 здається найшвидшим". як малий ти мав на увазі @JoshAlbert?
Mr.Zeus

2
Це не знаходить найближчого значення, воно знаходить наступне найменше значення.
ендоліт

@endolith це стосується лише бісектії.
Хомеро Есмеральдо

17

Ось розширення для пошуку найближчого вектора в масиві векторів.

import numpy as np

def find_nearest_vector(array, value):
  idx = np.array([np.linalg.norm(x+y) for (x,y) in array-value]).argmin()
  return array[idx]

A = np.random.random((10,2))*100
""" A = array([[ 34.19762933,  43.14534123],
   [ 48.79558706,  47.79243283],
   [ 38.42774411,  84.87155478],
   [ 63.64371943,  50.7722317 ],
   [ 73.56362857,  27.87895698],
   [ 96.67790593,  77.76150486],
   [ 68.86202147,  21.38735169],
   [  5.21796467,  59.17051276],
   [ 82.92389467,  99.90387851],
   [  6.76626539,  30.50661753]])"""
pt = [6, 30]  
print find_nearest_vector(A,pt)
# array([  6.76626539,  30.50661753])

Я думаю, що це norm(..., axis=-1)має бути швидше, ніж вилучення x,yзначень за допомогою ітерації Python. Також x,yтут скаляри? Тоді norm(x+y)помилка, оскільки, наприклад, відстань (+1, -1)буде розглядатись як 0.
cfh

Це працювало для менеidx = np.array([np.linalg.norm(x+y) for (x,y) in abs(array-value)]).argmin()
ezchx

9

Якщо ви не хочете використовувати numpy, зробіть це:

def find_nearest(array, value):
    n = [abs(i-value) for i in array]
    idx = n.index(min(n))
    return array[idx]

9

Ось версія, яка буде обробляти не скалярний масив "значень":

import numpy as np

def find_nearest(array, values):
    indices = np.abs(np.subtract.outer(array, values)).argmin(0)
    return array[indices]

Або версія, яка повертає числовий тип (наприклад, int, float), якщо вхід скалярний:

def find_nearest(array, values):
    values = np.atleast_1d(values)
    indices = np.abs(np.subtract.outer(array, values)).argmin(0)
    out = array[indices]
    return out if len(out) > 1 else out[0]

Хороша відповідь, я ніколи раніше не використовував outerметод ufunc, думаю, буду використовувати його більше в майбутньому. Перша функція повинна array[indices], до речі, повертатися .
Widjet

1
Це рішення не масштабується. np.subtract.outerбуде генерувати всю матрицю зовнішнього продукту, яка є дуже повільною і об'ємною, якщо arrayта / або valuesдуже велика.
anthonybell

8

Ось версія з scipy для @Ari Onasafari, відповідь " знайти найближчий вектор у масиві векторів "

In [1]: from scipy import spatial

In [2]: import numpy as np

In [3]: A = np.random.random((10,2))*100

In [4]: A
Out[4]:
array([[ 68.83402637,  38.07632221],
       [ 76.84704074,  24.9395109 ],
       [ 16.26715795,  98.52763827],
       [ 70.99411985,  67.31740151],
       [ 71.72452181,  24.13516764],
       [ 17.22707611,  20.65425362],
       [ 43.85122458,  21.50624882],
       [ 76.71987125,  44.95031274],
       [ 63.77341073,  78.87417774],
       [  8.45828909,  30.18426696]])

In [5]: pt = [6, 30]  # <-- the point to find

In [6]: A[spatial.KDTree(A).query(pt)[1]] # <-- the nearest point 
Out[6]: array([  8.45828909,  30.18426696])

#how it works!
In [7]: distance,index = spatial.KDTree(A).query(pt)

In [8]: distance # <-- The distances to the nearest neighbors
Out[8]: 2.4651855048258393

In [9]: index # <-- The locations of the neighbors
Out[9]: 9

#then 
In [10]: A[index]
Out[10]: array([  8.45828909,  30.18426696])

Побудова KDTree - це досить великі витрати для такої проблеми. Я б не рекомендував таке рішення, якщо вам не доведеться робити кілька запитів на великому масиві ... І тоді, краще було б один раз побудувати його і повторно використовувати, а не створювати його на ходу для кожного запиту.
Бен

8

Ось швидка векторизована версія рішення @ Димитрі, якщо вам потрібно багато valuesшукати ( valuesможе бути багатовимірний масив):

#`values` should be sorted
def get_closest(array, values):
    #make sure array is a numpy array
    array = np.array(array)

    # get insert positions
    idxs = np.searchsorted(array, values, side="left")

    # find indexes where previous index is closer
    prev_idx_is_less = ((idxs == len(array))|(np.fabs(values - array[np.maximum(idxs-1, 0)]) < np.fabs(values - array[np.minimum(idxs, len(array)-1)])))
    idxs[prev_idx_is_less] -= 1

    return array[idxs]

Орієнтири

> У 100 разів швидше, ніж використання forциклу з рішенням @ Demitri '

>>> %timeit ar=get_closest(np.linspace(1, 1000, 100), np.random.randint(0, 1050, (1000, 1000)))
139 ms ± 4.04 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

>>> %timeit ar=[find_nearest(np.linspace(1, 1000, 100), value) for value in np.random.randint(0, 1050, 1000*1000)]
took 21.4 seconds

якщо ви маєте постійну вибірку в масиві, вона стає ще простішою: idx = np.searchsorted(array, values)тоді: idx[array[idx] - values>np.diff(array).mean()*0.5]-=1і нарештіreturn array[idx]
Сергій Антопольський,

7

Для великих масивів (відмінна) відповідь, яку дав @Demitri, набагато швидша, ніж відповідь, яка в даний час позначена як найкраща. Я адаптував його точний алгоритм наступними двома способами:

  1. Функція нижче працює, сортуючи вхідний масив чи ні.

  2. Функція нижче повертає індекс вхідного масиву, що відповідає найближчому значенню, яке дещо більш загальне.

Зауважте, що функція нижче також обробляє конкретний край, який може призвести до помилки в початковій функції, написаній @Demitri. Інакше мій алгоритм ідентичний його.

def find_idx_nearest_val(array, value):
    idx_sorted = np.argsort(array)
    sorted_array = np.array(array[idx_sorted])
    idx = np.searchsorted(sorted_array, value, side="left")
    if idx >= len(array):
        idx_nearest = idx_sorted[len(array)-1]
    elif idx == 0:
        idx_nearest = idx_sorted[0]
    else:
        if abs(value - sorted_array[idx-1]) < abs(value - sorted_array[idx]):
            idx_nearest = idx_sorted[idx-1]
        else:
            idx_nearest = idx_sorted[idx]
    return idx_nearest

1
Варто зазначити, що це чудовий приклад того, як оптимізація коду, як правило, робить її більш бридкою та важкою для читання. Відповідь, надану @unutbu, слід (набагато) віддавати перевагу в тих випадках, коли швидкість не викликає особливих проблем, оскільки вона набагато прозоріша.
aph

Я не бачу відповіді, яку дав @Michael. Це помилка чи я сліпий?
Fookatchu

Ні, ти не сліпий, я просто неграмотний ;-) Це був @Demitri, чию відповідь я підробляв. Моє ліжко. Я щойно виправив свою посаду. Дякую!
aph

Я отримую різні відповіді з дімітрійськими та вашими. Будь-які ідеї? x = np.array([2038, 1758, 1721, 1637, 2097, 2047, 2205, 1787, 2287, 1940, 2311, 2054, 2406, 1471, 1460]). З find_nearest(x, 1739.5)(найближче значення до першого квантиля) я отримую 1637(розумне) і 1(помилка?).
PatrickT

3

Це векторизована версія відповіді унутбу :

def find_nearest(array, values):
    array = np.asarray(array)

    # the last dim must be 1 to broadcast in (array - values) below.
    values = np.expand_dims(values, axis=-1) 

    indices = np.abs(array - values).argmin(axis=-1)

    return array[indices]


image = plt.imread('example_3_band_image.jpg')

print(image.shape) # should be (nrows, ncols, 3)

quantiles = np.linspace(0, 255, num=2 ** 2, dtype=np.uint8)

quantiled_image = find_nearest(quantiles, image)

print(quantiled_image.shape) # should be (nrows, ncols, 3)

2

Я думаю, що найбільш пітонічним способом було б:

 num = 65 # Input number
 array = n.random.random((10))*100 # Given array 
 nearest_idx = n.where(abs(array-num)==abs(array-num).min())[0] # If you want the index of the element of array (array) nearest to the the given number (num)
 nearest_val = array[abs(array-num)==abs(array-num).min()] # If you directly want the element of array (array) nearest to the given number (num)

Це основний код. Ви можете використовувати його як функцію, якщо хочете


2

Усі відповіді корисні для збору інформації для написання ефективного коду. Однак я написав невеликий сценарій Python для оптимізації для різних випадків. Найкраще буде, якщо поданий масив буде відсортований. Якщо шукати індекс найближчої точки визначеного значення, bisectмодуль є найбільш ефективним у часі. Коли за одним пошуком індекси відповідають масиву, numpy searchsortedнайефективнішим є.

import numpy as np
import bisect
xarr = np.random.rand(int(1e7))

srt_ind = xarr.argsort()
xar = xarr.copy()[srt_ind]
xlist = xar.tolist()
bisect.bisect_left(xlist, 0.3)

In [63]:% time bisect.bisect_left (xlist, 0,3) CPU разів: користувач 0 ns, sys: 0 ns, всього: 0 ns Час стіни: 22,2 µs

np.searchsorted(xar, 0.3, side="left")

У [64]:% час np.searchsorted (xar, 0,3, сторона = "ліворуч") CPU разів: користувач 0 ns, sys: 0 ns, всього: 0 ns Час стіни: 98,9 µs

randpts = np.random.rand(1000)
np.searchsorted(xar, randpts, side="left")

% час np.searchsorted (xar, randpts, side = "зліва") Часи процесора: користувач 4 мс, систем: 0 нс, всього: 4 мс Час стіни: 1,2 мс

Якщо ми дотримуємось мультиплікативного правила, то numpy повинен приймати ~ 100 мс, що означає ~ 83X швидше.


1

Для масиву 2d для визначення i, j положення найближчого елемента:

import numpy as np
def find_nearest(a, a0):
    idx = (np.abs(a - a0)).argmin()
    w = a.shape[1]
    i = idx // w
    j = idx - i * w
    return a[i,j], i, j

0
import numpy as np
def find_nearest(array, value):
    array = np.array(array)
    z=np.abs(array-value)
    y= np.where(z == z.min())
    m=np.array(y)
    x=m[0,0]
    y=m[1,0]
    near_value=array[x,y]

    return near_value

array =np.array([[60,200,30],[3,30,50],[20,1,-50],[20,-500,11]])
print(array)
value = 0
print(find_nearest(array, value))

1
Привіт, Ласкаво просимо до Stack Overflow. Перевірте, як написати хорошу відповідь . Спробуйте дати короткий опис того, що ви робили в контексті питання!
Трісто

0

Може бути корисним для ndarrays:

def find_nearest(X, value):
    return X[np.unravel_index(np.argmin(np.abs(X - value)), X.shape)]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.