Dynet provides functions for constructing, analysing, and visualising temporal networks in R. The constructor accepts interval, contact, threaded, and co-presence data. Analyses include graph-level structure, vertex-level centrality, relational spell onsets and terminations, duration, burstiness, group mixing, and time-respecting paths. Measurement functions return tidy data frames identified by vertex names, temporal intervals, and measures, as applicable. Core calculations are implemented in base R.
Temporal networks map the dynamics of relationships as they occur over time, preserving when interactions occur, their duration, and their temporal order. Two common representations are contact sequences and interval networks (Holme and Saramäki, 2012). In a contact sequence, interactions are recorded as timestamps and treated as instantaneous because their duration is negligible for the analysis. In an interval network, relationships are represented as relational spells with onset and termination times. Both representations accommodate repeated interactions between the same vertices.
In temporal networks, interactions unfold as time progresses, and paths must follow their chronological order. For example, if A shares information with B on Monday and B interacts with C on Tuesday, the information can potentially reach C through B. If B’s interaction with C occurred on Sunday, that interaction could not carry information received on Monday. A sequence that follows this temporal order is called a time-respecting path. Unlike static networks, which connect interactions regardless of when they occurred, temporal networks permit only paths that follow the order and availability of interactions. Saqr and Peeters (2022).
Temporal networks also account for when relationships begin and end. A connection may be active during one period and absent during another, changing the network’s structure and the paths available between vertices. Combining all interactions into a static network obscures these changes: the resulting network may appear densely connected even when few connections were active at the same time. Saqr and Peeters (2022).
Temporal networks can be analysed at the graph, vertex, and edge levels. Graph-level measures, such as density and reciprocity, describe the overall network structure. Vertex-level measures, such as degree, closeness, and betweenness, describe the positions of individual vertices. Edge-level measures describe relationships between pairs of vertices, including their onset, termination, duration, and recurrence. Examining these measures over time reveals changes in the network, its vertices, and their relationships. Time-respecting paths provide additional information about reachability, the time required to reach other vertices, and the intermediate vertices involved.
The construction of a temporal network depends on how relationships and their timing are recorded. Dynet supports four data formats. Interval data explicitly record when each relationship begins and ends; a duration may be supplied instead of a termination time. Contact data record interactions at specific timestamps, with their duration treated as negligible. Threaded data, such as discussion replies, include timestamps and discussion identifiers. For these data, the constructor assigns each reply relationship an onset at its posting time and a termination at the last recorded event in its discussion. Co-presence data record participation in shared events, such as meetings or seminars; relationships between participants are derived from their attendance at the same event.
The released version can be installed from CRAN. The development version can be installed from the author’s R-universe repository or from GitHub. The installation commands are not evaluated when this page is rendered.
install.packages("Dynet")
# development version
install.packages("Dynet",
repos = c("https://mohsaqr.r-universe.dev",
"https://cloud.r-project.org"))
# or from GitHub
remotes::install_github("mohsaqr/Dynet")
The examples use bundled datasets in tidy format. school_contacts is a
simulated interval dataset containing 240 face-to-face contacts among
fourteen students over approximately 21.5 arbitrary time units.
forum_posts contains 241 simulated posts by twenty participants over
eight weeks; forum_people supplies participant attributes, including
role. seminar_attendance records simulated attendance at weekly
seminars. The package also includes mooc_posts, containing 2,529 posts
from the MOOC forum analysed by Saqr (2024), and thought_chains, a
synthetic reply dataset based on the study by Saqr, López-Pernas, and
Törmänen (2026).
library(Dynet)
head(school_contacts)
#> from to start end
#> 1 Jonas Dan 0.00 1.10
#> 2 Gita Ana 0.14 0.98
#> 3 Leo Mira 0.15 0.42
#> 4 Leo Iris 0.15 0.96
#> 5 Kira Ben 0.33 0.69
#> 6 Leo Iris 0.38 0.50
In school_contacts, from and to identify the relational endpoints,
while start and end specify contact onset and termination.
dynet() recognises common column names automatically, using
case-insensitive matching. Relational endpoints may be named
from/to, sender/receiver, or source/target; interval
boundaries may be named start/end or onset/terminus. For contact
data, recognised timestamp names include time, timestamp, date,
and datetime. Explicit column specification is needed only when names
do not match recognised aliases or when their intended interpretation is
ambiguous. The format argument can explicitly select the input format;
for example, format = "contact" specifies instantaneous interactions.
dynet() represents relational data in tidy format, retaining the
endpoints and recording the onset, termination, duration, and
multiplicity of each relational spell. For interval data, termination is
supplied through end or calculated as start + duration. Duration is
recorded as duration = end - start. Instantaneous contacts have equal
onset and termination times and therefore zero duration. Multiplicity is
recorded in weight, which defaults to 1 when no multiplicity variable
is supplied or recognised.
The following example constructs a temporal network from
school_contacts, a simulated dataset of 240 contacts among fourteen
students. The supplied variables from and to identify the relational
endpoints, while start and end record contact onset and termination
in arbitrary time units. These names are recognised automatically, so no
column arguments are required.
library(Dynet)
dn <- dynet(school_contacts)
dn
#> # Temporal network (interval format, directed) | a cograph netobject
#> # 14 vertices | 240 edge spells | 110 distinct pairs
#> # observed from 0 to 21.52 step, binned every 1
#>
#> from to start end duration weight
#> Jonas Dan 0.00 1.10 1.10 1
#> Gita Ana 0.14 0.98 0.84 1
#> Leo Mira 0.15 0.42 0.27 1
#> Leo Iris 0.15 0.96 0.81 1
#> Kira Ben 0.33 0.69 0.36 1
#> Leo Iris 0.38 0.50 0.12 1
#> # 234 more spells. summary() describes the network; plot() draws it.
summary() describes the network over its observation period, including
the number of vertices, relational spells, and distinct pairs of
connected vertices. It also reports mean snapshot density: the average
proportion of possible vertex pairs connected within each measurement
interval. This distinguishes the relationships accumulated over the full
observation period from the connectivity observed within individual
intervals.
summary(dn)
#> property value
#> 1 format interval
#> 2 directed yes
#> 3 vertices 14
#> 4 edge spells 240
#> 5 distinct pairs 110
#> 6 time unit step
#> 7 observed from 0
#> 8 observed to 21.52
#> 9 span 21.52
#> 10 bin width 1
#> 11 time bins 22
#> 12 mean snapshot density 0.0829
#> 13 temporal density not computed
#> 14 sessions none
#> 15 vertex attributes none
The same constructor can represent timestamped replies as either
instantaneous contacts or relationships that remain active within a
discussion. In the first call, time explicitly identifies the
timestamp column, and the constructor infers contact data. In the
second, thread selects threaded construction: each reply relationship
remains active until the last recorded event in its discussion. The
nodes argument supplies participant attributes from forum_people.
contacts <- dynet(forum_posts, time = "timestamp")
forum <- dynet(forum_posts, thread = "thread", nodes = forum_people)
Co-presence data record participation in shared events rather than
direct interactions between participants. The actor argument
identifies the participant column, and group identifies the shared
event. The constructor derives an undirected relationship between each
pair of participants attending the same event, with onset and
termination determined by the event’s recorded temporal extent.
seminars <- dynet(seminar_attendance, actor = "student", group = "seminar")
These ties represent shared attendance; they do not establish that participants interacted directly.
Dynet accepts numeric times, dates, and date-times. Numeric times retain
their supplied scale. Dates and date-times are converted to elapsed
time, with the unit selected automatically when time_unit = "auto",
the default. The unit can also be specified explicitly through
time_unit, for example as "hours" or "days". In the threaded
example, time is expressed in days.
The timeline plot displays when each pair of vertices is connected during the observation period. For interval data, colour indicates the proportion of each time bin during which at least one relational spell is active. Overlapping spells between the same endpoints are counted once, so the plot represents the duration of connectivity rather than the number of interactions.
plot(dn)

