Process Mining и Дракула, или как строить интерактивные графы в браузере?

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

Порой, полученные графы необходимо передать профильному специалисту, на компьютере которого нет специализированных программ для просмотра (python, graphviz, proM и т.д), в связи с чем встал вопрос о разработке приложения, поддерживающего методы Process Mining и работающем для просмотра и взаимодействия с полученными графами на любом компьютере. Решение – написать веб-приложение. Лог файл методами Process Mining будет обрабатываться на компьютере IT-специалиста с помощью python, а вот в построении графа процесса нам поможет Дракула! Но не спешите нести осиновые колья, это всего лишь javascript библиотека!

Для визуализации графа процесса необходимо предварительно извлечь необходимую информацию из предоставленных логов, а также обогатить её новыми данными (количеством переходов между событиями, средним временем и т.д).

В начале работы импортируем необходимую для обработки табличных данных библиотеку Pandas:

import pandas as pd

Считаем данные из нашего excel-файла, указав имя листа для чтения и требуемую кодировку (указание кодировки не является обязательным, но поможет избежать проблем с отображением информации в дальнейшем):

df = pd.read_excel('log.xlsx', sheet_name='data', encoding='utf-8') df.head()

Далее необходимо отсортировать данные нашего лог-файла по id случая и дате событий для каждого из id (так же необходимо найти список уникальных id, для дальнейшей работы):

sort_df = df.sort_values(['case_id','data'], ascending=True) unique_ids = sort_df['case_id'].unique().tolist()

Далее сформируем из наших данных цепочки событий, как показано на рисунке ниже:

dict_df = {'case_id':[],'event_1':[],'event_2':[],'time_delta':[]} for unique_id in unique_ids: sort = sort_df[sort_df['case_id'].isin([unique_id])] events = sort['event'].values dates = sort['data'].values dict_df['case_id'].append(unique_id) dict_df['event_1'].append("начало процесса") dict_df['event_2'].append(events[0]) dict_df['time_delta'].append(pd.Timedelta(dates[0]-dates[0]).total_seconds()) for i, event in enumerate(events[:-1]): dict_df['case_id'].append(unique_id) dict_df['event_1'].append(event) dict_df['event_2'].append(events[i+1]) dict_df['time_delta'].append(pd.Timedelta(dates[i+1]-dates[i]).total_seconds()) dict_df['case_id'].append(unique_id) dict_df['event_1'].append(events[-1]) dict_df['event_2'].append("конец процесса") dict_df['time_delta'].append(pd.Timedelta(dates[-1]-dates[-1]).total_seconds()) new_df = pd.DataFrame.from_dict(dict_df)

Для дальнейшей работы необходимо сформировать список уникальных цепочек событий, применив метод drop_duplicates, в качестве уникальных значений для subset указав первое и второе событие.

dup = new_df.drop_duplicates(subset = ['event_1','event_2'])

Далее, определим количество переходов между нашими событиями:

rows_df = new_df.groupby(['event_1','event_2']).size().reset_index(name="rows_count") rows_df.head()

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

def to_days(s): return s/86400 mean_time = new_df.groupby(['event_1','event_2'])[['time_delta']].mean() mean_time['time_delta'] = mean_time['time_delta'].apply(to_days) mean_time.head()

С помощью метода apply мы можем применить указанную функцию to_days, к элементам из указанного столбца (в данном случае преобразуем данные столбца time_delta из секунд в дни).

Сформируем итоговый DataFrame объединив все наши данные с помощью метода merge, в качестве ключей для объединения будем использовать наши «цепочки».

res_df=pd.merge(pd.merge(dup,rows_df,on=['event_1','event_2']), mean_time,on=['event_1','event_2'])

Из полученного DataFrame формируем js-файл с двумя переменными:

