Come si può calcolare la distanza euclidea con NumPy?


529

Ho due punti in 3D:

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

E voglio calcolare la distanza:

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

Qual è il modo migliore per farlo con NumPy o con Python in generale? Io ho:

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

Risposte:


885

Utilizzare numpy.linalg.norm:

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

Puoi trovare la teoria dietro questo in Introduzione al data mining

Questo funziona perché la distanza euclidea è l2 norma e il valore predefinito del parametro ord in numpy.linalg.norm è 2.

inserisci qui la descrizione dell'immagine


13
I documenti linalg.norm possono essere trovati qui: docs.scipy.org/doc/numpy/reference/generated/… Il mio unico vero commento è stato quello di sottolineare la connessione tra una norma (in questo caso la norma di Frobenius / 2-norma che è l'impostazione predefinita per la funzione norma) e una metrica (in questo caso la distanza euclidea).
Mark Lavin,

7
Se OP volesse calcolare la distanza tra un array di coordinate è anche possibile usare scipy.spatial.distance.cdist .
mnky9800n,

2
la mia domanda è: perché usarlo al contrario di questo? stackoverflow.com/a/21986532/189411 da scipy.spatial distanza di importazione a = (1,2,3) b = (4,5,6) DST = distance.euclidean (a, b)
Domenico Monaco

2
collegamento aggiornato alla funzione di cdista di SciPy: docs.scipy.org/doc/scipy/reference/generated/…
Steven C. Howell

ci sono metodi ancora più veloci di numpy.linalg.norm: semantive.com/blog/…
Muhammad Ashfaq

161

C'è una funzione per questo in SciPy. Si chiama Euclideo .

Esempio:

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

56
Se cerchi efficienza, è meglio usare la funzione numpy. La distanza scipy è due volte più lenta di numpy.linalg.norm (ab) (e numpy.sqrt (numpy.sum ((ab) ** 2))). Sulla mia macchina ottengo 19,7 µs con scipy (v0.15.1) e 8,9 µs con numpy (v1.9.2). Non è una differenza rilevante in molti casi, ma se in loop può diventare più significativo. Da una rapida occhiata al codice scipy sembra essere più lento perché convalida l'array prima di calcolare la distanza.
Algold,

@MikePalmice sì, le funzioni di scipy sono pienamente compatibili con numpy. Ma dai un'occhiata a ciò che aigold ha suggerito qui (che funziona anche su array intorpiditi, ovviamente)
Avision,

@ Avviso non sono sicuro che funzionerà per me poiché le mie matrici hanno un numero diverso di righe; cercare di sottrarli per ottenere una matrice non funziona
Bjorks numero uno fan il

@MikePalmice cosa stai esattamente cercando di calcolare con queste due matrici? qual è l'ingresso / uscita previsto?
Avision,

per il follow-up. C'è una descrizione qui: stats.stackexchange.com/questions/322620/… . Ho 2 tabelle di "operazioni"; ognuno ha un'etichetta 'codice', ma i due set di etichette sono totalmente diversi. il mio obiettivo è trovare il codice migliore o più vicino nella seconda tabella corrispondente a un codice fisso nella prima (so quale dovrebbe essere la risposta dall'ispezione manuale, ma voglio ridimensionare fino a centinaia di tabelle in seguito). Quindi il primo sottoinsieme è fisso; Calcolo la media di euclide tra questo e tutti i sottoinsiemi di codice del 2 °, quindi ordina
Bjorks numero uno fan

108

Per chiunque sia interessato a calcolare più distanze contemporaneamente, ho fatto un piccolo confronto usando perfplot (un mio piccolo progetto).

Il primo consiglio è quello di organizzare i tuoi dati in modo tale che gli array abbiano dimensione (3, n)(e ovviamente siano contigui in C). Se l'aggiunta avviene nella prima dimensione contigua, le cose sono più veloci e non importa troppo se lo usi sqrt-sumcon axis=0, linalg.normcon axis=0o

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

che è, con un leggero margine, la variante più veloce. (Questo è vero anche per una sola riga.)

Le varianti in cui si riassume nel secondo asse axis=1sono sostanzialmente più lente.

inserisci qui la descrizione dell'immagine


Codice per riprodurre la trama:

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
Grazie. Ho imparato qualcosa di nuovo oggi! Per la matrice a dimensione singola, la stringa sarài,i->
Tirtha R

4
Sarebbe molto più bello se ci fosse un confronto tra i consumi di memoria
dragonLOLz,

Vorrei usare il tuo codice ma sto lottando per capire come dovrebbero essere organizzati i dati. Puoi fare un esempio? Come datadeve apparire?
Johannes Wiesner,

1
Progetto e risultati davvero accurati. Ho fatto trame di mezza natura della stessa natura, quindi penso che passerò al tuo progetto e contribuirò alle differenze, se ti piacciono.
Fisico pazzo,

42

Voglio spiegare la semplice risposta con varie note di performance. np.linalg.norm farà forse più del necessario:

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

Innanzitutto, questa funzione è progettata per lavorare su un elenco e restituire tutti i valori, ad esempio per confrontare la distanza pAdall'insieme di punti sP:

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

Ricorda diverse cose:

  • Le chiamate alle funzioni Python sono costose.
  • [Regular] Python non memorizza nella cache le ricerche dei nomi.

Così

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

non è così innocente come sembra.

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

In primo luogo - ogni volta che lo chiamiamo, dobbiamo fare una ricerca globale per "np", una ricerca con ambito per "linalg" e una ricerca con ambito per "norma", e il sovraccarico di semplicemente chiamare la funzione può equivalere a dozzine di pitone Istruzioni.

Infine, abbiamo sprecato due operazioni per memorizzare il risultato e ricaricarlo per il ritorno ...

Primo passaggio al miglioramento: velocizza la ricerca, salta il negozio

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

Otteniamo il più snello:

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

Tuttavia, l'overhead della chiamata di funzione equivale ancora a qualche lavoro. E vorrai fare dei benchmark per determinare se potresti fare meglio la matematica da solo:

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

Su alcune piattaforme, **0.5è più veloce di math.sqrt. Il tuo chilometraggio può variare.

**** Note sulle prestazioni avanzate.

Perché stai calcolando la distanza? Se l'unico scopo è visualizzarlo,

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

andare avanti. Ma se si confrontano le distanze, si eseguono controlli di portata, ecc., Vorrei aggiungere alcune utili osservazioni sulle prestazioni.

Prendiamo due casi: ordinamento per distanza o abbattimento di un elenco per gli elementi che soddisfano un vincolo di intervallo.

# 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 prima cosa che dobbiamo ricordare è che stiamo usando Pitagora per calcolare la distanza ( dist = sqrt(x^2 + y^2 + z^2)), quindi stiamo facendo molte sqrtchiamate. Matematica 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

In breve: fino a quando non richiediamo effettivamente la distanza in un'unità di X anziché X ^ 2, possiamo eliminare la parte più difficile dei calcoli.

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

Fantastico, entrambe le funzioni non fanno più costose radici quadrate. Sarà molto più veloce. Possiamo anche migliorare in_range convertendolo in un generatore:

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

Ciò ha in particolare dei vantaggi se stai facendo qualcosa del tipo:

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

Ma se la prossima cosa che farai richiede una distanza,

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

considera di dare tuple:

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)

Ciò può essere particolarmente utile se si potrebbero verificare i controlli della catena ("trovare oggetti vicini a X e entro Nm di Y", poiché non è necessario calcolare nuovamente la distanza).

Ma cosa succede se stiamo cercando un elenco davvero ampio di thingse prevediamo che molti di loro non vadano presi in considerazione?

Esiste in realtà un'ottimizzazione molto semplice:

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

Se questo sia utile dipenderà dalla dimensione delle "cose".

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

E ancora, considera di cedere dist_sq. Il nostro esempio di hot dog diventa quindi:

# 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
Perché non aggiungere una funzione così ottimizzata a numpy? Un'estensione per i panda sarebbe ottima anche per una domanda come questa stackoverflow.com/questions/47643952/…
Keith

3
Ho modificato il tuo primo approccio matematico alla distanza. Stavi usando un pointZche non esisteva. Penso che volessi dire due punti nello spazio tridimensionale e ho modificato di conseguenza. Se ho sbagliato, per favore fatemi sapere.
Bram Vanroy,

37

Un'altra istanza di questo metodo di risoluzione dei problemi :

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
puoi usare le implementazioni sqrt e / o sum di numpy? Ciò dovrebbe renderlo più veloce (?).
u0b34a0f6ae,

1
L'ho trovato dall'altra parte delle interwebs norm = lambda x: N.sqrt(N.square(x).sum()); norm(x-y)
u0b34a0f6ae,

2
grattalo. doveva essere da qualche parte. eccolo:numpy.linalg.norm(x-y)
u0b34a0f6ae

13

Iniziando Python 3.8, il mathmodulo fornisce direttamente la distfunzione, che restituisce la distanza euclidea tra due punti (dati come tuple o elenchi di coordinate):

from math import dist

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

E se stai lavorando con le liste:

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

12

Può essere fatto come il seguente. Non so quanto sia veloce, ma non sta usando 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)))