Window-based analyses use four arguments to define when measurements are
taken and which interactions they include. start and end delimit the
measurement period. step specifies the interval between measurements,
while window specifies the duration covered by each measurement.
Setting window equal to step produces non-overlapping intervals; a
larger window produces overlapping intervals. For snapshot measures,
window = 0 evaluates the network at individual time points.
metrics() computes graph-level measures within the specified temporal
windows. These measures describe the structure of the network as a
whole. For example, density is the proportion of possible ties present
within a window, while reciprocity describes the extent to which
directed ties are reciprocated. Other measures characterise connected
components, local configurations of ties, and the distribution of
centrality across vertices.
The following call computes density at one-unit intervals, using a
seven-unit window for each measurement. Because window exceeds step,
successive measurements include overlapping periods.
density <- metrics(dn, measure = "density", step = 1, window = 7)
density
#> # Density (graph-level)
#> # 22 time points, step 1, window 7 (rolling) | time in step
#> time measure value
#> 0 density 0.3241758
#> 1 density 0.3461538
#> 2 density 0.3571429
#> 3 density 0.3791209
#> 4 density 0.3681319
#> 5 density 0.3791209
#> 6 density 0.3791209
#> 7 density 0.3791209
#> 8 density 0.3956044
#> 9 density 0.3736264
#> 10 density 0.3626374
#> 11 density 0.3351648
#> # 10 more rows. summary() aggregates them; plot() draws them.
The result is a tidy data frame identifying the measurement time,
measure, and value. Multiple statistics can be requested through
measure; their results are distinguished by the measure column.
Available measures include density, reciprocity, transitivity, dyad and
triad censuses, component counts, centralisation, and Krackhardt’s
indices. Their definitions and calculation conventions are documented in
?metrics.
summary() summarises the resulting time series, including its mean,
dispersion, range, and peak time. plot() displays how the measure
changes over the observation period.
summary(density)
#> measure n mean sd min max peak_time
#> 1 density 22 0.285964 0.111584 0.03296703 0.3956044 8
plot(density)

