Comment calculer la distance euclidienne avec NumPy?


529

J'ai deux points en 3D:

(xa, ya, za)
(xb, yb, zb)

Et je veux calculer la distance:

dist = sqrt((xa-xb)^2 + (ya-yb)^2 + (za-zb)^2)

Quelle est la meilleure façon de le faire avec NumPy, ou avec Python en général? J'ai:

import numpy
a = numpy.array((xa ,ya, za))
b = numpy.array((xb, yb, zb))

Réponses:


885

Utilisation numpy.linalg.norm:

dist = numpy.linalg.norm(a-b)

Vous pouvez trouver la théorie derrière cela dans Introduction to Data Mining

Cela fonctionne car la distance euclidienne est la norme l2 et la valeur par défaut du paramètre ord dans numpy.linalg.norm est 2.

entrez la description de l'image ici


13
Les documents linalg.norm peuvent être trouvés ici: docs.scipy.org/doc/numpy/reference/generated/… Mon seul vrai commentaire était en quelque sorte de souligner le lien entre une norme (dans ce cas, la norme Frobenius / 2-norm qui est la valeur par défaut pour la fonction normale) et une métrique (dans ce cas, la distance euclidienne).
Mark Lavin

7
Si OP voulait calculer la distance entre un tableau de coordonnées, il est également possible d'utiliser scipy.spatial.distance.cdist .
mnky9800n

2
ma question est: pourquoi utiliser ceci en face de cela? stackoverflow.com/a/21986532/189411 depuis scipy.spatial import distance a = (1,2,3) b = (4,5,6) dst = distance.euclidean (a, b)
Domenico Monaco

2
lien mis à jour vers la fonction cdist de SciPy: docs.scipy.org/doc/scipy/reference/generated/…
Steven C. Howell

il existe des méthodes encore plus rapides que numpy.linalg.norm: semantive.com/blog/…
Muhammad Ashfaq

161

Il y a une fonction pour cela dans SciPy. Cela s'appelle euclidien .

Exemple:

from scipy.spatial import distance
a = (1, 2, 3)
b = (4, 5, 6)
dst = distance.euclidean(a, b)

56
Si vous recherchez l'efficacité, il est préférable d'utiliser la fonction numpy. La distance scipy est deux fois plus lente que numpy.linalg.norm (ab) (et numpy.sqrt (numpy.sum ((ab) ** 2))). Sur ma machine, j'obtiens 19,7 µs avec scipy (v0.15.1) et 8,9 µs avec numpy (v1.9.2). Pas une différence pertinente dans de nombreux cas, mais en boucle peut devenir plus importante. D'un regard rapide sur le code scipy, il semble être plus lent car il valide le tableau avant de calculer la distance.
Algold

@MikePalmice oui, les fonctions scipy sont entièrement compatibles avec numpy. Mais jetez un oeil à ce que aigold a suggéré ici (qui fonctionne également sur la matrice numpy, bien sûr)
Avision

@Avision ne sait pas si cela fonctionnera pour moi car mes matrices ont différents nombres de lignes; essayer de les soustraire pour obtenir une matrice ne fonctionne pas
Fan numéro un de Bjorks le

@MikePalmice qu'essayez-vous exactement de calculer avec ces deux matrices? quelle est l'entrée / sortie attendue?
Avision

ty pour le suivi. Il y a une description ici: stats.stackexchange.com/questions/322620/… . J'ai 2 tableaux d '«opérations»; chacun a une étiquette «code», mais les deux ensembles d'étiquettes sont totalement différents. mon objectif est de trouver le code le meilleur ou le plus proche dans le deuxième tableau correspondant à un code fixe dans le premier (je sais quelle devrait être la réponse de l'inspection manuelle, mais je souhaite évoluer jusqu'à des centaines de tableaux plus tard). Le premier sous-ensemble est donc fixe; Je calcule la moyenne de dist euclide bw ceci et tous les sous-ensembles de codes du 2e, puis
je

108

Pour tous ceux qui souhaitent calculer plusieurs distances à la fois, j'ai fait une petite comparaison en utilisant perfplot (un petit projet à moi).

