Як можна обчислити евклідову відстань за допомогою 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))

Відповіді:


884

Використання 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/… Єдиним моїм реальним коментарем було вказування на зв'язок між нормою (у цьому випадку норма Frobenius / 2-норма що є типовим для функції норми) і метрикою (в даному випадку евклідовою відстані).
Марк Лавін

7
Якщо ОП хотів обчислити відстань між масивом координат, можливо також використовувати scipy.spatial.distance.cdist .
mnky9800n

2
моє запитання: навіщо використовувати це навпроти цього? stackoverflow.com/a/21986532/189411 від scipy.spatial відстані імпортувати = (1,2,3) Ь = (4,5,6) = distance.euclidean ДСТ (а, б)
Доменіко Монако

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. Відстань в області вуха вдвічі менша, ніж 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 масиві)
Avision

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

@MikePalmice що саме ви намагаєтеся обчислити з цими двома матрицями? який очікуваний вхід / вихід?
Видання

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

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 дорогі.
  • [Regular] 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)

Перше, що нам потрібно пам’ятати, це те, що ми використовуємо Піфагора для обчислення відстані ( 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? Розширення для панд також буде чудовим для такого питання, як це stackoverflow.com/questions/47643952/…
Кіт

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). Але корисна тим більше.
Сігекс

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

Гарний однолінійний:

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. Якщо у мене так багато очок, і мені потрібно знайти відстань між кожною парою, я не впевнений, що ще можу зробити, щоб вигодити нуд.
користувач118662

15
Я усвідомлюю, що ця нитка стара, але я просто хочу підкріпити те, що сказав Джо. Ви неправильно використовуєте numpy. Що ви обчислюєте, це сума відстані від кожної точки в p1 до кожної точки p2. Рішення з numpy / scipy на 70 разів швидше на моїй машині. Зробіть p1 і p2 в масив (навіть використовуючи цикл, якщо ви їх визначили як дикти). Тоді ви можете отримати загальну суму в одному кроці 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 також приймає списки як вхідні дані (не потрібно явно передавати нумерований масив)
Алехандро Сазо

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.