Trouver la valeur la plus proche dans le tableau numpy


336

Existe-t-il une méthode numpy-thonique, par exemple une fonction, pour trouver la valeur la plus proche dans un tableau?

Exemple:

np.find_nearest( array, value )

Réponses:


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()donne la mauvaise réponse. Cela vous donne le min de la distance de valeur absolue, et en quelque sorte nous devons retourner la valeur réelle du tableau. Nous pourrions ajouter valueet approcher, mais la valeur absolue jette une clé dans les choses ...
unutbu

9
@ ~ unutbu Tu as raison, ma mauvaise. Je ne vois rien de mieux que votre solution!
Eric O Lebigot

24
semble fou il n'y a pas un intégré numpy qui fait cela.
dbliss

3
@jsmedmar La méthode de bissection (voir ma réponse ci-dessous) est 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.Utiliser idxminau lieu de argminfonctionne pour moi avec la solution ci-dessus. (v3.6.4)
jorijnsmit

78

SI votre tableau est trié et est très grand, c'est une solution beaucoup plus rapide:

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]

Cela se transforme en très grands tableaux. Vous pouvez facilement modifier ce qui précède pour trier dans la méthode si vous ne pouvez pas supposer que le tableau est déjà trié. C'est exagéré pour les petits tableaux, mais une fois qu'ils sont grands, c'est beaucoup plus rapide.


Cela semble être la solution la plus raisonnable. Je me demande pourquoi c'est si lent de toute façon. Plain np.searchsortedprend environ 2 µs pour mon ensemble de test, la fonction entière environ 10 µs. L'utilisation np.absdevient encore pire. Aucune idée de ce que python fait là-bas.
Michael

2
@Michael Pour les valeurs uniques, les routines mathématiques Numpy seront plus lentes que les mathroutines, voir cette réponse .
Demitri

3
C'est la meilleure solution si vous avez plusieurs valeurs à rechercher en même temps (avec quelques ajustements). L'ensemble if/elsedoit être remplacé paridx = idx - (np.abs(value - array[idx-1]) < np.abs(value - array[idx])); return array[idx]
coderforlife

3
C'est très bien mais cela ne fonctionne pas s'il valueest plus grand que arrayle plus gros élément. J'ai changé la ifdéclaration pour la if idx == len(array) or math.fabs(value - array[idx - 1]) < math.fabs(value - array[idx])faire fonctionner pour moi!
nicoco

3
Cela ne fonctionne pas lorsque idx est 0. Le if devrait lire:if idx > 0 and (idx == len(array) or math.fabs(value - array[idx-1]) < math.fabs(value - array[idx])):
JPaget

52

Avec une légère modification, la réponse ci-dessus fonctionne avec des tableaux de dimension arbitraire (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, écrit en une seule ligne:

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

6
Le bit "plat" n'est pas nécessaire. a[np.abs(a-a0).argmin)]fonctionne bien.
Max Shron

2
En fait, cela ne fonctionne toujours que pour une dimension, car argmin () donne plusieurs résultats par colonne / dimension. J'ai aussi eu une faute de frappe. Cela fonctionne, au moins pour 2 dimensions: a[np.sum(np.square(np.abs(a-a0)),1).argmin()].
Max Shron

3
Donc, cela ne fonctionne pas pour les dimensions plus élevées, et la réponse doit être supprimée (ou modifiée pour refléter cela)
Hugues Fontenelle

11
Veuillez fournir un exemple où la réponse proposée ne fonctionne pas. Si vous en trouvez un, je modifierai ma réponse. Si vous n'en trouvez pas, pourriez-vous supprimer vos commentaires?
kwgoodman

18

Résumé de la réponse : si l'on a trié, arrayle code de bissection (donné ci-dessous) est le plus rapide. ~ 100-1000 fois plus rapide pour les grandes baies, et ~ 2-100 fois plus rapide pour les petites baies. Il ne nécessite pas non plus numpy. Si vous avez un tri non trié, arraysi if arrayest grand, vous devez d' abord envisager d'utiliser un tri O (n logn), puis une bissection, et s'il arrayest petit, la méthode 2 semble la plus rapide.

Vous devez d'abord clarifier ce que vous entendez par valeur la plus proche . Souvent, on veut l'intervalle en abscisse, par exemple tableau = [0,0.7,2.1], valeur = 1,95, la réponse serait idx = 1. C'est le cas dont je soupçonne que vous avez besoin (sinon les éléments suivants peuvent être modifiés très facilement avec une instruction conditionnelle de suivi une fois que vous avez trouvé l'intervalle). Je noterai que la manière optimale d'effectuer ceci est avec la bissection (que je fournirai en premier - notez qu'elle ne nécessite pas du tout numpy et est plus rapide que d'utiliser les fonctions numpy car elles effectuent des opérations redondantes). Ensuite, je fournirai une comparaison temporelle avec les autres présentées ici par d'autres utilisateurs.