Le premier conseil est d'organiser vos données de manière à ce que les tableaux aient une dimension (3, n)(et soient évidemment contigus en C). Si l'ajout se produit dans la première dimension contiguë, les choses sont plus rapides et peu importe si vous utilisez sqrt-sumavec axis=0, linalg.normavec axis=0ou

a_min_b = a - b
numpy.sqrt(numpy.einsum('ij,ij->j', a_min_b, a_min_b))

qui est, par une légère marge, la variante la plus rapide. (Cela vaut également pour une seule ligne.)

Les variantes où vous résumez le deuxième axe axis=1, sont toutes beaucoup plus lentes.

entrez la description de l'image ici


Code pour reproduire l'intrigue:

import numpy
import perfplot
from scipy.spatial import distance


def linalg_norm(data):
    a, b = data[0]
    return numpy.linalg.norm(a - b, axis=1)


def linalg_norm_T(data):
    a, b = data[1]
    return numpy.linalg.norm(a - b, axis=0)


def sqrt_sum(data):
    a, b = data[0]
    return numpy.sqrt(numpy.sum((a - b) ** 2, axis=1))


def sqrt_sum_T(data):
    a, b = data[1]
    return numpy.sqrt(numpy.sum((a - b) ** 2, axis=0))


def scipy_distance(data):
    a, b = data[0]
    return list(map(distance.euclidean, a, b))


def sqrt_einsum(data):
    a, b = data[0]
    a_min_b = a - b
    return numpy.sqrt(numpy.einsum("ij,ij->i", a_min_b, a_min_b))


def sqrt_einsum_T(data):
    a, b = data[1]
    a_min_b = a - b
    return numpy.sqrt(numpy.einsum("ij,ij->j", a_min_b, a_min_b))


def setup(n):
    a = numpy.random.rand(n, 3)
    b = numpy.random.rand(n, 3)
    out0 = numpy.array([a, b])
    out1 = numpy.array([a.T, b.T])
    return out0, out1


perfplot.save(
    "norm.png",
    setup=setup,
    n_range=[2 ** k for k in range(22)],
    kernels=[
        linalg_norm,
        linalg_norm_T,
        scipy_distance,
        sqrt_sum,
        sqrt_sum_T,
        sqrt_einsum,
        sqrt_einsum_T,
    ],
    logx=True,
    logy=True,
    xlabel="len(x), len(y)",
)

3
Je vous remercie. J'ai appris quelque chose de nouveau aujourd'hui! Pour un tableau à une dimension, la chaîne serai,i->
Tirtha R

4
ce serait toujours plus cool s'il y avait une comparaison des consommations de mémoire
dragonLOLz

Je voudrais utiliser votre code mais j'ai du mal à comprendre comment les données sont censées être organisées. Pouvez-vous donner un exemple? À quoi doit dataressembler?
Johannes Wiesner

1
Projet et résultats vraiment soignés. J'ai fait des parcelles à moitié entières de la même nature, donc je pense que je vais passer à votre projet et apporter les différences, si vous les aimez.
Physicien fou

42

Je veux exposer la réponse simple avec diverses notes de performance. np.linalg.norm fera peut-être plus que ce dont vous avez besoin:

dist = numpy.linalg.norm(a-b)

Premièrement - cette fonction est conçue pour travailler sur une liste et renvoyer toutes les valeurs, par exemple pour comparer la distance de pAà l'ensemble de points sP:

sP = set(points)
pA = point
distances = np.linalg.norm(sP - pA, ord=2, axis=1.)  # 'distances' is a list

Rappelez-vous plusieurs choses:

  • Les appels de fonction Python sont chers.
  • [Normal] Python ne met pas en cache les recherches de nom.

Donc

def distance(pointA, pointB):
    dist = np.linalg.norm(pointA - pointB)
    return dist

n'est pas aussi innocent qu'il n'y paraît.

>>> dis.dis(distance)
  2           0 LOAD_GLOBAL              0 (np)
              2 LOAD_ATTR                1 (linalg)
              4 LOAD_ATTR                2 (norm)
              6 LOAD_FAST                0 (pointA)
              8 LOAD_FAST                1 (pointB)
             10 BINARY_SUBTRACT
             12 CALL_FUNCTION            1
             14 STORE_FAST               2 (dist)

  3          16 LOAD_FAST                2 (dist)
             18 RETURN_VALUE

