--- title: "Adding temporal structure modeling to stream clustering" author: "Michael Hahsler" bibliography: rEMM.bib output: rmarkdown::html_vignette: toc: true vignette: > %\VignetteIndexEntry{Adding temporal structure modeling to stream clustering} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 5 ) set.seed(1234) ``` ## Introduction The **stream** package provides data stream clustering algorithms through its `DSC` interface [@Hahsler:2017]. A clusterer groups similar observations, but cluster membership alone does not describe the order in which clusters occur. `TRACDS` in **rEMM** adds a temporal model to a sequence of cluster assignments [@Hahsler+Dunham:2011]. Each cluster label becomes a state, and consecutive labels update the counts of transitions between states. These counts can be used to estimate transition probabilities and predict future states. This vignette demonstrates how to connect data stream clustering algorithms in package **stream** to `TRACDS()` by passing cluster assignments from each batch to the temporal model. ```{r packages} library(stream) library(rEMM) ``` ## Prepare the stream and models We use the simulated, ordered observations supplied with **rEMM**. `stream::DSD_Memory()` exposes the training data as a stream. Keep observations in their original order. ```{r data} data("EMMsim") dsd <- DSD_Memory(EMMsim_train) dsd ``` ```{r model} dsc <- DSC_DBSTREAM(r = 0.1, lambda = 0) tr <- TRACDS() ``` DBSTREAM forms micro-clusters using radius `r`. We disable fading here with `lambda = 0` to keep the example simple. Fading in the clusterer and the temporal model are configured separately; `TRACDS()` also defaults to no fading. ## Update both models For each batch, first update the clusterer and request its assignments. Then pass the assigned labels to `TRACDS()` in observation order. ```{r update-models} batch_size <- 50L for (i in seq_len(ceiling(nrow(EMMsim_train) / batch_size))) { points <- get_points(dsd, n = batch_size) assignment <- update(dsc, points, return = "assignment") update(tr, as.character(assignment$.mc_id)) } head(assignment) ``` DBSTREAM's assignment data frame contains two kinds of cluster labels: * `.class` is the current micro-cluster index. These indices can change when micro-clusters are removed. * `.mc_id` is the permanent micro-cluster identifier. Use this column as the state label in the temporal model. See the [DBSTREAM documentation](https://michael.hahsler.net/stream/reference/DSC_DBSTREAM.html) for details of the assignment format. Converting identifiers to character matches the label interface of `TRACDS()`. Successive calls to `update(tr, ...)` continue the same sequence. The last assigned state in one batch connects to the first assigned state in the next batch, unless an unassigned observation breaks the sequence. DBSTREAM can return `NA` for observations assigned to weak micro-clusters. Keep these values in the label sequence: `TRACDS()` treats an `NA` as a sequence boundary. Removing missing labels would incorrectly connect observations on either side of the gap. ## Inspect the temporal model The model stores states for the labels it has observed and counts transitions between consecutive assigned observations. `ntransitions()` counts distinct observed transitions, including transitions back to the same state. ```{r model-summary} c(states = nstates(tr), transitions = ntransitions(tr)) current_state(tr) edges <- transitions(tr) head(cbind(as.data.frame(edges), counts = transition(tr, edges, type = "counts", prior = FALSE))) ``` `transition_matrix()` returns a matrix with source states in rows and destination states in columns. By default, `prior = TRUE` adds one to each transition count before calculating probabilities. Use `prior = FALSE` to inspect the observed counts directly. ```{r transition-counts} counts <- transition_matrix(tr, type = "counts", prior = FALSE) sum(counts) ``` The total can be smaller than the number of observations minus one because missing assignments break the sequence. ```{r temporal-graph} plot(tr, vertex.size = 16, vertex.label.cex = 0.8, edge.arrow.size = 0.3, main = "Transitions between DBSTREAM micro-clusters") ``` Vertices are permanent micro-cluster identifiers, and directed edges represent observed transitions. This plot shows the temporal graph; the clusterer retains the micro-cluster centers and other spatial information. ## Predict the next state `predict()` can return a probability distribution over states, starting from the last assigned state. Here we show the six most likely states one step ahead. The default prior gives unobserved transitions a nonzero probability. ```{r predict-state} next_state <- predict(tr, n = 1, probabilities = TRUE) head(sort(next_state, decreasing = TRUE)) ``` The names in this distribution are DBSTREAM micro-cluster identifiers. They can be interpreted alongside the cluster information stored in `dsc`. ## Start an independent sequence Call `reset()` before processing a new, independent sequence. It clears the current state while retaining the learned states and transition counts, so no transition is added from the end of the previous sequence to the new one. ```{r reset-sequence} reset(tr) current_state(tr) ``` Continue with the same update loop when observations from the new sequence become available. ## Use other stream clusterers For other `DSC` implementation in **stream**, check whether `update(..., return = "assignment")` returns assignments and which columns it provides. Clusterers that do not return assignments during updates may support `predict(dsc, points, type = "micro")` after updating. Predictions can be also used to update the temporal model, however note that predictions assign each point to the clusters in the updated model, which can differ from the assignments made as each point arrived for clustering. Before passing labels to `TRACDS()`, ensure that a label continues to identify the same cluster across updates. Current indices such as `.class` need a mapping to persistent identifiers if clusters can be removed or reordered. Macro-cluster labels also need care when reclustering changes their labels. `TRACDS()` does not automatically synchronize cluster deletions, merges, or relabeling with the `DSC`. For a clusterer that changes cluster identities, maintain a consistent label mapping or rebuild the temporal model for the new clustering. The update loop above explicitly coordinates the two models. For the package's integrated clustering and temporal model, see `DSC_EMM()` and the [getting started vignette](rEMM.html). ## References