Как (умно) перебрать все точки в GeoDataframe и посмотреть на ближайших соседей

У меня есть большой набор данных (O (10 ^ 6) строк) (точки со значениями), где мне нужно сделать следующее для всех точек:

  • Найдите 3 ближайшие точки в пределах заданного радиуса.
  • Вычислите среднее значение, связанное с этими тремя точками.
  • Сохраните это среднее значение до точки, на которую я смотрю

«Невекторизованный» подход состоял бы в том, чтобы просто перебрать все точки ... для всех точек, а затем применить логику. Однако это плохо масштабируется.

Я включил игрушечный пример, который делает то, что я хочу. Из идей, которые я уже рассмотрел, следующие:

  • using shapely.ops.nearest_points: это, однако, похоже, возвращает только одну ближайшую точку.
  • буферизация вокруг каждой отдельной точки и создание соединения с исходным GeoDataframe: похоже, что он будет масштабироваться даже хуже, чем наивный подход.

Вот игрушечный пример логики, которую я хочу реализовать:

import pandas as pd
import numpy as np
from shapely.wkt import loads
import geopandas as gp

points=[
    'POINT (1 1.1)', 'POINT (1 1.9)', 'POINT (1 3.1)',
    'POINT (2 1)', 'POINT (2 2.1)', 'POINT (2 2.9)',
    'POINT (3 0.8)', 'POINT (3 2)', 'POINT (3 3)'
]
values=[9,8,7,6,5,4,3,2,1]

df=pd.DataFrame({'points':points,'values':values})
gdf=gp.GeoDataFrame(df,geometry=[loads(x) for x in df.points], crs={'init': 'epsg:' + str(25832)})

for index,row in gdf.iterrows(): # Looping over all points
    gdf['dist'] = np.nan
    for index2,row2 in gdf.iterrows(): # Looping over all the other points
        if index==index2: continue
        d=row['geometry'].distance(row2['geometry']) # Calculate distance
        if d<3: gdf.at[index2,'dist']=d # If within cutoff: Store
        else: gdf.at[index2,'dist']=np.nan # Otherwise, be paranoid and leave NAN
    # Calculating mean of values for the 3 nearest points and storing 
    gdf.at[index,'mean']=np.mean(gdf.sort_values('dist').head(3)['values'].tolist())

print(gdf)

Результирующий GeoDataframe находится здесь:

          points  values       geometry      dist      mean
0  POINT (1 1.1)       9  POINT (1 1.1)  2.758623  6.333333
1  POINT (1 1.9)       8  POINT (1 1.9)  2.282542  7.000000
2  POINT (1 3.1)       7  POINT (1 3.1)  2.002498  5.666667
3    POINT (2 1)       6    POINT (2 1)  2.236068  5.666667
4  POINT (2 2.1)       5  POINT (2 2.1)  1.345362  4.666667
5  POINT (2 2.9)       4  POINT (2 2.9)  1.004988  4.333333
6  POINT (3 0.8)       3  POINT (3 0.8)  2.200000  4.333333
7    POINT (3 2)       2    POINT (3 2)  1.000000  3.000000
8    POINT (3 3)       1    POINT (3 3)       NaN  3.666667

Вы можете увидеть состояние последней итерации.

  • Все дистанции были рассчитаны без учета итогового места, которое осталось за НАН.
  • Среднее значение последней итерации - это среднее значение трех ближайших точек: 2, 4 и 5, а именно 3,666667.

Как мне сделать это более масштабируемым образом?


person Rasmus Mackeprang    schedule 21.06.2019    source источник
comment
Это довольно много вычислений и шагов, вы должны добавить ожидаемый результат также в виде фрейма данных.   -  person Erfan    schedule 21.06.2019
comment
Добавлен вывод с комментарием.   -  person Rasmus Mackeprang    schedule 21.06.2019


Ответы (2)


Я бы использовал для этого пространственный индекс. Вы можете использовать возможность libpysal, которая использует KDTree под капотом. Для 2000 случайных точек следующий код работает на 3,5 секунды по сравнению с вашим, который работает целую вечность (я потерял терпение после первой минуты). Сохранение значений в списке и последующее преобразование списка в столбец DF также сэкономит вам время.

import pandas as pd
import numpy as np
from shapely.wkt import loads
import geopandas as gp
import libpysal

points=[
    'POINT (1 1.1)', 'POINT (1 1.9)', 'POINT (1 3.1)',
    'POINT (2 1)', 'POINT (2 2.1)', 'POINT (2 2.9)',
    'POINT (3 0.8)', 'POINT (3 2)', 'POINT (3 3)'
]
values=[9,8,7,6,5,4,3,2,1]

df=pd.DataFrame({'points':points,'values':values})
gdf=gp.GeoDataFrame(df,geometry=[loads(x) for x in df.points], crs={'init': 'epsg:' + str(25832)})

