Нахождение кратчайшего расстояния между двумя точками Python - PullRequest
0 голосов
/ 24 октября 2019

У меня есть два кадра данных. Один содержит properties locations, а другой содержит railway stations locations.

образец данных dataframe (исходный Dataframe состоит из ~ 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      

Любой совет о том, как подойти к этому, будет полезен. Спасибо

Ответы [ 2 ]

2 голосов
/ 24 октября 2019

Это своего рода обходной путь, но сначала я объединяю оба кадра данных с дополнительным «ключом». Затем я использую «Применить» для вычисления расстояния:

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 имеет видвсегда ближе всегоЭто правильно?

РЕДАКТИРОВАТЬ: Дальнейшее объяснение:

Однажды я пытался найти способ "объединить" два кадра данных, которые не имеют общего уникального идентификатора, который обычноиспользуется для слияния.

Я также хотел связать каждую строку одного кадра данных с другим кадром данных (в вашем случае каждая станция с каждым свойством), чтобы иметь возможность сравнивать эти записи. В своем исследовании я нашел этот умный обходной путь для использования фиктивного ключа.

Слияние обычно объединяет фрейм данных на основе уникального идентификатора, но только те строки, которые совпадают. Таким образом, фрейм данных «ID» = 1 совпадает только с теми, кто имеет «ID» = 1 в фрейме данных B. (Читайте здесь: https://pandas.pydata.org/pandas-docs/version/0.19.1/generated/pandas.DataFrame.merge.html)

В этом временном решении мы видим, что ключ для каждой строки равен 1поэтому каждая строка будет совпадать с каждой строкой из другого информационного кадра, делая именно то, что нам нужно.

С помощью функции apply вы можете применять любую функцию к вашему информационному кадру, переходя строка за строкой.

1 голос
/ 24 октября 2019

Использование BallTree от 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 раз быстрее, чем метод слияния фреймов данных

Подробности (предполагают библиотеки и данные предварительной загрузки)

Метод 1 - Использование 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...