问题描述
一般问题
首先让我们更一般地解释这个问题。我有一组具有 x,y 坐标的点,并希望找到最佳的唯一邻居对,以使所有对中的邻居之间的距离最小化,但点不能用于超过一对。
一些简单的例子
注意:点不是有序的,x 和 y 坐标都在 0 到 1000 之间变化,但为了简单起见,在下面的例子中 x==y 和项目是有序的。
首先,假设我有以下点矩阵:
matrix1 = np.array([[1,1],[2,2],[5,5],[6,6]])
对于此数据集,输出应为 [0,1,1]
,因为点 1 和 2 彼此最接近,而点 3 和 4,提供对 0 和 2。
其次,两点不能有相同的伙伴。如果我们有矩阵:
matrix2 = np.array([[1,[4,4],6]])
这里 pt1 和 pt3 最接近 pt2,但 pt1 相对更近,所以输出应该还是 [0,1]
。
第三,如果我们有矩阵:
matrix3 = np.array([[1,[3,3],4]])
现在 pt1 和 pt3 再次与 pt2 最接近,但现在它们的距离相同。现在输出应该再次为 [0,1]
,因为 pt4 最接近 pt3。
第四,在点数为奇数的情况下,最远点应为nan,例如
matrix4 = np.array([[1,4]])
应该给出输出[0,nan]
第五,如果有三个或更多点的距离完全相同,配对可以是随机的,例如
matrix5 = np.array([[1,3]])
'[0,nan]and
[nan,0]` 的输出都应该没问题。
我的努力
使用 sklearn:
import numpy as np
from sklearn.neighbors import NearestNeighbors
data = matrix3
nbrs = NearestNeighbors(n_neighbors=len(data),algorithm="ball_tree")
nbrs = nbrs.fit(data)
distances,indices = nbrs.kneighbors(data)
这输出实例:
array([[0,2,[1,3,0],0]]))
第二列提供最近的点:
nearinds = `indices[:,1]`
接下来,如果列表中有重复项,我们需要找到最近的距离:
if len(set(nearinds) != len(nearinds):
dupvals = [i for i in set(nearinds) if list(nearinds).count(i) > 1]
for dupval in dupvals:
dupinds = [i for i,j in enumerate(nearinds) if j == dupval]
dupdists = distances[dupinds,1]
使用这些 dupdists,我可以发现一个比另一个更接近 pt:
if len(set(dupdists))==len(dupdists):
duppriority = np.argsort(dupdists)
使用 duppriority
值,我们可以提供更接近 pt 的正确配对。但是为了给另一个点它的配对将取决于它的第二个最近的配对以及所有其他点到同一点的距离..此外,如果两个点到它们最近点的距离相同,我也需要去一个更深一层:
if len(set(dupdists))!=len(dupdists):
dupdists2 = [distances[i,2] for i,j in enumerate(inds) if j == dupval]```
if len(set(dupdists2))==len(dupdists2):
duppriority2 = np.argsort(dupdists2)
等等。
我有点卡在这里,也觉得这种方式效率不高,特别是对于比 4 个点更复杂的条件,并且多个点与一个或多个最近的、第二近的等点的距离相似。
我还发现 scipy 有一个类似的单行命令可用于获取距离和索引:
from scipy.spatial import cKDTree
distances,indices = cKDTree(matrix3).query(matrix3,k=len(matrix3))
所以我想知道继续使用与另一个相比是否更好。
我想解决的更具体的问题
我有一个点列表,需要将它们与之前的点列表进行最佳匹配。点数通常是有限的,范围从 2 到 10,但随着时间的推移通常是一致的(即它不会随着时间的推移在值之间跳跃太多)。数据看起来像:
prevdat = {'loc': [(300,200),(425,400),(400,300)],'contid': [0,2]}
currlocs = [(435,390),(405,295),(290,215),(440,330)]`
时间点通常更接近自己而不是他人。因此,我应该能够随着时间的推移将点的身份联系起来。然而,有许多复杂情况需要克服:
- 有时当前点和之前点的数量不同
- 点通常具有相同的最近邻,但不应分配相同的身份
- 点有时与最近邻的距离相同(但不太可能与第二、第三近邻等距离相同。
任何有助于解决我的问题的建议将不胜感激。我希望我上面的例子和努力会有所帮助。谢谢!
解决方法
这可以表述为混合整数线性规划问题。
在 python 中,您可以使用 cvxpy 建模和解决此类问题。
def connect_point_cloud(points):
'''
Given a set of points computes return pairs of points that
whose added distance is minimised
'''
N = points.shape[0];
I,J = np.indices((N,N))
d = np.sqrt(sum((points[I,i] - points[J,i])**2 for i in range(points.shape[1])));
use = cvxpy.Variable((N,N),integer=True)
# each entry use[i,j] indicates that the point i is connected to point j
# each pair may count 0 or 1 times
constraints = [use >= 0,use <= 1];
# point i must be used in at most one connection
constraints += [sum(use[i,:]) + sum(use[:,i]) <= 1 for i in range(N)]
# at least floor(N/2) connections must be presented
constraints += [sum(use[i,j] for i in range(N) for j in range(N)) >= N//2];
# let the solver to handle the problem
P = cvxpy.Problem(cvxpy.Minimize(sum(use[i,j] * d[i,j] for i in range(N) for j in range(N))),constraints)
dist = P.solve()
return use.value
这是一段代码,用于可视化二维问题的结果
# create a random set with 50 points
p = np.random.rand(50,2)
# find the pairs to with minimum distance
pairs = connect_point_cloud(p)
# plot all the points with circles
plt.plot(p[:,0],p[:,1],'o')
# plot lines connecting the points
for i1,i2 in zip(*np.nonzero(pairs)):
plt.plot([p[i1,p[i2,0]],[p[i1,1]])