Minería de procesos sin PM4PY





Es muy fácil construir un gráfico a partir de los registros del proceso. Actualmente, los analistas tienen a su disposición una variedad suficiente de desarrollos profesionales, como Celonis, Disco, PM4PY, ProM, etc., diseñados para facilitar el estudio de procesos. Es mucho más difícil encontrar desviaciones en los gráficos, sacar conclusiones correctas de ellos.



¿Qué pasa si algunos desarrollos profesionales que han demostrado su valía y son de especial interés no están disponibles por una razón u otra, o desea más libertad en los cálculos al trabajar con gráficos? ¿Qué tan difícil es escribir un minero usted mismo e implementar algunas de las capacidades necesarias para trabajar con gráficos? Haremos esto en la práctica utilizando las bibliotecas estándar de Python, implementaremos los cálculos y daremos, con su ayuda, respuestas a preguntas detalladas que podrían interesar a los propietarios del proceso.



Me gustaría hacer una reserva de inmediato de que la solución dada en el artículo no es una implementación industrial. Este es un intento de comenzar a trabajar con registros por su cuenta con la ayuda de un código simple que claramente funciona y, por lo tanto, facilita la adaptación. Esta solución no debe usarse en big data; esto requiere un refinamiento significativo, por ejemplo, usando cálculos vectoriales o cambiando el enfoque para recopilar y agregar información sobre eventos.



Antes de crear un gráfico, debe realizar cálculos. El cálculo real del gráfico será el mismo minero mencionado anteriormente. Para realizar el cálculo, es necesario recopilar conocimientos sobre los eventos, los vértices del gráfico y las conexiones entre ellos, y escribirlos, por ejemplo, en libros de referencia. Las referencias se llenan utilizando el procedimiento de cálculo de calc ( código en github). Las referencias completadas se pasan como parámetros al procedimiento para dibujar gráficos dibujar (ver el código del enlace de arriba). Este procedimiento formatea los datos como se muestra a continuación:



digraph f {"Permit SUBMITTED by EMPLOYEE (6255)" -> "Permit APPROVED by ADMINISTRATION (4839)" [label=4829 color=black penwidth=4.723857205400346] 
"Permit SUBMITTED by EMPLOYEE (6255)" -> "Permit REJECTED by ADMINISTRATION (83)" [label=83 color=pink2 penwidth=2.9590780923760738] 
"Permit SUBMITTED by EMPLOYEE (6255)" -> "Permit REJECTED by EMPLOYEE (231)" [label=2 color=pink2 penwidth=1.3410299956639813] 
start [color=blue shape=diamond] 
end [color=blue shape=diamond]}


y lo pasa al motor gráfico Graphviz para su renderizado.



Comencemos a construir y examinar gráficos usando el minero implementado. Repetiremos los procedimientos para leer y clasificar datos, calcular y dibujar gráficos, como en los ejemplos siguientes. Por ejemplo, los registros de eventos se toman de declaraciones internacionales de la competencia BPIC2020. Enlace a la competencia.



Leemos los datos del registro, los ordenamos por fecha y hora. El formato .xes se convirtió previamente a .xlsx.



df_full = pd.read_excel('InternationalDeclarations.xlsx')
df_full = df_full[['id-trace','concept:name','time:timestamp']]
df_full.columns = ['case:concept:name', 'concept:name', 'time:timestamp']
df_full['time:timestamp'] = pd.to_datetime(df_full['time:timestamp'])
df_full = df_full.sort_values(['case:concept:name','time:timestamp'], ascending=[True,True])
df_full = df_full.reset_index(drop=True)


Calculemos la gráfica.



dict_tuple_full = calc(df_full)


Dibujemos la gráfica.



draw(dict_tuple_full,'InternationalDeclarations_full')


Después de completar los procedimientos, obtenemos el gráfico de proceso:







dado que el gráfico resultante no es legible, lo simplificamos.



Hay varios enfoques para mejorar la legibilidad o simplificar el gráfico:



  1. utilizar el filtrado por pesos de vértices o enlaces;
  2. deshacerse del ruido;
  3. Agrupar eventos por similitud de nombres.


Tomemos el enfoque 3.



Creemos un diccionario para combinar eventos:



