寻找两点之间的最短距离Python

Posted

技术标签:

【中文标题】寻找两点之间的最短距离Python【英文标题】:Finding the shortest distance between two points Python 【发布时间】:2019-10-24 07:40:38 【问题描述】:

我有两个数据框。一个包含properties locations,另一个包含railway stations locations

属性数据框示例(原始数据框由约 700 行组成):

properties=pd.DataFrame('propertyID':['13425','32535','43255','52521'],
                 'lat':[-37.79230,-37.86400,-37.85450,-37.71870],
                'lon':[145.10290,145.09720,145.02190,144.94330])

火车站数据帧示例(原始数据帧由约 90 行组成):

stations=pd.DataFrame('stationID':['11','33','21','34','22'],
                 'lat':[-37.416861,-37.703293,-37.729261,-37.777764,-37.579206],
                'lon':[145.005372,144.572524,144.650631,144.772304,144.728165])

我有一个函数可以计算两个位置之间的距离

from math import radians, cos, sin, asin, sqrt

def haversine(lon1, lat1, lon2, lat2):
    """
    Calculate the great circle distance between two points 
    on the earth (specified in decimal degrees)
    """
    # convert decimal degrees to radians 
    lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])

    # haversine formula 
    dlon = lon2 - lon1 
    dlat = lat2 - lat1 
    a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
    c = 2 * asin(sqrt(a)) 
    r = 6378 # Radius of earth in kilometers
    return c * r

我想找出每个物业和所有车站之间的距离。然后选择距离最短的车站。

我尝试构建一个 for 循环,但它没有返回最短距离(分钟)

lst=[]
for stopLat in stations['lat']:
    for stopLon in stations['lon']:
        for propLat in properties['lat']:
            for propLon in properties['lon']:
                lst.append(haversine(propLon,propLat,stopLon,stopLat))

我的最终输出将如下所示。 (每个属性都连接到最近的车站)。

stationID propertyID 
11        52521
33        13425
21        32535
34        43255      

任何有关如何解决此问题的建议都会有所帮助。谢谢

【问题讨论】:

您指的是最短路径,例如图 Djikstra,我认为使用 networkx 很容易吗? BallTree 在 Sklearn 中支持haversine 距离。所以你可以使用属性创建一个 BallTree。然后找到每个站点到 BallTree 中一个点的最小距离(即最近邻)。这应该是快速高效的(因为 Sklearn 算法)。 【参考方案1】:

这是一种解决方法,但我首先将两个数据框与一个额外的“键”合并。 然后我用 apply 来计算距离:

properties['key'] = 1
stations['key'] = 1

df = properties.merge(stations,on='key')
del df['key']
df['distance'] = df.apply(lambda x: haversine(x['lon_x'],x['lat_x'],x['lon_y'],x['lat_y']),axis=1)
print(df)
df = df.loc[df.groupby("propertyID")["distance"].idxmin()]
df = df[['stationID','propertyID']]
print(df)

第一次打印:

   propertyID    lat_x     lon_x stationID      lat_y       lon_y   distance
0       13425 -37.7923  145.1029        11 -37.416861  145.005372  42.668639
1       13425 -37.7923  145.1029        33 -37.703293  144.572524  47.723406
2       13425 -37.7923  145.1029        21 -37.729261  144.650631  40.415507
3       13425 -37.7923  145.1029        34 -37.777764  144.772304  29.129338
4       13425 -37.7923  145.1029        22 -37.579206  144.728165  40.650436
5       32535 -37.8640  145.0972        11 -37.416861  145.005372  50.428078
6       32535 -37.8640  145.0972        33 -37.703293  144.572524  49.504807
7       32535 -37.8640  145.0972        21 -37.729261  144.650631  42.047056
8       32535 -37.8640  145.0972        34 -37.777764  144.772304  30.138684
9       32535 -37.8640  145.0972        22 -37.579206  144.728165  45.397047
10      43255 -37.8545  145.0219        11 -37.416861  145.005372  48.738487
11      43255 -37.8545  145.0219        33 -37.703293  144.572524  42.971083
12      43255 -37.8545  145.0219        21 -37.729261  144.650631  35.510616
13      43255 -37.8545  145.0219        34 -37.777764  144.772304  23.552690
14      43255 -37.8545  145.0219        22 -37.579206  144.728165  40.101407
15      52521 -37.7187  144.9433        11 -37.416861  145.005372  34.043280
16      52521 -37.7187  144.9433        33 -37.703293  144.572524  32.696875
17      52521 -37.7187  144.9433        21 -37.729261  144.650631  25.795774
18      52521 -37.7187  144.9433        34 -37.777764  144.772304  16.424364
19      52521 -37.7187  144.9433        22 -37.579206  144.728165  24.508280

