Encuentra el valor más cercano en una matriz numpy

336

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

Ejemplo:

np.find_nearest( array, value )
Fookatchu
fuente

Respuestas:

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
unutbu
fuente
52
@EOL: return np.abs(array-value).min()da la respuesta incorrecta. Esto le da el mínimo de la distancia del valor absoluto, y de alguna manera necesitamos devolver el valor real del conjunto. Podríamos añadir valuey acercarse, pero el valor absoluto lanza una llave en las cosas ...
unutbu
99
@ ~ unutbu Tienes razón, mi mal. ¡No puedo pensar en nada mejor que tu solución!
Eric O Lebigot
24
Parece una locura, no hay un numpy incorporado que hace esto.
dbliss
3
@jsmedmar El método de bisección (ver mi respuesta a continuación) es O (log (n)).
Josh Albert el
44
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.Usar en idxminlugar de argminfunciona para mí con la solución anterior. (v3.6.4)
jorijnsmit
78

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 escala a matrices muy grandes. Puede modificar fácilmente lo anterior para ordenar en el método si no puede asumir que la matriz ya está ordenada. Es excesivo para arreglos pequeños, pero una vez que se hacen grandes, esto es mucho más rápido.

Demitri
fuente
Eso suena como la solución más razonable. Me pregunto por qué es tan lento de todos modos. Plain np.searchsortedtoma alrededor de 2 µs para mi conjunto de prueba, toda la función unos 10 µs. Usarlo np.absestá empeorando. No tengo idea de qué hace Python allí.
Michael
2
@ Michael Para valores individuales, las rutinas matemáticas de Numpy serán más lentas que las mathrutinas, vea esta respuesta .
Demitri
3
Esta es la mejor solución si tiene varios valores que desea buscar a la vez (con algunos ajustes). Todo if/elsedebe ser reemplazado poridx = idx - (np.abs(value - array[idx-1]) < np.abs(value - array[idx])); return array[idx]
coderforlife
3
Esto es genial, pero no funciona si valuees más grande que arrayel elemento más grande. ¡Cambié la ifdeclaración para if idx == len(array) or math.fabs(value - array[idx - 1]) < math.fabs(value - array[idx])que funcione para mí!
nicoco
3
Esto no funciona cuando idx es 0. El if debería leer:if idx > 0 and (idx == len(array) or math.fabs(value - array[idx-1]) < math.fabs(value - array[idx])):
JPaget
52

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()]
kwgoodman
fuente
66
El bit "plano" no es necesario. a[np.abs(a-a0).argmin)]funciona bien.
Max Shron
2
En realidad, eso solo funciona para una dimensión, ya que argmin () da múltiples resultados por columna / dimensión. También tuve un error tipográfico. Esto funciona, al menos durante 2 dimensiones: a[np.sum(np.square(np.abs(a-a0)),1).argmin()].
Max Shron
3
Por lo tanto, no funciona para dimensiones superiores, y la respuesta debe eliminarse (o modificarse para reflejar esto)
Hugues Fontenelle
11
Proporcione un ejemplo donde la respuesta propuesta no funcione. Si encuentra uno, modificaré mi respuesta. Si no puede encontrar uno, ¿podría eliminar sus comentarios?
kwgoodman
18

Resumen de la respuesta : si uno tiene un orden array, el código de bisección (que se muestra a continuación) se realiza más rápido. ~ 100-1000 veces más rápido para matrices grandes y ~ 2-100 veces más rápido para matrices pequeñas. No requiere numpy tampoco. Si tiene un no ordenado, arrayentonces si arrayes grande, primero debe considerar usar una clasificación O (n logn) y luego una bisección, y si arrayes pequeño, entonces el método 2 parece el más rápido.

Primero debes aclarar lo que quieres decir con el valor más cercano . A menudo se 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 se puede modificar muy fácilmente con una declaración condicional de seguimiento una vez que encuentre el intervalo). Notaré que la forma óptima de realizar esto es con la bisección (que proporcionaré primero; tenga en cuenta que no requiere numpy en absoluto y es más rápido que usar funciones numpy porque realizan operaciones redundantes). Luego proporcionaré una comparación de tiempos con los otros presentados aquí por otros usuarios.

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 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 correctamente el intervalo. 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 redondea hacia arriba (por ejemplo, 1.45 -> 2). Solo 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 gran matriz, 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 arreglos más pequeños es ~ 2-100 veces más rápido.

