¿Cómo se puede calcular la distancia euclidiana con NumPy?

529

Tengo dos puntos en 3D:

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

Y quiero calcular la distancia:

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

¿Cuál es la mejor manera de hacer esto con NumPy, o con Python en general? Yo tengo:

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

Respuestas:

885

Uso numpy.linalg.norm:

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

Puede encontrar la teoría detrás de esto en Introducción a la minería de datos

Esto funciona porque la distancia euclidiana es la norma l2 y el valor predeterminado del parámetro ord en numpy.linalg.norm es 2.

enter image description here

u0b34a0f6ae
fuente
13
Los documentos de linalg.norm se pueden encontrar aquí: docs.scipy.org/doc/numpy/reference/generated/… Mi único comentario real fue señalar la conexión entre una norma (en este caso, la norma Frobenius / 2-norma que es el valor predeterminado para la función de norma) y una métrica (en este caso, distancia euclidiana).
Mark Lavin
77
Si OP quería calcular la distancia entre una matriz de coordenadas, también es posible usar scipy.spatial.distance.cdist .
mnky9800n
2
mi pregunta es: ¿por qué usar esto en frente de esto? stackoverflow.com/a/21986532/189411 desde scipy. distancia de importación espacial a = (1,2,3) b = (4,5,6) dst = distance.euclidean (a, b)
Domenico Monaco
2
enlace actualizado a la función cdist de SciPy: docs.scipy.org/doc/scipy/reference/generated/…
Steven C. Howell
hay métodos aún más rápidos que numpy.linalg.norm: semantive.com/blog/…
Muhammad Ashfaq
161

Hay una función para eso en SciPy. Se llama euclidiana .

Ejemplo:

from scipy.spatial import distance
a = (1, 2, 3)
b = (4, 5, 6)
dst = distance.euclidean(a, b)
Una vision
fuente
56
Si busca eficiencia, es mejor usar la función numpy. La distancia scipy es dos veces más lenta que numpy.linalg.norm (ab) (y numpy.sqrt (numpy.sum ((ab) ** 2))). En mi máquina obtengo 19.7 µs con scipy (v0.15.1) y 8.9 µs con numpy (v1.9.2). No es una diferencia relevante en muchos casos, pero si está en bucle puede ser más importante. De un vistazo rápido al código scipy, parece ser más lento porque valida la matriz antes de calcular la distancia.
Algold
@MikePalmice sí, las funciones scipy son totalmente compatibles con numpy. Pero eche un vistazo a lo que Aigold sugirió aquí (que también funciona en una matriz numpy, por supuesto)
Avision
@Avision no está seguro de si funcionará para mí ya que mis matrices tienen diferentes números de filas; tratar de restarlos para obtener una matriz no funciona
Bjorks número uno fan
@MikePalmice, ¿qué es exactamente lo que estás tratando de calcular con estas dos matrices? ¿Cuál es la entrada / salida esperada?
Avision
Ty para el seguimiento. Hay una descripción aquí: stats.stackexchange.com/questions/322620/… . Tengo 2 tablas de 'operaciones'; cada uno tiene una etiqueta de "código", pero los dos conjuntos de etiquetas son totalmente diferentes. mi objetivo es encontrar el código mejor o más cercano de la segunda tabla correspondiente a un código fijo en la primera (sé cuál debería ser la respuesta de la inspección manual, pero quiero escalar hasta cientos de tablas más adelante). Entonces el primer subconjunto es fijo; Calculo avg euclid dist bw this y todos los subconjuntos de códigos del segundo, luego clasifico
fanático número uno de Bjorks el
108

Para cualquier persona interesada en calcular múltiples distancias a la vez, he hecho una pequeña comparación usando perfplot (un pequeño proyecto mío).

El primer consejo es organizar sus datos de manera que las matrices tengan dimensión (3, n)(y obviamente sean contiguas en C). Si la adición ocurre en la primera dimensión contigua, las cosas son más rápidas y no importa demasiado si usa 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))