Premièrement - chaque fois que nous l'appelons, nous devons faire une recherche globale pour "np", une recherche de portée pour "linalg" et une recherche de portée pour "norm", et la surcharge de simplement appeler la fonction peut correspondre à des dizaines de python instructions.

Enfin, nous avons gaspillé deux opérations pour stocker le résultat et le recharger pour le retour ...

Première passe à l'amélioration: accélérer la recherche, sauter le magasin

def distance(pointA, pointB, _norm=np.linalg.norm):
    return _norm(pointA - pointB)

Nous obtenons le beaucoup plus rationalisé:

>>> dis.dis(distance)
  2           0 LOAD_FAST                2 (_norm)
              2 LOAD_FAST                0 (pointA)
              4 LOAD_FAST                1 (pointB)
              6 BINARY_SUBTRACT
              8 CALL_FUNCTION            1
             10 RETURN_VALUE

Cependant, la surcharge de l'appel de fonction représente encore un peu de travail. Et vous voudrez faire des repères pour déterminer si vous feriez mieux de faire les calculs vous-même:

def distance(pointA, pointB):
    return (
        ((pointA.x - pointB.x) ** 2) +
        ((pointA.y - pointB.y) ** 2) +
        ((pointA.z - pointB.z) ** 2)
    ) ** 0.5  # fast sqrt

Sur certaines plateformes, **0.5est plus rapide que math.sqrt. Votre kilométrage peut varier.

**** Notes de performance avancées.

Pourquoi calculez-vous la distance? Si le seul but est de l'afficher,

 print("The target is %.2fm away" % (distance(a, b)))

avancer. Mais si vous comparez des distances, effectuez des vérifications de portée, etc., j'aimerais ajouter quelques observations utiles sur les performances.

Prenons deux cas: tri par distance ou élimination d'une liste des éléments qui répondent à une contrainte de plage.

# Ultra naive implementations. Hold onto your hat.

def sort_things_by_distance(origin, things):
    return things.sort(key=lambda thing: distance(origin, thing))

def in_range(origin, range, things):
    things_in_range = []
    for thing in things:
        if distance(origin, thing) <= range:
            things_in_range.append(thing)

La première chose dont nous devons nous souvenir est que nous utilisons Pythagore pour calculer la distance ( dist = sqrt(x^2 + y^2 + z^2)), donc nous faisons beaucoup d' sqrtappels. Math 101:

dist = root ( x^2 + y^2 + z^2 )
:.
dist^2 = x^2 + y^2 + z^2
and
sq(N) < sq(M) iff M > N
and
sq(N) > sq(M) iff N > M
and
sq(N) = sq(M) iff N == M

En bref: jusqu'à ce que nous ayons réellement besoin de la distance dans une unité de X plutôt que X ^ 2, nous pouvons éliminer la partie la plus difficile des calculs.

# Still naive, but much faster.

def distance_sq(left, right):
    """ Returns the square of the distance between left and right. """
    return (
        ((left.x - right.x) ** 2) +
        ((left.y - right.y) ** 2) +
        ((left.z - right.z) ** 2)
    )

def sort_things_by_distance(origin, things):
    return things.sort(key=lambda thing: distance_sq(origin, thing))

def in_range(origin, range, things):
    things_in_range = []

    # Remember that sqrt(N)**2 == N, so if we square
    # range, we don't need to root the distances.
    range_sq = range**2

    for thing in things:
        if distance_sq(origin, thing) <= range_sq:
            things_in_range.append(thing)

Génial, les deux fonctions ne font plus de racines carrées coûteuses. Ce sera beaucoup plus rapide. Nous pouvons également améliorer in_range en le convertissant en générateur:

def in_range(origin, range, things):
    range_sq = range**2
    yield from (thing for thing in things
                if distance_sq(origin, thing) <= range_sq)

Cela a surtout des avantages si vous faites quelque chose comme:

if any(in_range(origin, max_dist, things)):
    ...

Mais si la prochaine chose que vous allez faire nécessite une distance,

for nearby in in_range(origin, walking_distance, hotdog_stands):
    print("%s %.2fm" % (nearby.name, distance(origin, nearby)))

pensez à donner des tuples:

def in_range_with_dist_sq(origin, range, things):
    range_sq = range**2
    for thing in things:
        dist_sq = distance_sq(origin, thing)
        if dist_sq <= range_sq: yield (thing, dist_sq)

