Tìm giá trị gần nhất trong mảng numpy


335

Có cách nào numpy-thonic, ví dụ như chức năng, để tìm giá trị gần nhất trong một mảng không?

Thí dụ:

np.find_nearest( array, value )

Câu trả lời:


514
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()đưa ra câu trả lời sai. Điều này cung cấp cho bạn tối thiểu khoảng cách giá trị tuyệt đối và bằng cách nào đó chúng ta cần trả về giá trị mảng thực tế. Chúng ta có thể thêm valuevà đến gần, nhưng giá trị tuyệt đối ném cờ lê vào mọi thứ ...
unutbu

9
@ ~ unutbu Bạn nói đúng, xấu của tôi. Tôi không thể nghĩ gì tốt hơn giải pháp của bạn!
Eric O Lebigot

24
có vẻ điên rồ không có một tích hợp numpy mà làm điều này.
dbliss

3
@jsmedmar Phương pháp chia đôi (xem câu trả lời dưới đây của tôi) là O (log (n)).
Josh Albert

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.Sử dụng idxminthay vì argminlàm việc cho tôi với giải pháp trên. (
v3.6.4

78

NẾU mảng của bạn được sắp xếp và rất lớn, đây là một giải pháp nhanh hơn nhiều:

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]

Quy mô này để mảng rất lớn. Bạn có thể dễ dàng sửa đổi phần trên để sắp xếp trong phương thức nếu bạn không thể cho rằng mảng đã được sắp xếp. Đó là quá mức cần thiết cho các mảng nhỏ, nhưng một khi chúng lớn, điều này nhanh hơn nhiều.


Nghe có vẻ là giải pháp hợp lý nhất. Tôi tự hỏi tại sao nó rất chậm. Plain np.searchsortedmất khoảng 2 bản vá cho bộ thử nghiệm của tôi, toàn bộ chức năng khoảng 10 bản. Sử dụng np.absnó thậm chí còn tồi tệ hơn. Không biết con trăn đang làm gì ở đó.
Michael

2
@Michael Đối với các giá trị đơn lẻ, các thói quen toán học Numpy sẽ chậm hơn các maththói quen, xem câu trả lời này .
Demitri

3
Đây là giải pháp tốt nhất nếu bạn có nhiều giá trị bạn muốn tra cứu cùng một lúc (với một vài điều chỉnh). Toàn bộ if/elsenhu cầu cần được thay thế bằngidx = idx - (np.abs(value - array[idx-1]) < np.abs(value - array[idx])); return array[idx]
coderforlife

3
Điều này thật tuyệt nhưng không hoạt động nếu valuelớn hơn arrayyếu tố lớn nhất. Tôi đã thay đổi iftuyên bố if idx == len(array) or math.fabs(value - array[idx - 1]) < math.fabs(value - array[idx])để làm cho nó hoạt động cho tôi!
nicoco

3
Điều này không hoạt động khi idx bằng 0. Nếu nên đọc:if idx > 0 and (idx == len(array) or math.fabs(value - array[idx-1]) < math.fabs(value - array[idx])):
JPaget

52

Với một chút sửa đổi, câu trả lời ở trên hoạt động với các mảng có kích thước tùy ý (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]

Hoặc, được viết dưới dạng một dòng duy nhất:

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

6
Các bit "phẳng" là không cần thiết. a[np.abs(a-a0).argmin)]hoạt động tốt
Max Shron

2
Trên thực tế, điều đó vẫn chỉ hoạt động cho một chiều, vì argmin () cho nhiều kết quả trên mỗi cột / thứ nguyên. Ngoài ra tôi đã có một lỗi đánh máy. Điều này hoạt động, ít nhất là cho 2 chiều : a[np.sum(np.square(np.abs(a-a0)),1).argmin()].
Max Shron

3
Vì vậy, nó không hoạt động cho kích thước cao hơn và câu trả lời nên bị xóa (hoặc sửa đổi để phản ánh điều này)
Hugues Fontenelle

11
Vui lòng cung cấp một ví dụ trong đó câu trả lời được đề xuất không hoạt động. Nếu bạn tìm thấy một tôi sẽ sửa đổi câu trả lời của tôi. Nếu bạn không thể tìm thấy một thì bạn có thể loại bỏ ý kiến ​​của bạn?
kwgoodman

18

Tóm tắt câu trả lời : Nếu ai đó đã sắp xếp arraythì mã chia đôi (được đưa ra dưới đây) thực hiện nhanh nhất. ~ 100-1000 lần nhanh hơn cho các mảng lớn và nhanh hơn ~ 2 - 100 lần cho các mảng nhỏ. Nó cũng không yêu cầu numpy. Nếu bạn có một loại chưa được sắp xếp arraythì nếu arraylớn, trước tiên bạn nên xem xét sử dụng sắp xếp O (n logn) và sau đó chia đôi, và nếuarray nhỏ thì phương thức 2 có vẻ là nhanh nhất.

