【问题标题】:How does condensed distance matrix work? (pdist)压缩距离矩阵如何工作? (pdist)
【发布时间】:2012-10-16 06:49:39
【问题描述】:

scipy.spatial.distance.pdist 返回一个压缩的距离矩阵。来自the documentation

返回一个压缩距离矩阵 Y。对于每个和 (其中 ),度量 dist(u=X[i], v=X[j]) 被计算并存储在条目 ij 中。

我认为ij 的意思是i*j。但我想我可能错了。考虑

X = array([[1,2], [1,2], [3,4]])
dist_matrix = pdist(X)

然后文档说dist(X[0], X[2]) 应该是dist_matrix[0*2]。但是,dist_matrix[0*2] 是 0,而不是应有的 2.8。

在给定ij 的情况下,我应该使用什么公式来访问两个向量的相似性?

【问题讨论】:

    标签: python numpy scipy


    【解决方案1】:

    你可以这样看:假设x是m乘n。一次选择两个可能的m 行对是itertools.combinations(range(m), 2),例如m=3

    >>> import itertools
    >>> list(combinations(range(3),2))
    [(0, 1), (0, 2), (1, 2)]
    

    所以如果d = pdist(x)combinations(range(m), 2)) 中的kth 元组给出与d[k] 关联的x 行的索引。

    例子:

    >>> x = array([[0,10],[10,10],[20,20]])
    >>> pdist(x)
    array([ 10.        ,  22.36067977,  14.14213562])
    

    第一个元素是dist(x[0], x[1]),第二个是dist(x[0], x[2]),第三个是dist(x[1], x[2])

    或者您可以将其视为平方距离矩阵的上三角部分中的元素,串在一起形成一维数组。

    例如

    >>> squareform(pdist(x)) 
    array([[  0.   ,  10.   ,  22.361],
           [ 10.   ,   0.   ,  14.142],
           [ 22.361,  14.142,   0.   ]])
    
    >>> y = array([[0,10],[10,10],[20,20],[10,0]])
    >>> squareform(pdist(y)) 
    array([[  0.   ,  10.   ,  22.361,  14.142],
           [ 10.   ,   0.   ,  14.142,  10.   ],
           [ 22.361,  14.142,   0.   ,  22.361],
           [ 14.142,  10.   ,  22.361,   0.   ]])
    >>> pdist(y)
    array([ 10.   ,  22.361,  14.142,  14.142,  10.   ,  22.361])
    

    【讨论】:

    • 我明白了,很有趣。看起来,方形更容易使用。 sq_form[i,j] 将准确地得到 y[i] 和 y[j] 之间的距离。但是,我认为浓缩形式在记忆方面更好。也许我应该多读一点关于 squareform 的内容。但是没有一个简单的公式可以将 i,j 转换为 dist 位置,那么呢?
    • 这是实际记录的行为吗?当然,这是有道理的,但是 API 中的任何内容都使它看起来应该与 combinations(range(m), 2)) 进行比较,这对应于距离矩阵的下三角形。为什么不是鞋面?
    • 为什么说它对应的是下三角呢?例如,list(combinations(range(4), 2)) 给出[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]。该列表中的每个元组都有 (row_index, column_index) 的形式,因此它对应于上三角形。
    【解决方案2】:

    压缩距离矩阵到全距离矩阵

    通过使用scipy.spatial.distance.squareform,可以将 pdist 返回的压缩距离矩阵转换为全距离矩阵:

    >>> import numpy as np
    >>> from scipy.spatial.distance import pdist, squareform
    >>> points = np.array([[0,1],[1,1],[3,5], [15, 5]])
    >>> dist_condensed = pdist(points)
    >>> dist_condensed
    array([  1.        ,   5.        ,  15.5241747 ,   4.47213595,
            14.56021978,  12.        ])
    

    使用squareform 转换为全矩阵:

    >>> dist = squareform(dist_condensed)
    array([[  0.        ,   1.        ,   5.        ,  15.5241747 ],
           [  1.        ,   0.        ,   4.47213595,  14.56021978],
           [  5.        ,   4.47213595,   0.        ,  12.        ],
           [ 15.5241747 ,  14.56021978,  12.        ,   0.        ]])
    

    点 i,j 之间的距离存储在 dist[i, j] 中:

    >>> dist[2, 0]
    5.0
    >>> np.linalg.norm(points[2] - points[0])
    5.0
    

    精简索引的索引

    可以将用于访问方阵元素的索引转换为压缩矩阵中的索引:

    def square_to_condensed(i, j, n):
        assert i != j, "no diagonal elements in condensed matrix"
        if i < j:
            i, j = j, i
        return n*j - j*(j+1)//2 + i - 1 - j
    

    例子:

    >>> square_to_condensed(1, 2, len(points))
    3
    >>> dist_condensed[3]
    4.4721359549995796
    >>> dist[1,2]
    4.4721359549995796
    

    索引的精简索引

    在运行时和内存消耗方面更好的 sqaureform 也可以实现另一个方向:

    import math
    
    def calc_row_idx(k, n):
        return int(math.ceil((1/2.) * (- (-8*k + 4 *n**2 -4*n - 7)**0.5 + 2*n -1) - 1))
    
    def elem_in_i_rows(i, n):
        return i * (n - 1 - i) + (i*(i + 1))//2
    
    def calc_col_idx(k, i, n):
        return int(n - elem_in_i_rows(i + 1, n) + k)
    
    def condensed_to_square(k, n):
        i = calc_row_idx(k, n)
        j = calc_col_idx(k, i, n)
        return i, j
    

    例子:

    >>> condensed_to_square(3, 4)
    (1.0, 2.0)
    

    与 squareform 的运行时比较

    >>> import numpy as np
    >>> from scipy.spatial.distance import pdist, squareform
    >>> points = np.random.random((10**4,3))
    >>> %timeit dist_condensed = pdist(points)
    1 loops, best of 3: 555 ms per loop
    

    事实证明,创建 sqaureform 真的很慢:

    >>> dist_condensed = pdist(points)
    >>> %timeit dist = squareform(dist_condensed)
    1 loops, best of 3: 2.25 s per loop
    

    如果我们搜索距离最大的两个点,那么在完整矩阵中搜索 O(n) 而在压缩形式中只有 O(n/2) 也就不足为奇了:

    >>> dist = squareform(dist_condensed)
    >>> %timeit dist_condensed.argmax()
    10 loops, best of 3: 45.2 ms per loop
    >>> %timeit dist.argmax()
    10 loops, best of 3: 93.3 ms per loop
    

    在这两种情况下,获取两个点的索引几乎都不需要时间,但计算压缩索引当然会有一些开销:

    >>> idx_flat = dist.argmax()
    >>> idx_condensed = dist.argmax()
    >>> %timeit list(np.unravel_index(idx_flat, dist.shape))
    100000 loops, best of 3: 2.28 µs per loop
    >>> %timeit condensed_to_square(idx_condensed, len(points))
    100000 loops, best of 3: 14.7 µs per loop
    

    【讨论】:

    • 我有一个正方形的距离矩阵——有没有转换为压缩形式的函数? (即与squareform 相反)...像linkage 这样的函数需要压缩形式... EDIT squareform 函数将双向... 方式 i> 酷..."... and vice-versa." EDIT 2 我称“方形”应该是“冗余”EDIT 3 并且链接将与 both 一起使用i> 表格...
    • @The Red Pea squareform 是它自己的逆矩阵(即,当在一个完整的距离矩阵上运行时,它会将其转换为 squareform)
    • 你是怎么知道calc_row_idxcalc_col_idx的?
    • @CMCDragonkai 如果我没记错的话,这个想法是从square_to_condensed() 中获取公式,并使用二次公式通过 i 或 j 求解。据我记得,计算有点麻烦,但它只涉及基本代数和一些不可能解决方案的推理(因为它是负数或复数)。绘制一个带有空对角线的示例三角形矩阵会有所帮助。
    • 在 Python 3 中,"Indices to condensed index" 应该使用整数除法 // 而不是浮点除法 /,以便输出是整数。
    【解决方案3】:

    压缩矩阵的向量对应方阵的底部三角形区域。要转换为该三角形区域中的点,您需要计算三角形左侧的点数,以及列中上方的点数。

    可以使用以下函数进行转换:

    q = lambda i,j,n: n*j - j*(j+1)/2 + i - 1 - j
    

    检查:

    import numpy as np
    from scipy.spatial.distance import pdist, squareform
    x = np.random.uniform( size = 100 ).reshape( ( 50, 2 ) )
    d = pdist( x )
    ds = squareform( d )
    for i in xrange( 1, 50 ):
        for j in xrange( i ):
            assert ds[ i, j ] == d[ q( i, j, 50 ) ]
    

    【讨论】:

    • 请注意,它确实是底部三角形区域,这对某些人来说可能很奇怪。
    • 下三角是上三角的转置,因为距离矩阵是对称的,即交换 j,i -> i,j 得到相同的结果。您的解决方案使用了下三角形的解释,但上三角形的版本并没有什么不正确的(我认为这是人们对此更常见的思考方式)
    • 我正试图向相反的方向移动:给定一个压缩距离矩阵(即平面向量)的索引,我怎样才能得到对应的矩阵索引(i,j)值而不将其强制为方形?
    • squareform 按行序列化距离向量。这意味着较小的索引总是第一个:(0,1),(0,2),(0,3)....
    【解决方案4】:

    我也有同样的问题。而且我发现使用numpy.triu_indices 更简单:

    import numpy as np
    from scipy.spatial.distance import pdist, squareform
    N = 10
    
    # Calculate distances
    X = np.random.random((N,3))
    dist_condensed = pdist(X)
    
    # Get indexes: matrix indices of dist_condensed[i] are [a[i],b[i]]
    a,b = np.triu_indices(N,k=1)
    
    # Fill distance matrix
    dist_matrix = np.zeros((N,N))
    for i in range(len(dist_condensed)):
        dist_matrix[a[i],b[i]] = dist_condensed[i]
        dist_matrix[b[i],a[i]] = dist_condensed[i]
    
    # Compare with squareform output
    np.all(dist_matrix == squareform(dist_condensed))
    

    【讨论】:

      【解决方案5】:

      这是上三角版本(i ),一定有些人会感兴趣:

      condensed_idx = lambda i,j,n: i*n + j - i*(i+1)/2 - i - 1
      

      这很容易理解:

      1. 使用i*n + j,您可以转到方形矩阵中的位置;
      2. - i*(i+1)/2 删除 i 之前所有行中的下三角形(包括对角线);
      3. 使用- i,您可以删除第 i 行中对角线之前的位置;
      4. 使用- 1 删除对角线上第 i 行的位置。

      检查:

      import scipy
      from scipy.spatial.distance import pdist, squareform
      condensed_idx = lambda i,j,n: i*n + j - i*(i+1)/2 - i - 1
      n = 50
      dim = 2
      x = scipy.random.uniform(size = n*dim).reshape((n, dim))
      d = pdist(x)
      ds = squareform(d)
      for i in xrange(1, n-1):
          for j in xrange(i+1, n):
              assert ds[i, j] == d[condensed_idx(i, j, n)]
      

      【讨论】:

        【解决方案6】:

        如果要访问与平方距离矩阵的第 (i,j) 个元素相对应的 pdist 的元素,计算如下:假设 i &lt; j(否则翻转索引)如果 i == j ,答案是0。

        X = random((N,m))
        dist_matrix = pdist(X)
        

        那么第 (i,j) 个元素是 dist_matrix[ind] 其中

        ind = (N - array(range(1,i+1))).sum()  + (j - 1 - i).
        

        【讨论】:

        • 请注意array(range(1,i+1))).sum() == ((i+1)*i)/2(谷歌为“年轻的高斯”)。
        • 如果我有一个数组array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),N = 5,那么你在第 i 行 = 3 列和第 j = 2 列的公式(5 - np.array(range(1,3+1))).sum() + (2 - 1 - 3) 会给我 7,应该给我 5。
        【解决方案7】:

        如果有人正在寻找逆变换(即给定元素索引idx,找出与之对应的(i, j) 元素),这里有一个合理的矢量解决方案:

        def actual_indices(idx, n):
            n_row_elems = np.cumsum(np.arange(1, n)[::-1])
            ii = (n_row_elems[:, None] - 1 < idx[None, :]).sum(axis=0)
            shifts = np.concatenate([[0], n_row_elems])
            jj = np.arange(1, n)[ii] + idx - shifts[ii]
            return ii, jj
        
        n = 5
        k = 10
        idx = np.random.randint(0, n, k)
        a = pdist(np.random.rand(n, n))
        b = squareform(a)
        
        ii, jj = actual_indices(idx, n)]
        assert np.allclose(b[ii, jj, a[idx])
        

        我用它来计算矩阵中最近行的索引。

        m = 3  # how many closest
        lowest_dist_idx = np.argpartition(-a, -m)[-m:]
        ii, jj = actual_indices(lowest_dist_idx, n)  # rows ii[0] and jj[0] are closest
        

        【讨论】:

          猜你喜欢
          • 2011-08-08
          • 2014-01-25
          • 2016-08-15
          • 1970-01-01
          • 2018-03-09
          • 2020-07-04
          • 1970-01-01
          • 2013-08-28
          • 2014-03-31
          相关资源
          最近更新 更多