Найти ближайшую точку в другом фрейме данных (С МНОГО ДАННЫХ)

Проблема проста, у меня два DataFrame:

  • один с 90 000 квартир и их широта / долгота

  • и один с 3000 аптек и их широта / долгота

И я хочу создать новую переменную для всех своих квартир: «расстояние до ближайшей аптеки».

Для этого я попробовал два метода, которые тратят много времени:

Первый метод: я создал матрицу с моими квартирами в ряду и моими аптеками в столбцах и расстоянием между ними на пересечении, после чего я просто беру минимальное значение матрицы, чтобы получить вектор-столбец 90 000 значение

Я просто использую двойной для с numpy:

m,n=len(result['latitude']),len(pharma['lat'])
M = np.ones((m,n))
for i in range(m):
     for j in range(n):
        if (result['Code departement'][i]==pharma['departement'][j]):
            M[i,j] =(pharma['lat'][j]-result['latitude'][i])**2+(pharma['lng'][j]-result['longitude'] [i])**2

ps: я знаю, что неправильная формула для широты / долготы, но апартаменты находятся в одном регионе, так что это хорошее приближение

Второй способ: я использую решение из этих тем (у кого такая же проблема, но с меньшим объемом данных) https://gis.stackexchange.com/questions/222315/geopandas-find-nearest-point-in-other-dataframe

Я использовал геопанды и ближайший метод:

from shapely.ops import nearest_points
pts3 = pharma.geometry.unary_union


def near(point, pts=pts3):
     nearest = pharma.geometry == nearest_points(point, pts)[1]
     return pharma[nearest].geometry.get_values()[0]

appart['Nearest'] = appart.apply(lambda row: near(row.geometry), axis=1)

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

Мой последний вопрос: есть ли у вас оптимизированный способ ускорить работу? возможно ? Если он уже оптимизирован, я куплю другой компьютер, но по каким критериям, но по каким критериям искать, чтобы компьютер мог выполнять такие быстрые вычисления?


person Arnaud H    schedule 16.11.2019    source источник
comment
Я думаю, вам следует следовать второму ответу на вопрос, на который вы нас указываете, то есть тот, который использует пространственный индекс, чтобы избежать вычисления расстояний по принципу «все: все».   -  person High Performance Mark    schedule 16.11.2019
comment
У тебя есть пример? Потому что у меня сложилось впечатление, что я уже использую пространственный индекс во втором решении с геопандами, и это ничего не изменило за потраченное время.   -  person Arnaud H    schedule 17.11.2019
comment
Тогда я неправильно понял ваш код, и мой предыдущий комментарий был ошибочным.   -  person High Performance Mark    schedule 17.11.2019
comment
Чтобы уточнить, второй вариант, основанный на shapely, не использует пространственный индекс.   -  person martinfleis    schedule 17.11.2019
comment
Нет, конечно, я не понимаю, что такое пространственный индекс. У тебя есть один пример? одна ссылка?   -  person Arnaud H    schedule 17.11.2019
comment
Вы можете начать здесь geoffboeing.com/2016/10/r-tree -spatial-index-python, но имейте в виду, что это для пересечения. Я реализовал аналогичные вещи здесь docs.momepy.org/en/ стабильный / _modules / momepy /. Надеюсь, это поможет.   -  person martinfleis    schedule 17.11.2019


Ответы (1)


Думаю, Ball Tree является подходящей структурой для этой задачи.

Вы можете использовать реализацию scikit-learn, см. код ниже для примера, адаптированного к вашему случаю:

import numpy as np
import geopandas as gpd
from shapely.geometry import Point
from sklearn.neighbors import BallTree

## Create the two GeoDataFrame to replicate your dataset
appart = gpd.GeoDataFrame({
        'geometry': Point(a, b),
        'x': a,
        'y': b,
    } for a, b in zip(np.random.rand(100000), np.random.rand(100000))
])

pharma = gpd.GeoDataFrame([{
        'geometry': Point(a, b),
        'x': a,
        'y': b,
    } for a, b in zip(np.random.rand(3000), np.random.rand(3000))
])

