Encontre o valor mais próximo na matriz numpy


336

Existe uma maneira numpy-tônica, por exemplo, função, para encontrar o valor mais próximo em uma matriz?

Exemplo:

np.find_nearest( array, value )

Respostas:


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()dá a resposta errada. Isso fornece o mínimo da distância do valor absoluto e, de alguma forma, precisamos retornar o valor real da matriz. Poderíamos acrescentar valuee chegar perto, mas o valor absoluto joga uma chave para as coisas ...
unutbu

9
@ ~ unutbu Você está certo, meu mal. Não consigo pensar em nada melhor que a sua solução!
Eric O Lebigot

24
parece louco, não há um entorpecido embutido que faça isso.
dbliss

3
@jsmedmar O método de bissecção (veja minha resposta abaixo) é 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.Usar em idxminvez de argminfunciona para mim com a solução acima. (v3.6.4)
jorijnsmit 15/05

78

Se sua matriz é classificada e é muito grande, esta é uma solução muito mais 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]

Isso é dimensionado para matrizes muito grandes. Você pode modificar facilmente o que foi descrito acima para classificar o método se não puder assumir que a matriz já está classificada. É um exagero para pequenas matrizes, mas uma vez que elas crescem, isso é muito mais rápido.


Parece a solução mais razoável. Eu me pergunto por que é tão lento assim mesmo. Plain np.searchsortedleva cerca de 2 µs para o meu conjunto de testes, toda a função cerca de 10 µs. Usando np.absestá ficando ainda pior. Nenhuma pista do que o python está fazendo lá.
Michael

2
@ Michael Para valores únicos, as rotinas matemáticas do Numpy serão mais lentas que as mathrotinas, consulte esta resposta .
18715 Demitri

3
Esta é a melhor solução se você tiver vários valores que deseja pesquisar de uma só vez (com alguns ajustes). Todo o if/elseprecisa ser substituído comidx = idx - (np.abs(value - array[idx-1]) < np.abs(value - array[idx])); return array[idx]
coderforlife

3
Isso é ótimo, mas não funciona se valuefor maior que arrayo maior elemento. Alterei a ifdeclaração if idx == len(array) or math.fabs(value - array[idx - 1]) < math.fabs(value - array[idx])para fazê-la funcionar para mim!
Nicoco # 3/16

3
Isso não funciona quando idx é 0. A se deve ler:if idx > 0 and (idx == len(array) or math.fabs(value - array[idx-1]) < math.fabs(value - array[idx])):
JPaget

52

Com pequenas modificações, a resposta acima funciona com matrizes de dimensão arbitrária (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]

Ou, escrito como uma única linha:

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

6
A parte "plana" não é necessária. a[np.abs(a-a0).argmin)]funciona bem.
Max Shron

2
Na verdade, isso ainda funciona apenas para uma dimensão, pois argmin () fornece vários resultados por coluna / dimensão. Também tive um erro de digitação. Isso funciona, pelo menos por 2 dimensões: a[np.sum(np.square(np.abs(a-a0)),1).argmin()].
Max Shron

3
Então, ele não funciona para dimensões mais elevadas, ea resposta deve ser excluído (ou modificada para refletir essa)
Hugues Fontenelle

11
Forneça um exemplo em que a resposta proposta não funcione. Se você encontrar um, modificarei minha resposta. Se você não encontrar um, poderá remover seus comentários?
Kwgoodman #

18

Resumo da resposta : se alguém tiver uma ordenada array, o código de bissecção (fornecido abaixo) executa o mais rápido. ~ 100-1000 vezes mais rápido para matrizes grandes e ~ 2-100 vezes mais rápido para matrizes pequenas. Também não requer dormência. Se você tiver uma classificação não classificada arraye, se arrayfor grande, considere primeiro usar uma classificação O (n logn) e, em seguida, bissecção, e se arrayfor pequena, o método 2 parecerá o mais rápido.

Primeiro, você deve esclarecer o que você quer dizer com valor mais próximo . Freqüentemente, se deseja o intervalo em uma abcissa, por exemplo, array = [0,0,7,2.1], valor = 1,95, a resposta seria idx = 1. Suspeito que você precise desse caso (caso contrário, o seguinte pode ser modificado com muita facilidade com uma instrução condicional de acompanhamento depois que você encontrar o intervalo). Observarei que a melhor maneira de fazer isso é com a bissecção (que fornecerei primeiro - note que não requer numpy e é mais rápido do que usar funções numpy porque elas executam operações redundantes). Então, fornecerei uma comparação de tempo com as outras apresentadas aqui por outros usuários.

Bissecção:

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

Agora vou definir o código das outras respostas, cada uma retornando um í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

Agora cronometrarei os códigos: Observe que os métodos 1,2,4,5 não fornecem o intervalo corretamente. Os métodos 1,2,4 arredondam para o ponto mais próximo na matriz (por exemplo,> = 1,5 -> 2) e o método 5 sempre arredonda para cima (por exemplo, 1,45 -> 2). Somente os métodos 3, 6 e, é claro, a bissecção fornecem o intervalo corretamente.

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 uma grande matriz, a bissecção fornece 4us em comparação aos próximos melhores 180us e 1,21ms mais longos (~ 100 - 1000 vezes mais rápido). Para matrizes menores, é ~ 2-100 vezes mais rápido.


2
Você está assumindo que a matriz está classificada. Há muitas razões pelas quais alguém não gostaria de classificar a matriz: por exemplo, se a matriz representasse os pontos de dados em um gráfico de linhas.
precisa saber é o seguinte

7
A biblioteca padrão do Python já contém na implementação do algoritmo de bissecção: docs.python.org/3.6/library/bisect.html
Felix