_dict = {'Permit SUBMITTED by EMPLOYEE': 'Permit SUBMITTED',
 'Permit APPROVED by ADMINISTRATION': 'Permit APPROVED',
 'Permit APPROVED by BUDGET OWNER': 'Permit APPROVED',
 'Permit APPROVED by PRE_APPROVER': 'Permit APPROVED',
 'Permit APPROVED by SUPERVISOR': 'Permit APPROVED',
 'Permit FINAL_APPROVED by DIRECTOR': 'Permit FINAL_APPROVED',
 'Permit FINAL_APPROVED by SUPERVISOR': 'Permit FINAL_APPROVED',
 'Start trip': 'Start trip',
 'End trip': 'End trip',
 'Permit REJECTED by ADMINISTRATION': 'Permit REJECTED',
 'Permit REJECTED by BUDGET OWNER': 'Permit REJECTED',
 'Permit REJECTED by DIRECTOR': 'Permit REJECTED',
 'Permit REJECTED by EMPLOYEE': 'Permit REJECTED',
 'Permit REJECTED by MISSING': 'Permit REJECTED',
 'Permit REJECTED by PRE_APPROVER': 'Permit REJECTED',
 'Permit REJECTED by SUPERVISOR': 'Permit REJECTED',
 'Declaration SUBMITTED by EMPLOYEE': 'Declaration SUBMITTED',
 'Declaration SAVED by EMPLOYEE': 'Declaration SAVED',
 'Declaration APPROVED by ADMINISTRATION': 'Declaration APPROVED',
 'Declaration APPROVED by BUDGET OWNER': 'Declaration APPROVED',
 'Declaration APPROVED by PRE_APPROVER': 'Declaration APPROVED',
 'Declaration APPROVED by SUPERVISOR': 'Declaration APPROVED',
 'Declaration FINAL_APPROVED by DIRECTOR': 'Declaration FINAL_APPROVED',
 'Declaration FINAL_APPROVED by SUPERVISOR': 'Declaration FINAL_APPROVED',
 'Declaration REJECTED by ADMINISTRATION': 'Declaration REJECTED',
 'Declaration REJECTED by BUDGET OWNER': 'Declaration REJECTED',
 'Declaration REJECTED by DIRECTOR': 'Declaration REJECTED',
 'Declaration REJECTED by EMPLOYEE': 'Declaration REJECTED',
 'Declaration REJECTED by MISSING': 'Declaration REJECTED',
 'Declaration REJECTED by PRE_APPROVER': 'Declaration REJECTED',
 'Declaration REJECTED by SUPERVISOR': 'Declaration REJECTED',
 'Request Payment': 'Request Payment',
 'Payment Handled': 'Payment Handled',
 'Send Reminder': 'Send Reminder'}


Agrupemos los eventos y dibujemos el gráfico de proceso nuevamente.



df_full_gr = df_full.copy()
df_full_gr['concept:name'] = df_full_gr['concept:name'].map(_dict)
dict_tuple_full_gr = calc(df_full_gr)
draw(dict_tuple_full_gr,'InternationalDeclarations_full_gr'




Después de agrupar los eventos por similitud de nombre, la legibilidad del gráfico ha mejorado. Intentemos encontrar respuestas a las preguntas. Enlace a la lista de preguntas. Por ejemplo, ¿cuántas declaraciones no fueron precedidas por una autorización preaprobada?



Para responder a la pregunta planteada, filtremos el gráfico por eventos de interés y dibujemos el gráfico de proceso nuevamente.



df_full_gr_f = df_full_gr[df_full_gr['concept:name'].isin(['Permit SUBMITTED',
                                                            'Permit APPROVED',
                                                            'Permit FINAL_APPROVED',
                                                            'Declaration FINAL_APPROVED',
                                                            'Declaration APPROVED'])]
df_full_gr_f = df_full_gr_f.reset_index(drop=True)
dict_tuple_full_gr_f = calc(df_full_gr_f)
draw(dict_tuple_full_gr_f,'InternationalDeclarations_full_gr_isin')






Con la ayuda del gráfico resultante, podemos dar fácilmente una respuesta a la pregunta planteada: las declaraciones 116 y 312 no fueron precedidas por un permiso preaprobado.



Además, puede "fallar" (filtrar por 'caso: concepto: nombre', participando en la conexión deseada) para las conexiones 116 y 312 y asegurarse de que no habrá eventos relacionados con los permisos en los gráficos.



Vamos a "fallar" para la comunicación 116:



df_116 = df_full_gr_f[df_full_gr_f['case:concept:name'].isin(d_case_start2['Declaration FINAL_APPROVED'])]
df_116 = df_116.reset_index(drop=True)
dict_tuple_116 = calc(df_116)
draw(dict_tuple_116,'InternationalDeclarations_full_gr_isin_116')






Vamos a "fallar" para la conexión 312:



df_312 = df_full_gr_f[df_full_gr_f['case:concept:name'].isin(d_case_start2['Declaration APPROVED'])]
df_312 = df_312.reset_index(drop=True)
dict_tuple_312 = calc(df_312)
draw(dict_tuple_312,'InternationalDeclarations_full_gr_isin_312')






Dado que no hay eventos relacionados con los permisos en los gráficos recibidos, se confirma la exactitud de las respuestas 116 y 312.



Como puede ver, escribir un minero e implementar las capacidades necesarias para trabajar con gráficos no es una tarea difícil, que las funciones integradas de Python y Graphviz han abordado con éxito como motor gráfico.



All Articles