web-dev-qa-db-fra.com

Calculer efficacement une matrice de distance euclidienne à l'aide de Numpy

J'ai un ensemble de points dans un espace à 2 dimensions et j'ai besoin de calculer la distance entre chaque point. 

J'ai un nombre relativement petit de points, peut-être au maximum 100. Mais comme je dois le faire souvent et rapidement afin de déterminer les relations entre ces points en mouvement, et comme je suis conscient que itérer à travers les points pourrait être aussi mauvais en tant que complexité O (n ^ 2), je cherche des moyens de tirer parti de la magie matricielle (ou scipy) de Numpy.

Tel qu'il se trouve dans mon code, les coordonnées de chaque objet sont stockées dans sa classe. Cependant, je pourrais aussi les mettre à jour dans un tableau numpy lorsque je mettrai à jour les coordonnées de la classe.

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

Il me semble de créer une matrice de distance euclidienne pour éviter la duplication, mais vous avez peut-être une structure de données plus intelligente.

Je suis également ouvert aux pointeurs d'algorithmes astucieux.

De plus, je note qu'il existe des questions similaires concernant la distance euclidienne et numpy mais n'en trouve aucune qui aborde directement cette question de remplissage efficace d'une matrice de distance complète.

13
Wes Modes

Vous pouvez tirer parti du type complex

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

Première solution 

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

Deuxième solution 

Le maillage est l'idée principale. Mais numpy est intelligent, vous n'avez donc pas à générer m & n. Il suffit de calculer la différence en utilisant une version transposée de z. Le maillage est fait automatiquement:

out = abs(z[..., np.newaxis] - z)

Troisième solution 

Et si z est directement défini comme un tableau à 2 dimensions, vous pouvez utiliser z.T au lieu du bizarre z[..., np.newaxis]. Donc, finalement, votre code ressemblera à ceci:

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

Exemple

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

En complément, vous pouvez éventuellement supprimer les doublons en prenant le triangle supérieur:

>>> np.triu(out)
array([[ 0.        ,  2.23606798,  4.12310563],
       [ 0.        ,  0.        ,  4.24264069],
       [ 0.        ,  0.        ,  0.        ]])

Quelques repères

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

Voici comment vous pouvez le faire en utilisant 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)

Maintenant, tout ce qui reste à faire est de calculer la norme L2 le long de l’axe 0 (comme discuté ici ):

(d**2).sum(axis=0)**0.5
=> array([[ 0.        ,  2.23606798,  4.47213595],
          [ 2.23606798,  0.        ,  2.23606798],
          [ 4.47213595,  2.23606798,  0.        ]])
6
shx2

Si vous n'avez pas besoin de la matrice de distance complète, utilisez plutôt kd-tree. Considérons scipy.spatial.cKDTree ou sklearn.neighbors.KDTree. Cela est dû au fait qu'un kd-tree kan trouve les k-voisins les plus proches dans le temps O (n log n) et vous évite donc la complexité O (n ** 2) du calcul de toutes les n distances par n.

3
Sturla Molden

Jake Vanderplas donne cet exemple en utilisant la diffusion dans Python Data Science Handbook , qui est très similaire à ce que @ shx2 avait proposé. 

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.        ]])
1
Rich Pauloo