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