Trova il valore più vicino nell'array numpy


336

Esiste un modo intorpidito-tonico, ad esempio la funzione, per trovare il valore più vicino in un array?

Esempio:

np.find_nearest( array, value )

Risposte:


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à la risposta sbagliata. Questo ti dà il minimo della distanza del valore assoluto e in qualche modo dobbiamo restituire il valore effettivo dell'array. Potremmo aggiungere valuee avvicinarci, ma il valore assoluto getta una chiave inglese nelle cose ...
unutbu

9
@ ~ unutbu Hai ragione, mia cattiva. Non riesco a pensare a niente di meglio della tua soluzione!
Eric O Lebigot,

24
sembra pazzo non c'è un insensibile built-in che lo fa.
dbliss,

3
@jsmedmar Il metodo di bisection (vedi la mia risposta di seguito) è 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.Utilizzando idxmininvece di argminfunziona per me con la soluzione sopra. (v3.6.4)
jorijnsmit

78

Se il tuo array è ordinato ed è molto grande, questa è una soluzione molto più veloce:

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]

Questo si adatta a matrici molto grandi. È possibile modificare facilmente quanto sopra per ordinare nel metodo se non si può presumere che l'array sia già ordinato. È eccessivo per gli array di piccole dimensioni, ma una volta ingranditi è molto più veloce.


Sembra la soluzione più ragionevole. Mi chiedo perché sia ​​così lento comunque. Plain np.searchsortedrichiede circa 2 µs per il mio set di test, l'intera funzione circa 10 µs. Usarlo np.abssta peggiorando ancora. Non ho idea di cosa ci faccia Python.
Michael,

2
@Michael Per valori singoli, le routine matematiche di Numpy saranno più lente delle mathroutine, vedi questa risposta .
Demitri,

3
Questa è la soluzione migliore se si hanno più valori che si desidera cercare contemporaneamente (con alcune modifiche). Il tutto if/elsedeve essere sostituito conidx = idx - (np.abs(value - array[idx-1]) < np.abs(value - array[idx])); return array[idx]
coderforlife

3
Questo è fantastico ma non funziona se valueè più grande del più arraygrande elemento. Ho cambiato la ifdichiarazione per if idx == len(array) or math.fabs(value - array[idx - 1]) < math.fabs(value - array[idx])farlo funzionare per me!
Nicoco,

3
Questo non funziona quando idx è 0. L'if dovrebbe leggere:if idx > 0 and (idx == len(array) or math.fabs(value - array[idx-1]) < math.fabs(value - array[idx])):
JPaget

52

Con una leggera modifica, la risposta sopra funziona con matrici di dimensione 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]

Oppure, scritto come una riga singola:

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

6
Il bit "piatto" non è necessario. a[np.abs(a-a0).argmin)]funziona bene.
Max Shron,

2
In realtà, funziona ancora solo per una dimensione, poiché argmin () fornisce più risultati per colonna / dimensione. Inoltre ho avuto un refuso. Questo funziona, almeno per 2 dimensioni: a[np.sum(np.square(np.abs(a-a0)),1).argmin()].
Max Shron,

3
Quindi, non funziona per dimensioni superiori e la risposta dovrebbe essere eliminata (o modificata per riflettere questo)
Hugues Fontenelle

11
Fornisci un esempio in cui la risposta proposta non funziona. Se ne trovi uno, modificherò la mia risposta. Se non riesci a trovarne uno, puoi rimuovere i tuoi commenti?
kwgoodman,

18

Riepilogo della risposta : se si dispone di un ordinamento, arrayil codice di sezione (indicato di seguito) esegue il più veloce. ~ 100-1000 volte più veloce per array di grandi dimensioni e ~ 2-100 volte più veloce per array di piccole dimensioni. Non richiede neanche intorpidimento. Se si dispone di un ordinamento non differenziato, arrayse arrayè grande, si dovrebbe considerare prima di utilizzare un ordinamento O (n logn) e quindi la bisection, e se arrayè piccolo il metodo 2 sembra il più veloce.

