Road Traffic Fine Management is a real event log of an information system that handles traffic fines: 150,370 fines, 561,470 events, 11 activities. This example takes it from file to a judgement of how well a discovered model fits, in five steps that each fit on a screen.
import r4pm
import r4pm.petri_net
# 1. Load the log and look at its variants.
log = r4pm.import_item("EventLog", "data/road-traffic-fines.xes.gz")
projection = r4pm.bindings.log_to_activity_projection(log)
print(r4pm.bindings.get_num_cases(projection), "fines in", r4pm.bindings.get_num_variants(projection), "variants")
for variant in r4pm.bindings.get_top_n_variants(projection, 3):
print(f" {variant['percentage']:4.1f}% {' > '.join(variant['activities'])}")
# 2. Discover a Petri net.
net = r4pm.bindings.discover_alphaplusplusplus(
projection,
{
"balance_thresh": 0.1,
"fitness_thresh": 0.8,
"replay_thresh": 0.0,
"log_repair_skip_df_thresh_rel": 4.0,
"log_repair_loop_df_thresh_rel": 4.0,
"absolute_df_clean_thresh": 1,
"relative_df_clean_thresh": 0.01,
},
)
print(len(net["places"]), "places,", len(net["transitions"]), "transitions")
# 3. Check how well the model explains the log.
alignments = r4pm.bindings.align_variants(net, projection)
fitness = r4pm.bindings.compute_fitness(alignments, net)
print(f"log fitness {fitness['log_fitness']:.3f}, {fitness['perfectly_fitting_frac']:.1%} of fines fit perfectly")
# 4. Which behavior does the model miss most often?
missed = {}
for a in alignments:
for move in a["result"]["Ok"]["moves"]:
if "LogMove" in move:
activity = a["activities"][move["LogMove"]["trace_event_index"]]
missed[activity] = missed.get(activity, 0) + a["frequency"]
for activity, count in sorted(missed.items(), key=lambda item: -item[1])[:3]:
print(f" {count:>6} events of '{activity}' do not fit the model")
# 5. Save the model for ProM, PM4Py or Propel.
r4pm.petri_net.export_pnml(net, "road-traffic-fines.pnml")150370 fines in 231 variants
37.6% Create Fine > Send Fine > Insert Fine Notification > Add penalty > Send for Credit Collection
30.8% Create Fine > Payment
13.6% Create Fine > Send Fine
10 places, 16 transitions
log fitness 0.886, 51.7% of fines fit perfectly
72999 events of 'Payment' do not fit the model
960 events of 'Send Appeal to Prefecture' do not fit the model
673 events of 'Insert Date Appeal to Prefecture' do not fit the modelPython and Rust print the same lines. Alpha+++ also prints about 20 lines of progress information while it runs, which the output above leaves out.
Reading the result
Variants first. Three variants cover 82% of all fines. Most fines are either sent to credit collection after a penalty, or paid right after they are created. A model that handles these two well already explains most of the log.
Model and fitness. Alpha+++ finds a net with 10 places and 16 transitions (11 visible ones and 5 silent ones that let a trace skip an activity). Its log fitness is 0.886, and 51.7% of the fines follow a path that the model allows exactly.
Where it does not fit. A log move is an event that the model cannot place. Counting log moves per activity shows that Payment is the problem: 72,999 payment events, mostly from the 30.8% of fines paid right away, do not fit. In the alignment of Create Fine > Payment (46,371 fines), Payment is a log move: The model does not allow paying directly after a fine is created.
Next steps
- Change the Alpha+++ thresholds in step 2 and compare the fitness. The discovery how-to explains each threshold.
- Open
road-traffic-fines.pnmlin Propel or PM4Py to see the model. - Check conformance covers the alignment options, including move costs and the search limit.