Skip to contents

pipeline status coverage report

What is ONA?

Ordered Network Analysis (ONA) extends Epistemic Network Analysis to model directed connections — capturing not just which codes co-occur, but the temporal order in which they appear within a conversational window. Each connection from code A to code B represents A appearing in context (prior rows) when B is observed, making the network asymmetric and directional.

ONA builds on tma for accumulation, rENA for the model (which carries the libqe C++ math), and qeviz for plotting.


Installation

Development version

install.packages("ona", repos = c("https://cran.qe-libs.org", "https://cran.rstudio.org"))

Quick Start

The accumulation and the step-by-step verbs (accumulate(), sphere_norm(), center(), rotate(), project(), optimize()) are rENA’s; model() is ona’s, applying the ONA defaults on top of rENA::model(). Attach rENA before ona, since both export a model() and ona’s must be the one found (or call ona::model()).

library(rENA)
library(ona)

codes        <- c("Data", "Technical.Constraints", "Performance.Parameters",
                  "Client.and.Consultant.Requests", "Design.Reasoning", "Collaboration")
units        <- c("UserName", "Condition", "GroupName")
conversation <- c("Condition", "GroupName")

# Simple form
ona_model <- RS.data |>
  accumulate(units, codes, conversation, default_window = 4, ordered = TRUE) |>
  model()

# Granular form (equivalent)
ona_model <- RS.data |>
  accumulate(units, codes, conversation, default_window = 4, ordered = TRUE) |>
  sphere_norm() |>
  center(exclude_zero_networks = TRUE) |>
  rotate() |>
  project() |>
  optimize()

Advanced: HOO-rule accumulation with tma

Use tma when you need conversation rules that go beyond simple windowing — e.g. units whose window resets based on group membership or other conditions.

library(ona)
library(tma)
library(data.table)

rs_dat <- as.data.table(tma::RS.data)

codes    <- c("Data", "Technical.Constraints", "Performance.Parameters",
              "Client.and.Consultant.Requests", "Design.Reasoning", "Collaboration")
units.by <- c("Condition", "UserName")

hoo_rules <- tma::conversation_rules(
  (Condition %in% UNIT$Condition & GroupName %in% UNIT$GroupName & UserName %in% UNIT$UserName)
)

ona_model <- tma::contexts(rs_dat, units_by = units.by, hoo_rules = hoo_rules) |>
  tma::accumulate_contexts(codes = codes, return.ena.set = FALSE, norm.by = NULL) |>
  model()

Accessing Results

ona_model$points                  # unit positions in ONA space
ona_model$line.weights            # normalised adjacency vectors (units × connections)
ona_model$connection.counts       # raw directed co-occurrence counts
ona_model$rotation$nodes          # code node positions
ona_model$model$centroids         # unit centroids
ona_model$model$variance          # variance explained per dimension

Plotting

Plots are drawn by qeviz: plot() on a model returns a qe_plot, and edges(), nodes() and units() add the layers. The result prints in the viewer or browser and exports with qeviz::qe_export_html().

# Mean network of every unit, its code nodes, and the unit points with their mean
plot(ona_model) |>
  edges() |>
  nodes() |>
  units()

# One condition: its points and mean in blue, and its mean network
first_points  <- ona_model$points$Condition$FirstGame
first_weights <- ona_model$line.weights$Condition$FirstGame

plot(ona_model) |>
  units(points = first_points, points_color = "blue") |>
  edges(weights = first_weights, edge_color = "blue")

# Other dimensions
plot(ona_model, dimensions = c("SVD2", "SVD3")) |> edges() |> units()

# qeviz's own chain functions work on the same plot
plot(ona_model) |>
  qeviz::qe_edges("FirstGame", compare = "SecondGame") |>
  qeviz::qe_group()

# Figures comparable with ones made before plotting moved to qeviz: axes fixed
# at +/-3 with the axis cross, edge widths and node sizes in data units
plot(ona_model, legacy = TRUE) |> edges() |> nodes() |> units()

Rotation Methods

Rotation Function
SVD (default) model(rotate.using = "svd")
Means model(rotate.using = "mean", rotation.params = groups)
Reuse a fitted rotation model(rotation.set = fitted_model)
# Means rotation: the first dimension runs through the means of two groups,
# given as a pair of logical vectors over the units (or of unit names)
acc <- RS.data |>
  accumulate(units, codes, conversation, default_window = 4, ordered = TRUE)
first  <- acc$meta.data$Condition == "FirstGame"
groups <- list(list(first, !first))

ona_model <- model(acc, rotate.using = "mean", rotation.params = groups)

# Place new data in a fitted model's space: same rotation and node positions
new_model <- new_acc |> model(rotation.set = ona_model)

Key Differences from rENA

rENA (unordered) ONA
Network type Undirected (symmetric) Directed (asymmetric)
Connection matrix Upper triangle, n(n-1)/2 columns Full square, n² columns
Accumulation rENA::accumulate() rENA::accumulate(ordered = TRUE) or tma::accumulate_contexts()
Node positions node_positions_by_key (undirected key) directed_node_positions (a self-connection counts twice)
Empty networks At the origin, outside the fit (default) At the origin, outside the fit (always)
Plotting rENA::ena.plot*() or qeviz qeviz through plot(), edges(), nodes(), units()