Skip to contents

This is a visual gallery of every VaSSTra plot, run on the longitudinal engagement data from Chapter 11 of Learning Analytics Methods and Tutorials. It follows the method’s own order: the state views, the sequence views, the trajectory views, and the combined per-trajectory grid (all base graphics, no ggplot2 dependency), then the evaluate() selection panels, and last the cograph-rendered flow and transition networks. Every view shares one state palette and one state order, both of which can be pinned on the fit with state_colors and state_order.

It focuses only on the plots. For the workflow around them (running vasstra(), comparing counts, reading fit indices, relabeling groups, and extracting tidy tables), see vignette("get-started") and vignette("vasstra-tutorial"), and vignette("clustering-algorithms") for the estimation methods.

The fit

Every figure below is drawn from one fit, and to keep the gallery about the plots rather than the tuning, that fit is the fully automatic default: vasstra(engagement) detects the roles, selects the number of states and trajectories, and fits them. It settles on five states and three trajectories here, drawn in their discovery order on the default palette. Naming, reordering, and recolouring are shown in Palette and state order below; they change how a plot reads, not which plot each call draws.

library(VaSSTra)
data("engagement", package = "VaSSTra")

fit <- vasstra(engagement)
#> Selected n_states = 5 (lpa, bic = 19008.964); see `diagnostics$selection`.
#> Registered S3 method overwritten by 'Nestimate':
#>   method     from   
#>   print.mcml cograph
#> Selected n_trajectories = 3 (hamming + pam, silhouette = 0.222); see `diagnostics$selection`.

State overview: profile + bars + heatmap + sizes

type = "all" draws the four state views together on the shared palette; the profile heatmap reads with the first state as its top row, matching the bars, sizes, and legends.

plot(fit, which = "states", type = "all")

Each view is also available alone:

plot(fit, which = "states", type = "profile")

plot(fit, which = "states", type = "bars")

plot(fit, which = "states", type = "heatmap")

plot(fit, which = "states", type = "sizes")

Indicators by state

type = "variables" takes the profile apart along time. It draws one panel per time point and, within each, a grouped boxplot of every clustering indicator split by state, so the aggregated profile above becomes a question you can check at each moment: does each indicator keep separating the states throughout, or only on average? The indicators stay on the scale the states were clustered on, so the shared y-axis is comparable across panels, and the call returns the tidy time-by-variable-by-state medians behind the boxes.

plot(fit, which = "states", type = "variables")

by_time = FALSE pools the time points into a single panel, the boxplot counterpart of the profile plot: one box per indicator and state, over all observations.

plot(fit, which = "states", type = "variables", by_time = FALSE)

state_descriptives() is the numeric companion: one row per state and indicator with the count, mean, standard deviation, median, and range. scale = "original" reports the raw indicators instead of the clustered (standardized) ones.

head(state_descriptives(fit))
#>     state                 variable   n mean   sd median  min   max
#> 1 State 1      course_view_count_z 155 -1.3 0.57   -1.3 -2.8  0.57
#> 2 State 1    forum_consume_count_z 155 -1.4 0.58   -1.4 -2.7  0.29
#> 3 State 1 forum_contribute_count_z 155 -1.1 0.76   -1.2 -2.8  1.52
#> 4 State 1     lecture_view_count_z 155 -1.0 0.79   -1.1 -2.5  1.58
#> 5 State 1 course_view_regularity_z 155 -1.4 0.58   -1.3 -3.7 -0.33
#> 6 State 1          session_count_z 155 -1.5 0.43   -1.5 -2.9 -0.75

by_time = TRUE matches the faceted plot: it adds a time column and reports the same statistics per state, indicator, and time point.