Trước tiên, bạn nên làm rõ những gì bạn có nghĩa là giá trị gần nhất . Thông thường người ta muốn khoảng trong một abscissa, ví dụ mảng = [0,0.7,2.1], value = 1.95, câu trả lời sẽ là idx = 1. Đây là trường hợp mà tôi nghi ngờ bạn cần (nếu không, những điều sau đây có thể được sửa đổi rất dễ dàng với một tuyên bố điều kiện tiếp theo một khi bạn tìm thấy khoảng). Tôi sẽ lưu ý rằng cách tối ưu để thực hiện việc này là chia đôi (mà tôi sẽ cung cấp trước - lưu ý rằng nó không yêu cầu numpy chút nào và nhanh hơn so với sử dụng các hàm numpy vì chúng thực hiện các hoạt động dự phòng). Sau đó, tôi sẽ cung cấp một so sánh thời gian so với những người khác được trình bày ở đây bởi những người dùng khác.

Sai lệch:

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

Bây giờ tôi sẽ xác định mã từ các câu trả lời khác, mỗi câu trả về một chỉ mục:

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

Bây giờ tôi sẽ tính thời gian cho các mã: Lưu ý các phương thức 1,2,4,5 không đưa ra khoảng thời gian chính xác. Các phương thức 1,2,4 làm tròn đến điểm gần nhất trong mảng (ví dụ> = 1,5 -> 2) và phương thức 5 luôn làm tròn (ví dụ: 1,45 -> 2). Chỉ có các phương pháp 3 và 6, và tất nhiên là chia đôi cho khoảng thời gian đúng.

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

Đối với một mảng lớn, chia 4us so với 180us tốt nhất tiếp theo và dài nhất 1,21ms (nhanh hơn 100 - 1000 lần). Đối với các mảng nhỏ hơn, nó nhanh hơn ~ 2 - 100 lần.


2
Bạn đang giả định rằng mảng được sắp xếp. Có nhiều lý do tại sao một người nào đó không muốn sắp xếp mảng: ví dụ: nếu mảng đại diện cho các điểm dữ liệu trên biểu đồ đường.
dùng1917407

7
Thư viện chuẩn python đã chứa trong việc thực hiện thuật toán chia đôi: docs.python.org/3.6/l Library / bisect.html
Felix

Khi bạn nói, "nếu arraynhỏ thì phương pháp 2 có vẻ là nhanh nhất". bạn có ý nghĩa như thế nào @JoshAlbert?
Mr.Zeus

2
Điều này không tìm thấy giá trị gần nhất , nó tìm thấy giá trị thấp nhất tiếp theo.
endolith

@endolith đó chỉ là trường hợp của bisect.
Homero Esmeraldo

17

Đây là một phần mở rộng để tìm vectơ gần nhất trong một mảng các vectơ.

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])

Tôi nghĩ norm(..., axis=-1)nên nhanh hơn trích xuất các x,ygiá trị thông qua Lặp lại Python. Ngoài ra, x,ycó vô hướng ở đây? Sau đó norm(x+y)là một lỗi vì, ví dụ, khoảng cách (+1, -1)sẽ được coi là 0.
cfh

Điều này làm việc cho tôiidx = np.array([np.linalg.norm(x+y) for (x,y) in abs(array-value)]).argmin()
ezchx

9

Nếu bạn không muốn sử dụng numpy, điều này sẽ làm điều đó:

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

9

Đây là phiên bản sẽ xử lý mảng "giá trị" không vô hướng:

import numpy as np

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

Hoặc một phiên bản trả về kiểu số (ví dụ: int, float) nếu đầu vào là vô hướng:

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]

Câu trả lời hay, tôi chưa bao giờ sử dụng outerphương pháp ufunc trước đây, tôi nghĩ rằng tôi sẽ sử dụng nó nhiều hơn trong tương lai. Nhân tiện, chức năng đầu tiên sẽ trở lại array[indices].
Máy bay phản lực

1
Giải pháp này không mở rộng. np.subtract.outersẽ tạo ra toàn bộ ma trận sản phẩm bên ngoài rất chậm và tốn nhiều bộ nhớ nếu arrayvà / hoặc valuesrất lớn.
kêu

8

Đây là một phiên bản với scipy cho @Ari Onasafari, trả lời " để tìm vectơ gần nhất trong một mảng các vectơ "

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])

Xây dựng một KDTree là một chi phí khá lớn cho một vấn đề như vậy. Tôi sẽ không đề xuất một giải pháp như vậy trừ khi bạn phải thực hiện nhiều truy vấn trên một mảng lớn ... Và sau đó, sẽ tốt hơn nếu xây dựng nó một lần và sử dụng lại, thay vì tạo ra nó cho mỗi truy vấn.
Ben

8

