pathways() example pools three named sources
instead of all fourteen, and the animate() examples draw fewer frames,
so every example runs in well under a second.thought_chains is documented as based on the Trees of Thought study
(Saqr, López-Pernas and Törmänen, 2026) with about 20 percent of the
records removed, dates and rates changed and anonymised, and the data
augmented by simulation.mooc_people cites its source chapter directly; a stale reference to a
removed article is gone from the mooc_posts example.First CRAN release. CRAN preparation: the maintainer is recorded as copyright holder, the README gives the CRAN installation line, and the animation article's GIFs carry alternative text.
durations() is up to 37 times faster on large logs (about 20,000 spells:
26 s to under 1 s). A spell between two vertices without declared activity
is now built as its own fragment in one vectorised step, and each pair's
fragments are indexed once rather than matched against every pair. Results
are identical to 0.4.13.
The time-respecting path search behind paths(), reachability(),
path_centrality() and pathways() is much faster. A search state's
candidate entries are computed for all its ties at once, the ties leaving
each vertex are indexed once, and state keys are built in one call. Forward
searches also drop dominated states: a vertex reached no earlier than an
existing state with fewer hops. Such a state can never be, or lead to, a
shortest-foremost path, so arrival times, hop counts, path counts and
betweenness are identical to 0.4.13. On the thought_chains data (23,017
contacts), path_centrality() drops from 314 s to 5 s and one-source
paths() from 35 s to under 1 s. Backward searches are vectorised but not
pruned.
dyn_reachability() is renamed reachability(). The old name still works,
forwards every argument unchanged and warns with class dynet_deprecated;
it will be removed in a future release. Note that sna also exports a
reachability(): with both attached, the one attached last wins, so call
Dynet::reachability() when in doubt.
dyn_centrality() is split in two, because its two scopes returned
different things. centrality_series() is the old default
(scope = "snapshot"): centrality in every window, a series per vertex.
path_centrality() is the old scope = "temporal" for "closeness" and
"betweenness": one value per vertex from time-respecting paths across the
period, with no time column. Temporal "reach" and "reach_count" are
reachability(). Neither new function has a scope argument, so each
returns one shape. path_centrality(plot = TRUE) now draws, which the old
temporal scope silently ignored. dyn_centrality() still works, returns
exactly what it returned before and warns with class dynet_deprecated.
The new names avoid cograph::centrality() and tna::centralities().
centrality_series(measure = "strength") now weights each spell by the
share of its duration inside the window (weight * overlap / duration)
instead of counting its full weight in every window it touches. Tiled
windows therefore add back up to the network's total weight rather than
counting a long spell once per window. Point contacts keep their full
weight in the window that holds them, window = 0 still uses full weights
at the instant, and the part of a spell outside the observation period is
not reassigned to observed windows. Degree and every binary measure are
unchanged. snapshots(), animate() and the network plots still count
each spell's full weight in every bin it touches, as
networkDynamic::network.collapse() does; their documentation now says so.
plot(type = "events") marks where each link starts the way cograph's TNA
styling does: the first 20% of each link, from its source, is dotted. The
new edge_start_style and edge_start_length arguments, named as in
cograph::splot(), change or turn off the mark. Row labels are now drawn in
their actor's colour, so they work as the colour key, and nodes are larger.
A nodes = or ties = condition that cannot be evaluated, such as one
naming a column the table does not have, now raises dynet_bad_selection
(also dynet_bad_input) instead of base R's raw error, whose class changed
in R-devel and failed the devel CI check.
metrics()' "temporal_density", "observed_pair_density",
"onset_intensity" and "observed_pair_onset_intensity" no longer count
time after the data end as exposure. Without explicit observation bounds, a
last window reaching past the final spell divided by its full width, so
tiled windows did not pool to the whole-period value. Integration now stops
at the observation period, which defaults to the data's span, as
tsna::tEdgeDensity() does. On school_contacts with weekly windows the
final partial week's temporal density goes from 0.0013 to 0.0181; explicit
observation_end values are honoured as before.
metrics()' "concurrent_nodes" and "concurrent_share" now require
simultaneity. With a positive window they were read from the window's
union snapshot, so a vertex tied to one partner early in the window and to
another later was counted as concurrent although the two ties never
overlapped. A vertex now counts when relations to two distinct neighbours
are active at the same instant somewhere in the window; spells that only
meet at a boundary do not overlap, and a point contact is concurrent with
whatever is active at its timestamp. window = 0 results are unchanged.
The result records the rule in the concurrency_window_rule attribute.
mixing() is unaffected: it counts group pairs connected anywhere in the
window and never implied simultaneity.
loops = FALSE drops is now removed
before the thread's lifetime is computed, so a dropped post no longer keeps
its thread alive. Threaded networks built from logs with self-replies can
have shorter spells than before; loops = TRUE is unchanged.dynet() gains min_thread_posts: for a threaded log, threads with fewer
surviving posts are dropped whole and reported with a message, so the
"threads that never became an exchange" rule of the chapter-17 analysis is
one argument rather than a hand-written filter. Requires thread; a value
below 1 or a non-threaded log raises dynet_bad_input /
dynet_needs_thread.plot() on a mixing() result accepts a group name in highlight, which
colours every flow into or out of that group. A highlight that matches no
series raises dynet_unknown_highlight instead of drawing everything grey.mooc_people carries expert_level, the chapter's label for the
experience code, so the mixing attribute needs no recode.vignette("ch17-temporal-networks") builds the same
network from the bundled data in one call. The site deploy now clears
files that are no longer built.set_tie_sessions() gains breaks and labels: sessions can be cut on
the time axis (breaks = c(7, 14) gives three weeks) instead of being
derived by hand as a column and matched positionally against the spell
table.add_vertex_spells() and update_vertex_spells() now refuse input they
cannot honour instead of accepting it silently. Supplying session to a
network with no session scheme raises dynet_incompatible_vertex_spells
rather than dropping the label; add_vertex_spells() used to discard it and
return normally while update_vertex_spells() already errored on the same
input. Supplying a column outside the vertex-spell schema to
update_vertex_spells() raises dynet_unknown_column rather than returning
the object unchanged, so a misspelled field is no longer a silent no-op.summary() gains temporal_density, FALSE by default. That one row
integrates exact occupancy over every eligible ordered pair, so its cost is
quadratic in the vertex count: on a 442-vertex forum network it alone took
about 32 seconds, while the other fourteen rows were immediate. It now reads
"not computed" unless asked for, and summary() on that network takes
0.3 seconds. Pass temporal_density = TRUE for the number.as_dynet() placed per-edge attributes on the wrong spell when importing an
undirected networkDynamic. dynet() canonicalises an undirected pair
before sorting its spells, and the importer derived its ordering from the raw
tail and head, so the two permutations disagreed whenever the endpoints were
stored in the other order. Attributes now follow the canonical endpoints.
remove_ties() matched the start and end selectors with exact equality
on doubles, so a spell that accumulated as 0.1 + 0.1 + 0.1 could not be
removed by naming 0.3. Times are now compared with the same
magnitude-relative tolerance the rest of the package uses.
dyn_centrality(measure = "closeness", scope = "temporal") returned Inf
without a word when every reachable vertex was joined within one instant.
Inf is still returned, since it is the honest limit, but a
dynet_zero_latency warning now accompanies it.
Five statements that contradicted the code are corrected: kept self-loops
are counted by degree and contribute two, snapshots(at = ) can return
zero rows when the nearest bin holds no active tie, animate(seed = NULL)
leaves the caller's random state advanced rather than restored,
similarity(sessions = "separate") adds no session column, and only one of
the four Krackhardt indices is an index of hierarchy.
set_tie_sessions() now documents that a full-length vector is matched
positionally against the sorted spell table, not against the data frame
the network was built from. Derive labels from as.data.frame(dn).
dynet() now documents that canonical spell column names -- duration,
weight, session, thread, onset_censored, terminus_censored -- are
dropped from tie attributes even when never named as arguments.
Internal specification identifiers that had leaked into the manual pages with no definition anywhere are replaced by the measure names they referred to.
animate(): the measurement grid as a film, written to a
GIF (gifski) or an mp4 or webm video (av), chosen by the extension of
file. It takes the same four grid arguments as every measuring verb, so
an animation shows exactly what snapshots() tabulates and what
plot(dn, type = "snapshots") draws as a filmstrip, and a test pins that
the three agree bin for bin.Each bin is drawn tween times, six by default. Between bins the vertices
glide along the smoothstep curve, a tie about to appear fades in dotted
and green, one about to vanish fades out dashed and vermilion, and with
measure = node size follows a snapshot measure from dyn_centrality()
on the same grid. Tie width follows weight on one scale fixed across the
whole animation, so the same weight has the same width in every frame. A
vertex not present in a bin is drawn as absent says: faded in place,
parked out of sight at the edge of the layout and gliding in when it
arrives and out when it leaves, or hidden; a present vertex with no tie
is drawn as isolates says. A timeline strip under the network shows
the grid, a marker at the current time, and the key; both the tie
states and the strip can be turned off.
Five layouts and a coordinate table. "spring", the default, lays out
the union of every bin once; "circle", "oval" and "groups" are
rings; "relaxed" re-runs cograph::layout_spring() per bin, seeded
from the bin before it and held within max_displacement, then smooths
every vertex's path with a centred triangular kernel, which halved the
direction reversals between consecutive moves on school_contacts at a
two per cent cost in structure. Under every layout but "relaxed" a
vertex never moves, and every layout covers the whole vertex set, so a
vertex never changes place because its neighbours came and went.
The file goes to tempfile() unless file says otherwise, so nothing
reaches the working directory by accident. gifski and av are
Suggests; without the one the extension needs the verb raises
dynet_needs_gifski or dynet_needs_av, and an extension it cannot
write raises dynet_unknown_format. A bin holding nothing to draw is
skipped with a message that counts the skipped bins. The tidy bin table
comes back invisibly, one row per bin with time, nodes, ties,
forming, dissolving and the bin's first rendered frame, as class
dynet_animation with print(), summary() and as.data.frame();
as.data.frame(x, what = "frames") maps every rendered frame to its
time.
New website article Animating a temporal network, a tutorial on
animate() over the classroom and the MOOC forum data.
set_vertex_spells(dn, "ties") declares each vertex present from the
start of its first tie spell to the end of its last, so a network built
from a tie log alone can say when each vertex arrived and left.
New vignette ch17-temporal-networks: chapter 17 of Learning Analytics
Methods and Tutorials (Saqr, 2024), "Temporal network analysis:
Introduction, methods and analysis with R", re-run with Dynet's verbs in
the chapter's own order -- build, active subnetwork, visualisation,
graph-level and node-level measures, reachability, mixing. Its data is
bundled as mooc_posts (2529 posts across 338 discussion threads of a
MOOC forum, April to June 2013) and mooc_people (445 participants with
their experience level), so the vignette reaches no network at render
time. The two places where the chapter's own code changes its numbers --
the thread spell rule, and ties admitted before single-post discussions
are dropped -- are named and measured rather than reproduced.
Backward routes on interval spells keep the route family of an
unattained supremum. An interval spell is half-open, so the latest
departure into a target is a supremum no journey reaches exactly.
paths(direction = "backward") used to report NA hops, zero paths and
no steps for such a vertex, which left every backward trajectory tree on
interval data drawing only its source. The family that approaches the
supremum is now reported in full -- hops, exact path count and
reconstructed steps -- and attained is what records that the instant
itself is not realised. Route reconstruction under sessions = "bounded"
and sessions = "separate" is unchanged.
Reachability is anchored at each vertex's own presence. With vertex
spells declared, dyn_reachability(), dyn_centrality(scope =
"temporal") and the default origin of paths() start a vertex's
forward search at its first appearance inside the window and its
backward search at its last, instead of at the window bound. A vertex
that entered the network late no longer scores zero; a backward search
may anchor at the instant a vertex leaves. An explicit at is still
used exactly.
dynet(nodes = ) names the vertices from a node table whose key is not
name but which has a name column, as network(vertex.attrnames = )
does: edge endpoints and vertex spells given by the key are translated,
and the key stays on the node table as an attribute. vertex.id,
node, vertex and node.id are recognised as vertex keys.
dynet(vertex_spells = ) resolves its node, start and end columns
through the alias table and ignores other columns, so a node table with
onset and terminus columns is accepted as it is.
rename_nodes() takes the name of a vertex attribute whose values
become the node names.
remove_ties(), remove_arcs() and update_ties() take ties as a
condition on the spell table (ties = duration > 2), as
induce_subgraph() already did; positions and masks still work.dynet() now canonicalises endpoints before sorting, the order every
rebuild uses, so update_ties(ties = 1:2) edits the rows the caller saw.NA under a warning of class dynet_kernel_singular instead of silently;
the spectral warning and this one share the parent class
dynet_measure_undefined.sample and indegree/outdegree deprecation warnings carry class
dynet_deprecated; the duplicate-nodes warning carries
dynet_duplicate_nodes.n - 1 (directed) and n - 2 (undirected); pshifts() orders
simultaneous turns by speaker, group turn, then target in vertex order;
temporal closeness is Inf when every reachable vertex is reached at zero
latency; co-presence connects every member pair for the whole group span.eigenvector, hub and authority are certified like eigenvector
prestige: a snapshot whose spectral radius is zero or whose Perron root is
repeated returns NA for that block under a warning of class
dynet_eigen_undefined, instead of one arbitrary basis vector.dynet() picks up a column named weight, weights or strength as the
tie weight and says so; before, an unnamed weight column was silently
replaced by ones.window = 0 on the default grid now samples through the last observed
instant, as tsna does; a positive window is unchanged.plot(dn, type = "timeline") and "events" take
step, a bin width (1/24 on a network in days is hourly), and the
timeline is clipped to the declared observation window; collapse_network()
and path_network() results have plot() methods with Dynet's rendering
defaults, so plot(collapsed, layout = "oval") is the whole call.dynet() keeps every column of the log it did not consume as a tie
attribute, for interval, contact and threaded logs, so
induce_subgraph(ties = group == "A_01") works on a freshly built
network as it already did after as_dynet() and add_ties().dynet(thread_clock = "relative") puts each thread of a threaded log on its
own clock, measured from the thread's first post: the Trees of Thought
construction in one call.inst/reproduction/thought-chains/thought_chains.Rmd: the
Trees of Thought analysis on the bundled thought_chains, with a gallery
of every temporal view and result plot.thought_chains: the Trees of Thought reply table
(code of a message to the code of the message it answers) with identities
stripped, the bottom 20% of authors trimmed, the two sparse weekdays
removed, the calendar shifted by whole weeks, and Evaluation merged with
Acceptance into Approving. 23,017 links, 240 participants, nine codes.start + k * step with a step such as 1/24 fell one ulp short of the
exact spell boundaries a date-converted network carries, so a spell ending
exactly at a window's start was counted inside it. Integer-hour and
POSIXct-hour encodings of one network now give identical series
(tests/testthat/test-bin-edge-snap.R).pathways(): whole time-respecting routes ranked by how often
they are used, with print, summary, plot and as.data.frame(what =
"steps").plot = TRUE on the thirteen measurement verbs draws as a side effect and
still returns the tidy table, in the manner of hist().induce_subgraph(ties = ) takes a condition on the spell table, as
nodes = already did.as.data.frame(x, what = "diagnostics") exposes the prestige diagnostics;
dynet_pshifts and dynet_collapsed_list gained the full method set and
as.data.frame(x, session = ) reaches one session by argument.synthdata, a resampled synthetic twin of the Trees
of Thought interaction data, with the reproduction under
inst/reproduction/.%||%.
Continuous checking on macOS, Windows and Linux (devel, release,
oldrel-1, and a pinned 4.1).similarity() verb, centrality mode ("all", "out", "in"),
node selection by condition, temporal reach, closeness and betweenness,
bounded and session-aware path searches, per-hop traversal time, and the
first two vignettes.dyn_ prefix except dyn_centrality() and
dyn_reachability().Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.