Вычислить расстояния между одной точкой в ​​матрице и всеми остальными точками

Я новичок в Python, и мне нужно реализовать алгоритм кластеризации. Для этого мне нужно будет вычислить расстояния между заданными входными данными.

Рассмотрим следующие входные данные -

    [[1,2,8],
     [7,4,2],
     [9,1,7],
     [0,1,5],
     [6,4,3]]

Я хочу достичь здесь: я хочу рассчитать расстояние [1,2,8] от ВСЕХ других точек и найти точку, где расстояние минимально.

И я должен повторить это для ВСЕХ других пунктов.

Я пытаюсь реализовать это с помощью цикла FOR, но я уверен, что в SciPy / NumPy должна быть функция, которая может помочь мне эффективно достичь этого результата.

Я посмотрел онлайн, но команда pdist не смогла выполнить мою работу.

Может ли кто-нибудь направить меня?

TIA


person Adhish Thite    schedule 12.10.2017    source источник


Ответы (4)


Используйте np.linalg.norm в сочетании с широковещательной передачей (numpy external subtraction), вы можете:

np.linalg.norm(a - a[:,None], axis=-1)

a[:,None] вставить новую ось в a, a - a[:,None] затем будет выполнять вычитание строки за строкой из-за широковещательной передачи. np.linalg.norm вычисляет np.sqrt(np.sum(np.square(...))) по последней оси:


a = np.array([[1,2,8],
     [7,4,2],
     [9,1,7],
     [0,1,5],
     [6,4,3]])

np.linalg.norm(a - a[:,None], axis=-1)
#array([[ 0.        ,  8.71779789,  8.1240384 ,  3.31662479,  7.34846923],
#       [ 8.71779789,  0.        ,  6.164414  ,  8.18535277,  1.41421356],
#       [ 8.1240384 ,  6.164414  ,  0.        ,  9.21954446,  5.83095189],
#       [ 3.31662479,  8.18535277,  9.21954446,  0.        ,  7.        ],
#       [ 7.34846923,  1.41421356,  5.83095189,  7.        ,  0.        ]])

Например, элементы [0,1], [0,2] соответствуют:

np.sqrt(np.sum((a[0] - a[1]) ** 2))
# 8.717797887081348

np.sqrt(np.sum((a[0] - a[2]) ** 2))
# 8.1240384046359608

соответственно.

person Psidom    schedule 12.10.2017
comment
Спасибо за ответ ! Работает отлично. Еще один вопрос. Чтобы найти минимальное расстояние между точками, мне придется удалить «0» из каждой строки и найти минимальное. Но если одна и та же точка появляется более одного раза, я должен рассматривать ее как две разные точки. Итак, мне придется уменьшить a [i, i], так как оно будет равно нулю, но я должен использовать другой «0». Есть идеи, как я могу этого добиться? - person Adhish Thite; 12.10.2017
comment
Быстрое решение - заменить все диагонали на np.nan, а затем использовать np.nanmin или np.nanargmin: dist = np.linalg.norm(a - a[:,None], axis=-1); dist[np.arange(dist.shape[0]), np.arange(dist.shape[0])] = np.nan; np.nanargmin(dist, axis=0) - person Psidom; 12.10.2017

Вот один из подходов с использованием SciPy's cdist -

from scipy.spatial.distance import cdist
def closest_rows(a):
    # Get euclidean distances as 2D array
    dists = cdist(a, a, 'sqeuclidean')

    # Fill diagonals with something greater than all elements as we intend
    # to get argmin indices later on and then index into input array with those
    # indices to get the closest rows
    dists.ravel()[::dists.shape[1]+1] = dists.max()+1
    return a[dists.argmin(1)]

Пробный прогон -

In [72]: a
Out[72]: 
array([[1, 2, 8],
       [7, 4, 2],
       [9, 1, 7],
       [0, 1, 5],
       [6, 4, 3]])

In [73]: closest_rows(a)
Out[73]: 
array([[0, 1, 5],
       [6, 4, 3],
       [6, 4, 3],
       [1, 2, 8],
       [7, 4, 2]])

Тест во время выполнения

Другой рабочий подход (ы) -

def norm_app(a): # @Psidom's soln
    dist = np.linalg.norm(a - a[:,None], axis=-1); 
    dist[np.arange(dist.shape[0]), np.arange(dist.shape[0])] = np.nan
    return a[np.nanargmin(dist, axis=0)]

Сроки с 10,000 баллами -

In [79]: a = np.random.randint(0,9,(10000,3))

In [80]: %timeit norm_app(a) # @Psidom's soln
1 loop, best of 3: 3.83 s per loop

In [81]: %timeit closest_rows(a)
1 loop, best of 3: 392 ms per loop

