--- title: "Getting started with VaSSTra" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Getting started with VaSSTra} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 5 ) options(digits = 2) ``` VaSSTra turns repeated multivariate measurements into states, state sequences, and groups of similar trajectories. The whole analysis is one call, and every part of it can also be run as an explicit step. This guide uses the built-in `engagement` data: 142 students measured on eight indicators at eight course positions, from the [VaSSTra chapter](https://lamethods.org/book1/chapters/ch11-vasstra/ch11-vasstra.html) of *Learning Analytics Methods and Tutorials*. It ships analysis-ready with attached role metadata, so no column plumbing is needed. ```{r data} library(VaSSTra) data("engagement", package = "VaSSTra") ``` ## Using your own data `vasstra()` needs three roles: the **subject** (`id`), the **time** column (`time`), and the numeric **indicators** (`variables`). The `engagement` data carry these as attached metadata, so they are found automatically; your own data frame will not, so you name the subject and time columns. On `engagement` this is redundant, but it is exactly the call you would write for data with different column names: ```{r roles} fit <- vasstra( engagement, id = "user_id", # one row per subject and time point time = "sequence_position", # the ordering column state_labels = c("Disengaged", "Average", "Active") ) ``` `variables` was left out on purpose: the numeric columns that are not the `id` or `time` become the indicators (columns ending in `"_z"` are preferred when present, so a mix of raw and standardized columns picks the standardized set, as it does here). Name them to fit a specific subset. Here we keep only the three activity-volume indicators: ```{r variables} fit <- vasstra( engagement, id = "user_id", time = "sequence_position", variables = c("session_count_z", "total_duration_z", "active_days_z"), state_labels = c("Disengaged", "Average", "Active") ) ``` Columns that follow a common convention are detected even without `id` and `time` (`id`: `id`, `student`, `subject`, `actor`, …; `time`: `time`, `week`, `wave`, `session`, …); anything else raises a clear error asking you to supply that role. Two more notes: pass `time_levels` when `time` is a character or unordered factor so the chronology is explicit, and use `state` instead of `variables` when the data already carry a categorical state column (the analysis then starts at `step2_sequences()`). ## One call `vasstra()` detects the subject, time, and indicator roles from the attached role metadata (or common column names), compares two through six states and trajectories, and fits the recommended counts. Every automated decision is reported as it is made and recorded inside the fit. Here three state labels fix three states; omit them and the state count is selected too. ```{r one-call} fit <- vasstra(engagement, state_labels = c("Disengaged", "Average", "Active")) fit ``` Name any decision to take it over; automation only fills what you omit. Labels are the shortest way to fix a count (three labels mean three states), and a candidate vector such as `n_states = 2:4` compares exactly those counts and fits the recommended one. Two more arguments control how states display everywhere: `state_order` fixes the order states appear in across every plot, and `state_colors` sets a state palette that is stored on the fit and reused by every plot. ```{r explicit-call, eval = FALSE} fit <- vasstra( engagement, state_labels = c("Disengaged", "Average", "Active"), trajectory_labels = c("Mostly active", "Mostly average", "Mostly disengaged"), dissimilarity = "lcs", cluster_method = "ward.D2", positive_states = "Active", negative_states = "Disengaged" ) ``` ## Evaluate the clustering `evaluate()` compares the fitted numbers of states and trajectories against the alternative counts and reports per-cluster quality. Printing shows both tables; plotting draws the selection curve, per-cluster silhouette widths, and group sizes. ```{r evaluate} evaluation <- evaluate(fit) ``` ```{r evaluate-plot, fig.height = 5, fig.width=8.15} plot(evaluation) ``` The same verb works on a single step, with any candidate range: ```{r evaluate-states} state_evaluation <- evaluate(fit$states, n_states = 2:5) summary(state_evaluation) ``` ## Tidy fit indices `fit_indices()` reports the fit statistics of the selected clustering as one tidy row, or one row per compared candidate with `compare = TRUE`. LPA models report the complete information-criterion family together with entropy and the extreme average posterior class probabilities; hard-clustering methods report their own objectives instead, and columns that do not apply are dropped. ```{r fit-indices} fit_indices(fit) fit_indices(fit, compare = TRUE) fit_indices(fit, step = "trajectories", compare = TRUE) ``` ## Name groups after inspecting them `set_labels()` renames fitted states or trajectories in place (nothing is refitted, no value changes), and the new names flow through every table, sequence, and plot. Rename everything with a full vector, or only some groups by name. ```{r set-labels} fit <- set_labels( fit, trajectories = c("Mostly active", "Mostly average", "Mostly disengaged") ) summary(fit) ``` ## The same analysis in four explicit steps Each step accepts the result of the previous step and also runs alone with the same automated defaults. Use the steps when intermediate results are needed, or when states come from another method. ```{r steps} states <- step1_states(engagement, labels = c("Disengaged", "Average", "Active")) sequences <- step2_sequences(states) trajectories <- step3_trajectories(sequences, n_trajectories = 3) description <- step4_describe( trajectories, positive_states = "Active", negative_states = "Disengaged" ) summary(description) ``` Existing states from LPA, LCA, or any other source enter at step 2: pass a data frame whose state column is detected automatically (or named with the `state` argument). ## State plots State results support profile, bar, heatmap, and size plots without adding a plotting dependency, plus a combined overview. ```{r state-plots, fig.height = 6} plot(states, type = "all") ``` ```{r state-profile} plot(states, type = "profile") ``` ## Sequence and trajectory plots Every plot containing state sequences is rendered by Nestimate: ```{r sequence-plot} plot(trajectories, type = "index") plot(trajectories, type = "distribution") ``` ## Compare candidates in full detail When automation should be replaced by an explicit comparison, `state_choices()` and `trajectory_choices()` fit a grid across methods and counts and return one tidy row per candidate. Nothing is selected silently; pass the inspected `candidate_id` to fit. ```{r state-choices} state_options <- state_choices( engagement, n_states = 2:4, method = c("kmeans", "pam", "ward.D2") ) state_options plot(state_options, metric = "silhouette") chosen_states <- fit_state_choice( state_options, n_states = 3, method = "kmeans", labels = c("Disengaged", "Average", "Active") ) ``` `fit_state_choice(state_options)` with no selection fits the recommended candidate; `candidate_id` remains available for exact row selection. Average silhouette is the common diagnostic across all methods; larger values indicate better-separated groups. Recommendations identify the best eligible candidate separately for each method rather than declaring one algorithm universally best. LPA candidates are recommended by conventional BIC, with AIC, silhouette, or native mclust ICL available through `lpa_criterion`. ```{r trajectory-choices} trajectory_options <- trajectory_choices( step2_sequences(chosen_states), n_trajectories = 2:4, dissimilarity = c("hamming", "lcs"), method = c("pam", "ward.D2") ) trajectory_options plot(trajectory_options, metric = "silhouette") ``` Recommendations are made within each distance-method pair because mean within-trajectory distances are only comparable within one dissimilarity. States default to `mclust` latent profiles (`"EEI"`, tidyLPA model 1); base R supplies k-means and hierarchical clustering, `cluster` supplies PAM, CLARA, FANNY, and DIANA, and `Nestimate` supplies sequence distances, trajectory clustering, and plots. No `TraMineR` plotting path or dependency is required. See `vignette("clustering-algorithms")` for the full list of state and trajectory methods.