Per prima cosa dovresti chiarire cosa intendi per valore più vicino . Spesso si desidera l'intervallo in un'ascissa, ad esempio array = [0,0.7,2.1], valore = 1.95, la risposta sarebbe idx = 1. Questo è il caso di cui sospetto sia necessario (altrimenti è possibile modificare molto facilmente quanto segue con un'istruzione condizionale di follow-up una volta trovato l'intervallo). Noterò che il modo ottimale per eseguire ciò è con la dissezione (che fornirò per prima cosa - nota che non richiede affatto intorpidimento ed è più veloce dell'uso delle funzioni intorpidite perché eseguono operazioni ridondanti). Quindi fornirò un confronto temporale con gli altri presentati qui da altri utenti.

Bisezione:

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

Ora definirò il codice dalle altre risposte, ognuna restituisce un indice:

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

Ora cronometrerò i codici: nota che i metodi 1,2,4,5 non danno correttamente l'intervallo. I metodi 1,2,4 arrotondano al punto più vicino dell'array (ad es.> = 1,5 -> 2) e il metodo 5 arrotonda sempre per eccesso (ad es. 1,45 -> 2). Solo i metodi 3 e 6 e, naturalmente, la bisection danno l'intervallo correttamente.

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

Per una vasta gamma bisection dà 4us rispetto ai successivi 180us migliori e 1,21ms più lunghi (~ 100 - 1000 volte più veloce). Per array più piccoli è ~ 2-100 volte più veloce.


2
Stai presupponendo che l'array sia ordinato. Ci sono molti motivi per cui qualcuno non vorrebbe ordinare l'array: ad esempio, se l'array rappresentava i punti dati su un grafico a linee.
user1917407

7
La libreria standard di Python contiene già in fase di implementazione l'algoritmo bisection
Felix

Quando hai detto "se arrayè piccolo, il metodo 2 sembra il più veloce". quanto piccolo volevi dire @JoshAlbert?
Mr.Zeus,

2
Questo non trova il valore più vicino , trova il valore più basso successivo.
endolith,

@endolith è il caso solo per bisect.
Homero Esmeraldo,

17

Ecco un'estensione per trovare il vettore più vicino in una matrice di vettori.

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

Penso che norm(..., axis=-1)dovrebbe essere più veloce dell'estrazione dei x,yvalori tramite l'iterazione Python. Inoltre, gli x,yscalari sono qui? Quindi norm(x+y)è un bug poiché, ad esempio, la distanza (+1, -1)sarà trattata come 0.
cfr

Questo ha funzionato per meidx = np.array([np.linalg.norm(x+y) for (x,y) in abs(array-value)]).argmin()
ezchx il

9

Se non vuoi usare numpy questo lo farà:

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

9

Ecco una versione che gestirà un array "valori" non scalare:

import numpy as np

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

O una versione che restituisce un tipo numerico (ad esempio int, float) se l'input è scalare:

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]

Buona risposta, non ho mai usato il outermetodo di un ufunc prima, penso che lo userò di più in futuro. A array[indices]proposito, la prima funzione dovrebbe tornare .
Widjet,

1
Questa soluzione non si ridimensiona. np.subtract.outergenererà l'intera matrice del prodotto esterno che è veramente lenta e richiede molta memoria se arraye / o valuesè molto grande.
anthonybell,

8

Ecco una versione con scipy per @Ari Onasafari, rispondi " per trovare il vettore più vicino in una serie di vettori "

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

Costruire un KDTree è piuttosto un sovraccarico per un tale problema. Non consiglierei una soluzione del genere a meno che non sia necessario effettuare più query su un array di grandi dimensioni ... E quindi, sarebbe meglio costruirlo una volta e riutilizzarlo, piuttosto che crearlo al volo per ogni query.
Ben

8

