计算熊猫数据框中最近邻居的平均距离

时间:2018-07-12 12:10:16

标签: python pandas dataframe euclidean-distance

我有一组对象及其随时间的位置。我想获得每辆汽车与其最近邻居之间的距离,并计算每个时间点的平均值。数据帧示例如下:

 time = [0, 0, 0, 1, 1, 2, 2]
 x = [216, 218, 217, 280, 290, 130, 132]
 y = [13, 12, 12, 110, 109, 3, 56]
 car = [1, 2, 3, 1, 3, 4, 5]
 df = pd.DataFrame({'time': time, 'x': x, 'y': y, 'car': car})
 df

         x       y      car
 time
  0     216     13       1
  0     218     12       2
  0     217     12       3
  1     280     110      1
  1     290     109      3
  2     130     3        4
  2     132     56       5

对于每个时间点,我想知道每辆汽车最近的汽车邻居。示例:

df2

          car    nearest_neighbour    euclidean_distance  
 time
  0       1            3                    1.41
  0       2            3                    1.00
  0       3            1                    1.41
  1       1            3                    10.05
  1       3            1                    10.05
  2       4            5                    53.04
  2       5            4                    53.04

我知道我可以从How to apply euclidean distance function to a groupby object in pandas dataframe?计算出汽车之间的成对距离,但是如何获得每辆汽车的最近邻居?

在那之后,使用groupby来获取每一帧的平均距离似乎很简单,但是第二步确实让我失望了。 帮助表示赞赏!

2 个答案:

答案 0 :(得分:2)

可能有点矫over过正,但您可以使用nearest neighbors from scikit

一个例子:

import numpy as np 
from sklearn.neighbors import NearestNeighbors
import pandas as pd

def nn(x):
    nbrs = NearestNeighbors(n_neighbors=2, algorithm='auto', metric='euclidean').fit(x)
    distances, indices = nbrs.kneighbors(x)
    return distances, indices

time = [0, 0, 0, 1, 1, 2, 2]
x = [216, 218, 217, 280, 290, 130, 132]
y = [13, 12, 12, 110, 109, 3, 56] 
car = [1, 2, 3, 1, 3, 4, 5]
df = pd.DataFrame({'time': time, 'x': x, 'y': y, 'car': car})

#This has the index of the nearest neighbor in the group, as well as the distance
nns = df.drop('car', 1).groupby('time').apply(lambda x: nn(x.as_matrix()))

groups = df.groupby('time')
nn_rows = []
for i, nn_set in enumerate(nns):
    group = groups.get_group(i)
    for j, tup in enumerate(zip(nn_set[0], nn_set[1])):
        nn_rows.append({'time': i,
                        'car': group.iloc[j]['car'],
                        'nearest_neighbour': group.iloc[tup[1][1]]['car'],
                        'euclidean_distance': tup[0][1]})

nn_df = pd.DataFrame(nn_rows).set_index('time')

结果:

      car  euclidean_distance  nearest_neighbour
time                                            
0       1            1.414214                  3
0       2            1.000000                  3
0       3            1.000000                  2
1       1           10.049876                  3
1       3           10.049876                  1
2       4           53.037722                  5
2       5           53.037722                  4

(请注意,在时间0,汽车3的最近邻居是汽车2。sqrt((217-216)**2 + 1)大约是1.4142135623730951,而sqrt((218-217)**2 + 0) = 1

答案 1 :(得分:2)

使用cdist from scipy.spatial.distance获得一个矩阵,该矩阵表示每辆汽车到每辆其他汽车的距离。由于每辆车到自己的距离为0,所以对角线元素均为0。

示例(对于time == 0):

X = df[df.time==0][['x','y']]
dist = cdist(X, X)
dist
array([[0.        , 2.23606798, 1.41421356],
       [2.23606798, 0.        , 1.        ],
       [1.41421356, 1.        , 0.        ]])

使用np.argsort获取将对距离矩阵进行排序的索引。第一列只是行号,因为对角元素为0。

idx = np.argsort(dist)
idx
array([[0, 2, 1],
       [1, 2, 0],
       [2, 1, 0]], dtype=int64)

然后,只需使用idx

挑选汽车和最近距离
dist[v[:,0], v[:,1]]
array([1.41421356, 1.        , 1.        ])

df[df.time==0].car.values[v[:,1]]
array([3, 3, 2], dtype=int64)

将上述逻辑组合成一个返回所需数据帧的函数:

 def closest(df):
     X = df[['x', 'y']]
     dist = cdist(X, X)
     v = np.argsort(dist)
     return df.assign(euclidean_distance=dist[v[:, 0], v[:, 1]],
                      nearest_neighbour=df.car.values[v[:, 1]])

&与groupby一起使用,最后删除索引,因为groupby-apply添加了附加索引

df.groupby('time').apply(closest).reset_index(drop=True)

   time    x    y  car  euclidean_distance  nearest_neighbour
0     0  216   13    1            1.414214                  3
1     0  218   12    2            1.000000                  3
2     0  217   12    3            1.000000                  2
3     1  280  110    1           10.049876                  3
4     1  290  109    3           10.049876                  1
5     2  130    3    4           53.037722                  5
6     2  132   56    5           53.037722                  4

顺便说一句,您的样本输出在时间0上是错误的。我的答案和Bacon's的答案都显示正确的结果

相关问题