Cela peut être particulièrement utile si vous pouvez enchaîner des vérifications de plage (`` trouver des choses qui sont proches de X et à Nm de Y '', car vous n'avez pas à calculer à nouveau la distance).

Mais qu'en est-il si nous recherchons une très grande liste thingset que nous prévoyons que beaucoup d'entre eux ne méritent pas d'être pris en considération?

Il y a en fait une optimisation très simple:

def in_range_all_the_things(origin, range, things):
    range_sq = range**2
    for thing in things:
        dist_sq = (origin.x - thing.x) ** 2
        if dist_sq <= range_sq:
            dist_sq += (origin.y - thing.y) ** 2
            if dist_sq <= range_sq:
                dist_sq += (origin.z - thing.z) ** 2
                if dist_sq <= range_sq:
                    yield thing

Son utilité dépendra de la taille des «choses».

def in_range_all_the_things(origin, range, things):
    range_sq = range**2
    if len(things) >= 4096:
        for thing in things:
            dist_sq = (origin.x - thing.x) ** 2
            if dist_sq <= range_sq:
                dist_sq += (origin.y - thing.y) ** 2
                if dist_sq <= range_sq:
                    dist_sq += (origin.z - thing.z) ** 2
                    if dist_sq <= range_sq:
                        yield thing
    elif len(things) > 32:
        for things in things:
            dist_sq = (origin.x - thing.x) ** 2
            if dist_sq <= range_sq:
                dist_sq += (origin.y - thing.y) ** 2 + (origin.z - thing.z) ** 2
                if dist_sq <= range_sq:
                    yield thing
    else:
        ... just calculate distance and range-check it ...

Et encore une fois, envisagez de fournir le dist_sq. Notre exemple de hot-dog devient alors:

# Chaining generators
info = in_range_with_dist_sq(origin, walking_distance, hotdog_stands)
info = (stand, dist_sq**0.5 for stand, dist_sq in info)
for stand, dist in info:
    print("%s %.2fm" % (stand, dist))

1
Pourquoi ne pas ajouter une telle fonction optimisée à numpy? Une extension pour les pandas serait également idéale pour une question comme celle-ci stackoverflow.com/questions/47643952/…
Keith

3
J'ai édité votre première approche mathématique de la distance. Vous utilisiez un pointZqui n'existait pas. Je pense que vous vouliez dire deux points dans un espace tridimensionnel et j'ai édité en conséquence. Si je me trompais, faites-le moi savoir.
Bram Vanroy

37

Un autre exemple de cette méthode de résolution de problèmes :

def dist(x,y):   
    return numpy.sqrt(numpy.sum((x-y)**2))

a = numpy.array((xa,ya,za))
b = numpy.array((xb,yb,zb))
dist_a_b = dist(a,b)

1
pouvez-vous utiliser les implémentations sqrt et / ou sum de numpy? Cela devrait le rendre plus rapide (?).
u0b34a0f6ae

1
J'ai trouvé cela de l'autre côté des interwebs norm = lambda x: N.sqrt(N.square(x).sum()); norm(x-y)
u0b34a0f6ae

2
grattez ça. ça devait être quelque part. le voici:numpy.linalg.norm(x-y)
u0b34a0f6ae

13

En commençant Python 3.8, le mathmodule fournit directement la distfonction, qui retourne la distance euclidienne entre deux points (donnée sous forme de tuples ou de listes de coordonnées):

from math import dist

dist((1, 2, 6), (-2, 3, 2)) # 5.0990195135927845

Et si vous travaillez avec des listes:

dist([1, 2, 6], [-2, 3, 2]) # 5.0990195135927845

12

Cela peut être fait comme suit. Je ne sais pas à quelle vitesse c'est, mais il n'utilise pas NumPy.

from math import sqrt
a = (1, 2, 3) # Data point 1
b = (4, 5, 6) # Data point 2
print sqrt(sum( (a - b)**2 for a, b in zip(a, b)))

Faire des maths directement en python n'est pas une bonne idée car python est très lent, en particulier for a, b in zip(a, b). Mais utile néanmoins.
Sigex

10

Je trouve une fonction 'dist' dans matplotlib.mlab, mais je ne pense pas que ce soit assez pratique.

Je le poste ici juste pour référence.

import numpy as np
import matplotlib as plt

a = np.array([1, 2, 3])
b = np.array([2, 3, 4])

# Distance between a and b
dis = plt.mlab.dist(a, b)

Ce n'est plus applicable. (mpl 3.0)
Nico Schlömer

8

J'aime np.dot(produit scalaire):

a = numpy.array((xa,ya,za))
b = numpy.array((xb,yb,zb))

distance = (np.dot(a-b,a-b))**.5

8

Une belle doublure:

dist = numpy.linalg.norm(a-b)

Cependant, si la vitesse est un problème, je recommanderais d'expérimenter sur votre machine. J'ai trouvé que l'utilisation de la mathbibliothèque sqrtavec l' **opérateur pour le carré est beaucoup plus rapide sur ma machine que la solution NumPy à une ligne.

J'ai exécuté mes tests en utilisant ce programme simple:

#!/usr/bin/python
import math
import numpy
from random import uniform

def fastest_calc_dist(p1,p2):
    return math.sqrt((p2[0] - p1[0]) ** 2 +
                     (p2[1] - p1[1]) ** 2 +
                     (p2[2] - p1[2]) ** 2)

def math_calc_dist(p1,p2):
    return math.sqrt(math.pow((p2[0] - p1[0]), 2) +
                     math.pow((p2[1] - p1[1]), 2) +
                     math.pow((p2[2] - p1[2]), 2))

def numpy_calc_dist(p1,p2):
    return numpy.linalg.norm(numpy.array(p1)-numpy.array(p2))

TOTAL_LOCATIONS = 1000

p1 = dict()
p2 = dict()
for i in range(0, TOTAL_LOCATIONS):
    p1[i] = (uniform(0,1000),uniform(0,1000),uniform(0,1000))
    p2[i] = (uniform(0,1000),uniform(0,1000),uniform(0,1000))

total_dist = 0
for i in range(0, TOTAL_LOCATIONS):
    for j in range(0, TOTAL_LOCATIONS):
        dist = fastest_calc_dist(p1[i], p2[j]) #change this line for testing
        total_dist += dist

print total_dist

Sur ma machine, math_calc_disttourne beaucoup plus vite que numpy_calc_dist: 1,5 seconde contre 23,5 secondes.

Pour obtenir une différence mesurable entre fastest_calc_distet math_calc_distj'ai dû jusqu'à TOTAL_LOCATIONS6000. Prend ensuite fastest_calc_dist~ 50 secondes tandis que math_calc_distprend ~ 60 secondes.

Vous pouvez également expérimenter avec numpy.sqrtet numpy.squarebien que les deux soient plus lents que les mathalternatives sur ma machine.

Mes tests ont été exécutés avec Python 2.6.6.


48
Vous comprenez mal comment utiliser numpy ... N'utilisez pas de boucles ou de listes de compréhension. Si vous parcourez et appliquez la fonction à chaque élément, alors, oui, les fonctions numpy seront plus lentes. Le tout est de vectoriser les choses.
Joe Kington

Si je déplace l'appel numpy.array dans la boucle où je crée les points, j'obtiens de meilleurs résultats avec numpy_calc_dist, mais il est toujours 10 fois plus lent que rapid_calc_dist. Si j'ai autant de points et que je dois trouver la distance entre chaque paire, je ne suis pas sûr de ce que je peux faire d'autre pour profiter de numpy.
user118662

15
Je me rends compte que ce fil est vieux, mais je veux juste renforcer ce que Joe a dit. Vous n'utilisez pas numpy correctement. Ce que vous calculez est la somme de la distance de chaque point de p1 à chaque point de p2. La solution avec numpy / scipy est plus de 70 fois plus rapide sur ma machine. Transformez p1 et p2 en tableau (même en utilisant une boucle si vous les avez définis comme des dict). Ensuite , vous pouvez obtenir la somme totale en une seule étape, scipy.spatial.distance.cdist(p1, p2).sum(). C'est ça.
Scott B

3
Ou utilisez numpy.linalg.norm(p1-p2).sum()pour obtenir la somme entre chaque point de p1 et le point correspondant de p2 (c'est-à-dire pas tous les points de p1 à tous les points de p2). Et si vous voulez chaque point de p1 à chaque point de p2 et que vous ne voulez pas utiliser scipy comme dans mon commentaire précédent, vous pouvez utiliser np.apply_along_axis avec numpy.linalg.norm pour le faire encore beaucoup, beaucoup plus rapidement alors votre solution "la plus rapide".
Scott B

2
Les versions précédentes de NumPy avaient des implémentations de normes très lentes. Dans les versions actuelles, il n'y a pas besoin de tout cela.
Fred Foo

8

Vous pouvez simplement soustraire les vecteurs puis le produit interne.

En suivant votre exemple,

a = numpy.array((xa, ya, za))
b = numpy.array((xb, yb, zb))

tmp = a - b
sum_squared = numpy.dot(tmp.T, tmp)
result = sqrt(sum_squared)

5
cela me donnera le carré de la distance. il vous manque un sqrt ici.
Nathan Fellman

6

Ayant aet bcomme vous les avez définis, vous pouvez également utiliser:

distance = np.sqrt(np.sum((a-b)**2))

6

Avec Python 3.8, c'est très simple.

https://docs.python.org/3/library/math.html#math.dist

math.dist(p, q)

Renvoie la distance euclidienne entre deux points p et q, chacun étant donné comme une séquence (ou itérable) de coordonnées. Les deux points doivent avoir la même dimension.

À peu près équivalent à:

sqrt(sum((px - qx) ** 2.0 for px, qx in zip(p, q)))


5

Voici un code concis pour la distance euclidienne en Python étant donné deux points représentés sous forme de listes en Python.

def distance(v1,v2): 
    return sum([(x-y)**2 for (x,y) in zip(v1,v2)])**(0.5)

1
Numpy accepte également les listes comme entrées (pas besoin de passer explicitement un tableau numpy)
Alejandro Sazo

4

Depuis Python 3.8

Depuis Python 3.8, le mathmodule inclut la fonction math.dist().
Voir ici https://docs.python.org/3.8/library/math.html#math.dist .

math.dist (p1, p2)
Retourne la distance euclidienne entre deux points p1 et p2, chacun étant donné comme une séquence (ou itérable) de coordonnées.

import math
print( math.dist( (0,0),   (1,1)   )) # sqrt(2) -> 1.4142
print( math.dist( (0,0,0), (1,1,1) )) # sqrt(3) -> 1.7321

3

Calculez la distance euclidienne pour l'espace multidimensionnel:

 import math

 x = [1, 2, 6] 
 y = [-2, 3, 2]

 dist = math.sqrt(sum([(xi-yi)**2 for xi,yi in zip(x, y)]))
 5.0990195135927845

2
import numpy as np
from scipy.spatial import distance
input_arr = np.array([[0,3,0],[2,0,0],[0,1,3],[0,1,2],[-1,0,1],[1,1,1]]) 
test_case = np.array([0,0,0])
dst=[]
for i in range(0,6):
    temp = distance.euclidean(test_case,input_arr[i])
    dst.append(temp)
print(dst)

2
Quelle est la différence avec cette réponse ?
xskxzr


2

Vous pouvez facilement utiliser la formule

distance = np.sqrt(np.sum(np.square(a-b)))

ce qui ne fait en fait rien de plus que d'utiliser le théorème de Pythagore pour calculer la distance, en ajoutant les carrés de Δx, Δy et Δz et enracinant le résultat.


1

Trouvez d'abord la différence de deux matrices. Ensuite, appliquez la multiplication par élément avec la commande multiply de numpy. Après cela, trouvez la somme de la nouvelle matrice multipliée par élément. Enfin, trouvez la racine carrée de la sommation.

def findEuclideanDistance(a, b):
    euclidean_distance = a - b
    euclidean_distance = np.sum(np.multiply(euclidean_distance, euclidean_distance))
    euclidean_distance = np.sqrt(euclidean_distance)
    return euclidean_distance

1
import numpy as np
# any two python array as two points
a = [0, 0]
b = [3, 4]

Vous devez d' abord la liste des modifications à tableau numpy et faire comme ceci: print(np.linalg.norm(np.array(a) - np.array(b))). Deuxième méthode directement à partir de la liste python:print(np.linalg.norm(np.subtract(a,b)))

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.