knn3 = libpysal.weights.KNN.from_dataframe(gdf, k=3)

means = []
for index, row in gdf.iterrows(): # Looping over all points
    knn_neighbors = knn3.neighbors[index]
    knnsubset = gdf.iloc[knn_neighbors]
    neighbors = []
    for ix, r in knnsubset.iterrows():
        if r.geometry.distance(row.geometry) < 3: # max distance here
            neighbors.append(ix)

    subset = gdf.iloc[list(neighbors)]
    means.append(np.mean(subset['values']))
gdf['mean'] = means

Вот результат:

          points  values       geometry      mean
0  POINT (1 1.1)       9  POINT (1 1.1)  6.333333
1  POINT (1 1.9)       8  POINT (1 1.9)  7.000000
2  POINT (1 3.1)       7  POINT (1 3.1)  5.666667
3    POINT (2 1)       6    POINT (2 1)  5.666667
4  POINT (2 2.1)       5  POINT (2 2.1)  4.666667
5  POINT (2 2.9)       4  POINT (2 2.9)  4.333333
6  POINT (3 0.8)       3  POINT (3 0.8)  4.333333
7    POINT (3 2)       2    POINT (3 2)  3.000000
8    POINT (3 3)       1    POINT (3 3)  3.666667
person martinfleis    schedule 24.06.2019
comment
Спасибо @martinfleis. Кажется, это именно то, что мне нужно. Я пока прибегал к чему-то вроде for index, row в gdf.iterrows (): # Цикл по всем точкам df_tmp = gdf [gdf.geometry.within (row ['geometry']. Buffer (3) )] df_tmp ['dist'] = df_tmp.geometry.distance (row ['geometry']), но это определенно похоже на то, что это может иметь преимущества. Спасибо. - person Rasmus Mackeprang; 24.06.2019
comment
Вы можете использовать свой подход, но в этом случае я бы рекомендовал использовать пространственный индекс для большего количества точек, иначе вы все равно сверяли бы каждую точку друг с другом. Я все равно думаю, что все равно будет медленнее. - person martinfleis; 25.06.2019
comment
Хорошо, теперь я попробовал это, и он действительно дает несколько порядков по скорости. Хороший. Однако я получаю предупреждающее сообщение: UserWarning: матрица весов не полностью подключена. Всего 159 компонентов. Что это обозначает? - person Rasmus Mackeprang; 27.06.2019
comment
libpysal.weights.KNN генерирует матрицу весов, своего рода связи между точками. Вы можете представить это как сеть. Если вы не можете достичь каждой точки от каждого из них, следующих по этой сети, это означает, что она не полностью подключена. Для вашей цели это не имеет никаких последствий, вы можете с радостью игнорировать это. - person martinfleis; 27.06.2019
comment
В ПОРЯДКЕ. Я беспокоился, что совпадение точек может привести к этой ошибке. Еще раз спасибо. - person Rasmus Mackeprang; 27.06.2019

Это напоминает мне математическую задачу, которую я решал недавно в колледже. Он тесно связан с примером 7 главы 7. Таким образом, проблема

Рассмотрим набор мобильных вычислительных клиентов в определенном городе, каждый из которых должен быть подключен к одной из нескольких возможных базовых станций. Предположим, что имеется n клиентов, причем положение каждого клиента определяется его координатами (x, y) на плоскости. Также есть k базовых станций; положение каждого из них также определяется координатами (x, y). Для каждого клиента мы хотим подключить его ровно к одной из базовых станций. Наш выбор соединений ограничен следующими способами. Существует параметр диапазона r, такой, что клиент может подключаться только к базовой станции, находящейся на расстоянии r. Также существует параметр нагрузки L, такой, что к любой отдельной базовой станции может быть подключено не более L клиентов. Ваша цель - разработать алгоритм с полиномиальным временем для следующей задачи. Учитывая положение набора клиентов и набора базовых станций, а также параметры диапазона и нагрузки, решите, можно ли подключать каждого клиента одновременно к базовой станции, в зависимости от диапазона и условий нагрузки, указанных в предыдущем абзаце.

Я считаю, что вы можете преобразовать эту проблему в проблему сетевого потока за полиномиальное время, а затем использовать модифицированный алгоритм Форд-Фулкерсона, чтобы решить ее для того, что вы ищете, за время O (n * m + cmax), предполагая, что вы добавляете только постоянное время операции на форд-фулкерсон. Это может быть не масштабируемая проблема и может быть в списке проблем с полиномиальным временем, но, возможно, это будет лучший подход, чем постоянное время выполнения O (n ^ 2).

Для получения информации о том, как преобразовать это в проблему сетевого потока, я бы попытался прочитать псевдокод. Пароль pdf - птицы

person plum 0    schedule 21.06.2019