Fare matematica direttamente in Python non è una buona idea in quanto Python è molto lento, in particolare for a, b in zip(a, b). Ma utile comunque.
Sigex,

10

Trovo una funzione 'dist' in matplotlib.mlab, ma non credo sia abbastanza utile.

Lo sto postando qui solo per riferimento.

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)

Questo non è più applicabile. (mpl 3.0)
Nico Schlömer,

8

Mi piace np.dot(punto prodotto):

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

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

8

Un bel one-liner:

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

Tuttavia, se la velocità è un problema, consiglierei di sperimentare sulla tua macchina. Ho scoperto che l'uso della mathlibreria sqrtcon il** operatore per il quadrato è molto più veloce sulla mia macchina rispetto alla soluzione NumPy a una riga.

Ho eseguito i miei test usando questo semplice programma:

#!/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

Sulla mia macchina, math_calc_distfunziona molto più velocemente dinumpy_calc_dist : 1,5 secondi contro 23,5 secondi.

Per ottenere una differenza misurabile tra fastest_calc_diste math_calc_distho dovuto fino TOTAL_LOCATIONSa 6000. Quindi ci fastest_calc_distvogliono ~ 50 secondi mentre ci math_calc_distvogliono ~ 60 secondi.

Puoi anche sperimentare numpy.sqrte numpy.squaresebbene entrambi fossero più lenti delle mathalternative sulla mia macchina.