head(state_descriptives(fit, by_time = TRUE))
#>     state            variable time  n mean   sd median  min    max
#> 1 State 1 course_view_count_z    1 25 -1.3 0.58   -1.3 -2.4 -0.057
#> 2 State 1 course_view_count_z    2 16 -1.4 0.56   -1.5 -2.3  0.022
#> 3 State 1 course_view_count_z    3 23 -1.4 0.60   -1.3 -2.6 -0.504
#> 4 State 1 course_view_count_z    4 18 -1.2 0.75   -1.3 -2.3  0.568
#> 5 State 1 course_view_count_z    5 16 -1.2 0.57   -1.2 -2.2 -0.420
#> 6 State 1 course_view_count_z    6 18 -1.2 0.49   -1.2 -2.0  0.087

Palette and state order

Two arguments control how states display everywhere. state_colors pins a palette on the fit (a named vector is matched by state and survives reordering and set_labels()), and state_order fixes the order the states read in every plot (stacking, legends, transition and flow nodes), independent of the order the clusters were discovered in. Both are stored on the fit and reused by every plot, so the styling is set once; a colors argument on an individual plot() call overrides them for that call alone.

fit_styled <- vasstra(
  engagement,
  state_labels = c("Disengaged", "Average", "Active"),
  state_order  = c("Active", "Average", "Disengaged"),
  state_colors = c(Disengaged = "#D55E00", Average = "#0072B2", Active = "#009E73")
)
#> Using n_states = 3 to match the supplied labels.
#> Selected n_trajectories = 3 (hamming + pam, silhouette = 0.435); see `diagnostics$selection`.
plot(fit_styled, type = "distribution")

All sequences: heatmap (default), distribution, index

Every plot containing state sequences is rendered by Nestimate::sequence_plot(). The heatmap is the default view for the complete sequence set.

plot(fit, which = "sequences")

plot(fit, which = "sequences", type = "distribution")

plot(fit, which = "sequences", type = "index")

Trajectories: distribution, index, heatmap

plot(fit, type = "distribution")

plot(fit, type = "index")

plot(fit, type = "heatmap")

The combined per-trajectory grid

Passing several views in type composes them into one figure with one row per trajectory and one column per view, in the order requested. This is the standard per-cluster VaSSTra display, drawn in one call with a shared ordered legend. The transition column needs the suggested cograph package.

plot(fit, type = c("index", "distribution", "transition"))

Evaluation panels

plot(evaluate(fit)) draws the selection curve, per-cluster silhouette widths, and group sizes for both clustering steps. The state and trajectory rows use distinct palettes, so the two groupings are not read as corresponding.

Choice comparisons across methods

The explicit comparison draws a metric-aware plot, on the shared palette with light gridlines.

state_options <- state_choices(
  engagement,
  n_states = 2:5,
  method = c("kmeans", "pam", "ward.D2", "lpa"),
  lpa_model = "EEI",
  seed = 22294
)
plot(state_options, metric = "silhouette")

plot(state_options, metric = "bic", groups = "lpa/EEI")

Flow plots (cograph)

flow_plot() renders state movement between consecutive time points through cograph, on the same palette and state order as every other plot, and returns a ggplot object rather than drawing base graphics. Aggregated bands and individual lines are the two views:

flow_plot(fit, type = "individual")

The vignette("flow-plots") article covers the rest: colouring by destination or by first/last state, restricting to one trajectory, line bundling, thresholding negligible flows, and tuning a figure for a manuscript.

Transition network (Nestimate builds, cograph draws)

Node size is a transition-network centrality from Nestimate::net_centrality(); the network comes from Nestimate::build_tna(). Drawn by cograph::splot(); returns the tidy node table invisibly. The paired form places the sequences beside the network on one shared palette.

transition_plot(fit, sequences = TRUE)

The centralities are also available as a tidy table:

transition_centrality(fit)
#>     state InStrength OutStrength
#> 1 State 1       0.20        0.46
#> 2 State 2       0.86        0.45
#> 3 State 3       0.56        0.56
#> 4 State 4       0.53        0.54
#> 5 State 5       0.26        0.40

See vignette("flow-plots") for the centrality-driven node sizing (size, weights, loops), the per-trajectory networks, and the other paired views.