Josh Albert
fuente
2
Estás asumiendo que la matriz está ordenada. Hay muchas razones por las cuales alguien no querría ordenar la matriz: por ejemplo, si la matriz representa los puntos de datos en un gráfico lineal.
user1917407
77
La biblioteca estándar de Python ya contiene la implementación del algoritmo de bisección: docs.python.org/3.6/library/bisect.html
Felix
Cuando dijo: "si arrayes pequeño, entonces el método 2 parece el más rápido". ¿Qué tan pequeño quisiste decir @JoshAlbert?
Mr.Zeus
2
Esto no encuentra el valor más cercano , encuentra el siguiente valor más bajo.
Endolito
@endolith ese es el caso solo para bisectos.
Homero Esmeraldo
17

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])
Onasafari
fuente
Creo que norm(..., axis=-1)debería ser más rápido que extraer los x,yvalores a través de la iteración de Python. Además, ¿ x,yhay escalares aquí? Entonces norm(x+y)es un error ya que, por ejemplo, la distancia (+1, -1)se tratará como 0.
cfh
Esto funcionó para míidx = np.array([np.linalg.norm(x+y) for (x,y) in abs(array-value)]).argmin()
ezchx
9

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]
Nick Crawford
fuente
9

Aquí hay una versión que manejará una matriz de "valores" no escalares:

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]
Ryggyr
fuente
Buena respuesta, nunca he usado el outermétodo de un ufunc antes, creo que lo usaré más en el futuro. La primera función debería volver array[indices], por cierto.
Widjet
1
Esta solución no escala. np.subtract.outergenerará toda la matriz del producto externo que es realmente lenta y requiere mucha memoria si arrayy / o valueses muy grande.
anthonybell
8

Aquí hay una versión con scipy para @Ari Onasafari, responda " para encontrar el vector más cercano en una matriz 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])
efirvida
fuente
Construir un KDTree es una sobrecarga para tal problema. No recomendaría tal solución a menos que tenga que hacer múltiples consultas en una gran matriz ... Y luego, sería mejor construirla una vez y reutilizarla, en lugar de crearla sobre la marcha para cada consulta.
Ben
8

Aquí hay una versión rápida y vectorizada de la solución de @ Dimitri si tiene muchas valuespara buscar ( valuespuede 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 forbucle 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
anthonybell
fuente
en caso de que tenga un muestreo constante en la matriz, se vuelve aún más simple: idx = np.searchsorted(array, values)luego: idx[array[idx] - values>np.diff(array).mean()*0.5]-=1y finalmentereturn array[idx]
Sergey Antopolskiy
7

Para matrices grandes, la respuesta (excelente) 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 función a continuación también maneja un caso de borde específico que conduciría a 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
aph
fuente
1
Vale la pena señalar que este es un gran ejemplo de cómo la optimización del código tiende a hacerlo más feo y difícil de leer. La respuesta dada por @unutbu debería ser (mucho) preferida en casos donde la velocidad no es una preocupación importante, ya que es mucho más transparente.
aph
No veo la respuesta dada por @Michael. ¿Es esto un error o estoy ciego?
Fookatchu
No, no eres ciego, solo soy analfabeto ;-) Fue @Demitri cuya respuesta estaba hablando. Culpa mía. Acabo de arreglar mi publicación. ¡Gracias!
aph
Recibo diferentes respuestas con Demitri y las suyas. ¿Algunas ideas? x = np.array([2038, 1758, 1721, 1637, 2097, 2047, 2205, 1787, 2287, 1940, 2311, 2054, 2406, 1471, 1460]). Con find_nearest(x, 1739.5)(valor más cercano al primer cuantil), obtengo 1637(razonable) y 1(¿error?).
PatrickT
3

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)
Zhanwen Chen
fuente
2

Creo que la forma más pitónica 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

Ishan Tomar
fuente
2

Todas las respuestas son beneficiosas para recopilar la información para escribir código eficiente. Sin embargo, he escrito un pequeño script de Python para optimizarlo en varios casos. Será el mejor caso si se ordena la matriz proporcionada. Si uno busca el índice del punto más cercano de un valor especificado, entonces el bisectmódulo es el más eficiente en el tiempo. Cuando uno busca los índices corresponden a una matriz, el numpy searchsortedes 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]:% de tiempo np.searchsorted (xar, 0.3, side = "left") Tiempo 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")

% de tiempo np.searchsorted (xar, randpts, side = "left") Tiempo de CPU: usuario 4 ms, sys: 0 ns, total: 4 ms Tiempo de muro: 1,2 ms

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

Soumen
fuente
1

Para la matriz 2d, para determinar la posición i, j del elemento más cercano:

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
Eduardo S. Pereira
fuente
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))
kareem mohamed
fuente
1
Hola, bienvenido a Stack Overflow. Mira cómo escribir una buena respuesta . ¡Intenta dar una breve descripción de lo que hiciste en el contexto de la pregunta!
Tristo
0

Quizás útil para ndarrays:

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