centrality_series() computes measures that describe how a vertex is
connected to the rest of the network. Each centrality captures a
different aspect of structural position. Degree counts direct
connections, indicating the extent of a vertex’s immediate
neighbourhood. In directed networks, indegree counts incoming
connections and outdegree counts outgoing connections. Strength
accounts for the weights of those connections, distinguishing the number
of connected neighbours from the multiplicity of their relationships.
Closeness centrality is based on the shortest-path distances from a vertex to other vertices. Higher closeness indicates shorter average distances, meaning that fewer steps are required to reach other vertices. In a directed network, these paths follow tie direction.
Betweenness measures the extent to which a vertex lies on shortest paths between other vertices. It therefore identifies potential intermediary positions, although occupying such a position does not establish that information actually passed through that vertex.
Other measures incorporate the positions of a vertex’s neighbours. Eigenvector centrality assigns higher scores to vertices connected to other highly central vertices. PageRank and hub and authority scores also use neighbouring vertices’ scores but apply different rules for directed relationships. Prestige measures characterise a vertex through its incoming connections, including, for some variants, the vertices that can reach it indirectly. The choice of measure should therefore follow the substantive question and the meaning assigned to tie direction.
By default, these measures are calculated within successive temporal windows. This produces a centrality trajectory for each vertex, showing when it becomes more or less connected, accessible, or prominent as an intermediary. The following call computes degree using the default measurement intervals:
degree <- centrality_series(dn, measure = "degree")
degree
#> # Degree (node-level)
#> # 14 vertices | 22 time points, 1 per bin | time in step
#> time node measure value
#> 0 Ana degree 1
#> 0 Ben degree 1
#> 0 Cara degree 1
#> 0 Dan degree 1
#> 0 Eve degree 2
#> 0 Finn degree 1
#> 0 Gita degree 1
#> 0 Hugo degree 1
#> 0 Iris degree 2
#> 0 Jonas degree 2
#> 0 Kira degree 2
#> 0 Leo degree 2
#> # 296 more rows. summary() aggregates them; plot() draws them.
The result is a tidy data frame containing vertex names, measurement
times, measures, and values. summary() summarises each trajectory;
peak_time identifies when the corresponding centrality attained its
maximum.
summary(degree)
#> node measure n mean sd min max peak_time
#> 1 Ana degree 22 2.181818 2.015095 0 7 6
#> 2 Ben degree 22 2.000000 1.234427 0 4 4
#> 3 Cara degree 22 2.227273 1.342770 0 5 4
#> 4 Dan degree 22 2.090909 1.444500 1 5 13
#> 5 Eve degree 22 2.272727 2.051290 0 8 14
#> 6 Finn degree 22 2.000000 1.661898 0 6 12
#> 7 Gita degree 22 1.727273 1.777688 0 7 6
#> 8 Hugo degree 22 2.318182 1.861550 0 6 6
#> 9 Iris degree 22 1.772727 1.066004 0 4 11
#> 10 Jonas degree 22 2.863636 2.076982 0 7 13
#> 11 Kira degree 22 2.636364 1.255292 1 6 6
#> 12 Leo degree 22 1.636364 1.432462 0 5 6
#> 13 Mira degree 22 2.272727 1.695423 0 6 13
#> 14 Nils degree 22 2.181818 2.174229 0 7 14
path_centrality() instead computes centrality from time-respecting
paths across the specified observation period. Temporal closeness
measures how quickly a vertex can reach other reachable vertices, using
the inverse of their mean arrival latency. Temporal betweenness
measures a vertex’s contribution as an intermediary on optimal temporal
paths between other vertices. These measures account for the order and
availability of interactions, whereas window-based centralities describe
the connections present within each window.
closeness <- path_centrality(dn, measure = "closeness")
closeness
#> # Closeness (node-level)
#> # 14 vertices | time in step
#> # computed on time-respecting paths across the whole window
#> node measure value
#> Ana closeness 0.1313662
#> Ben closeness 0.1815896
#> Cara closeness 0.1931075
#> Dan closeness 0.2230994
#> Eve closeness 0.2616221
#> Finn closeness 0.1644945
#> Gita closeness 0.1404950
#> Hugo closeness 0.1878341
#> Iris closeness 0.2398082
#> Jonas closeness 0.3674392
#> Kira closeness 0.2107994
#> Leo closeness 0.3747478
#> # 2 more rows. summary() aggregates them; plot() draws them.
paths() examines how a source vertex can reach other vertices through
a sequence of interactions over time. A path may involve a direct
interaction or several intermediate vertices. Each interaction must
occur when its tie is available, and a vertex cannot pass something
onward before receiving it. Waiting between interactions is permitted:
if A contacts B on Monday and B next contacts C on Wednesday, a path
from A to C can include the intervening wait.
The timing of these interactions determines which path arrives earliest.
Suppose A can contact C directly on Friday, but can reach C through B on
Wednesday. The two-hop path through B arrives earlier than the direct
path. paths() therefore minimises arrival time first, rather than the
number of hops. If several paths arrive equally early, it retains those
with the fewest hops.
The following call searches from Ana, beginning at the start of the
observation period in this example:
from_ana <- paths(dn, from = "Ana")
from_ana
#> # Time-respecting paths from 'Ana', from t = 0
#> # reaches 13 of 13 other vertices | time in step
#> node reachable arrival_time attained latency n_hops n_paths
#> Ana TRUE 0.00 TRUE 0.00 0 1
#> Ben TRUE 9.59 TRUE 9.59 3 3
#> Cara TRUE 6.67 TRUE 6.67 1 1
#> Dan TRUE 7.98 TRUE 7.98 4 1
#> Eve TRUE 11.66 TRUE 11.66 4 3
#> Finn TRUE 6.96 TRUE 6.96 2 1
#> Gita TRUE 6.36 TRUE 6.36 2 1
#> Hugo TRUE 7.98 TRUE 7.98 3 1
#> Iris TRUE 10.00 TRUE 10.00 3 1
#> Jonas TRUE 2.12 TRUE 2.12 1 1
#> Kira TRUE 6.12 TRUE 6.12 2 2
#> Leo TRUE 9.65 TRUE 9.65 3 1
#> # 2 more rows. summary() aggregates them; plot() draws the tree.
The result describes the optimal paths to each destination.
arrival_time records the earliest time at which the destination can be
reached. latency is the elapsed time between the search origin and
arrival, including any waiting for subsequent interactions. n_hops
records how many ties the path traverses, while n_paths records how
many distinct paths achieve both the earliest arrival and the minimum
hop count. A destination with n_paths = 0 is unreachable under the
specified temporal constraints.
By default, traversing a tie takes zero time, although a path may still
require waiting for a later interaction. A positive traversal_time
assigns a duration to each traversal, expressed in the network’s time
unit. This is an analytical assumption that affects arrival times and
potentially reachability; it should be selected according to the process
being represented.
summary(from_ana)
#> property value
#> 1 source Ana
#> 2 direction forward
#> 3 reachable 13
#> 4 reachable share 1
#> 5 median latency 7.51
#> 6 max latency 11.66
#> 7 median hops 2
#> 8 max hops 4
The summary describes the reachable set and its path characteristics. These results concern possible routes through the recorded relationships. They do not establish that information, resources, or other content actually travelled along those routes.
Reachability also depends on when the search begins. Starting later excludes earlier contacts that may have connected the source to other vertices. The following call starts at time 18:
from_ana_late <- paths(dn, from = "Ana", start = 18)
summary(from_ana_late)
#> property value
#> 1 source Ana
#> 2 direction forward
#> 3 reachable 5
#> 4 reachable share 0.385
#> 5 median latency 2.43
#> 6 max latency 2.68
#> 7 median hops 2
#> 8 max hops 3
Comparing the two results shows how the timing of participation changes the destinations Ana can reach and the routes available to her. This distinction is lost in a static network, where all recorded connections are available without regard to their timing.
pathways() provides the corresponding vertex sequences, such as A → B
→ C, and counts the optimal paths following each sequence. The path plot
displays the routes and their temporal progression.
pathways(dn, from = "Ana")
#> # Time-respecting pathways (5 distinct routes)
#> # 7 optimal routes counted
#> route endpoint count share n_hops
#> Ana -> Jonas -> Kira -> Ben -> Eve Eve 3 0.4285714 4
#> Ana -> Mira -> Gita Gita 1 0.1428571 2
#> Ana -> Cara -> Finn -> Iris Iris 1 0.1428571 3
#> Ana -> Cara -> Finn -> Leo Leo 1 0.1428571 3
#> Ana -> Cara -> Nils -> Hugo -> Dan Dan 1 0.1428571 4
#> arrival_time
#> 11.66
#> 6.36
#> 10.00
#> 9.65
#> 7.98
plot(from_ana)

