No membership function in {netrics} currently handles a multiplex or
longitudinal network, although {manynet} exports everything needed to split
one: to_slices(), to_layers(), is_longitudinal(), is_multiplex().
Multislice modularity (Mucha, Richardson, Macon, Porter & Onnela 2010) is the
established way to detect communities across slices at once, so that a community
keeps its identity from one wave, or one layer, to the next.
Reference: Science 328:876-878, https://doi.org/10.1126/science.1184819
The trap, first
Do not build one supra-adjacency network and hand it to
igraph::modularity(). That applies a single global configuration null model
across all slices, which is not what Mucha's Q measures.
Q needs a per-slice null model: the expected tie between i and j in
slice s is k_is * k_js / 2m_s, with the degrees and the tie count taken
within slice s alone. So the modularity matrix has to be built directly:
B[(i,s),(j,r)] = ( A_ijs - resolution * k_is*k_js / (2*m_s) ) * delta(s,r)
+ coupling * delta(i,j) * C_sr
coupling is Mucha's omega. C_sr is the coupling scheme:
- ordinal, coupling consecutive slices only, for a longitudinal network;
- categorical, coupling every pair of layers, for a multiplex one.
Slices come from to_slices() and to_layers(). Re-pad each to the full node
set: to_uniplex() drops nodes holding none of the retained ties, as the
comment at R/measure_centrality_degree.R:192-195 warns.
Where it goes
Generalized Louvain is Louvain run on B instead of on an adjacency matrix. So
it belongs as a coupling argument on the existing node_in_louvain(), and on
net_by_modularity() for scoring, rather than as a new function. This also
matches the body convention of branching on network type inside the function.
igraph has no multilayer backend, and the cluster_leiden() + node_weights
route cannot express a per-slice null model, so a self-contained generalized
Louvain in plain R is needed. Roughly 150 lines: local moves that maximise the
gain in B, then aggregate node-slice tuples into supernodes, then repeat.
Open question: the return shape
A multislice partition labels each node-slice tuple, not each node.
make_node_member() at R/class_metrics.R:146 produces a flat vector of length
net_nodes(), and print.node_member() and summary.node_member() in
{manynet} both assume that.
Suggested first answer: return a named list of node_member vectors, one per
slice, with labels consistent across slices. Consistent labelling is the whole
point of detecting across slices, and a list needs no change in {manynet}. It
also matches the {manynet} convention that to_*s() returns a list.
The eventual home is a node x slice matrix carried by an extended
make_node_member(), but that needs companion print and summary methods in
{manynet} first.
Test that catches a wrong null model
On a network whose slices are identical, Q at coupling = 0 must equal the mean
of the per-slice single-layer Q values. An implementation built on a global null
model fails this.
Related
Ensemble-based detection over multilayer networks is the neighbouring idea, and
was stubbed out in R/member_community.R before being removed:
Tagarelli, Andrea, Alessia Amelio, and Francesco Gullo. 2017.
"Ensemble-based Community Detection in Multilayer Networks".
Data Mining and Knowledge Discovery 31: 1506-1543.
https://doi.org/10.1007/s10618-017-0528-8
No membership function in
{netrics}currently handles a multiplex orlongitudinal network, although
{manynet}exports everything needed to splitone:
to_slices(),to_layers(),is_longitudinal(),is_multiplex().Multislice modularity (Mucha, Richardson, Macon, Porter & Onnela 2010) is the
established way to detect communities across slices at once, so that a community
keeps its identity from one wave, or one layer, to the next.
Reference: Science 328:876-878, https://doi.org/10.1126/science.1184819
The trap, first
Do not build one supra-adjacency network and hand it to
igraph::modularity(). That applies a single global configuration null modelacross all slices, which is not what Mucha's Q measures.
Q needs a per-slice null model: the expected tie between
iandjinslice
sisk_is * k_js / 2m_s, with the degrees and the tie count takenwithin slice
salone. So the modularity matrix has to be built directly:couplingis Mucha's omega.C_sris the coupling scheme:Slices come from
to_slices()andto_layers(). Re-pad each to the full nodeset:
to_uniplex()drops nodes holding none of the retained ties, as thecomment at
R/measure_centrality_degree.R:192-195warns.Where it goes
Generalized Louvain is Louvain run on
Binstead of on an adjacency matrix. Soit belongs as a
couplingargument on the existingnode_in_louvain(), and onnet_by_modularity()for scoring, rather than as a new function. This alsomatches the body convention of branching on network type inside the function.
igraphhas no multilayer backend, and thecluster_leiden()+node_weightsroute cannot express a per-slice null model, so a self-contained generalized
Louvain in plain R is needed. Roughly 150 lines: local moves that maximise the
gain in
B, then aggregate node-slice tuples into supernodes, then repeat.Open question: the return shape
A multislice partition labels each node-slice tuple, not each node.
make_node_member()atR/class_metrics.R:146produces a flat vector of lengthnet_nodes(), andprint.node_member()andsummary.node_member()in{manynet}both assume that.Suggested first answer: return a named list of
node_membervectors, one perslice, with labels consistent across slices. Consistent labelling is the whole
point of detecting across slices, and a list needs no change in
{manynet}. Italso matches the
{manynet}convention thatto_*s()returns a list.The eventual home is a node x slice matrix carried by an extended
make_node_member(), but that needs companionprintandsummarymethods in{manynet}first.Test that catches a wrong null model
On a network whose slices are identical, Q at
coupling = 0must equal the meanof the per-slice single-layer Q values. An implementation built on a global null
model fails this.
Related
Ensemble-based detection over multilayer networks is the neighbouring idea, and
was stubbed out in
R/member_community.Rbefore being removed: