Encuentra el valor más cercano en la matriz numpy

¿Existe una forma numpy-tónica, por ejemplo, la función, para encontrar el valor más cercano en una matriz?

Ejemplo:

np.find_nearest( array, value ) 

 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 

Si su matriz está ordenada y es muy grande, esta es una solución mucho más rápida:

 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] 

Esto se puede escalar a matrices muy grandes. Puede modificar fácilmente lo anterior para ordenar el método si no puede asumir que la matriz ya está ordenada. Es excesivo para los arreglos pequeños, pero una vez que se hacen grandes, esto es mucho más rápido.

Con una ligera modificación, la respuesta anterior funciona con matrices de dimensión arbitraria (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] 

O, escrito como una sola línea:

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

Aquí hay una extensión para encontrar el vector más cercano en una matriz de vectores.

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

Resumen de la respuesta : Si uno tiene una array ordenada, entonces el código de bisección (que se muestra a continuación) es el que realiza la operación más rápida. ~ 100-1000 veces más rápido para arreglos grandes, y ~ 2-100 veces más rápido para arreglos pequeños. Tampoco requiere numpy. Si tiene una array sin clasificar, entonces si la array es grande, primero debe considerar usar una clasificación O (n logn) y luego la bisección, y si la array es pequeña, entonces el método 2 parece ser el más rápido.

Primero debes aclarar qué quieres decir con el valor más cercano . A menudo, uno quiere el intervalo en una abscisa, por ejemplo, matriz = [0,0.7,2.1], valor = 1.95, la respuesta sería idx = 1. Este es el caso que sospecho que necesita (de lo contrario, lo siguiente puede modificarse muy fácilmente con una statement condicional de seguimiento una vez que encuentre el intervalo). Observaré que la forma óptima de realizar esto es con la bisección (que proporcionaré en primer lugar: no requiere ningún número y es más rápido que usar las funciones de números porque realizan operaciones redundantes). A continuación, proporcionaré una comparación de tiempos con los otros usuarios presentados aquí.

Bisección:

 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 

Ahora definiré el código de las otras respuestas, cada una de ellas devuelve un índice:

 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 

Ahora cronometraré los códigos: los métodos de nota 1,2,4,5 no dan el intervalo correctamente. Los métodos 1,2,4 redondean al punto más cercano en la matriz (por ejemplo,> = 1.5 -> 2), y el método 5 siempre se redondea (por ejemplo, 1.45 -> 2). Sólo los métodos 3 y 6, y por supuesto la bisección, dan el intervalo correctamente.

 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 

Para una matriz grande, la bisección da 4us en comparación con el siguiente mejor 180us y el más largo 1.21ms (~ 100 - 1000 veces más rápido). Para matrices más pequeñas es ~ 2-100 veces más rápido.

Si no quieres usar numpy esto lo hará:

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

Aquí hay una versión con scipy para @Ari Onasafari, responde ” para encontrar el vector más cercano en una variedad de vectores

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

Aquí hay una versión que manejará una matriz de “valores” no escalar:

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

O una versión que devuelve un tipo numérico (por ejemplo, int, float) si la entrada es escalar:

 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] 

Para arreglos grandes, la (excelente) respuesta dada por @Demitri es mucho más rápida que la respuesta actualmente marcada como la mejor. He adaptado su algoritmo exacto de las siguientes dos maneras:

  1. La siguiente función funciona independientemente de si la matriz de entrada está ordenada o no.

  2. La siguiente función devuelve el índice de la matriz de entrada correspondiente al valor más cercano, que es algo más general.

Tenga en cuenta que la siguiente función también maneja un caso de borde específico que podría provocar un error en la función original escrita por @Demitri. De lo contrario, mi algoritmo es idéntico al suyo.

 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 

Aquí hay una versión vectorizada rápida de la solución de @Dimitri si tiene muchos values que buscar (los values pueden ser una matriz multidimensional):

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

Puntos de referencia

> 100 veces más rápido que usar un bucle for con la solución de @ 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 

Esta es una versión vectorizada de la respuesta de 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) 

Creo que la forma más pythonica sería:

  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) 

Este es el código básico. Puedes usarlo como una función si quieres.

Todas las respuestas son beneficiosas para recostackr la información para escribir código eficiente. Sin embargo, he escrito un pequeño script de Python para optimizar varios casos. Será el mejor caso si se ordena la matriz proporcionada. Si se busca el índice del punto más cercano de un valor específico, el módulo bisect es el más eficiente en cuanto al tiempo. Cuando una búsqueda de los índices corresponde a una matriz, la numpy searchsorted es más eficiente.

 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) 

En [63]:% de tiempo bisect.bisect_left (xlist, 0.3) tiempos de CPU: usuario 0 ns, sys: 0 ns, total: 0 ns Tiempo de pared: 22.2 µs

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

En [64]:% time np.searchsorted (xar, 0.3, side = “left”) tiempos de CPU: usuario 0 ns, sys: 0 ns, total: 0 ns Tiempo de pared: 98.9 µs

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

% time np.searchsorted (xar, randpts, side = “left”) tiempos de CPU: usuario 4 ms, sys: 0 ns, total: 4 ms Tiempo de pared: 1.2 ms

Si seguimos la regla multiplicativa, entonces numpy debería tomar ~ 100 ms, lo que implica ~ 83X más rápido.

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

Tal vez útil para ndarrays :

 def find_nearest(X, value): return X[np.unravel_index(np.argmin(np.abs(X - value)), X.shape)]