que es, por un ligero margen, la variante más rápida. (Eso también es cierto para solo una fila).

Las variantes en las que sumas sobre el segundo eje axis=1, son sustancialmente más lentas.

ingrese la descripción de la imagen aquí


Código para reproducir 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)",
)
Nico Schlömer
fuente
3
Gracias. ¡Aprendí algo nuevo hoy! Para la matriz de una sola dimensión, la cadena serái,i->
Tirtha R
44
ITD ser más fresco hasta el mío si había una comparación de los consumos de memoria
dragonLOLz
Me gustaría usar su código, pero me cuesta entender cómo se supone que se deben organizar los datos. ¿Puede dar un ejemplo? ¿Cómo datatiene que verse?
Johannes Wiesner
1
Proyecto realmente limpio y hallazgos. He estado haciendo tramas a medias de la misma naturaleza, así que creo que cambiaré a tu proyecto y contribuiré con las diferencias, si te gustan.
Físico loco
42

Quiero exponer la respuesta simple con varias notas de rendimiento. np.linalg.norm hará quizás más de lo que necesita:

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

En primer lugar, esta función está diseñada para trabajar sobre una lista y devolver todos los valores, por ejemplo, para comparar la distancia desde pAel conjunto de puntos sP:

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

Recuerda varias cosas:

  • Las llamadas a funciones de Python son caras.
  • [Regular] Python no almacena en caché las búsquedas de nombres.

Entonces

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

No es tan inocente como parece.

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

En primer lugar, cada vez que lo llamamos, tenemos que hacer una búsqueda global de "np", una búsqueda de alcance para "linalg" y una búsqueda de alcance para "norma", y la sobrecarga de simplemente llamar la función puede equivaler a docenas de python instrucciones.

Por último, desperdiciamos dos operaciones para almacenar el resultado y volver a cargarlo para devolverlo ...

Primer paso en la mejora: agilice la búsqueda, salte la tienda

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

Obtenemos el más simplificado:

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

Sin embargo, la sobrecarga de llamadas a funciones todavía equivale a algo de trabajo. Y querrás hacer puntos de referencia para determinar si podrías ser mejor haciendo los cálculos tú mismo:

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

En algunas plataformas, **0.5es más rápido quemath.sqrt . Su experiencia puede ser diferente.

**** Notas de rendimiento avanzadas.

¿Por qué estás calculando la distancia? Si el único propósito es mostrarlo,

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

superar. Pero si está comparando distancias, haciendo verificaciones de rango, etc., me gustaría agregar algunas observaciones útiles de rendimiento.

Tomemos dos casos: ordenar por distancia o seleccionar una lista de elementos que cumplan con una restricción de rango.

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

Lo primero que debemos recordar es que estamos usando Pitágoras para calcular la distancia ( dist = sqrt(x^2 + y^2 + z^2)), por lo que estamos haciendo muchas sqrtllamadas. Matemáticas 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 resumen: hasta que realmente necesitemos la distancia en una unidad de X en lugar de X ^ 2, podemos eliminar la parte más difícil de los cálculos.

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

Genial, ambas funciones ya no tienen raíces cuadradas caras. Eso será mucho más rápido. También podemos mejorar in_range convirtiéndolo en un generador:

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

Esto tiene beneficios especialmente si está haciendo algo como:

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

Pero si lo siguiente que vas a hacer requiere una distancia,

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

considere ceder tuplas:

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)

Esto puede ser especialmente útil si puede encadenar verificaciones de rango ('encontrar cosas que están cerca de X y dentro de Nm de Y', ya que no tiene que calcular la distancia nuevamente).

Pero, ¿qué pasa si estamos buscando una lista realmente grande thingsy anticipamos que muchos de ellos no merecen ser considerados?

En realidad, hay una optimización muy 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

