Как рассчитать евклидово расстояние с помощью NumPy?


529

У меня есть две точки в 3D:

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

И я хочу рассчитать расстояние:

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

Какой лучший способ сделать это с NumPy или с Python в целом? Я имею:

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

Ответы:


885

Используйте numpy.linalg.norm:

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

Вы можете найти теорию за этим в Введение в интеллектуальный анализ данных

Это работает, потому что евклидово расстояние равно l2 норме, а значение по умолчанию для параметра ord в numpy.linalg.norm равно 2.

введите описание изображения здесь


13
Документы по linalg.norm можно найти здесь: docs.scipy.org/doc/numpy/reference/generated/… Единственным моим реальным комментарием было указание на связь между нормой (в данном случае норма Фробениуса / 2-норма который является значением по умолчанию для функции нормы) и метрики (в данном случае евклидово расстояние).
Марк Лавин

7
Если OP хотел вычислить расстояние между массивом координат, также можно использовать scipy.spatial.distance.cdist .
mnky9800n

2
мой вопрос: зачем использовать это в противоположность этому? stackoverflow.com/a/21986532/189411 от scipy.spatial import import a a ((1,2,3) b = (4,5,6) dst = distance.euclidean (a, b)
Доменико Монако,

2
обновлена ​​ссылка на функцию cdist
Стивен Хоуэлл,

Есть даже более быстрые методы, чем numpy.linalg.norm: semantive.com/blog/…
Мухаммед Ашфак

161

Для этого в SciPy есть функция. Это называется евклидово .

Пример:

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

56
Если вы ищете эффективность, лучше использовать функцию numpy. Расстояние scipy в два раза медленнее, чем numpy.linalg.norm (ab) (и numpy.sqrt (numpy.sum ((ab) ** 2))). На моей машине я получаю 19,7 мкс с scipy (v0.15.1) и 8,9 мкс с numpy (v1.9.2). Во многих случаях это несущественная разница, но если в цикле может стать более значительным. При быстром взгляде на код scipy он выглядит медленнее, поскольку он проверяет массив перед вычислением расстояния.
Алгольд

@MikePalmice да, функции scipy полностью совместимы с numpy. Но взгляните на то, что здесь предложил aigold (который, конечно же, также работает и с numpy array)
Avision

@Avision не уверен, сработает ли это для меня, так как мои матрицы имеют разное количество строк; попытка вычесть их, чтобы получить одну матрицу, не работает
поклонник Бьоркса номер один

@MikePalmice, что именно вы пытаетесь вычислить с этими двумя матрицами? каков ожидаемый ввод / вывод?
Avision

Ты за продолжение. Здесь есть описание: stats.stackexchange.com/questions/322620/… . У меня есть 2 таблицы «операций»; у каждого есть метка «код», но два набора меток абсолютно разные. моя цель - найти лучший или ближайший код из второй таблицы, соответствующий фиксированному коду в первой (я знаю, каким должен быть ответ при ручной проверке, но позже я хочу увеличить его до сотен таблиц). Итак, первое подмножество фиксировано; Я вычисляю avg euclid dist bw this и все кодовые подмножества второго, затем сортирую
фанат Бьоркса номер один

108

Для тех, кто заинтересован в одновременном вычислении нескольких расстояний, я провел небольшое сравнение с использованием perfplot (небольшой мой проект).

Первый совет состоит в том, чтобы организовать ваши данные таким образом, чтобы массивы имели размерность (3, n)(и, очевидно, C-смежны). Если добавление происходит в смежном первом измерении, все происходит быстрее, и это не имеет большого значения, если вы используете sqrt-sumс axis=0, linalg.normс axis=0или

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

что, с небольшим отрывом, самый быстрый вариант. (Это действительно справедливо только для одного ряда.)

Варианты, где вы суммируете по второй оси, axis=1все существенно медленнее.

введите описание изображения здесь


Код для воспроизведения сюжета:

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
Спасибо. Я узнал что-то новое сегодня! Для одноразмерного массива строка будетi,i->
Tirtha R

4
было бы еще круче, если бы было сравнение потребления памяти
dragonLOLz

Я хотел бы использовать ваш код, но я пытаюсь понять, как должны быть организованы данные. Можете привести пример? Как dataдолжно выглядеть?
Йоханнес Виснер,

1
Действительно аккуратный проект и выводы. Я делал несколько полусумных сюжетов той же природы, поэтому я думаю, что переключусь на ваш проект и внесу различия, если они вам понравятся.
Безумный физик

42

Я хочу изложить простой ответ с различными заметками о производительности. np.linalg.norm сделает, возможно, больше, чем вам нужно:

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

Во-первых, эта функция предназначена для работы со списком и возврата всех значений, например, для сравнения расстояния pAдо набора точек sP:

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

Помните несколько вещей:

  • Вызовы функций Python стоят дорого.
  • [Обычный] Python не кэширует поиск имен.

Так

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

не так невинно, как кажется.

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

Во-первых - каждый раз, когда мы вызываем его, мы должны выполнить глобальный поиск для «np», поиск в области видимости для «linalg» и поиск в области видимости для «norm» и накладные расходы, связанные с простым вызовом функции могут равняться десяткам Python инструкции.

Наконец, мы потратили две операции, чтобы сохранить результат и перезагрузить его для возврата ...

Первый шаг к улучшению: сделайте поиск быстрее, пропустите магазин

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

Мы получаем гораздо более упорядоченный:

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

Затраты на вызов функции по-прежнему составляют некоторую работу. И вы захотите сделать тесты, чтобы определить, лучше ли вам делать математику самостоятельно:

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

На некоторых платформах **0.5это быстрее, чем math.sqrt. Ваш пробег может варьироваться.

**** Расширенные заметки производительности.

Почему вы рассчитываете расстояние? Если единственной целью является его отображение,

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

двигаться вперед. Но если вы сравниваете расстояния, проводите проверки дальности и т. Д., Я хотел бы добавить некоторые полезные наблюдения за производительностью.

Давайте рассмотрим два случая: сортировка по расстоянию или отбор списка по элементам, которые соответствуют ограничению диапазона.

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

Первое, что нам нужно помнить, это то, что мы используем Pythagoras для вычисления расстояния ( dist = sqrt(x^2 + y^2 + z^2)), поэтому мы делаем много sqrtвызовов. Математика 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

Короче говоря: пока нам не потребуется расстояние в единице X, а не X ^ 2, мы можем исключить самую сложную часть вычислений.

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

Отлично, обе функции больше не делают дорогих квадратных корней. Это будет намного быстрее. Мы также можем улучшить in_range, преобразовав его в генератор:

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

Это особенно полезно, если вы делаете что-то вроде:

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

Но если следующая вещь, которую вы собираетесь сделать, требует расстояния,

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

рассмотреть возможность получения кортежей:

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)