Bissection:

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

Je vais maintenant définir le code des autres réponses, elles renvoient chacune un index:

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

Je vais maintenant chronométrer les codes: Notez que les méthodes 1, 2, 4, 5 ne donnent pas correctement l'intervalle. Les méthodes 1,2,4 arrondissent au point le plus proche dans le tableau (par exemple> = 1,5 -> 2), et la méthode 5 arrondit toujours (par exemple 1,45 -> 2). Seules les méthodes 3 et 6, et bien sûr la bissection donnent correctement l'intervalle.

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

Pour un grand tableau, la bissection donne 4us par rapport au 180us suivant et au plus long 1,21 ms (~ 100 - 1000 fois plus rapide). Pour les baies plus petites, c'est ~ 2-100 fois plus rapide.


2
Vous supposez que le tableau est trié. Il existe de nombreuses raisons pour lesquelles quelqu'un ne souhaite pas trier le tableau: par exemple, si le tableau représente les points de données sur un graphique linéaire.
user1917407

7
La bibliothèque standard python contient déjà dans l'implémentation de l'algorithme de bissection: docs.python.org/3.6/library/bisect.html
Felix

Lorsque vous avez dit: "si arrayc'est petit, la méthode 2 semble la plus rapide". à quel point vouliez-vous dire @JoshAlbert?
Mr.Zeus

2
Cela ne trouve pas la valeur la plus proche , il trouve la valeur immédiatement inférieure.
endolith

@endolith c'est le cas pour la bissect seulement.
Homero Esmeraldo

17

Voici une extension pour trouver le vecteur le plus proche dans un tableau de vecteurs.

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

Je pense que cela norm(..., axis=-1)devrait être plus rapide que d'extraire les x,yvaleurs via l'itération Python. Aussi, x,yles scalaires sont-ils ici? Il norm(x+y)y a ensuite un bug puisque, par exemple, la distance (+1, -1)sera traitée comme 0.
cfh

Cela a fonctionné pour moiidx = np.array([np.linalg.norm(x+y) for (x,y) in abs(array-value)]).argmin()
ezchx

9

Si vous ne voulez pas utiliser numpy, cela le fera:

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

9

Voici une version qui gérera un tableau de "valeurs" non scalaire:

import numpy as np

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

Ou une version qui retourne un type numérique (par exemple int, float) si l'entrée est scalaire:

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]

Bonne réponse, je n'ai jamais utilisé la outerméthode d'un ufunc auparavant, je pense que je l'utiliserai plus à l'avenir. Soit dit array[indices]en passant, la première fonction devrait revenir .
Widjet

1
Cette solution n'est pas évolutive. np.subtract.outergénérera toute la matrice du produit externe qui est vraiment lente et gourmande en mémoire si arrayet / ou valuesest très grande.
anthonybell le

8

Voici une version avec scipy pour @Ari Onasafari, répondez " pour trouver le vecteur le plus proche dans un tableau de vecteurs "

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

La construction d'un KDTree est une surcharge pour un tel problème. Je ne recommanderais pas une telle solution à moins que vous n'ayez à faire plusieurs requêtes sur un grand tableau ... Et puis, il serait préférable de le construire une fois et de le réutiliser, plutôt que de le créer à la volée pour chaque requête.
Ben

8

