在没有for循环的情况下获取到1D / 2D中每个最近的元素的距离

时间:2020-10-06 23:08:49

标签: python pandas numpy

我正在使用numpy在python中工作(也可能是熊猫系列),并且正在尝试进行以下计算:

让我们说我有一个与x轴上的点相对应的数组:

2, 9, 5, 6, 55, 8

对于此数组中的每个元素,我都希望获得到最接近的元素的距离,因此输出应如下所示:

3, 1, 1, 1, 46, 1

我正在尝试找到一种可以缩放到2D(到最近的XY点的距离)的解决方案,理想情况下会避免for循环。有可能吗?

4 个答案:

答案 0 :(得分:3)

这里似乎有一个O(N ^ 2)解决方案的主题。对于1D,获得O(N log N)很简单:

x = np.array([2, 9, 5, 6, 55, 8])
i = np.argsort(x)
dist = np.diff(x[i])
min_dist = np.r_[dist[0], np.minimum(dist[1:], dist[:-1]), dist[-1]])
min_dist = min_dist[np.argsort(i)]

这显然不能很好地扩展到多个维度,因此请改用scipy.special.KDTree。假设您的数据是N维的并且形状为(M, N),则可以

k = KDTree(data)
dist = k.query(data, k=2)[0][:, -1]

Scipy具有KDTreecKDTree的Cython实现。 Sklearn的sklearn.neighbors.KDTree也具有相似的界面。

答案 1 :(得分:2)

方法1

您可以使用广播来获取距离矩阵:

>>> data = np.array([2,9,5,6,55,8])
>>> dst_matrix = data - data[:, None]
>>> dst_matrix
array([[  0,   7,   3,   4,  53,   6],
       [ -7,   0,  -4,  -3,  46,  -1],
       [ -3,   4,   0,   1,  50,   3],
       [ -4,   3,  -1,   0,  49,   2],
       [-53, -46, -50, -49,   0, -47],
       [ -6,   1,  -3,  -2,  47,   0]])

然后我们可以按照建议的in this post消除对角线:

dst_matrix = dst_matrix[~np.eye(dst_matrix.shape[0],dtype=bool)].reshape(dst_matrix.shape[0],-1)
>>> dst_matrix
array([[  7,   3,   4,  53,   6],
       [ -7,  -4,  -3,  46,  -1],
       [ -3,   4,   1,  50,   3],
       [ -4,   3,  -1,  49,   2],
       [-53, -46, -50, -49, -47],
       [ -6,   1,  -3,  -2,  47]])

最后,可以找到最少的物品:

>>> np.min(np.abs(dst_matrix), axis=1)
array([ 3,  1,  1,  1, 46,  1])

方法2 如果您正在寻找节省时间和内存的有效解决方案,那么最好的选择是scipy.spatial.cKDTree,它将(任意维度的)点打包到为查询最近点而优化的特定数据结构中。也可以扩展为2D或3D。

import scipy.spatial
data = np.array([2,9,5,6,55,8])

ckdtree = scipy.spatial.cKDTree(data[:,None])
distances, idx = ckdtree.query(data[:,None], k=2) 
output = distances[:,1] #distances to not coincident points

对于每个点,此处需要查询前两个最近的点,因为它们中的第一个应该是重合的。这是我在所有提出的答案中找到的唯一的解决方案,而且没有时效(平均性能为100万点为4秒)。 警告:在应用此方法之前,您需要过滤重复的点。

答案 2 :(得分:2)

有很多方法可以实现它。一些可读且可概括的方法是:

方法1

dist = np.abs(a[:,None]-a)
np.min(dist, where=~np.eye(len(a),dtype=bool), initial=dist.max(), axis=1)
#[ 3  1  1  1 46  1]

方法2

dist = np.abs(np.subtract.outer(a,a))
np.min(dist, where=~np.eye(len(a),dtype=bool), initial=dist.max(), axis=1)

对于2D情况,方法1 (假定欧几里德距离。也可以是其他距离):

from scipy.spatial.distance import cdist
dist = cdist(a,a)
np.min(dist, where=~np.eye(len(a),dtype=bool), initial=dist.max(), axis=1)

对于仅适用于numpy的二维案例方法2

dist=np.sqrt(((a[:,None]-a)**2).sum(-1))
np.min(dist, where=~np.eye(len(a),dtype=bool), initial=dist.max(), axis=1)

您可以实现faster distance calculation by using np.dot

答案 3 :(得分:0)

您可以对熊猫系列进行一些列表理解:

s = pd.Series([2,9,5,6,55,8])
s.apply(lambda x: min([abs(x - s[y]) for y in s.index if s[y] != x]))
Out[1]: 
0     3
1     1
2     1
3     1
4    46
5     1

然后,您只需在末尾添加.to_list().to_numpy()即可摆脱系列索引:

s.apply(lambda x: min([abs(x - s[y]) for y in s.index if s[y] != x])).to_numpy()

array([ 3,  1,  1,  1, 46,  1], dtype=int64)