Interoperability with tna and Nestimate

lagseq belongs to the Dynalytics ecosystem alongside tna (transition network analysis), Nestimate (network estimation), cograph (rendering), and TraMineR. The ecosystem separates estimation from rendering and analysis, and the packages share a common data grammar and object schemas. In practice this means two things: lsa() can read sequences straight out of those packages’ objects, and a fitted lsa model converts back into them in one call.

Into lagseq: fit from another package’s object

lsa() recognises sequence-bearing objects from tna (tna, tna_data, tna_seq_data), Nestimate, and TraMineR (stslist). It pulls the sequences out and fits, so no manual extraction is needed.

A tna object

tna and lsa() share the same long-format grammar (actor / action / time / order / session). Prepare the data with tna, build a model, and hand either object to lsa().

log <- tna::group_regulation_long
prepared  <- tna::prepare_data(log, actor = "Actor",
                               action = "Action", time = "Time")
tna_model <- tna::tna(prepared)

lsa(prepared)     # fit from the prepared sequence object
#> Lag Sequential Analysis  —  classical  (lag 1, directed)
#>   9 states | 25533 transitions | 27533 events | 2000 sequences
#>   states: adapt, cohesion, consensus, coregulate, discuss, emotion, monitor, plan, synthesis
#>   independence: G² = 13203.8, df = 64, p <2e-16
#> 
#>   Significant transitions (p < 0.05): 72 of 81
#>   strongest over-represented (of 23):
#>     emotion -> cohesion      z =  +58.2  ***
#>     discuss -> synthesis     z =  +48.0  ***
#>     synthesis -> adapt       z =  +38.8  ***
#>     consensus -> coregulate  z =  +35.3  ***
#>     consensus -> plan        z =  +32.6  ***
#>     ... and 18 more
#> 
#>   Initial states:
#>     consensus  0.214  ████████████████████████
#>     plan       0.204  ███████████████████████
#>     discuss    0.175  ████████████████████
#>     emotion    0.151  █████████████████
#>     monitor    0.144  ████████████████
#>     cohesion   0.060  ███████
#>     synthesis  0.019  ██
#>     coregulate 0.019  ██
#>     adapt      0.011  █
lsa(tna_model)    # or from a built tna model -- same sequences, same fit
#> Lag Sequential Analysis  —  classical  (lag 1, directed)
#>   9 states | 25533 transitions | 27533 events | 2000 sequences
#>   states: adapt, cohesion, consensus, coregulate, discuss, emotion, monitor, plan, synthesis
#>   independence: G² = 13203.8, df = 64, p <2e-16
#> 
#>   Significant transitions (p < 0.05): 72 of 81
#>   strongest over-represented (of 23):
#>     emotion -> cohesion      z =  +58.2  ***
#>     discuss -> synthesis     z =  +48.0  ***
#>     synthesis -> adapt       z =  +38.8  ***
#>     consensus -> coregulate  z =  +35.3  ***
#>     consensus -> plan        z =  +32.6  ***
#>     ... and 18 more
#> 
#>   Initial states:
#>     consensus  0.214  ████████████████████████
#>     plan       0.204  ███████████████████████
#>     discuss    0.175  ████████████████████
#>     emotion    0.151  █████████████████
#>     monitor    0.144  ████████████████
#>     cohesion   0.060  ███████
#>     synthesis  0.019  ██
#>     coregulate 0.019  ██
#>     adapt      0.011  █

A TraMineR state sequence object

A TraMineR stslist (the standard state-sequence object) is read directly.

sts <- TraMineR::seqdef(engagement)
lsa(sts)
#> Lag Sequential Analysis  —  classical  (lag 1, directed)
#>   3 states | 1734 transitions | 1870 events | 136 sequences
#>   states: Active, Average, Disengaged
#>   independence: G² = 618.3, df = 4, p <2e-16
#> 
#>   Significant transitions (p < 0.05): 7 of 9
#>   strongest over-represented (of 3):
#>     Active -> Active          z =  +21.7  ***
#>     Disengaged -> Disengaged  z =  +15.4  ***
#>     Average -> Average        z =  +12.5  ***
#> 
#>   Initial states:
#>     Active     0.382  ████████████████████████
#>     Average    0.368  ███████████████████████
#>     Disengaged 0.250  ████████████████

Out of lagseq: hand a fit to another toolkit

A fitted model is a directed weighted network, so it converts to the native object of either network package. Estimation stays in lagseq; the downstream analysis runs in the toolkit built for it.

fit <- lsa(engagement)

To a tna network

lsa_to_tna() returns a tna object. Choose the edge weight with weights ("prob" for a transition-probability network, "count" for a frequency one). From there, tna’s analysis verbs apply.

tn <- lsa_to_tna(fit, weights = "prob")
tna::centralities(tn) |> head()
#> # A tibble: 3 × 10
#>   state    OutStrength InStrength ClosenessIn ClosenessOut Closeness Betweenness
#>   <fct>          <dbl>      <dbl>       <dbl>        <dbl>     <dbl>       <dbl>
#> 1 Active         0.302      0.324      0.0811       0.0779     0.100           0
#> 2 Average        0.390      0.664      0.160        0.0973     0.160           2
#> 3 Disenga…       0.517      0.221      0.0691       0.101      0.114           0
#> # ℹ 3 more variables: BetweennessRSP <dbl>, Diffusion <dbl>, Clustering <dbl>

Shared rendering: the transition network is a tna model

Because estimation and rendering are separate layers, the transition network view of an lsa fit is a tna model: lsa_to_tna() builds it on the fly and tna’s own plot method renders it (coloured nodes, initial-probability arcs, weighted directed edges).

plot(fit, type = "network", weights = "prob")

The residual network, by contrast, is rendered by cograph and keeps the lag-sequential meaning (each edge a tested departure from independence). One fit, two rendering layers, one shared object model.

One grammar across the ecosystem

The practical payoff is a single mental model. The arguments that sequence a raw log are the same in lsa() and in tna::prepare_data():

tna::prepare_data(log, actor =, action =, time =, order =, session =)
lsa(log,               actor =, action =, time =, order =, session =)

So an analysis can move between transition-network tooling and lag-sequential testing without reshaping the data, and an object built in one package is valid input to the other.