PWL is a pMineR internal formalism for representing workflows and guidelines. The class implementing PWL is called confCheck_easy() : it let the traces flow through the workflow and then observe the behaviors.
We have to load the needed libraries
library(pMineR,quietly = TRUE)
library(kableExtra,quietly = TRUE)
First of all let’s suppose to have an Event Log composed from the following events: Imaging, MedicalVisit, Therapy, Dismission. A possible trace can be:
aaa <- read.csv2(file = "./csv/one.overview.csv",sep = ",")
aaa %>%
kbl() %>%
kable_minimal()
ID | DATE | EVENT |
---|---|---|
66738 | 02/10/2016 | Imaging |
66738 | 09/10/2016 | MedicalVisit |
66738 | 18/10/2016 | Imaging |
66738 | 02/11/2016 | MedicalVisit |
66738 | 10/11/2016 | Chemotherapy |
66738 | 01/12/2016 | Dismission |
Which can be the meaning of that? For example the patient got some Image investigation (fist event) and then he went to the first Medical Visit (second Event). In such Visit the clinician decided to go more in deep with another diagnostic image analysis (the third event). With the report of the new investigation, the patients was evaluated in a new Medical visit (the fourth event) where the therapy was planned (the fifth event). After almost 20 days, the therapy ended and the patient was dismissed (sixth and last event).
A simple workflow can be aimed at identifying the DIAGNOSTIC time interval, the THERAPY time interval and the DISMISSION. In this WF, we will create a dummy-state called BEGIN (in green) and we will define as final state the DISMISSION (in red):
objPWL <- confCheck_easy()
objPWL$loadWorkFlow(WF.fileName = "./XML-PWL/one.overview.xml")
grViz( objPWL$plot(giveBack.grVizScript = TRUE) )
The corresponding XML, in PWL, is the following:
<xml>
<workflow>
<node name='Diagnosis'></node>
<node name='Therapy'></node>
<node name='Dismission' type='END'></node>
<trigger name='Trigger 1'>
<condition>'BEGIN' %in% $st.ACTIVE$ AND ( $ev.NOW$=='Imaging' or $ev.NOW$=='MedicalVisit' )</condition>
<set>'Diagnosis'</set>
<unset>'BEGIN'</unset>
</trigger>
<trigger name='Trigger 2'>
<condition>'Diagnosis' %in% $st.ACTIVE$ AND $ev.NOW$=='Chemotherapy' </condition>
<set>'Therapy'</set>
<unset>'Diagnosis'</unset>
</trigger>
<trigger name='Trigger 3'>
<condition>'Therapy' %in% $st.ACTIVE$ AND $ev.NOW$=='Dismission' </condition>
<unset>'Therapy'</unset>
<set>'Dismission'</set>
</trigger>
</workflow>
</xml>
Now we will analyze the computation from the point of view of the confChech_easy engine. He places a cursor at the first event of the track, then moves the cursor along the events in chronological order. In this activity it checks which triggers can be fired and which corresponding states have to be set or unset. At the end of the computation it returns a report. In or specific case.
Note: with events we mean the events as they are present in the Event Log while the states are freely definable by the Process Mining Analyst and represent the different states or phases or steps in the process we want to check.
The dummy-node BEGIN is automatically added to begin the computation and set to ACTIVE. This is done by default because the engine need an entry point.
Then the engine positions the Cursor on the first Event (‘Imaging’). Now let’s consider the condition of the TRIGGER 1. It means: “Fire the trigger when BEGIN is active and the cursor is positioned on an event of type ‘Imaging’ OR ‘MedicalVisit’”. This is clearly our case, at the beginning of the computation. Looking at the other triggers, it’s easy to observe that none of the other conditions can be satisfied. Due to that reason, the engine will fire the TRIGGER 1 and, according to their set/unset rules, set the state Diagnosis and unset the state BEGIN.
Then the engine can move the cursor to the next event (‘MedicalVisit’) and check again if any trigger can be fired. In this case we can see that no trigger can be fired: ‘BEGIN’ is no more active (so Trigger 1 cannot be fired) the cursor is not pointing an event ‘Chemotherapy’ or ’Dismission, so Trigger 2 and Trigger 3 cannot be fired). Due to that, the engine moves the cursor to the next event.
The next event under the cursor is ‘Imaging’ and, again, nothing can be fired. It goes on.
The next event under the cursor is ‘MedicalVisit’ and, again, nothing can be fired. It goes on.
Now the event is ‘Chemotherapy’ and in this case Trigger 2 can be fired because the node Diagnosis is set (it was set at the begin and then nobody unset it) and the cursor is now positioned on an event of ‘Chemotherapy’. So, the engine unset Diagnosis and set Therapy. Once done, it moves the cursor to the next event.
Now, the engine can fire the Trigger 3. It sets the node Dismission and this ends the computation because the node Dismission is declared to be an ‘END’ state (see definition in the xml).
As usual, we need a dataLoader object
objDL = dataLoader(verbose.mode = FALSE)
objDL$load.csv(nomeFile = "./csv/one.overview.csv", IDName = "ID", EVENTName = "EVENT", dateColumnName = "DATE", sep = ",", format.column.date = "%d/%m/%Y");
we can now load the Event Log into the object objPWL:
objPWL$loadDataset(dataList = objDL$getData())
and run the conformance checking:
objPWL$replay()
##
## Doing: 66738
## Beginning Pat 66738...
## processing:Imaging
## processing:MedicalVisit
## processing:Imaging
## processing:MedicalVisit
## processing:Chemotherapy
## processing:Dismission
## Pat 66738 done;
One of the ways to get the results is the following:
aaa <- objPWL$get.list.replay.result()
resultingTale <- aaa$list.computation.matrix$stati.timeline
resultingTale[["66738"]] %>%
kbl() %>%
kable_minimal()
node | node.status | eventDateTime | deltaTimeFromBegin |
---|---|---|---|
Diagnosis | begin | 02/10/2016 00:00:00 | 0 |
BEGIN | end | 02/10/2016 00:00:00 | 0 |
Therapy | begin | 10/11/2016 00:00:00 | 56220 |
Diagnosis | end | 10/11/2016 00:00:00 | 56220 |
Dismission | begin | 01/12/2016 00:00:00 | 86460 |
Therapy | end | 01/12/2016 00:00:00 | 86460 |
note : This table does not show the Event in the Event Log but what happened to the nodes in the PWL XML. Here, for example, we can read that:
With the confCheck_easy::method get.XML.replay.result(), as shown below, we can write on an xml the cursor point of view
objPWL$get.XML.replay.result(fileName = "./text.xml",writeToFile = TRUE)
The output is the following:
<xml>
<computation n='1' IDPaz='66738'>
<step n='1' trg='TRUE' evt='Imaging' date='02/10/2016 00:00:00' pMineR.internal.ID.Evt='1'>
<st.ACTIVE.PRE name='BEGIN'></st.ACTIVE.PRE>
<fired.trigger name='Trigger 1'></fired.trigger>
<st.ACTIVE.POST name='Diagnosis'></st.ACTIVE.POST>
</step>
<step n='2' trg='FALSE' evt='MedicalVisit' date='09/10/2016 00:00:00' pMineR.internal.ID.Evt='2'>
</step>
<step n='3' trg='FALSE' evt='Imaging' date='18/10/2016 00:00:00' pMineR.internal.ID.Evt='3'>
</step>
<step n='4' trg='FALSE' evt='MedicalVisit' date='02/11/2016 00:00:00' pMineR.internal.ID.Evt='4'>
</step>
<step n='5' trg='TRUE' evt='Chemotherapy' date='10/11/2016 00:00:00' pMineR.internal.ID.Evt='5'>
<st.ACTIVE.PRE name='Diagnosis'></st.ACTIVE.PRE>
<fired.trigger name='Trigger 2'></fired.trigger>
<st.ACTIVE.POST name='Therapy'></st.ACTIVE.POST>
</step>
<step n='6' trg='TRUE' evt='Dismission' date='01/12/2016 00:00:00' pMineR.internal.ID.Evt='6'>
<st.ACTIVE.PRE name='Therapy'></st.ACTIVE.PRE>
<fired.trigger name='Trigger 3'></fired.trigger>
<st.ACTIVE.POST name='Dismission'></st.ACTIVE.POST>
</step>
<atTheEnd>
<finalState name='Dismission'></finalState>
<last.fired.trigger name='Trigger 3'></last.fired.trigger>
</atTheEnd>
</computation>
</xml>