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

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
unutbu
fonte
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.

Demitri
fonte
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()]
kwgoodman
fonte
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.

Josh Albert
fonte
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])
Onasafari
fonte
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]
Nick Crawford
fonte
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]
ryggyr
fonte
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])
efirvida
fonte
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
anthonybell
fonte
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
aph
fonte
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)
Zhanwen Chen
fonte
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

Ishan Tomar
fonte
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.

Soumen
fonte
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
Eduardo S. Pereira
fonte
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
fonte
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)]
Gusev Slava
fonte