I miei test sono stati eseguiti con Python 2.6.6.


48
Stai fraintendendo gravemente come usare l'intorpidimento ... Non usare loop o comprendere le liste. Se stai ripetendo e applicando la funzione a ciascun elemento, allora, sì, le funzioni intorpidite saranno più lente. Il punto è di vettorializzare le cose.
Joe Kington,

Se sposto la chiamata numpy.array nel loop in cui sto creando i punti, ottengo risultati migliori con numpy_calc_dist, ma è ancora 10 volte più lento di fast_calc_dist. Se ho così tanti punti e devo trovare la distanza tra ogni coppia, non sono sicuro di cos'altro posso fare per avvantaggiarmi.
user118662

15
Mi rendo conto che questo thread è vecchio, ma voglio solo rinforzare quello che ha detto Joe. Non stai usando numpy correttamente. Quello che stai calcolando è la somma della distanza da ogni punto in p1 a ogni punto in p2. La soluzione con numpy / scipy è oltre 70 volte più veloce sulla mia macchina. Trasforma p1 e p2 in un array (anche usando un ciclo se li hai definiti come dicts). Poi si può ottenere la somma totale in un unico passaggio, scipy.spatial.distance.cdist(p1, p2).sum(). Questo è tutto.
Scott B,

3
Oppure usa numpy.linalg.norm(p1-p2).sum()per ottenere la somma tra ogni punto in p1 e il punto corrispondente in p2 (cioè non tutti i punti in p1 per ogni punto in p2). E se vuoi ogni punto in p1 per ogni punto in p2 e non vuoi usare scipy come nel mio commento precedente, allora puoi usare np.apply_along_axis insieme a numpy.linalg.norm per farlo ancora molto, molto più velocemente quindi la tua soluzione "più veloce".
Scott B,

2
Le versioni precedenti di NumPy avevano implementazioni di norme molto lente. Nelle versioni attuali, non è necessario tutto questo.
Fred Foo,

8

Puoi semplicemente sottrarre i vettori e quindi il prodotto interno.

Seguendo il tuo esempio,

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
questo mi darà il quadrato della distanza. ti stai perdendo un sqrt qui.
Nathan Fellman,

6

Avendoli ae bcome li hai definiti, puoi usare anche:

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

6

Con Python 3.8 è molto semplice.

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

math.dist(p, q)

Restituisce la distanza euclidea tra due punti peq, ciascuno dato come una sequenza (o iterabile) di coordinate. I due punti devono avere la stessa dimensione.

Praticamente equivalente a:

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


5

Ecco un breve codice per la distanza euclidea in Python dati due punti rappresentati come elenchi in Python.

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

1
Numpy accetta anche elenchi come input (non è necessario passare esplicitamente un array numpy)
Alejandro Sazo,

4

Da Python 3.8

Da Python 3.8 il mathmodulo include la funzione math.dist().
Vedi qui https://docs.python.org/3.8/library/math.html#math.dist .

math.dist (p1, p2)
Restituisce la distanza euclidea tra due punti p1 e p2, ciascuno dato come una sequenza (o iterabile) di coordinate.

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

Calcola la distanza euclidea per lo spazio multidimensionale:

 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
Qual è la differenza da questa risposta ?
xskxzr,


2

Puoi facilmente usare la formula

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

che in realtà non fa altro che usare il teorema di Pitagora per calcolare la distanza, aggiungendo i quadrati di Δx, Δy e Δz e radicando il risultato.


1

Trova prima la differenza di due matrici. Quindi, applica la moltiplicazione saggia dell'elemento con il comando moltiplica di numpy. Successivamente, trova la somma dell'elemento saggia moltiplicata per la nuova matrice. Infine, trova la radice quadrata della sommatoria.

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]

È primo elenco modifica alla gamma NumPy e fare in questo modo: print(np.linalg.norm(np.array(a) - np.array(b))). Secondo metodo direttamente dall'elenco python come:print(np.linalg.norm(np.subtract(a,b)))

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.