For multi-layer networks, you may be interested in how community structure changes from one layer to another. We say that variable nodes are “flexible.” This idea has been pursued often, usually in the case of time-varying networks but also in multi-subject networks. In either case, you need a mathematical framework for representing networks at different times or from different individuals and also a way to detect communities given that mathematical representation.

To do this, we use multi-layer networks and, more specifically, multi-layer modularity maximization. Using this approach we assemble a series of otherwise independent networks into a single mathematical object — a multi-layer network — which can be clustered and decomposed into communities like any other network. Given these communities, we can track their changes across layers, enabling us to measure each node’s flexibility.

Here, we show an example using time-varying functional networks. As with most applications of modularity maximization, we’ll use the excellent GenLouvain package. In addition to this explanation, all of the code is freely available here.

To start, we load in some fMRI BOLD time series and set some parameters. First, in order to create a series of network “snapshots” — estimates of network structure at windows in time — we need to decide how long these windows will be. Here, I chose to make them span 60 samples (with the 2 second TR, this amounts to windows that are 120 seconds long).

We also need to set three parameters for the community detection part of this code. First, we choose the resolution parameter, gamma, which determines the number/size of communities. The second is the interlayer coupling parameter, omega. Omega determines how strongly any two layers are dependent upon one another. Effectively, it controls the temporal variability of communities — when omega is small, the detected communities will emphasize the unique features of each layer, but when omega is large the detected communities will reflect shared features. While gamma can, in principle be less than zero, omega must be zero or greater.

The final parameter is really more of a modeling decision and concerns whether we link all layers to all layers or whether we only link adjacent layers to one another, i.e. layer 1 to 2, layer 2 to 3, layer 3 to 4, etc. This choice is not trivial but is usually made on the basis of whether or not our networks incorporate some notion of time. In this case, we are modeling time-varying networks, so we use the temporal coupling (which we call “ordered” in this example). For multi-layer networks with no temporal information where layers correspond to different subjects, imaging modalities, or frequency bands, the all-to-all coupling structure is more appropriate.

Once we’ve specified these parameters, we can populate our multi-layer matrix. The matrix is represented by the variable B and is really the “flattened” form a four-dimensional tensor. It has dimensions equal to N x T, where N is the number of nodes and T the number of layers (windows). Along the diagonal are blocks of length N in which we embed single-layer modularity matrices for each of the T layers. That is, we go layer by layer (window by window) calculate the current network, generate its modularity matrix, and stick it on the diagonal.

Now, we incorporate the inter-layer connections. The code is written so that if you set “couplingtype = ‘ordered’” then you’ll get the temporal couplings, but if you set it to ‘categorical’ then you get all-to-all.

We’re almost done with setup. All that’s left to do is run community detection and calculate flexibility. Community detection is carried out just like before — give genlouvain.m our multi-layer modularity matrix, B, and it returns community labels for each node. Here, we reshape the vectorized list of communities so that we have T columns (one for each window/layer) and N rows (one for each node).

Lastly, we calculate flexibility. Flexibility literally is counts the frequency with which a given node changes its community assignment between consecutive layers. We can calculate this using logical operations to identify changes and then average those changes across layers. The end result is a vector of node-level flexibility values, which we save as “flx.” To obtain global flexibility, just average of the whole vector.