Getting started with simOutrank

simOutrank clusters the traces of an event log by an outranking model of similarity. Instead of collapsing every perspective into a single distance, it scores each pair of traces on several problem-specific criteria – activities, transitions, duration, case attributes – and aggregates them with ELECTRE-III-style concordance and discordance. The result is a credibility matrix S that a spectral or hierarchical algorithm then partitions.

This vignette walks through the whole pipeline on the bundled illustrative_log, a 25-case customer-service log from Delias et al. (2023).

library(simOutrank)
head(illustrative_log)
#>   case_id activity           timestamp status satisfaction
#> 1       1        B 2021-01-01 00:01:00   GOLD         High
#> 2       1        E 2021-01-01 00:02:00   GOLD         High
#> 3       2        B 2021-01-01 00:01:00   GOLD         High
#> 4       2        E 2021-01-01 00:02:00   GOLD         High
#> 5       3        B 2021-01-01 00:01:00   GOLD         High
#> 6       3        E 2021-01-01 00:02:00   GOLD         High

1. Traces

as_traces() turns an event log into a traces object: the time-ordered, integer-encoded activity sequence of every case, plus the case-level attribute table.

traces <- as_traces(illustrative_log,
                    case_id = "case_id", activity = "activity",
                    timestamp = "timestamp")
traces
#> <traces>: 25 cases, 5 distinct activities
#>   trace length: min 2, median 5, max 7
#>   case attributes: status, satisfaction
summary(traces)[1:5, ]
#>   case_id n_events n_distinct
#> 1       1        2          2
#> 2       2        2          2
#> 3       3        2          2
#> 4       4        2          2
#> 5       5        2          2

2. Criteria

Criteria are built with the crit_* helpers. Each carries a direction (similarity or dissimilarity), a weight, and ELECTRE-III thresholds (indifference, similarity, and an optional veto). Here we mirror the four criteria of the source paper: what activities occur, in what order, and two case attributes.

criteria <- list(
  crit_activity_profile(weight = 0.2, indifference = 0.7, similarity = 0.8,
                        veto = 0.4),
  crit_edit_distance(weight = 0.2, similarity = 2, indifference = 3, veto = 6),
  crit_nominal("status", weight = 0.3),
  crit_nominal("satisfaction", weight = 0.3)
)
criteria[[1]]
#> <criterion 'activity_profile'>: direction = similarity, weight = 0.2
#>   indifference = 0.7, similarity = 0.8, veto = 0.4

Thresholds may be fixed numbers or quantiles of the criterion’s own value distribution via as_quantile(); the parameter-tuning vignette explores that choice.

3. Similarity

outrank_similarity() resolves any quantile thresholds, normalises the weights, and aggregates the per-criterion indices into S.

sim <- outrank_similarity(traces, criteria)
sim
#> <outrank_sim>: 25 cases, 4 criteria
#>   S in [0.000, 1.000], symmetric = TRUE
round(sim$S[1:5, 1:5], 2)
#>   1 2 3 4 5
#> 1 1 1 1 1 1
#> 2 1 1 1 1 1
#> 3 1 1 1 1 1
#> 4 1 1 1 1 1
#> 5 1 1 1 1 1

4. How many clusters?

eigengap() plots the smallest eigenvalues of the normalized Laplacian; a gap after the k-th value suggests k groups.

eigengap(sim, k_max = 8)

5. Cluster

The paper targets four natural groups (tier x satisfaction). Spectral clustering uses a seed for reproducible k-means.

clust <- cluster_traces(sim, k = 4, seed = 42)
clust
#> <outrank_clust>: 25 cases in 4 clusters (spectral)
#>   cluster sizes: 1=6, 2=8, 3=5, 4=6
split(names(clust$memberships), clust$memberships)
#> $`1`
#> [1] "11" "12" "13" "14" "15" "24"
#> 
#> $`2`
#> [1] "16" "17" "18" "19" "20" "21" "22" "23"
#> 
#> $`3`
#> [1] "6"  "7"  "8"  "9"  "10"
#> 
#> $`4`
#> [1] "1"  "2"  "3"  "4"  "5"  "25"

6. Use the result

Join memberships back onto the event log, inspect a cluster’s attribute profile, and compute validity indices.

augmented <- augment_log(clust, illustrative_log)
#> Joining on case-id column 'case_id'.
head(augmented)
#>   case_id activity           timestamp status satisfaction .cluster
#> 1       1        B 2021-01-01 00:01:00   GOLD         High        4
#> 2       1        E 2021-01-01 00:02:00   GOLD         High        4
#> 3       2        B 2021-01-01 00:01:00   GOLD         High        4
#> 4       2        E 2021-01-01 00:02:00   GOLD         High        4
#> 5       3        B 2021-01-01 00:01:00   GOLD         High        4
#> 6       3        E 2021-01-01 00:02:00   GOLD         High        4

plot(clust, type = "profile", attribute = "status")


if (requireNamespace("clValid", quietly = TRUE)) {
  validate_clusters(clust)
}
#> connectivity         dunn 
#>    20.276190     0.489198

Where next

Reference

Delias, P., Doumpos, M., Grigoroudis, E. and Matsatsinis, N. (2023). Improving the non-compensatory trace-clustering decision process. International Transactions in Operational Research, 30(3), 1387–1406. doi:10.1111/itor.13062 ```