Si esto es útil dependerá del tamaño de las "cosas".

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

Y nuevamente, considere obtener el dist_sq. Nuestro ejemplo de hot dog se convierte en:

# 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))
kfsone
fuente
1
¿Por qué no agregar una función tan optimizada a numpy? Una extensión para pandas también sería genial para una pregunta como esta stackoverflow.com/questions/47643952/…
Keith
3
Edité tu primer acercamiento matemático a la distancia. Estabas usando un pointZque no existía. Creo que lo que querías decir era dos puntos en el espacio tridimensional y edité en consecuencia. Si me equivoqué, avíseme.
Bram Vanroy
37

Otra instancia de este método de resolución de problemas :

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)
Nathan Fellman
fuente
1
¿puedes usar las implementaciones de numpy sqrt y / o sum? Eso debería hacerlo más rápido (?).
u0b34a0f6ae
1
Encontré esto al otro lado de las redes norm = lambda x: N.sqrt(N.square(x).sum()); norm(x-y)
u0b34a0f6ae
2
rasca eso. Tenía que estar en alguna parte. aquí está:numpy.linalg.norm(x-y)
u0b34a0f6ae
13

Comenzando Python 3.8, el mathmódulo proporciona directamente la distfunción, que devuelve la distancia euclidiana entre dos puntos (dados como tuplas o listas de coordenadas):

from math import dist

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

Y si estás trabajando con listas:

dist([1, 2, 6], [-2, 3, 2]) # 5.0990195135927845
Xavier Guihot
fuente
12

Se puede hacer de la siguiente manera. No sé qué tan rápido es, pero no está 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)))
El demz
fuente
Hacer matemáticas directamente en Python no es una buena idea ya que Python es muy lento, específicamente for a, b in zip(a, b). Pero útil no obstante.
Sigex
10

Encuentro una función 'dist' en matplotlib.mlab, pero no creo que sea lo suficientemente útil.

Lo estoy publicando aquí solo como referencia.

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)
Alan Wang
fuente
Esto ya no es aplicable. (mpl 3.0)
Nico Schlömer
8

Me gusta np.dot(producto de punto):

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

distance = (np.dot(a-b,a-b))**.5
huesos de viaje
fuente
8

Una buena frase:

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

Sin embargo, si la velocidad es una preocupación, recomendaría experimentar en su máquina. He encontrado que usar la mathbiblioteca sqrtcon** operador para el cuadrado es mucho más rápido en mi máquina que la solución NumPy de una sola línea.

Ejecuté mis pruebas usando este sencillo programa:

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

En mi máquina, math_calc_distcorre mucho más rápido quenumpy_calc_dist : 1.5 segundos versus 23.5 segundos.

Para obtener una diferencia medible entre fastest_calc_disty math_calc_disttuve que hasta TOTAL_LOCATIONS6000. Luego fastest_calc_disttoma ~ 50 segundos mientrasmath_calc_dist toma ~ 60 segundos.

También puede experimentar numpy.sqrty numpy.squareaunque ambos fueron más lentos que las mathalternativas en mi máquina.

Mis pruebas se ejecutaron con Python 2.6.6.