json1 – список из событий, которые будут использоваться для рисования графа. json2 – полная информация о цепочках событий. json1, json2 = {},{} json1['events'] = df['event'].unique().tolist() json2['events'] = [] for index, row in res_df.iterrows(): obj = {} obj['case_id'] = row['case_id'] obj['event1'] = row['event_1'] obj['event2'] = row['event_2'] obj['count_edges'] = row['rows_count'] obj['mean_time'] = row['time_delta_y'] json2['events'].append(obj) json_file = open('events.js', 'w', encoding='utf-8') json_file.write('json1={0}\njson2={1}'.format(str(json1),str(json2))) json_file.close()

Итоговый js-файл будет содержать переменные:

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

Dracula.js – это набор инструментов для отображения и компоновки интерактивных связанных графов и сетей, а также различных связанных алгоритмов из области теории графов. Название библиотеки навеяно игрой слов – Граф Дракула (Dracula Graph Library).

Cоздадим html-файл со структурой, указанной ниже:

<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script type="text/javascript" src="js/raphael-min.js"></script> <script type="text/javascript" src="js/dracula_graffle.js"></script> <script type="text/javascript" src="js/dracula_graph.js"></script> <script type="text/javascript" src="events.js"></script> <script type="text/javascript" src="graph_maker.js"></script> <style> #canvas{ border-width:2px; border-color:black; border-style:solid; } </style> </header> <body> <div id="canvas"></div> <button id="redraw" onclick="redraw();">redraw</button> </body>

Методы, необходимые для формирования графа и его отображения на рабочей области, будут храниться в файле graph_maker.js.

var redraw; /*

Для отрисовки графа задаём функцию, которая будет выполняться при загрузке страницы в браузере (window.onload)

*/ window.onload = function() { /*

Ширина и высота рабочей области в этом примере задается исходя из ширины и высоты страницы в браузере.

*/ width=document.body.clientWidth; height=document.body.clientHeight; //Инициализируем объект будущего графа var g = new Graph(); //Задаем функцию отрисовки var renderer = function(r, n) { var set = r.set().push( */

r.rect задает свойства прямоугольной области, в которой будет храниться информация о событии (размер, текст внутри области, цвет заливки и т.д).

r.rect(n.point[0]-100, n.point[1]-13, 200, 40).attr({"fill": "#fa8", "stroke-width": 2, r : "9px"})).push( r.text(n.point[0], n.point[1] + 10, n.label).attr({"font-size":"10px"})); set.items.forEach(function(el) {el.tooltip(r.set().push(r.rect(0, 0, 70, 70).attr({"fill": "#fec", "stroke-width": 1, r : "9px"})))}); return set; };

В цикле перебираем элементы из списка json1 и создаём вершины нашего графа, используя функцию addNode

for (var i=0; i<json1['events'].length; i++){ event = json1['events'][i]; g.addNode(event, {label:event, render:renderer});}

Перебираем элементы из переменной json2, соединяем ребрами вершины графа, согласно цепочкам событий и указываем информацию о них (количество переходов, среднее время переходов)

for (var j=0; j<json2['events'].length; j++) { event1 = json2['events'][j]['event1']; event2 = json2['events'][j]['event2']; mean_time = json2['events'][j]['mean_time']; count_edges = json2['events'][j]['count_edges']; g.addEdge(event1, event2, { stroke : "#bfa" , fill : "#56f", directed : true, label : count_edges}); }

Инициализируем рабочую область, на которой будет нарисован наш граф и задаём для нее параметры

var layouter = new Graph.Layout.Spring(g); layouter.layout(); var renderer = new Graph.Renderer.Raphael('canvas', g, width, height); renderer.draw(); redraw = function() { layouter.layout(); renderer.draw(); };};

Открыв наш html-файл в браузере, мы сможем увидеть результат:

Стоит отметить, что изображенные на холсте объекты поддерживают технологию «Drag-and-drop», что позволяет изменять вид полученного графа в реальном времени. Также код в файле graph_maker.js может быть изменён в зависимости от требований к представлению процесса, при этом формирование нового графа будет произведено сразу после обновления страницы браузера.

В итоге у нас получилось разработать приложение, после работы которого может быть сформирован набор скриптов, которые можно переслать профильному сотруднику для просмотра и анализа, не задумываясь о средствах просмотра на его компьютере.

0
Комментарии
-3 комментариев
Раскрывать всегда