--- title: "Interoperability: wide data, long logs, tna, and Nestimate" author: - "Mohammed Saqr" - "Sonsoles López-Pernas" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Interoperability: wide data, long logs, tna, and Nestimate} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", message = FALSE, warning = FALSE, dpi = 150, fig.width = 7, fig.height = 5.6, out.width = "100%", fig.align = "center") set.seed(2026) library(lagdynamics) old_options <- options(digits = 3) has_tna <- requireNamespace("tna", quietly = TRUE) has_Nestimate <- requireNamespace("Nestimate", quietly = TRUE) has_cograph <- requireNamespace("cograph", quietly = TRUE) ``` `lagdynamics` is designed to sit inside the same event-sequence workflow as Transition Network Analysis (TNA), `Nestimate`, `cograph`, and other sequence tools. The important interoperability promise is simple: - the same `lsa()` front door accepts wide sequence data, raw long event logs, `tna` sequence objects, and `Nestimate` prepared objects; - long logs use the same column grammar as TNA-style workflows: `actor`, `action`, `time`, `order`, and `session`; - fitted models expose transition probabilities, initial probabilities, tidy transition tables, and a `cograph_network` object for rendering. # Same grammar as TNA In a TNA workflow, a raw event log is usually sequenced by naming the actor, the event/action code, and either a time or order column. `lsa()` uses the same grammar directly. ```r prepare_data(log, actor = "Actor", action = "Action", time = "Time") lsa(log, actor = "Actor", action = "Action", time = "Time") ``` The column names are passed as strings. `action` is the only mandatory long-format column. If neither `actor` nor `session` is supplied, the log is treated as one long sequence. If `session` is supplied, each session is a sequence. If both `actor` and `session` are supplied, each actor-session pair is a sequence. # Wide sequence data Wide data is the simplest input: rows are sequences and columns are ordered time points. This is the form used by `engagement`. ```{r wide} head(engagement) fit_wide <- lsa(engagement) fit_wide ``` The result is read through the same verbs used everywhere else. ```{r wide-read} transitions(fit_wide, significant = TRUE) nodes(fit_wide) initial(fit_wide) ``` # Long event logs Long logs have one row per event. The bundled `group_regulation_long` data shows the TNA-style grammar directly: actor, action, time, and a grouping column. ```{r long-data} head(group_regulation_long) ``` Fit a single model from the raw log by naming the columns. ```{r long-fit} fit_long <- lsa(group_regulation_long, actor = "Actor", action = "Action", time = "Time") fit_long ``` If the log already has session identifiers, use `session`. In the `Nestimate`-derived `ai_long` data, `project` and `session_id` define the sequence boundary and `order_in_session` defines event order. ```{r long-session} head(ai_long) fit_session <- lsa(ai_long, actor = "project", session = "session_id", action = "code", order = "order_in_session") fit_session ``` Grouping uses the same call. The grouping column must be fixed within each recovered sequence. ```{r grouped-long} gfit <- lsa(group_regulation_long, actor = "Actor", action = "Action", time = "Time", group = "Achiever") gfit transitions(gfit, significant = TRUE) |> head(6) ``` # tna objects `lsa()` accepts a real `tna` model when that model carries its source sequences. The example below starts from the same included wide sequence data, fits a TNA model with `tna`, and then hands that fitted object to `lsa()`. ```{r tna-object, eval = has_tna} tna_fit <- tna::tna(engagement) tna_fit fit_from_tna <- lsa(tna_fit) fit_from_tna ``` Fitting the `tna` object and fitting the original wide data give the same LSA transition table. ```{r tna-equivalence, eval = has_tna} isTRUE(all.equal(transitions(fit_from_tna), transitions(fit_wide), check.attributes = FALSE)) ``` # Nestimate objects `Nestimate::build_network()` uses the same long-log grammar. The object it returns carries the prepared sequence data, so `lsa()` can read it directly. ```{r nestimate-object, eval = has_Nestimate} nestimate_fit <- Nestimate::build_network( ai_long, method = "tna", actor = "project", session = "session_id", action = "code", order = "order_in_session" ) nestimate_fit fit_nestimate <- lsa(nestimate_fit) fit_nestimate ``` The same raw log can also be fitted directly with the long-format grammar. The `Nestimate` object and the direct `lsa()` call recover the same transition table. ```{r nestimate-equivalence, eval = has_Nestimate} fit_ai_direct <- lsa(ai_long, actor = "project", session = "session_id", action = "code", order = "order_in_session") isTRUE(all.equal(transitions(fit_nestimate), transitions(fit_ai_direct), check.attributes = FALSE)) ``` # Back to tna Use `lsa_to_tna()` when the model is estimated with `lagdynamics` and then handed to `tna` for TNA-specific tooling. ```{r lsa-to-tna, eval = has_tna} tn <- lsa_to_tna(fit_wide, weights = "prob") tn tna::centralities(tn) ``` The same quantities are also exposed without conversion: the row-stochastic transition matrix and the initial-state distribution. ```{r probabilities} transition_probabilities(fit_long) initial(fit_long) ``` It also inherits `cograph_network`, so cograph-compatible renderers can read the nodes and edges directly. The probability-weighted network uses the TNA interpretation: edge weights are conditional transition probabilities. ```{r tna-plot, eval = has_cograph} plot(fit_long, type = "network", weights = "tna") ``` Use the residual network when the claim is lag-sequential: which transitions occur more or less often than expected under independence. Use the probability/TNA network when the claim is descriptive: where the process tends to go next. ```{r cleanup, include = FALSE} options(old_options) ```