Одним из возможных способов является использование пространственной кластеризации каждой точки данных. В следующем коде используется DBSCAN, но, возможно, другие типы подойдут лучше. Вот обзор того, как они работают: http://scikit-learn.org/stable/modules/clustering.html
from matplotlib import pyplot as plt
from sklearn.cluster import DBSCAN
from sklearn.preprocessing import StandardScaler
import numpy as np
import pandas as pd
import geopandas as gpd
df = gpd.GeoDataFrame.from_file("stackex_dataset.shp")
Каждая строка df представляет собой количество точек. Мы хотим получить их все, чтобы получить кластеры:
ids = []
coords = []
for row in df.itertuples():
geom = np.asarray(row.geometry)
coords.extend(geom)
ids.extend([row.id] * geom.shape[0])
нам нужны идентификаторы здесь, чтобы вернуть кластеры в df после вычислений. Вот получение кластеров для каждой точки (мы также делаем нормализацию данных для лучшего качества):
clust = DBSCAN(eps=0.5)
clusters = clust.fit_predict(StandardScaler().fit_transform(coords))
Следующая часть немного запутана, но мы хотим убедиться, что получаем только один кластер для каждого идентификатора. Мы выбираем наиболее частое скопление точек для каждого идентификатора.
points_clusters = pd.DataFrame({"id":ids, "cluster":clusters})
points_clusters["count"] = points_clusters.groupby(["id", "cluster"])["id"].transform('size')
max_inds = points_clusters.groupby(["id", "cluster"])['count'].transform(max) == points_clusters['count']
id_to_cluster = points_clusters[max_inds].drop_duplicates(subset ="id").set_index("id")["cluster"]
Затем мы возвращаем номер кластера в наш фрейм данных, чтобы мы могли перечислить наши улицы с помощью этого номера.
df["cluster"] = df["id"].map(id_to_cluster)
Для этих данных с DBSCAN и eps=0.5 (с этим параметром можно поиграться — это максимальное расстояние между точками, чтобы они попали в один кластер. Чем больше eps, тем меньше кластеров получается), имеем такую картину:
plt.scatter(np.array(coords)[:, 0], np.array(coords)[:, 1], c=clusters, cmap="autumn")
plt.show()
А количество отдельных улиц 8:
print(len(df["cluster"].drop_duplicates()))
Если мы сделаем более низкие eps, например. clust = DBSCAN(eps=0.15) мы получаем больше кластеров (на данный момент 12), которые лучше разделяют данные:
Насчет грязной части кода: в исходном DataFrame у нас 170 строк, каждая строка — это отдельный объект LINESTRING. Каждая LINESTRING состоит из 2d точек, количество точек в LINESTRING разное. Итак, сначала мы получаем все точки (список координат в коде) и предсказываем кластеры для каждой точки. Существует небольшая вероятность того, что мы получим разные кластеры, которые будут представлены в точках одной LINESTRING. Чтобы решить эту ситуацию, мы получаем количество каждого кластера, а затем фильтруем максимумы.
person
Alexey Trofimov
schedule
27.12.2017