用Numpy有效地计算欧氏距离矩阵

Wes*_*des 17 python performance numpy matrix euclidean-distance

我在二维空间中有一组点,需要计算从每个点到另一个点的距离.

我有一个相对较少的分数,也许最多100分.但是因为我需要经常快速地做这些以确定这些移动点之间的关系,并且因为我知道迭代这些点可能会一样糟糕因为O(n ^ 2)的复杂性,我正在寻找利用numpy的矩阵魔法(或scipy)的方法.

正如我的代码所示,每个对象的坐标都存储在其类中.但是,当我更新类坐标时,我也可以在numpy数组中更新它们.

class Cell(object):
    """Represents one object in the field."""
    def __init__(self,id,x=0,y=0):
        self.m_id = id
        self.m_x = x
        self.m_y = y
Run Code Online (Sandbox Code Playgroud)

在我看来,创建一个欧几里德距离矩阵来防止重复,但也许你有一个更聪明的数据结构.

我也很开心指向漂亮的算法.

此外,我注意到有类似的问题涉及欧几里德距离和numpy,但没有找到任何直接解决这个有效填充全距离矩阵的问题.

Kiw*_*iwi 30

您可以利用以下complex类型:

# build a complex array of your cells
z = np.array([complex(c.m_x, c.m_y) for c in cells])
Run Code Online (Sandbox Code Playgroud)

第一解决方案

# mesh this array so that you will have all combinations
m, n = np.meshgrid(z, z)
# get the distance via the norm
out = abs(m-n)
Run Code Online (Sandbox Code Playgroud)

二解决方案

网格化是主要的想法.但是numpy很聪明,所以你不必生成m&n.只需使用转置版本来计算差异z.网格自动完成:

out = abs(z[..., np.newaxis] - z)
Run Code Online (Sandbox Code Playgroud)

第三种方案

如果z直接设置为二维数组,则可以使用z.T而不是奇怪的z[..., np.newaxis].最后,您的代码将如下所示:

z = np.array([[complex(c.m_x, c.m_y) for c in cells]]) # notice the [[ ... ]]
out = abs(z.T-z)
Run Code Online (Sandbox Code Playgroud)

>>> z = np.array([[0.+0.j, 2.+1.j, -1.+4.j]])
>>> abs(z.T-z)
array([[ 0.        ,  2.23606798,  4.12310563],
       [ 2.23606798,  0.        ,  4.24264069],
       [ 4.12310563,  4.24264069,  0.        ]])
Run Code Online (Sandbox Code Playgroud)

作为补充,你可能想要删除重复项,取上面的三角形:

>>> np.triu(out)
array([[ 0.        ,  2.23606798,  4.12310563],
       [ 0.        ,  0.        ,  4.24264069],
       [ 0.        ,  0.        ,  0.        ]])
Run Code Online (Sandbox Code Playgroud)

一些基准

>>> timeit.timeit('abs(z.T-z)', setup='import numpy as np;z = np.array([[0.+0.j, 2.+1.j, -1.+4.j]])')
4.645645342274779
>>> timeit.timeit('abs(z[..., np.newaxis] - z)', setup='import numpy as np;z = np.array([0.+0.j, 2.+1.j, -1.+4.j])')
5.049334864854522
>>> timeit.timeit('m, n = np.meshgrid(z, z); abs(m-n)', setup='import numpy as np;z = np.array([0.+0.j, 2.+1.j, -1.+4.j])')
22.489568296184686
Run Code Online (Sandbox Code Playgroud)

  • 你有没有找到距离?如果是这样,你就失去了我.那发生在哪里? (3认同)

Ric*_*loo 10

Jake Vanderplas 在Python Data Science Handbook 中使用广播给出了这个例子,这与@shx2 提出的非常相似。

import numpy as np
rand = random.RandomState(42)
X = rand.rand(3, 2)  
dist_sq = np.sum((X[:, np.newaxis, :] - X[np.newaxis, :, :]) ** 2, axis = -1)

dist_sq
array([[0.        , 0.18543317, 0.81602495],
       [0.18543317, 0.        , 0.22819282],
       [0.81602495, 0.22819282, 0.        ]])
Run Code Online (Sandbox Code Playgroud)

  • @Tweakimp - 您应该编写一个调用“%timeit”的答案,也许对于一个小(10x10)和大(1,000,000 x 1,000,000)距离矩阵。这对人们来说确实是有用的信息! (2认同)

shx*_*hx2 7

以下是使用numpy的方法:

import numpy as np

x = np.array([0,1,2])
y = np.array([2,4,6])

# take advantage of broadcasting, to make a 2dim array of diffs
dx = x[..., np.newaxis] - x[np.newaxis, ...]
dy = y[..., np.newaxis] - y[np.newaxis, ...]
dx
=> array([[ 0, -1, -2],
          [ 1,  0, -1],
          [ 2,  1,  0]])

# stack in one array, to speed up calculations
d = np.array([dx,dy])
d.shape
=> (2, 3, 3)
Run Code Online (Sandbox Code Playgroud)

现在剩下的就是沿着0轴计算L2范数(如这里所讨论的):

(d**2).sum(axis=0)**0.5
=> array([[ 0.        ,  2.23606798,  4.47213595],
          [ 2.23606798,  0.        ,  2.23606798],
          [ 4.47213595,  2.23606798,  0.        ]])
Run Code Online (Sandbox Code Playgroud)


Stu*_*den 5

如果不需要全距离矩阵,则使用kd-tree会更好。考虑scipy.spatial.cKDTreesklearn.neighbors.KDTree。这是因为kd-tree kan在O(n log n)的时间内找到了k个最近的邻居,因此避免了计算所有n x n距离的O(n ** 2)复杂性。