# Create a BallTree 
tree = BallTree(pharma[['x', 'y']].values, leaf_size=2)

# Query the BallTree on each feature from 'appart' to find the distance
# to the nearest 'pharma' and its id
appart['distance_nearest'], appart['id_nearest'] = tree.query(
    appart[['x', 'y']].values, # The input array for the query
    k=1, # The number of nearest neighbors
)

С помощью этого метода вы можете решить свою проблему довольно быстро (в приведенном выше примере на моем компьютере потребовалось менее секунды, чтобы найти индекс ближайшей точки из 3000 точек во входном наборе данных из 100000 точек).

По умолчанию query метод BallTree возвращает расстояние до ближайшего соседа и его идентификатор. Если вы хотите, вы можете отключить возврат расстояния до этого ближайшего соседа, установив для параметра return_distance значение False. Если вас действительно волнует только расстояние, вы можете сохранить только это значение:

appart['distance_nearest'], _ = tree.query(appart[['x', 'y']].values, k=1)
person mgc    schedule 19.11.2019
comment
Ооооо, спасибо, чувак, это так эффективно и быстро: o Это очень хорошие новости :) Я пытался сделать сегментацию по регионам, чтобы было меньше вычислений, и это тоже сработало, но меньше, чем BallTree Последний вопрос: как иметь расстояние в километрах с шариковым деревом по широте и долготе? Потому что здесь у меня есть расстояние, но я не знаю, что чат, который он представляет на самом деле (и, если он имеет смысл). - person Arnaud H; 22.11.2019
comment
Я думаю, вам следует преобразовать ваши pharma и appart геоданные для использования системы координат проекции (например, epsg: 2154 для Франции или epsg: 3035 для Европы), выполнив appart.to_crs(epsg=2154, inplace=True) (то же самое для pharma). Затем вы создаете столбцы x и y, выполняя appart['x'] = appart.geometry.x и appart['y'] = appart.geometry.y (то же самое для pharma). Затем вы можете использовать ballTree, как описано в моем ответе, возвращаемое расстояние будет в метрах. - person mgc; 22.11.2019
comment
@ArnaudH, не стесняйтесь сказать, предпочитаете ли вы, чтобы я расширил свое объяснение в своем ответе, а не в комментариях! - person mgc; 22.11.2019
comment
Это прекрасно в комментариях, но как вы хотите :) Я просто меняю метрики на Balltree: tree = BallTree(pharma[['lat_r', 'lng_r']].values, leaf_size=2, metric='haversine') И я конвертирую свои градусы в радианы с помощью: appart['latitude_r']=pd.DataFrame(np.deg2rad(appart['latitude'].values)) appart['longitude_r']=pd.DataFrame(np.deg2rad(appart['longitude'].values)) pharma['lat_r']=pd.DataFrame(np.deg2rad(pharma['lat'].values)) pharma['lng_r']=pd.DataFrame(np.deg2rad(pharma['lng'].values)) Это все еще не хорошо, но это небольшая проблема, которая будет решена через некоторое время, я думаю :) - person Arnaud H; 22.11.2019
comment
О, это ограничение символов в комментариях, и это правда, что я не понимаю, что такое система координат, если у вас есть документация по этому поводу и почему это решит мою проблему, я открыт;) - person Arnaud H; 22.11.2019
comment
Отлично, я забыл о метрике «гаверсинус»! Обратите внимание, что в этом случае выходные данные также будут в радианах. - person mgc; 23.11.2019
comment
Что касается вопроса о системах координат, возможно, вы можете начать с такого введения: procedure.esri.com/library/userconf/proc16/tech-workshops/. По сути, идея состоит в том, чтобы преобразовать координаты по широте и долготе (соответствующие положению на глобальной трехмерной сферической поверхности) в координаты на плоской 2-мерной поверхности. Это невозможно сделать для всего мира без деформации; поэтому существуют локальные прогнозы (например, epsg: 2154 для Франции). - person mgc; 23.11.2019