web-dev-qa-db-fra.com

Comment fonctionne la matrice de distance condensée? (pdist)

scipy.spatial.distance.pdist Renvoie une matrice de distance condensée. De la documentation :

Renvoie une matrice de distance condensée Y. Pour chaque et (où), la dist métrique (u = X [i], v = X [j]) est calculée et stockée dans l'entrée ij.

Je pensais que ij voulait dire i*j. Mais je pense que je peux me tromper. Considérer

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

alors la documentation indique que dist(X[0], X[2]) devrait être dist_matrix[0*2]. Cependant, dist_matrix[0*2] Vaut 0 - pas 2,8 comme il se doit.

Quelle est la formule à utiliser pour accéder à la similitude de deux vecteurs, étant donné i et j?

59
Rafael Almeida

Vous pouvez le voir de cette façon: Supposons que x est m par n. Les paires possibles de m lignes, choisies deux à la fois, sont itertools.combinations(range(m), 2), par exemple, pour m=3:

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

Donc si d = pdist(x), le kème Tuple dans combinations(range(m), 2)) donne les indices des lignes de x associées à d[k].

Exemple:

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

Le premier élément est dist(x[0], x[1]), le second est dist(x[0], x[2]) et le troisième est dist(x[1], x[2]).

Ou vous pouvez le voir comme les éléments dans la partie triangulaire supérieure de la matrice de distance carrée, enchaînés ensemble dans un tableau 1D.

Par exemple.

>>> 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])
79
Warren Weckesser

Matrice de distance condensée à matrice de distance complète

Une matrice de distance condensée renvoyée par pdist peut être convertie en matrice de distance complète en utilisant scipy.spatial.distance.squareform:

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

Utilisez squareform pour convertir en matrice complète:

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

La distance entre les points i, j est stockée dans dist [i, j]:

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

Indices à indice condensé

On peut convertir les indices utilisés pour accéder aux éléments de la matrice carrée à l'indice de la matrice condensée:

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

Exemple:

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

Index condensé en indices

De plus, l'autre direction est possible sans sqaureform, ce qui est meilleur en termes d'exécution et de consommation de mémoire:

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

Exemple:

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

Comparaison de l'exécution avec la forme carrée

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

La création du sqaureform s'avère très lente:

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

Si nous recherchons deux points avec une distance maximale, il n'est pas surprenant que la recherche en matrice complète soit O(n) alors que sous forme condensée uniquement 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

Obtenir les inidéces pour les deux points ne prend presque pas de temps dans les deux cas, mais bien sûr, il y a des frais généraux pour calculer l'indice condensé:

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

Le vecteur de la matrice compressée correspond à la région triangulaire inférieure de la matrice carrée. Pour convertir un point dans cette région triangulaire, vous devez calculer le nombre de points à gauche dans le triangle et le nombre au-dessus dans la colonne.

Vous pouvez utiliser la fonction suivante pour convertir:

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

Vérifier:

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 ) ]
18
shaunc

Il s'agit de la version triangle supérieur (i <j), qui doit être intéressante pour certains:

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

C'est très simple à comprendre:

  1. avec i*n + j vous allez à la position dans la matrice carrée;
  2. avec - i*(i+1)/2 vous supprimez le triangle inférieur (y compris la diagonale) dans toutes les lignes avant i;
  3. avec - i vous supprimez les positions de la ligne i avant la diagonale;
  4. avec - 1 vous supprimez les positions de la ligne i sur la diagonale.

Vérifier:

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)]
5
HongboZhu

Si vous souhaitez accéder à l'élément de pdist correspondant au (i, j) -ème élément de la matrice de distance carrée, le calcul est le suivant: Supposons que i < j (sinon inverser les indices) si i == j, la réponse est 0.

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

Alors le (i, j) -ème élément est dist_matrix [ind] où

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

Si quelqu'un recherche une transformation inverse (c'est-à-dire étant donné un indice d'élément idx, déterminez lequel (i, j) y correspond), voici une solution vectorisée de manière résonnable:

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

Je l'ai utilisé pour comprendre les index des lignes les plus proches dans une matrice.

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
2
Ben Usman

J'avais la même question. Et j'ai trouvé plus simple d'utiliser 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(distances))
2
Rustam Guliev