Đây là phiên bản được vector hóa nhanh của giải pháp @ Dimitri nếu bạn có nhiều thứ valuesđể tìm kiếm ( valuescó thể là mảng đa chiều):

#`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]

Điểm chuẩn

> Nhanh hơn 100 lần so với sử dụng forvòng lặp với giải pháp của @ 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

trong trường hợp bạn lấy mẫu liên tục trong mảng, nó sẽ trở nên đơn giản hơn: idx = np.searchsorted(array, values)sau đó: idx[array[idx] - values>np.diff(array).mean()*0.5]-=1và cuối cùngreturn array[idx]
Serge Antopolskiy

7

Đối với mảng lớn, câu trả lời (xuất sắc) do @Demitri đưa ra nhanh hơn nhiều so với câu trả lời hiện được đánh dấu là tốt nhất. Tôi đã điều chỉnh thuật toán chính xác của mình theo hai cách sau:

  1. Hàm bên dưới hoạt động cho dù mảng đầu vào có được sắp xếp hay không.

  2. Hàm bên dưới trả về chỉ mục của mảng đầu vào tương ứng với giá trị gần nhất, có phần chung hơn.

Lưu ý rằng chức năng bên dưới cũng xử lý một trường hợp cạnh cụ thể sẽ dẫn đến lỗi trong chức năng ban đầu được viết bởi @Demitri. Nếu không, thuật toán của tôi là giống hệt của mình.

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
Thật đáng để chỉ ra rằng đây là một ví dụ tuyệt vời về cách tối ưu hóa mã có xu hướng làm cho nó xấu hơn và khó đọc hơn. Câu trả lời được đưa ra bởi @unutbu nên được ưu tiên (nhiều) trong trường hợp tốc độ không phải là mối quan tâm chính, vì nó minh bạch hơn nhiều.
aph

Tôi không thấy câu trả lời được đưa ra bởi @Michael. Đây là một lỗi hay tôi bị mù?
Fookatchu

Không, bạn không mù, tôi chỉ là người mù chữ ;-) Đó là @Demitri có câu trả lời mà tôi đang nói. Lỗi của tôi. Tôi chỉ sửa bài của tôi. Cảm ơn!
aph

Tôi nhận được câu trả lời khác nhau với Demitri và của bạn. Có ý kiến ​​gì không? x = np.array([2038, 1758, 1721, 1637, 2097, 2047, 2205, 1787, 2287, 1940, 2311, 2054, 2406, 1471, 1460]). Với find_nearest(x, 1739.5)(giá trị gần nhất với định lượng đầu tiên), tôi nhận được 1637(hợp lý) và 1(lỗi?).
PatrickT

3

Đây là phiên bản véc tơ của câu trả lời của unutbu :

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

Tôi nghĩ rằng cách pythonic nhất sẽ là:

 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)

Đây là mã cơ bản. Bạn có thể sử dụng nó như một chức năng nếu bạn muốn


2

Tất cả các câu trả lời đều có lợi để thu thập thông tin để viết mã hiệu quả. Tuy nhiên, tôi đã viết một tập lệnh Python nhỏ để tối ưu hóa cho các trường hợp khác nhau. Nó sẽ là trường hợp tốt nhất nếu mảng được cung cấp được sắp xếp. Nếu một người tìm kiếm chỉ mục của điểm gần nhất của một giá trị được chỉ định, thì bisectmô-đun là hiệu quả nhất về thời gian. Khi một tìm kiếm các chỉ số tương ứng với một mảng, numpy searchsortedhiệu quả nhất.

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)

Trong [63]:% time bisect.bisect_left (xlist, 0.3) Thời gian CPU: người dùng 0 ns, sys: 0 ns, tổng: 0 ns Thời gian trên tường: 22.2.

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

Trong [64]:% time np.searchsort (xar, 0.3, side = "left") Thời gian CPU: người dùng 0 ns, sys: 0 ns, tổng: 0 ns Thời gian trên tường: 98.9.

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

% time np.searchsort (xar, randpts, side = "left") Thời gian CPU: người dùng 4 ms, sys: 0 ns, tổng cộng: 4 ms Thời gian trên tường: 1,2 ms

Nếu chúng ta tuân theo quy tắc nhân, thì numpy sẽ mất ~ 100 ms nghĩa là nhanh hơn 83 lần.


1

Đối với mảng 2d, để xác định vị trí i, j của phần tử gần nhất:

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
Xin chào, chào mừng bạn đến với Stack Overflow. Kiểm tra làm thế nào để viết một câu trả lời tốt . Hãy thử đưa ra một mô tả ngắn về những gì bạn đã làm trong bối cảnh của câu hỏi!
Tristo

0

Có thể hữu ích cho ndarrays:

def find_nearest(X, value):
    return X[np.unravel_index(np.argmin(np.abs(X - value)), X.shape)]
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.