reachability() summarises which vertices can be connected through
time-respecting paths. Forward reachability identifies the vertices
that a source can reach during the specified observation period.
Backward reachability identifies the vertices from which a focal
vertex can be reached by the end of that period. These sets need not be
identical: the order of interactions may permit a path from A to B
without permitting a return path from B to A.
The direction argument selects "forward", "backward", or "both".
Setting measure = "reach_count" returns the number of reachable
vertices other than the focal vertex. Setting measure = "reach"
expresses this count as a proportion of all other vertices in the
network.
reach <- reachability(
dn,
direction = "both",
measure = c("reach", "reach_count")
)
reach
#> # Reachability (node-level)
#> # 14 vertices | time in step
#> # measures: forward_reach, forward_reach_count, backward_reach, backward_reach_count
#> # count and share of other vertices joined by a time-respecting path
#> node measure value
#> Ana forward_reach 1
#> Ben forward_reach 1
#> Cara forward_reach 1
#> Dan forward_reach 1
#> Eve forward_reach 1
#> Finn forward_reach 1
#> Gita forward_reach 1
#> Hugo forward_reach 1
#> Iris forward_reach 1
#> Jonas forward_reach 1
#> Kira forward_reach 1
#> Leo forward_reach 1
#> # 44 more rows. summary() aggregates them; plot() draws them.
A forward reach of 1 indicates that the source can reach every other vertex within the specified period. A value of 0 indicates that it cannot reach any other vertex. Intermediate values describe partial reachability. These measures include indirect connections through intermediate vertices, so they capture more than the immediate neighbourhood measured by degree.
Reachability describes whether a temporal connection is possible; it does not describe how quickly the destination can be reached. Two vertices can therefore have equal reachability but different temporal closeness because their arrival latencies differ. Examining reachability alongside path timing distinguishes the extent of possible contact from the time required to achieve it.
Relational spells have an onset, a termination, and a duration. Examining these properties describes when relationships become active, when they cease, and how long they persist. In datasets with repeated interactions, several spells may connect the same pair of vertices. Counts of spell onsets and terminations therefore differ from counts of distinct connected pairs.
events() summarises spell onsets and terminations within temporal
windows through the measures "formation" and "dissolution", which
are requested by default. The following call uses the network’s default
measurement intervals:
turnover <- events(dn)
summary(turnover)
#> measure n mean sd min max peak_time
#> 1 dissolution 22 10.90909 6.132731 3 27 14
#> 2 formation 22 10.90909 6.689787 0 29 13
These counts describe changes in relational activity. A spell onset does not necessarily introduce a previously unconnected pair: it may represent another interaction between endpoints that already have an active spell. Similarly, the termination of one spell does not necessarily disconnect its endpoints if another spell remains active.
durations() summarises the duration of relationships. By default,
results are grouped by endpoint pair. Setting measure = "mean" returns
the mean spell duration for each pair:
lengths <- durations(dn, measure = "mean")
lengths
#> # Relationship duration (edge-level)
#> # time in step
#> # durations in step
#> from to measure value
#> Ana Cara mean 0.1000000
#> Ana Dan mean 0.3400000
#> Ana Gita mean 0.4220000
#> Ana Iris mean 0.5000000
#> Ana Jonas mean 0.5850000
#> Ana Kira mean 0.1100000
#> Ana Leo mean 1.1900000
#> Ana Mira mean 0.3466667
#> Ben Eve mean 0.6100000
#> Ben Finn mean 0.2300000
#> Ben Gita mean 0.3400000
#> Ben Hugo mean 0.1475000
#> # 98 more rows. summary() aggregates them; plot() draws them.
Different duration summaries answer different questions. Summed duration adds the durations of individual spells, including overlapping spells separately. Union duration measures the total time during which at least one spell connects the endpoints, counting overlap once. For example, if two spells connect the same pair during hours 1–3 and 2–4, their summed duration is four hours, while their union duration is three hours. Mean duration describes the average length of an individual spell.
The unit argument determines whether durations are summarised for
endpoint pairs, individual spells, vertex activity, or ties incident to
a vertex. Vertex activity describes when a vertex is present or eligible
to participate; incident-tie duration describes its recorded
relationships. These quantities should be interpreted separately.
burstiness() describes how interactions are distributed over time
using the intervals between successive events. Regular activity produces
intervals of similar length, whereas clustered activity produces short
intervals within bursts and longer intervals between them. The
burstiness coefficient summarises this variation: −1 corresponds to
equal intervals, 0 to the exponential inter-event distribution
associated with a Poisson process, and values approaching 1 indicate
increasingly heterogeneous intervals (Goh and Barabási, 2008).
rhythm <- burstiness(dn)
summary(rhythm)
#> node measure n mean sd min max
#> 1 Ana burstiness 1 0.24575324 NA 0.24575324 0.24575324
#> 2 Ana events 1 36.00000000 NA 36.00000000 36.00000000
#> 3 Ana memory 1 -0.03128243 NA -0.03128243 -0.03128243
#> 4 Ben burstiness 1 0.03288611 NA 0.03288611 0.03288611
#> 5 Ben events 1 34.00000000 NA 34.00000000 34.00000000
#> 6 Ben memory 1 -0.19926343 NA -0.19926343 -0.19926343
#> 7 Cara burstiness 1 -0.05610924 NA -0.05610924 -0.05610924
#> 8 Cara events 1 35.00000000 NA 35.00000000 35.00000000
#> 9 Cara memory 1 0.07458054 NA 0.07458054 0.07458054
#> 10 Dan burstiness 1 -0.05061772 NA -0.05061772 -0.05061772
#> 11 Dan events 1 35.00000000 NA 35.00000000 35.00000000
#> 12 Dan memory 1 0.21654310 NA 0.21654310 0.21654310
#> 13 Eve burstiness 1 0.09217906 NA 0.09217906 0.09217906
#> 14 Eve events 1 34.00000000 NA 34.00000000 34.00000000
#> 15 Eve memory 1 0.25882509 NA 0.25882509 0.25882509
#> 16 Finn burstiness 1 0.02938507 NA 0.02938507 0.02938507
#> 17 Finn events 1 29.00000000 NA 29.00000000 29.00000000
#> 18 Finn memory 1 -0.22653754 NA -0.22653754 -0.22653754
#> 19 Gita burstiness 1 0.06104200 NA 0.06104200 0.06104200
#> 20 Gita events 1 31.00000000 NA 31.00000000 31.00000000
#> 21 Gita memory 1 0.21165690 NA 0.21165690 0.21165690
#> 22 Hugo burstiness 1 0.09885443 NA 0.09885443 0.09885443
#> 23 Hugo events 1 34.00000000 NA 34.00000000 34.00000000
#> 24 Hugo memory 1 0.11283700 NA 0.11283700 0.11283700
#> 25 Iris burstiness 1 -0.05968068 NA -0.05968068 -0.05968068
#> 26 Iris events 1 30.00000000 NA 30.00000000 30.00000000
#> 27 Iris memory 1 -0.09356972 NA -0.09356972 -0.09356972
#> 28 Jonas burstiness 1 -0.02750549 NA -0.02750549 -0.02750549
#> 29 Jonas events 1 46.00000000 NA 46.00000000 46.00000000
#> 30 Jonas memory 1 0.23391957 NA 0.23391957 0.23391957
#> 31 Kira burstiness 1 -0.07048265 NA -0.07048265 -0.07048265
#> 32 Kira events 1 38.00000000 NA 38.00000000 38.00000000
#> 33 Kira memory 1 -0.14972726 NA -0.14972726 -0.14972726
#> 34 Leo burstiness 1 -0.01020265 NA -0.01020265 -0.01020265
#> 35 Leo events 1 28.00000000 NA 28.00000000 28.00000000
#> 36 Leo memory 1 0.48001912 NA 0.48001912 0.48001912
#> 37 Mira burstiness 1 0.09968547 NA 0.09968547 0.09968547
#> 38 Mira events 1 36.00000000 NA 36.00000000 36.00000000
#> 39 Mira memory 1 0.18691159 NA 0.18691159 0.18691159
#> 40 Nils burstiness 1 0.08408761 NA 0.08408761 0.08408761
#> 41 Nils events 1 34.00000000 NA 34.00000000 34.00000000
#> 42 Nils memory 1 0.22784733 NA 0.22784733 0.22784733
Burstiness and memory describe different properties of event timing. Burstiness measures variation in interval lengths, without considering their order. Memory measures the correlation between consecutive intervals. Positive memory indicates that short intervals tend to follow short intervals and long intervals tend to follow long intervals. Negative memory indicates a tendency for short and long intervals to alternate. A value near zero indicates little linear association between successive intervals.
A vertex can therefore exhibit bursty activity without substantial memory: its inter-event intervals may vary considerably while consecutive interval lengths remain weakly correlated. Interpreting both measures helps distinguish variability in activity timing from dependence between successive intervals.
mixing() counts distinct active vertex pairs within and between groups
defined by a vertex attribute. Repeated spells and their weights do not
multiply these binary-dyad counts. The forum network inherits role
from forum_people. Setting step = 60, which exceeds this network’s
observation period, produces a single interval for the comparison.
mixing(forum, attribute = "role", step = 60)
#> # Mixing by role (graph-level)
#> # 1 time points, 60 per bin | time in days
#> # measures: Facilitator -> Facilitator, Student -> Facilitator, Teacher -> Facilitator, Facilitator -> Student, Student -> Student, Teacher -> Student, Facilitator -> Teacher, Student -> Teacher, Teacher -> Teacher
#> # active binary-dyad counts between vertex groups per time bin
#> time measure value from_group to_group
#> 0 Facilitator -> Facilitator 0 Facilitator Facilitator
#> 0 Student -> Facilitator 3 Student Facilitator
#> 0 Teacher -> Facilitator 0 Teacher Facilitator
#> 0 Facilitator -> Student 4 Facilitator Student
#> 0 Student -> Student 136 Student Student
#> 0 Teacher -> Student 12 Teacher Student
#> 0 Facilitator -> Teacher 1 Facilitator Teacher
#> 0 Student -> Teacher 16 Student Teacher
#> 0 Teacher -> Teacher 0 Teacher Teacher
collapse_network() summarises a temporal network as a weighted static
network. Each edge represents a pair of vertices connected during the
observation period, and its weight summarises their relational spells
according to the selected rule. This provides an overall description of
relationships while removing their temporal order.
Setting weight = "union_duration" assigns each edge the total time
during which at least one spell connects its endpoints. Overlapping
spells are counted once. For example, spells spanning hours 1–3 and 2–4
produce an edge weight of three hours.
flat <- collapse_network(dn, weight = "union_duration")
flat
#> # Collapsed temporal network | 14 vertices | 110 edges | weight: union_duration
#> # 0 to 21.52 step
#> from to binary union_duration total_duration duration_fraction spell_count
#> Ana Cara 1 0.10 0.10 0.004646840 1
#> Ana Dan 1 1.02 1.02 0.047397770 3
#> Ana Gita 1 1.99 2.11 0.092472119 5
#> Ana Iris 1 0.50 0.50 0.023234201 1
#> Ana Jonas 1 2.34 2.34 0.108736059 4
#> Ana Kira 1 0.11 0.11 0.005111524 1
#> weight_sum weighted_duration latest_weight first last activity.duration
#> 1 0.10 1 6.67 6.77 0.10
#> 3 1.02 1 12.04 20.10 1.02
#> 5 2.11 1 6.57 14.16 1.99
#> 1 0.50 1 13.80 14.30 0.50
#> 4 2.34 1 2.12 9.13 2.34
#> 1 0.11 1 11.60 11.71 0.11
#> activity.count
#> 1
#> 3
#> 5
#> 1
#> 4
#> 1
induce_subgraph() selects part of the network for further analysis.
Selection may be based on vertex attributes or structural measures. In
the following example, degree is calculated over the full observation
period, and vertices with degree greater than 16 are retained together
with the relational spells connecting them.
core <- induce_subgraph(dn, degree > 16)
core
#> # Temporal network (interval format, directed) | a cograph netobject
#> # 5 vertices | 31 edge spells | 14 distinct pairs
#> # observed from 0 to 20.46 step, binned every 1
#>
#> from to start end duration weight
#> Jonas Dan 0.00 1.10 1.10 1
#> Eve Kira 0.77 1.42 0.65 1
#> Kira Eve 1.95 2.47 0.52 1
#> Jonas Kira 2.05 2.09 0.04 1
#> Dan Jonas 3.14 3.49 0.35 1
#> Dan Eve 3.20 3.33 0.13 1
#> # 25 more spells. summary() describes the network; plot() draws it.
snapshots() extracts connections within specified temporal windows.
The following call returns the connections in the default measurement
window containing time 3:
snapshots(dn, at = 3)
#> # Snapshot edges | 1 bin | 12 tie rows | time in step
#> time from to weight n_spells
#> 1 3 Ana Jonas 1 1
#> 2 3 Kira Leo 1 1
#> 3 3 Leo Finn 1 1
#> 4 3 Nils Eve 1 1
#> 5 3 Ben Jonas 1 1
#> 6 3 Ben Eve 1 1
#> 7 3 Dan Ana 1 1
#> 8 3 Dan Jonas 1 1
#> 9 3 Dan Eve 1 1
#> 10 3 Gita Jonas 1 1
#> # 2 more rows. summary() counts them by bin.
A snapshot summarises connectivity within its window; it does not retain the ordering of interactions within that interval.
Dynet provides functions for modifying vertices, relational spells, and observation settings. Each editing function returns a modified network without changing the input object. This allows alternative network specifications to be constructed while retaining the original for comparison.
add_nodes() adds vertices and their attributes. add_ties() adds
relational spells using their endpoints, onset, and termination. The
following example adds a vertex named Omar, then adds a directed
relationship from Ana to Omar, active from time 4 to time 6:
dn2 <- add_nodes(dn, data.frame(name = "Omar"))
dn2 <- add_ties(dn2, data.frame(from = "Ana", to = "Omar", start = 4, end = 6))
dn2
#> # Temporal network (interval format, directed) | a cograph netobject
#> # 15 vertices | 241 edge spells | 111 distinct pairs
#> # observed from 0 to 21.52 step, binned every 1
#>
#> from to start end duration weight
#> Jonas Dan 0.00 1.10 1.10 1
#> Gita Ana 0.14 0.98 0.84 1
#> Leo Mira 0.15 0.42 0.27 1
#> Leo Iris 0.15 0.96 0.81 1
#> Kira Ben 0.33 0.69 0.36 1
#> Leo Iris 0.38 0.50 0.12 1
#> # 235 more spells. summary() describes the network; plot() draws it.
Related functions support removal, revision, and relabelling.
remove_nodes() and remove_ties() remove selected vertices or spells;
update_nodes() and update_ties() revise their attributes or values;
and rename_nodes() changes vertex names while maintaining their
references in the network.
set_vertex_spells() specifies periods during which vertices are
present or eligible to participate. set_observations() specifies the
periods over which the network is observed. These declarations serve
different purposes: vertex activity constrains participation, whereas
observation periods constrain measurement. Changing observation settings
does not alter the original relational spell endpoints.
Temporal network visualisations describe both the structure of relationships and their development over time. Different views address different questions: which vertices are connected during a particular period, how connectivity changes between periods, and when individual relationships are active.
Setting type = "network" displays connections within a selected
measurement window. The at argument identifies the time of interest.
Setting type = "snapshots" displays several temporal slices using a
shared layout, making changes in connections easier to compare.
plot(dn, type = "network", at = 3)