Это может быть особенно полезно, если вы можете связать проверки диапазона («найдите вещи, которые находятся около X и в пределах Nm от Y», так как вам не нужно снова вычислять расстояние).

Но что делать, если мы ищем действительно большой список thingsи ожидаем, что многие из них не заслуживают рассмотрения?

Там на самом деле очень простая оптимизация:

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

Будет ли это полезно, будет зависеть от размера «вещей».

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

И снова, рассмотрите возможность выдачи dist_sq. Наш пример хот-дога становится:

# 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
Почему бы не добавить такую ​​оптимизированную функцию в NumPy? Расширение для панд также
Кейт

3
Я отредактировал ваш первый математический подход к расстоянию. Вы использовали то pointZ, чего не было. Я думаю, что вы имели в виду две точки в трехмерном пространстве, и я отредактировал соответственно. Если я ошибся, пожалуйста, дайте мне знать.
Брэм Ванрой

37

Еще один пример этого метода решения проблем :

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
Вы можете использовать sqrt и / или суммы реализации numpy? Это должно сделать это быстрее (?).
u0b34a0f6ae

1
Я нашел это по другую сторону от паутины norm = lambda x: N.sqrt(N.square(x).sum()); norm(x-y)
u0b34a0f6ae

2
сотрите это. это должно было быть где-то. вот оно:numpy.linalg.norm(x-y)
u0b34a0f6ae

13

Запуск Python 3.8, то mathмодуль непосредственно обеспечивает distфункцию, которая возвращает евклидово расстояния между двумя точками ( с учетом как кортежи или списки координатов):

from math import dist

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

И если вы работаете со списками:

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

12

Это можно сделать следующим образом. Я не знаю, как это быстро, но он не использует 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)))

Делать математику непосредственно в python не очень хорошая идея, так как python очень медленный, в частности for a, b in zip(a, b). Но тем не менее полезно.
Sigex

10

Я нахожу функцию dist в matplotlib.mlab, но не думаю, что она достаточно удобна.

Я публикую это здесь только для справки.

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)

Это больше не применимо. (mpl 3,0)
Нико Шломер

8

Мне нравится np.dot(точечный продукт):

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

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

8

Хороший однострочник:

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

