разделить строки на основе имен

У меня есть кадр данных GeoPandas, созданный из объекта шейп-файла. Однако некоторые линии имеют одно и то же имя, но находятся в разных местах.

Я хочу иметь уникальное имя для каждой строки! Поэтому мне нужно как-то разделить линию, если они геометрически разделены и переименовать их.

Можно попытаться вычислить расстояния между всеми участками улиц и перегруппировать их, если они находятся рядом.

Вычисление расстояния можно легко выполнить в Geopandas: Расстояние между Linestring Geopandas

Набор строк, чтобы попробовать:

from shapely.geometry import Point, LineString
import geopandas as gpd


line1 = LineString([
    Point(0, 0),
    Point(0, 1),
    Point(1, 1),
    Point(1, 2),
    Point(3, 3),
    Point(5, 6),
])

line2 = LineString([
    Point(5, 3),
    Point(5, 5),
    Point(9, 5),
    Point(10, 7),
    Point(11, 8),
    Point(12, 12),
])

line3 = LineString([
    Point(9, 10),
    Point(10, 14),
    Point(11, 12),
    Point(12, 15),
])

df = gpd.GeoDataFrame(
    data={'name': ['A', 'A', 'A']},
    geometry=[line1, line2, line3]
)

person james    schedule 27.12.2017    source источник
comment
dbscan кластеризация sklearn по координатам является опцией здесь. scikit-learn.org/stable/modules/generated/. Пример использования данных: scikit-learn.org/stable/auto_examples/cluster/   -  person Alexey Trofimov    schedule 27.12.2017
comment
Также, пожалуйста, поделитесь всеми необходимыми файлами. Только файла shp не хватает для загрузки данных. Подробности: gis.stackexchange.com/questions/262505/   -  person Alexey Trofimov    schedule 27.12.2017
comment
Да, теперь все хорошо с загрузкой данных. Я проверю подход к кластеризации.   -  person Alexey Trofimov    schedule 27.12.2017
comment
Я добавил базовый подход с библиотекой scikit-learn. Вы можете поиграть с этой строкой: clust = DBSCAN(eps=0.5) (изменить eps или даже взять другие алгоритмы кластеризации отсюда scikit-learn.org/stable/modules/clustering.html), чтобы получить необходимый результат.   -  person Alexey Trofimov    schedule 27.12.2017


Ответы (1)


Одним из возможных способов является использование пространственной кластеризации каждой точки данных. В следующем коде используется 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
comment
@james, df[STREET] = df[name] + _ + df[cluster].astype(str), чтобы получить название улицы. Вскоре я добавлю объяснение в ответ. - person Alexey Trofimov; 27.12.2017
comment
@james, я обновил пост. Также eps=1.5 лучше разделяет данные. - person Alexey Trofimov; 27.12.2017
comment
@james, пожалуйста, добавьте subset=id в drop_duplicates к этой строке: id_to_cluster = points_clusters[max_inds].drop_duplicates().set_index(id)[cluster]. Это может быть вызвано тем, что у нас одинаковое максимальное количество двух кластеров. - person Alexey Trofimov; 28.12.2017