plot(dn, type = "snapshots", panels = 6)
#> Drawing 6 of 22 bins, evenly spaced across the window.

A timeline places relational activity along a time axis. For interval data, colour represents the proportion of each time bin during which an endpoint pair is connected. This view shows periods of activity and inactivity that may be difficult to distinguish in successive network diagrams.
plot(dn, type = "timeline")

A proximity timeline follows the changing relationships among vertices. Network distances are calculated within temporal slices and represented as positions along a single axis. Lines connect each vertex’s positions across slices, showing how its proximity to other vertices changes. Nearby lines indicate shorter network distances in the corresponding slice; they do not necessarily indicate a direct tie.
plot(dn, type = "proximity")

These views complement one another: network diagrams show connections, activity timelines show their timing, and proximity timelines summarise changes in network distances.
animate() displays a sequence of network snapshots over the
observation period. Vertices retain a shared layout across frames,
allowing changes in connections to be followed without changes in
position obscuring the comparison.
The temporal settings follow the same conventions as window-based
measurement. start and end delimit the period, step determines the
time between successive frames, and window determines the period
represented in each frame. A positive window displays connections active
within that interval; it does not imply that all displayed connections
were active simultaneously.
The output filename determines the format. GIF output requires the
gifski package, while MP4 and WebM output require av. The animation
tutorial provides examples of temporal settings, visual options, and
export procedures.
The package documentation provides additional examples and methodological details:
vignette("dynet") presents an analysis from network construction
through measurement and interpretation.vignette("building-networks") explains input formats, column
recognition, vertex attributes, observation periods, sessions, and
censoring.vignette("ch17-temporal-networks") reproduces the temporal network
analysis chapter of Learning Analytics Methods and Tutorials using
the bundled MOOC data.thought_chains.Function help pages document arguments, defaults, return values, and
calculation conventions. For example, ?dynet, ?metrics, and ?paths
describe network construction, graph-level measurement, and temporal
path analysis, respectively.
Butts, C. T. (2008). A relational event framework for social action. Sociological Methodology, 38(1), 155–200.
Goh, K.-I., & Barabási, A.-L. (2008). Burstiness and memory in complex systems. EPL (Europhysics Letters), 81(4), 48002.
Holme, P., & Saramäki, J. (2012). Temporal networks. Physics Reports, 519(3), 97–125.
Kempe, D., Kleinberg, J., & Kumar, A. (2002). Connectivity and inference problems for temporal networks. Journal of Computer and System Sciences, 64(4), 820–842.
Masuda, N., & Lambiotte, R. (2016). A guide to temporal networks. World Scientific.
Moody, J. (2002). The importance of relationship timing for diffusion. Social Forces, 81(1), 25–56.
Nicosia, V., Tang, J., Mascolo, C., Musolesi, M., Russo, G., & Latora, V. (2013). Graph metrics for temporal networks. In P. Holme & J. Saramäki (Eds.), Temporal networks (pp. 15–40). Springer.
Pan, R. K., & Saramäki, J. (2011). Path lengths, correlations, and centrality in temporal networks. Physical Review E, 84(1), 016105.
Saqr, M. (2024). Temporal network analysis: Introduction, methods and analysis with R. In M. Saqr & S. López-Pernas (Eds.), Learning analytics methods and tutorials: A practical guide using R. Springer.
Saqr, M., López-Pernas, S., & Törmänen, T. (2026). A temporal network approach to reveal the longitudinal dynamics of CSCL group regulation and productive collaboration. International Journal of Computer-Supported Collaborative Learning, 21, 237–270. https://doi.org/10.1007/s11412-025-09464-5
Saqr, M., & Nouri, J. (2020). High resolution temporal network analysis to understand and improve collaborative learning. In Proceedings of the Tenth International Conference on Learning Analytics & Knowledge (pp. 314–319). ACM.
Saqr, M., & Peeters, W. (2022). Temporal networks in collaborative learning: A case study. British Journal of Educational Technology, 53(5), 1283–1303. https://doi.org/10.1111/bjet.13187
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.