Quando você disse "se arrayfor pequeno, o método 2 parece o mais rápido". quão pequeno você quis dizer @ JoshAlbert?
Mr.Zeus

2
Não encontra o valor mais próximo , encontra o próximo valor mais baixo.
endolith

@ endolith é o caso apenas da bissecção.
Homero Esmeraldo

17

Aqui está uma extensão para encontrar o vetor mais próximo em uma matriz de vetores.

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

Eu acho que norm(..., axis=-1)deveria ser mais rápido do que extrair os x,yvalores através da iteração Python. Além disso, x,yexistem escalares aqui? Então norm(x+y)é um bug, já que, por exemplo, a distância (+1, -1)será tratada como 0.
cfh

Isso funcionou para mimidx = np.array([np.linalg.norm(x+y) for (x,y) in abs(array-value)]).argmin()
ezchx 24/04

9

Se você não quiser usar o numpy, isso será feito:

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

9

Aqui está uma versão que manipulará uma matriz de "valores" não escalar:

import numpy as np

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

Ou uma versão que retorna um tipo numérico (por exemplo, int, float) se a entrada for 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]

Boa resposta, eu nunca usei o outermétodo de um ufunc antes, acho que vou usá-lo mais no futuro. A primeira função deve retornar array[indices], a propósito.
Widjet

1
Esta solução não é escalável. np.subtract.outerirá gerar toda a matriz do produto externo, que é realmente lenta e consome muita memória se arraye / ou valuesé muito grande.
Anthonybell 12/09

8

Aqui está uma versão com scipy para @Ari Onasafari, responda " para encontrar o vetor mais próximo em uma matriz de vetores "

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

Construir um KDTree é uma sobrecarga para esse problema. Eu não recomendaria essa solução, a menos que você precise fazer várias consultas em uma grande matriz ... E então, seria melhor compilá-la uma vez e reutilizá-la, em vez de criá-la rapidamente para cada consulta.
Ben

8

Aqui está uma versão vetorizada rápida da solução do @ Dimitri, se você tiver muitos valuespara pesquisar ( valuespode ser um array 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]

Benchmarks

> 100 vezes mais rápido do que usar um forloop com a solução da @ 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

no caso de você ter amostragem constante na matriz, torna-se ainda mais simples: idx = np.searchsorted(array, values)então: idx[array[idx] - values>np.diff(array).mean()*0.5]-=1e finalmentereturn array[idx]
Sergey Antopolskiy

7

Para matrizes grandes, a resposta (excelente) dada por @Demitri é muito mais rápida que a resposta atualmente marcada como melhor. Eu adaptei o algoritmo exato das duas maneiras a seguir:

  1. A função abaixo funciona se a matriz de entrada é ou não classificada.

  2. A função abaixo retorna o índice da matriz de entrada correspondente ao valor mais próximo, que é um pouco mais geral.

Observe que a função abaixo também lida com um caso de borda específico que levaria a um erro na função original escrita por @Demitri. Caso contrário, meu algoritmo é idêntico ao dele.

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
Vale ressaltar que este é um ótimo exemplo de como o código de otimização tende a torná-lo mais feio e difícil de ler. A resposta dada por @unutbu deve ser (muito) preferida nos casos em que a velocidade não é uma grande preocupação, pois é muito mais transparente.
APH

Não vejo a resposta dada por @ Michael. Isso é um erro ou estou cego?
Fookatchu 09/04

Não, você não é cego, sou apenas analfabeto ;-) Foi @Demitri cuja resposta eu estava falando. Foi mal. Acabei de corrigir minha postagem. Obrigado!
APH

Eu recebo respostas diferentes com a Demitri e a sua. Alguma ideia? x = np.array([2038, 1758, 1721, 1637, 2097, 2047, 2205, 1787, 2287, 1940, 2311, 2054, 2406, 1471, 1460]). Com find_nearest(x, 1739.5)(valor mais próximo do primeiro quantil), recebo 1637(razoável) e 1(bug?).
precisa saber é o seguinte

3

Esta é uma versão vetorizada da resposta 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)

2

Eu acho que a maneira mais pitônica seria:

 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 é o código básico. Você pode usá-lo como uma função, se quiser


2

Todas as respostas são benéficas para reunir as informações para escrever um código eficiente. No entanto, escrevi um pequeno script Python para otimizar para vários casos. Será o melhor caso se a matriz fornecida for classificada. Se alguém pesquisar o índice do ponto mais próximo de um valor especificado, o bisectmódulo será o mais eficiente em termos de tempo. Quando uma pesquisa nos índices corresponde a uma matriz, numpy searchsortedé mais 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)

Em [63]:% de tempo bisect.bisect_left (xlist, 0,3) Tempo de CPU: usuário 0 ns, sys: 0 ns, total: 0 ns Tempo de parede: 22,2 µs

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

Em [64]:% time np.searchsorted (xar, 0,3, side = "left") tempos de CPU: usuário 0 ns, sys: 0 ns, total: 0 ns tempo de parede: 98,9 µs

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

% time np.searchsorted (xar, randpts, side = "left") Tempos de CPU: usuário 4 ms, sys: 0 ns, total: 4 ms Tempo de parede: 1,2 ms

Se seguirmos a regra multiplicativa, numpy deve demorar ~ 100 ms, o que implica ~ 83X mais rápido.


1

Para matriz 2d, para determinar a posição i, j do elemento mais próximo:

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
Olá, bem-vindo ao Stack Overflow. Veja como escrever uma boa resposta . Tente fazer uma breve descrição do que você fez no contexto da pergunta!
tristo

0

Talvez útil para ndarrays:

def find_nearest(X, value):
    return X[np.unravel_index(np.argmin(np.abs(X - value)), X.shape)]
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.