Voici une version vectorisée rapide de la solution de @ Dimitri si vous en avez beaucoup valuesà rechercher ( valuespeut être un tableau multidimensionnel):

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

Repères

> 100 fois plus rapide que l'utilisation d'une forboucle avec la solution 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

au cas où vous auriez un échantillonnage constant dans le tableau, cela devient encore plus simple: idx = np.searchsorted(array, values)alors: idx[array[idx] - values>np.diff(array).mean()*0.5]-=1et enfinreturn array[idx]
Sergey Antopolskiy

7

Pour les grands tableaux, la (excellente) réponse donnée par @Demitri est beaucoup plus rapide que la réponse actuellement indiquée comme la meilleure. J'ai adapté son algorithme exact des deux manières suivantes:

  1. La fonction ci-dessous fonctionne que le tableau d'entrée soit trié ou non.

  2. La fonction ci-dessous renvoie l' index du tableau d'entrée correspondant à la valeur la plus proche, ce qui est un peu plus général.

Notez que la fonction ci-dessous gère également un cas de bord spécifique qui entraînerait un bogue dans la fonction d'origine écrite par @Demitri. Sinon, mon algorithme est identique au sien.

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
Il convient de souligner qu'il s'agit d'un excellent exemple de la façon dont l'optimisation du code a tendance à le rendre plus laid et plus difficile à lire. La réponse donnée par @unutbu devrait être (de beaucoup) préférée dans les cas où la vitesse n'est pas une préoccupation majeure, car elle est beaucoup plus transparente.
aph

Je ne vois pas la réponse donnée par @Michael. Est-ce une erreur ou suis-je aveugle?
Fookatchu le

Non, tu n'es pas aveugle, je suis juste analphabète ;-) C'était @Demitri dont je répondais avec ferveur. Ma faute. Je viens de corriger mon message. Merci!
aph

J'obtiens des réponses différentes avec Demitri et la vôtre. Des idées? x = np.array([2038, 1758, 1721, 1637, 2097, 2047, 2205, 1787, 2287, 1940, 2311, 2054, 2406, 1471, 1460]). Avec find_nearest(x, 1739.5)(valeur la plus proche du premier quantile), j'obtiens 1637(raisonnable) et 1(bug?).
PatrickT

3

Ceci est une version vectorisée de la réponse d' 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

Je pense que la manière la plus pythonique serait:

 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)

Ceci est le code de base. Vous pouvez l'utiliser comme fonction si vous le souhaitez


2

Toutes les réponses sont utiles pour rassembler les informations pour écrire du code efficace. Cependant, j'ai écrit un petit script Python à optimiser pour divers cas. Ce sera le meilleur cas si le tableau fourni est trié. Si l'on recherche l'index du point le plus proche d'une valeur spécifiée, alors le bisectmodule est le plus efficace en temps. Lorsqu'une recherche les indices correspondent à un tableau, le numpy searchsortedplus efficace.

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)

Dans [63]:% temps bisect.bisect_left (xlist, 0,3) Temps CPU: utilisateur 0 ns, sys: 0 ns, total: 0 ns Temps de mur: 22,2 µs

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

Dans [64]:% time np.searchsorted (xar, 0.3, side = "left") Temps CPU: utilisateur 0 ns, sys: 0 ns, total: 0 ns Temps de mur: 98,9 µs

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

% time np.searchsorted (xar, randpts, side = "left") Temps CPU: utilisateur 4 ms, sys: 0 ns, total: 4 ms Temps de mur: 1,2 ms

Si nous suivons la règle multiplicative, alors numpy devrait prendre ~ 100 ms, ce qui implique ~ 83X plus rapide.


1

Pour un tableau 2d, pour déterminer la position i, j de l'élément le plus proche:

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
Salut, bienvenue dans Stack Overflow. Découvrez comment rédiger une bonne réponse . Essayez de donner une brève description de ce que vous avez fait dans le contexte de la question!
Tristo

0

Peut-être utile pour ndarrays:

def find_nearest(X, value):
    return X[np.unravel_index(np.argmin(np.abs(X - value)), X.shape)]
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.