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 dimensionPlotting
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()
|