Ecco una versione vettoriale veloce della soluzione di @ Dimitri se ne hai molti valuesda cercare ( valuespuò essere un array multidimensionale):

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

Punti di riferimenti

> 100 volte più veloce rispetto all'uso di un forloop con la soluzione di @ 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

nel caso in cui si abbia un campionamento costante nell'array, diventa ancora più semplice: idx = np.searchsorted(array, values)quindi: idx[array[idx] - values>np.diff(array).mean()*0.5]-=1e infinereturn array[idx]
Sergey Antopolskiy,

7

Per array di grandi dimensioni, la risposta (eccellente) fornita da @Demitri è molto più rapida della risposta attualmente contrassegnata come migliore. Ho adattato il suo algoritmo esatto nei due modi seguenti:

  1. La funzione seguente funziona indipendentemente dall'ordinamento dell'array di input.

  2. La funzione seguente restituisce l' indice dell'array di input corrispondente al valore più vicino, che è in qualche modo più generale.

Si noti che la funzione seguente gestisce anche un caso limite specifico che porterebbe a un bug nella funzione originale scritta da @Demitri. Altrimenti, il mio algoritmo è identico al suo.

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 la pena sottolineare che questo è un ottimo esempio di come l'ottimizzazione del codice tende a renderlo più brutto e difficile da leggere. La risposta data da @unutbu dovrebbe essere (molto) preferita nei casi in cui la velocità non è una delle maggiori preoccupazioni, poiché è molto più trasparente.
af

Non vedo la risposta data da @Michael. È un errore o sono cieco?
Fookatchu,

No, non sei cieco, sono solo analfabeta ;-) È stata @Demitri la cui risposta mi ha dato una risposta. Colpa mia. Ho appena corretto il mio post. Grazie!
af

Ricevo risposte diverse con Demitri e le tue. Qualche idea? x = np.array([2038, 1758, 1721, 1637, 2097, 2047, 2205, 1787, 2287, 1940, 2311, 2054, 2406, 1471, 1460]). Con find_nearest(x, 1739.5)(valore più vicino al primo quantile), ottengo 1637(ragionevole) e 1(bug?).
Patrick

3

Questa è una versione vettoriale della risposta di 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

Penso che il modo più pitonico sarebbe:

 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)

Questo è il codice base. Puoi usarlo come una funzione se vuoi


2

Tutte le risposte sono utili per raccogliere le informazioni per scrivere un codice efficiente. Tuttavia, ho scritto un piccolo script Python da ottimizzare per vari casi. Sarà il caso migliore se viene ordinato l'array fornito. Se si cerca l'indice del punto più vicino di un valore specificato, il bisectmodulo è il più efficiente in termini di tempo. Quando una ricerca gli indici corrispondono a un array, numpy searchsortedè il più efficiente.

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)

In [63]:% tempo bisect.bisect_left (xlist, 0.3) tempi CPU: utente 0 ns, sys: 0 ns, totale: 0 ns Tempo muro: 22.2 µs

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

In [64]:% tempo np.searchsorted (xar, 0.3, side = "left") Tempi CPU: utente 0 ns, sys: 0 ns, totale: 0 ns Tempo muro: 98,9 µs

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

% tempo np.searchsorted (xar, randpts, side = "left") Tempi CPU: utente 4 ms, sys: 0 ns, totale: 4 ms Tempo muro: 1,2 ms

Se seguiamo la regola moltiplicativa, allora numpy dovrebbe richiedere ~ 100 ms, il che implica ~ 83 volte più veloce.


1

Per la matrice 2d, per determinare la posizione i, j dell'elemento più vicino:

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
Ciao, benvenuto in Stack Overflow. Scopri come scrivere una buona risposta . Prova a dare una breve descrizione di ciò che hai fatto nel contesto della domanda!
Tristo,

0

Forse utile per ndarrays:

def find_nearest(X, value):
    return X[np.unravel_index(np.argmin(np.abs(X - value)), X.shape)]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.