Дальнейшее повышение производительности

Есть пакет eucl_dist (отказ от ответственности: я его автор), который содержит различные методы для вычисления евклидовых расстояний, которые намного больше эффективнее, чем SciPy's cdist, особенно для больших массивов.

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

from eucl_dist.cpu_dist import dist
def closest_rows_v2(a):
    dists = dist(a,a, matmul="gemm", method="ext") 
    dists.ravel()[::dists.shape[1]+1] = dists.max()+1
    return a[dists.argmin(1)]

Сроки -

In [162]: a = np.random.randint(0,9,(10000,3))

In [163]: %timeit closest_rows(a)
1 loop, best of 3: 394 ms per loop

In [164]: %timeit closest_rows_v2(a)
1 loop, best of 3: 229 ms per loop
person Divakar    schedule 12.10.2017

Предлагаю использовать pdist и squareform из scipy.spatial.distance

Рассмотрим следующий массив точек:

a = np.array([[1,2,8], [7,4,2], [9,1,7], [0,1,5], [6,4,3]])

Если вы хотите отобразить все расстояния между точкой [1,2,8] и другими точками:

squareform(pdist(a))

Out[1]: array([[ 0.        ,  8.71779789,  8.1240384 ,  3.31662479,  7.34846923],
               [ 8.71779789,  0.        ,  6.164414  ,  8.18535277,  1.41421356],
               [ 8.1240384 ,  6.164414  ,  0.        ,  9.21954446,  5.83095189],
               [ 3.31662479,  8.18535277,  9.21954446,  0.        ,  7.        ],
               [ 7.34846923,  1.41421356,  5.83095189,  7.        ,  0.        ]])

I вы хотите отобразить кратчайшее расстояние между точкой [1,2,8] и ближайшей точкой:

sorted(squareform(pdist(a))[0])[1]

Out[2]: 3.3166247903553998

[0] - индекс вашей первой точки ([1,2,8])

[1] является индексом второго минимального значения (чтобы избежать нулей)

Если вы хотите отобразить индекс точки, ближайшей к [1,2,8]:

np.argsort(squareform(pdist(a))[0])[1]

Out[3]: 3
person solub    schedule 05.01.2018

В этой ветке вы можете использовать функцию e_dist и получить те же результаты.

Дополнение

Время: на моем ноутбуке с нехваткой памяти я могу провести сравнение только с меньшей выборкой, чем у @Psidom, используя его функцию norm_app.

a = np.random.randint (0,9, (5000,3))

% timeit norm_app (a) 1,91 с ± 13,5 мс на цикл (среднее ± стандартное отклонение из 7 прогонов, по 1 циклу в каждом)

% timeit e_dist (a, a) 631 мс ± 3,64 мс на цикл (среднее ± стандартное отклонение из 7 прогонов, по 1 циклу в каждом)

a 
array([[1, 2, 8],
       [7, 4, 2],
       [9, 1, 7],
       [0, 1, 5],
       [6, 4, 3]])

dm = e_dist(a, a)  # get the def from the link

dm
Out[7]: 
array([[ 0.  ,  8.72,  8.12,  3.32,  7.35],
       [ 8.72,  0.  ,  6.16,  8.19,  1.41],
       [ 8.12,  6.16,  0.  ,  9.22,  5.83],
       [ 3.32,  8.19,  9.22,  0.  ,  7.  ],
       [ 7.35,  1.41,  5.83,  7.  ,  0.  ]])

idx = np.argsort(dm)

closest = a[idx]

closest
Out[10]: 
array([[[1, 2, 8],
        [0, 1, 5],
        [6, 4, 3],
        [9, 1, 7],
        [7, 4, 2]],

       [[7, 4, 2],
        [6, 4, 3],
        [9, 1, 7],
        [0, 1, 5],
        [1, 2, 8]],

       [[9, 1, 7],
        [6, 4, 3],
        [7, 4, 2],
        [1, 2, 8],
        [0, 1, 5]],

       [[0, 1, 5],
        [1, 2, 8],
        [6, 4, 3],
        [7, 4, 2],
        [9, 1, 7]],

       [[6, 4, 3],
        [7, 4, 2],
        [9, 1, 7],
        [0, 1, 5],
        [1, 2, 8]]])
person NaN    schedule 12.10.2017
comment
Я не получил ожидаемых результатов от вашего closest, поскольку предполагал, что результат будет той же формы, что и входной - каждая точка будет иметь одну ближайшую точку. Итак, я не мог включить ваше в свои результаты по времени. Кроме того, это norm_app принадлежит Псидому. - person Divakar; 12.10.2017
comment
исправил имя, спасибо, я решил отсортировать точки, а не результаты, на случай, если эта точка тоже нужна - person NaN; 12.10.2017