Skip to content

Add multislice modularity (Mucha et al. 2010) #22

Description

@jhollway

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions