Визуализация графа взаимосвязей на карте

Теория шести рукопожатий – социологическая теория, согласно которой двух любых людей разделяет всего пять уровней знакомых. Любители отечественного кинематографа могли увидеть её воплощение в одном из популярных новогодних фильмов – люди из разных уголков страны оказывались связаны друг с другом.

Но что делать, если необходимо в реальной задаче отобразить подобные связи на карте? В таком случае, первым делом на ум приходит GraphMining.

GraphMining (далее –GM) – одно из направлений анализа данных, которое позволяет представить комплексные данные в виде графов.

В Python наиболее популярными библиотеками для GM являются NetworkX, pyviz и graph-tool. С их помощью можно формировать и кастомизировать различные виды графов, а, так же, вычислять множество метрик для анализа. Однако, есть проблема: стандартные библиотеки GM не работают с картами, а библиотеки для работы с картами не формируют графы. На самом деле существует очевидное и простое решение, которое я опишу далее.

В начале – импорт необходимых библиотек:

import folium import pandas as pd import numpy as np

Допустим, что у меня имеется датасет с аггрегированной информацией о переводах от одного клиента другому:

data = pd.read_csv('data.csv', sep = ';')
Визуализация графа взаимосвязей на карте

Id_send – идентификатор отправителя;

Id_recei – идентификатор получателя;

lat_send – широта точки местоположения клиента отправителя;

lon_send – долгота точки местоположения клиента отправителя;

lat_rec – широта точки местоположения клиента получателя;

lon_rec – долгота точки местоположения клиента получателя;

opers_cnt – количество операций по направлению от id_send к id_recei;

opers_sum – сумма операций по направлению от id_send к id_recei.

Предлагаю рассмотреть описание датасета:

Визуализация графа взаимосвязей на карте

В 75% строк количество операций от отправителя к получателю 5 или меньше. Отфильтрую данные, оставив наиболее сильные связи:

data_clean = data[data['opers_cnt']>5]

Далее, необходимо получить набор точек (nodes) с идентификаторами клиентов и их координатами и посчитать общую сумму операций у клиента – отправлений и поступлений:

data_senders = data_clean.rename( columns = {'id_send':'id','lat_send':'lat','lon_send':'lon'})[['id','lat','lon','opers_sum']] data_receivers = data_clean.rename( columns = {'id_recei':'id','lat_rec':'lat','lon_rec':'lon'})[['id','lat','lon','opers_sum']] nodes = (pd.concat([data_senders, data_receivers]) .groupby(['id','lat','lon'])['opers_sum'] .sum() .reset_index())

Нормализую объем операций, данный столбец будет использоваться в качестве параметра размера точки:

nodes['opers_sum_scaled'] = (nodes['opers_sum']-nodes['opers_sum'].min()) / (nodes['opers_sum'].max()-nodes['opers_sum'].min())*20

Получаю датасет:

Визуализация графа взаимосвязей на карте

Обогащаю информацией о суммах отправлений и поступлений каждого идентификатора:

id_send_opers = (data_clean.groupby(['id_send'])['opers_sum'].sum() .reset_index() .rename(columns = {'id_send':'id','opers_sum':'send_sum'})) id_rec_opers = (data_clean.groupby(['id_recei'])['opers_sum'].sum() .reset_index() .rename(columns = {'id_recei':'id','opers_sum':'rec_sum'})) nodes = nodes.merge(id_send_opers, on ='id', how = 'left') nodes = nodes.merge(id_rec_opers, on ='id', how = 'left') nodes = nodes.fillna(0)

Получил всю необходимую информацию для нанесения точек на карту:

Визуализация графа взаимосвязей на карте

Далее эти точки необходимо соединить – формирую список ребер:

edges = (pd.DataFrame(np.unique(np.array(['-'.join(sorted(edge)) for edge in zip(for_edges['id_send'],for_edges['id_recei'])])))[0] .str.split('-', expand = True).rename(columns=({0:'id_x', 1:'id_y'}))) coords_list = nodes[['id','lat','lon']] edges = edges.merge(coords_list.rename(columns={'id':'id_x'}), on ='id_x', how = 'left') edges = edges.merge(coords_list.rename(columns={'id':'id_y'}), on ='id_y', how = 'left')
Визуализация графа взаимосвязей на карте

Для визуализации буду использовать библиотеку folium, создаю карту:

wm = folium.Map(tiles='cartodbpositron', prefer_canvas = True, control_scale = True, disable_3d = True)

Наношу слой с точками на карту:

for index, row in nodes.iterrows(): pers_id = row[0] lat = row[1] lon = row[2] wei = row[4] op_sum_total = round(row[3]/1000) op_sum_send = round(row[5]/1000) op_sum_rec = round(row[6]/1000) html = ( "ID: {pers_id}</br>" "Суммарный объем операций: {op_sum_total} т.р.<br>" "Отправлено: {op_sum_send} т.р.<br>" "Получено: {op_sum_rec} т.р.<br>" ).format(pers_id=pers_id, op_sum_total=op_sum_total, op_sum_send=op_sum_send, op_sum_rec=op_sum_rec) folium.CircleMarker([lat, lon], radius=1, color="#571e63", weight=wei, opacity = 0.6, fill_opacity = 0, popup= folium.Popup(html, max_width = 1000) ).add_to(wm)

Цикл бежит по строчкам из датасета, и на карте отмечается точка в зависимости от координат, указанных в строке. Также динамически определяется текст для pop-up, который будет отображаться при нажатии на точку, и размер ноды. В зависимости от задачи можно менять параметры, например, не задавать динамический размер точек (weight).

Результат:

Визуализация графа взаимосвязей на карте

Добавляю ребра:

for index, row in edges.iterrows(): folium.PolyLine([[float(row[2]), float(row[3])], [float(row[4]), float(row[5])]], color="#571e63", weight=0.5, opacity = 0.5 ).add_to(wm)

Полученный результат:

Визуализация графа взаимосвязей на карте

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

568568 показов
4.9K4.9K открытий
11 репост
Начать дискуссию