Однако, если скорость вызывает беспокойство, я бы порекомендовал поэкспериментировать на вашей машине. Я обнаружил, что с помощью mathбиблиотеки sqrtс** оператором для квадрата намного быстрее на моей машине, чем однострочное решение NumPy.

Я провел свои тесты, используя эту простую программу:

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

На моей машине math_calc_distработает намного быстрее чем numpy_calc_dist: 1,5 секунды против 23,5 секунды.

Чтобы получить измеримую разницу между мной fastest_calc_distи math_calc_distмне нужно было до TOTAL_LOCATIONS6000. Затем fastest_calc_distтребуется ~ 50 секунд, а math_calc_distзанимает ~ 60 секунд.

Вы также можете поэкспериментировать, numpy.sqrtи numpy.squareхотя оба mathварианта были медленнее, чем альтернативы на моей машине.

Мои тесты были запущены с Python 2.6.6.


48
Вы плохо понимаете, как использовать NumPy ... Не используйте циклы или списки. Если вы выполняете итерацию и применяете функцию к каждому элементу, то да, функции с кучей будут медленнее. Весь смысл в том, чтобы векторизовать вещи.
Джо Кингтон

Если я переместу вызов numpy.array в цикл, где я создаю точки, я получу лучшие результаты с numpy_calc_dist, но он все равно будет в 10 раз медленнее, чем fastest_calc_dist. Если у меня так много очков, и мне нужно найти расстояние между каждой парой, я не уверен, что еще я могу сделать, чтобы выиграть numpy.
user118662

15
Я понимаю, что эта тема старая, но я просто хочу подтвердить то, что сказал Джо. Вы не используете NumPy правильно. Вы рассчитываете сумму расстояния от каждой точки в p1 до каждой точки в p2. Решение с NumPy / Scipy более чем в 70 раз быстрее на моей машине. Превратите p1 и p2 в массив (даже используя цикл, если они определены как dicts). Тогда вы можете получить общую сумму за один шаг scipy.spatial.distance.cdist(p1, p2).sum(). Вот и все.
Скотт Б

3
Или используйте numpy.linalg.norm(p1-p2).sum()для получения суммы между каждой точкой в ​​p1 и соответствующей точкой в ​​p2 (то есть не каждой точкой в ​​p1 для каждой точки в p2). И если вы хотите, чтобы каждая точка в p1 совпадала с каждой точкой в ​​p2, и не хотите использовать scipy, как в моем предыдущем комментарии, тогда вы можете использовать np.apply_along_axis вместе с numpy.linalg.norm, чтобы делать это намного, намного быстрее тогда ваше "самое быстрое" решение.
Скотт Б

2
Предыдущие версии NumPy имели очень медленные реализации норм. В текущих версиях нет необходимости во всем этом.
Фред Фу

8

Вы можете просто вычесть векторы и затем получить внутренний продукт.

Следуя вашему примеру,

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
это даст мне квадрат расстояния. вам не хватает sqrt здесь.
Натан Феллман


6

С Python 3.8 это очень просто.

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

math.dist(p, q)

Вернуть евклидово расстояние между двумя точками p и q, каждая из которых представлена ​​в виде последовательности (или итерируемой) координат. Две точки должны иметь одинаковое измерение.

Примерно эквивалентно:

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


5

Вот краткий код евклидова расстояния в Python с учетом двух точек, представленных в виде списков в Python.

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

1
Numpy также принимает списки в качестве входных данных (нет необходимости явно передавать массив numpy)
Алехандро

4

Начиная с Python 3.8

Начиная с Python 3.8 mathмодуль включает в себя функцию math.dist().
Смотрите здесь https://docs.python.org/3.8/library/math.html#math.dist .

math.dist (p1, p2)
Возвращает евклидово расстояние между двумя точками p1 и p2, каждая из которых задана как последовательность (или итерация) координат.

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

Рассчитаем евклидово расстояние для многомерного пространства:

 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
В чем отличие от этого ответа ?
xskxzr


2

Вы можете легко использовать формулу

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

что на самом деле не более чем использование теоремы Пифагора для вычисления расстояния путем сложения квадратов Δx, Δy и Δz и укоренения результата.


1

Сначала найдите разницу двух матриц. Затем примените поэлементное умножение с помощью команды умножения numpy. После этого найдите суммирование умноженной на элемент новой матрицы. Наконец, найдите квадратный корень суммирования.

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]

Вы первый список изменений в Numpy массив и сделать так: print(np.linalg.norm(np.array(a) - np.array(b))). Второй метод прямо из списка Python:print(np.linalg.norm(np.subtract(a,b)))

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.