第二次打印:

   stationID propertyID
3         34      13425
8         34      32535
13        34      43255
18        34      52521

但是根据这个输出站 34 总是最近的。对吗?

编辑:进一步解释:

我曾经试图找到一种方法将两个数据帧“合并”在一起,这些数据帧没有通常用于合并的通用唯一标识符。

我还想将一个数据帧的每一行与另一个数据帧(在您的情况下,每个站点与每个属性)配对,以便能够比较这些条目。 在我的研究中,我发现了使用虚拟密钥的巧妙解决方法。

合并通常基于唯一标识符组合数据帧,但仅匹配那些匹配的行。所以数据框 A "ID" = 1 只与数据框 B 中 "ID" = 1 的人匹配。(阅读此处:https://pandas.pydata.org/pandas-docs/version/0.19.1/generated/pandas.DataFrame.merge.html

在这个使用的解决方法中,我们看到每一行的键都是 1,因此每一行都将与其他数据帧中的每一行匹配,完全符合我们的要求。

使用 apply 函数,您可以将任何函数逐行应用于数据帧。

【讨论】:

感谢您提供这个简洁的解决方案。它似乎正在工作。我将绘制我的数据以确保我的输出是正确的。谢谢。 如果可能的话,您能否对您的方法进行一些说明?合并是如何工作的?是否将每个物业位置与所有车站位置进行比较? 谢谢mgruber【参考方案2】:

使用BallTree from Sklearn,它提供了一种更快的方法来查找最近的邻居

import numpy as np
import pandas as pd
from sklearn.neighbors import KDTree, BallTree

properties=pd.DataFrame('propertyID':['13425','32535','43255','52521'],
                 'lat':[-37.79230,-37.86400,-37.85450,-37.71870],
                'lon':[145.10290,145.09720,145.02190,144.94330])

stations=pd.DataFrame('stationID':['11','33','21','34','22'],
                 'lat':[-37.416861,-37.703293,-37.729261,-37.777764,-37.579206],
                'lon':[145.005372,144.572524,144.650631,144.772304,144.728165])

property_coords = properties.as_matrix(columns=['lat', 'lon'])
station_coords = stations.as_matrix(columns=['lat', 'lon'])

# Create BallTree using station coordinates and specify distance metric
tree = BallTree(station_coords, metric = 'haversine')

print('PropertyID StationID Distance')
for i, property in enumerate(property_coords):
    dist, ind = tree.query(property.reshape(1, -1), k=1) # distance to first nearest station
    print(properties['propertyID'][i], stations['stationID'][ind[0][0]], dist[0][0], sep ='\t')

输出

PropertyID StationID Distance
13425   34  0.329682946662
32535   34  0.333699645179
43255   34  0.259425428922
52521   34  0.180690281514

性能

总结--BallTree > 比合并数据帧的方法快 5 倍

详细信息(假设预加载库和数据)

方法一--使用BallTree

%%timeit

property_coords = properties.as_matrix(columns=['lat', 'lon'])
station_coords = stations.as_matrix(columns=['lat', 'lon'])

# Create BallTree using station coordinates and specify distance metric
tree = BallTree(station_coords, metric = 'haversine')

for i, property in enumerate(property_coords):
    dist, ind = tree.query(property.reshape(1, -1), k=1) # distance to first nearest station

100 loops, best of 3: 1.79 ms per loop

方法2——合并两个数据框

%%timeit

properties['key'] = 1
stations['key'] = 1

df = properties.merge(stations,on='key')
del df['key']
df['distance'] = df.apply(lambda x: haversine(x['lon_x'],x['lat_x'],x['lon_y'],x['lat_y']),axis=1)
#print(df)
df = df.loc[df.groupby("propertyID")["distance"].idxmin()]
df = df[['stationID','propertyID']]

100 loops, best of 3: 10 ms per loop

【讨论】:

谢谢 DarrylG。我尝试了k-最近邻法。由于某种原因,它返回前 6 个观察值,然后返回错误。 KeyError: 78。第二种方法有效。再次感谢。 @leena--您是在尝试与上面完全相同的代码还是修改? @leena--这是我试过的link to code(注意:您可以暂时忽略关于 as_matrix 将在未来版本中删除的警告)。 谢谢DarrylG

以上是关于寻找两点之间的最短距离Python的主要内容,如果未能解决你的问题,请参考以下文章

求助:求任意两点之间的最短路问题用啥算法比较好?

计算地球平面地图上2点之间的最短路径

计算 3D 中两条线(线段)之间的最短距离

Dijkstra 算法,用于对有权图进行搜索,找出图中两点的最短距离

使用纬度和经度iphone地图上两点之间的距离方程

python 平面内怎么计算两条线段之间的最短距离?