Tools for temporal networks that change over time, where nodes enter and leave, groups form, split, merge and dissolve, and periods differ in size. The temporal fitters track nodes and communities across periods. A community keeps one identifier while it persists, and a newly formed group receives a new one. Two-stage fitters run Louvain or Leiden community detection in each period and link communities in linked periods by Jaccard similarity, the overlap coefficient or node-strength weighted variants. The 'layer_links' argument sets which periods are directly linked, adjacent periods only by default. By default all periods are fitted jointly, so later periods can change earlier labels, and tracking = 'filtered' prevents this. Multislice modularity (Mucha et al. 2010)
dynamicmultiplex is the R implementation of DynMux. DynMux is a suite of tools for temporal networks that change over time. Nodes enter and leave, groups form, split, merge and dissolve, and periods can differ in size. The temporal fitters track nodes and communities across periods. A community keeps one id for as long as it persists, and a newly formed group gets a new id. The two-stage fitters detect communities in each period and link them to communities in the linked periods. layer_links sets which periods are directly linked, and by default that is adjacent periods only. With the default tracking = "smoothed", all periods are fitted jointly, so later periods can change earlier labels. tracking = "filtered" prevents that. Multislice modularity (Mucha et al. 2010) and Hungarian matching are included as comparison methods. The bootstrap stability score helps choose between DynMux and multislice.
Standard multislice approaches (Mucha et al. 2010) connect all layers to all layers, meaning community structure at distant time periods influences assignments everywhere. When applied to temporal data, this creates a pooling problem: the community configuration at 2010 affects the structure detected at 1950. dynamicmultiplex provides explicit control over which layers influence which layers via a layer_links argument (from, to, weight), defaulting to adjacent-only temporal coupling.
Note:
igraph's Louvain and Leiden are undirected. Whendirected = TRUE, layers are collapsed to undirected weighted graphs (reciprocal arcs summed) before per-layer clustering; the Python package keeps edge direction for Leiden. The multislice fit (fit_multilayer_identity_ties()) keeps edge direction in each slice whendirected = TRUE.
fit_multilayer_jaccard()
fit_multilayer_overlap()
fit_multilayer_weighted_jaccard()
fit_multilayer_weighted_overlap()
fit_multilayer_identity_ties()
omega) and detects meta-communities jointly on the coupled layers.fit_multilayer_hungarian()
simulate_and_fit_multilayer()
jaccard, overlap, weighted_jaccard, weighted_overlap, identity).layers_from_membership()
bootstrap_multilayer(), partition_stability()
plot_multilayer_series(), animate_multilayer_gif(), plot_multilayer_alluvial()
allow_unequal_nodes = TRUE. Nodes may then enter and leave, with or without a change in layer size; every layer needs vertex names (igraph names or adjacency-matrix dimnames), and nodes are matched across layers by name. This holds for every fit function and for bootstrap_multilayer().repair_chains = TRUE rejoins groups that stage 2 cut along time (mutual best match with similarity at least repair_min_similarity, default 0.2); tracking = "filtered" makes the meta ids of layers 1..t depend only on layers 1..t. Both are off by default.objective sets the first-stage (per-layer) objective only; the second stage always maximizes modularity.seed = 123 by default (since 1.4.0, as in the Python package); pass seed = NULL for unseeded detection. The caller's random number stream is restored after the fit.Development install:
# install.packages("remotes")
remotes::install_local(".")
GitHub install (recommended for collaborators):
# install.packages("remotes")
remotes::install_github("jfedgerton/dynamic_multiplex", subdir = "r_code")
library(dynamicmultiplex)
sim <- simulate_and_fit_multilayer(
directed = TRUE,
n_nodes = 50,
n_layers = 4,
n_communities = 3,
fit_type = "jaccard",
algorithm = "louvain",
seed = 123
)
head(sim$fit$interlayer_ties)
# Custom layer influence map
custom_links <- data.frame(
from = c(1, 2),
to = c(2, 4),
weight = c(1, 0.6)
)
fit_overlap <- fit_multilayer_overlap(
sim$layers,
algorithm = "leiden",
layer_links = custom_links,
min_similarity = 0.1,
add_self_loops = TRUE,
self_loop_multiplier = 1
)
# 1) Static series of network panels
plot_multilayer_series(sim$layers, fit = sim$fit, directed = TRUE, palette = "Dark2")
# 2) GIF animation with colorblind-friendly community colors (`Set2`/`Dark2` from RColorBrewer)
animate_multilayer_gif(
sim$layers,
fit = sim$fit,
output_file = "multilayer_communities.gif",
directed = TRUE,
fps = 2,
palette = "Set2"
)
# 3) Alluvial plot for community flow across temporal layers
plot_multilayer_alluvial(sim$fit, max_nodes = 100, palette = "Dark2")