usuario118662
fuente
48
Estás malentendido sobre cómo usar numpy ... No uses bucles o listas de comprensiones. Si está iterando y aplicando la función a cada elemento, entonces, sí, las funciones numpy serán más lentas. El objetivo es vectorizar las cosas.
Joe Kington el
Si muevo la llamada numpy.array al bucle donde estoy creando los puntos, obtengo mejores resultados con numpy_calc_dist, pero aún así es 10 veces más lento que rapid_calc_dist. Si tengo tantos puntos y necesito encontrar la distancia entre cada par, no estoy seguro de qué más puedo hacer para obtener ventaja.
user118662
15
Me doy cuenta de que este hilo es viejo, pero solo quiero reforzar lo que dijo Joe. No estás usando numpy correctamente. Lo que está calculando es la suma de la distancia desde cada punto en p1 a cada punto en p2. La solución con numpy / scipy es más de 70 veces más rápida en mi máquina. Convierta p1 y p2 en una matriz (incluso utilizando un bucle si los tiene definidos como dictos). Entonces se puede obtener la suma total en un solo paso, scipy.spatial.distance.cdist(p1, p2).sum(). Eso es.
Scott B
3
O use numpy.linalg.norm(p1-p2).sum()para obtener la suma entre cada punto en p1 y el punto correspondiente en p2 (es decir, no todos los puntos en p1 a cada punto en p2). Y si desea cada punto en p1 a cada punto en p2 y no desea usar scipy como en mi comentario anterior, puede usar np.apply_along_axis junto con numpy.linalg.norm para hacerlo aún mucho, mucho más rápido entonces su solución "más rápida".
Scott B
2
Las versiones anteriores de NumPy tenían implementaciones de normas muy lentas. En las versiones actuales, no hay necesidad de todo esto.
Fred Foo
8

Simplemente puede restar los vectores y luego el producto interno.

Siguiendo tu ejemplo,

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)
PuercoPop
fuente
55
Esto me dará el cuadrado de la distancia. Te estás perdiendo un sqrt aquí.
Nathan Fellman
6

Teniendo ay bcomo los definiste, puedes usar también:

distance = np.sqrt(np.sum((a-b)**2))
Alejandro Sazo
fuente
6

Con Python 3.8, es muy fácil.

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

math.dist(p, q)

Devuelve la distancia euclidiana entre dos puntos p y q, cada uno dado como una secuencia (o iterable) de coordenadas. Los dos puntos deben tener la misma dimensión.

Aproximadamente equivalente a:

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

hakiko
fuente
5

Aquí hay un código conciso para la distancia euclidiana en Python dados dos puntos representados como listas en Python.

def distance(v1,v2): 
    return sum([(x-y)**2 for (x,y) in zip(v1,v2)])**(0.5)
Andy Lee
fuente
1
Numpy también acepta listas como entradas (no es necesario pasar explícitamente una matriz numpy)
Alejandro Sazo
4

Desde Python 3.8

Desde Python 3.8 el mathmódulo incluye la función math.dist().
Ver aquí https://docs.python.org/3.8/library/math.html#math.dist .

math.dist (p1, p2)
Devuelve la distancia euclidiana entre dos puntos p1 y p2, cada uno dado como una secuencia (o iterable) de coordenadas.

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
ePi272314
fuente
3

Calcule la distancia euclidiana para el espacio multidimensional:

 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
Gennady Nikitin
fuente
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)
Ankur Nadda
fuente
2
¿Cuál es la diferencia con esta respuesta ?
xskxzr
2
import math

dist = math.hypot(math.hypot(xa-xb, ya-yb), za-zb)
Jonas De Schouwer
fuente
2

Puedes usar fácilmente la fórmula

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

que en realidad no hace nada más que usar el teorema de Pitágoras para calcular la distancia, agregando los cuadrados de Δx, Δy y Δz y enraizando el resultado.

Jonas De Schouwer
fuente
1

Encuentra la diferencia de dos matrices primero. Luego, aplique la multiplicación sabia de elementos con el comando de multiplicación de numpy. Después de eso, encuentre la suma de la nueva matriz multiplicada por elementos sabios. Finalmente, encuentre la raíz cuadrada de la sumatoria.

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
johncasey
fuente
1
import numpy as np
# any two python array as two points
a = [0, 0]
b = [3, 4]

En primer lugar, la lista de cambios de serie numpy y hacerlo de esta manera: print(np.linalg.norm(np.array(a) - np.array(b))). Segundo método directamente desde la lista de Python como:print(np.linalg.norm(np.subtract(a,b)))

Uddhav Gautam
fuente