Multilayer Networks: Independent Layers

SI
multilayer networks
network structure
beginner
The simplest multilayer model: an SI epidemic over two independent network layers (same nodes, different edge sets). The entry point to multilayer modeling, before cross-layer dependency.
Author

Samuel M. Jenness

Published

June 28, 2026

Overview

A multilayer network is two or more network layers that share the same node set but have different edge sets, representing distinct kinds of relationships. For example, the same people may have main partnerships (steady, long-lasting) and casual partnerships (more transient). An epidemic can spread over edges in either layer.

This example is the entry point to multilayer modeling. The two layers here are independent: a person’s number of partners in one layer does not constrain the other. That keeps the mechanics minimal, so the only genuinely new idea is that you fit each layer as an ordinary network model and then hand netsim() a list of them. There is no simulated annealing, no cross-layer attribute, and no update callback. Those are needed only when the layers depend on each other, which is the companion Multilayer Networks (cross-layer dependency) example.

If you have built a single-layer network model before (an edges model with netest() and netsim()), you already know almost everything here.

TipDownload standalone script
  • model.R — Main simulation script (no module-fx.R needed; built-in SI modules)

Model Structure

Compartment Label Description
Susceptible S Not infected; at risk via contact on either layer
Infectious I Infected and capable of transmitting (no recovery in SI)

flowchart LR
    S["<b>S</b><br/>Susceptible"] -->|"infection<br/>(si.flow)"| I["<b>I</b><br/>Infectious"]
    style S fill:#3498db,color:#fff
    style I fill:#e74c3c,color:#fff

The key picture to hold in mind: one set of people, two separate edge sets drawn over them. A node can have several main partners and no casual partners, or the reverse. Because the layers are independent here, those two partner counts are unrelated.

Property Layer 1 (main) Layer 2 (casual)
Interpretation Steady, long-lasting partnerships Transient, higher-turnover contacts
Target edges 90 75
Formation terms edges + nodematch("race") edges + degree(1)
Mean duration 200 time steps 20 time steps
Cross-layer term none (independent) none (independent)

Setup

suppressMessages(library(EpiModel))

nsims <- 5
ncores <- 5
nsteps <- 500

Network Model

One shared node set carries both layers. We add a binary race attribute used for homophily in layer 1.

n <- 500
nw <- network_initialize(n)
nw <- set_vertex_attribute(nw, "race", rep(0:1, length.out = n))

Each layer is an ordinary single-layer TERGM, fit exactly as in the earlier Gallery examples. Nothing here is multilayer-specific yet.

est1 <- netest(
  nw,
  formation = ~edges + nodematch("race"),
1  target.stats = c(90, 60),
2  coef.diss = dissolution_coefs(~offset(edges), duration = 200)
)

est2 <- netest(
  nw,
3  formation = ~edges + degree(1),
  target.stats = c(75, 120),
4  coef.diss = dissolution_coefs(~offset(edges), duration = 20)
)
1
Layer 1: 90 edges total (mean degree 0.36), 60 of them race-homophilous.
2
Layer 1 ties are long-lasting (mean duration 200 time steps).
3
Layer 2’s formation references only its own structure (degree(1)); it does not reference layer 1. That independence is what removes the need for any cross-layer machinery.
4
Layer 2 ties turn over quickly (mean duration 20 time steps), the main-vs-casual contrast.

Diagnostics

Diagnose each layer separately, as you would any single-layer model.

dx1 <- netdx(est1, nsims = nsims, ncores = ncores, nsteps = nsteps)

Network Diagnostics
-----------------------
- Simulating 5 networks
- Calculating formation statistics
print(dx1)
EpiModel Network Diagnostics
=======================
Diagnostic Method: Dynamic
Simulations: 5
Time Steps per Sim: 500

Formation Diagnostics
----------------------- 
               Target Sim Mean Pct Diff Sim SE Z Score SD(Sim Means)
edges              90   87.658   -2.602  1.449  -1.616         6.651
nodematch.race     60   59.190   -1.351  1.209  -0.670         3.877
               SD(Statistic)
edges                  8.058
nodematch.race         5.910

Duration Diagnostics
----------------------- 
      Target Sim Mean Pct Diff Sim SE Z Score SD(Sim Means) SD(Statistic)
edges    200  203.373    1.686  4.032   0.837        12.322        19.226

Dissolution Diagnostics
----------------------- 
      Target Sim Mean Pct Diff Sim SE Z Score SD(Sim Means) SD(Statistic)
edges  0.005    0.005    1.094      0   0.365             0         0.008
plot(dx1)

dx2 <- netdx(est2, nsims = nsims, ncores = ncores, nsteps = nsteps)

Network Diagnostics
-----------------------
- Simulating 5 networks
- Calculating formation statistics
print(dx2)
EpiModel Network Diagnostics
=======================
Diagnostic Method: Dynamic
Simulations: 5
Time Steps per Sim: 500

Formation Diagnostics
----------------------- 
        Target Sim Mean Pct Diff Sim SE Z Score SD(Sim Means) SD(Statistic)
edges       75   75.434    0.578  0.874   0.496         1.899         8.295
degree1    120  119.623   -0.314  1.190  -0.317         2.926        12.398

Duration Diagnostics
----------------------- 
      Target Sim Mean Pct Diff Sim SE Z Score SD(Sim Means) SD(Statistic)
edges     20   19.749   -1.253  0.203  -1.234         0.461         2.039

Dissolution Diagnostics
----------------------- 
      Target Sim Mean Pct Diff Sim SE Z Score SD(Sim Means) SD(Statistic)
edges   0.05     0.05   -0.759  0.001  -0.685         0.001         0.026
plot(dx2)
Figure 1: Per-layer network diagnostics confirming each layer reproduces its target statistics.
Figure 2: Per-layer network diagnostics confirming each layer reproduces its target statistics.

Epidemic Simulation

A simple SI model in a closed population, so the focus stays on the network mechanics. Transmission can occur over edges in either layer.

param <- param.net(inf.prob = 0.5, act.rate = 2)
init <- init.net(i.num = 10)

The control settings differ from a single-layer model in only two ways, and one of them is optional.

control <- control.net(
  type = "SI",
  nsteps = nsteps,
  nsims = nsims,
  ncores = ncores,
  tergmLite = TRUE,
1  resimulate.network = TRUE,
2  nwstats.formula = multilayer(
    ~edges + nodematch("race"),
    ~edges + degree(0:3)
  )
)

3sim <- netsim(list(est1, est2), param, init, control)
1
Redraw both layers each time step. Required whenever the network is dynamic.
2
multilayer() maps a per-layer diagnostic formula to each layer by position. This is optional; omit it and EpiModel tracks each layer’s own formation formula by default.
3
Passing a list of netest objects is the one step that makes this multilayer. The list order is the layer numbering. Note what is absent: no san(), no cross-layer attributes, and no dat.updates callback are needed, because neither layer’s formation model reads from the other.

Analysis

First confirm the network structure held up during the run.

print(sim, network = 1)
EpiModel Simulation
=======================
Model class: netsim

Simulation Summary
-----------------------
Model type: SI
No. simulations: 5
No. time steps: 500
No. NW groups: 1

Fixed Parameters
---------------------------
inf.prob = 0.5
act.rate = 2
groups = 1

Model Output
-----------------------
Variables: s.num i.num num si.flow
Networks: sim1 ... sim5
Transmissions: sim1 ... sim5

Formation Statistics
----------------------- 
               Target Sim Mean Pct Diff Sim SE Z Score SD(Sim Means)
edges              90   95.142    5.713  2.236   2.300         9.702
nodematch.race     60   61.643    2.739  2.214   0.742         8.798
               SD(Statistic)
edges                 11.007
nodematch.race         9.961


Duration and Dissolution Statistics
----------------------- 
Not available when:
- `control$tergmLite == TRUE`
- `control$save.network == FALSE`
- `control$save.diss.stats == FALSE`
- dissolution formula is not `~ offset(edges)`
- `keep.diss.stats == FALSE` (if merging)
plot(sim, network = 1, type = "formation", main = "Layer 1 (main) formation")

print(sim, network = 2)
EpiModel Simulation
=======================
Model class: netsim

Simulation Summary
-----------------------
Model type: SI
No. simulations: 5
No. time steps: 500
No. NW groups: 1

Fixed Parameters
---------------------------
inf.prob = 0.5
act.rate = 2
groups = 1

Model Output
-----------------------
Variables: s.num i.num num si.flow
Networks: sim1 ... sim5
Transmissions: sim1 ... sim5

Formation Statistics
----------------------- 
        Target Sim Mean Pct Diff Sim SE Z Score SD(Sim Means) SD(Statistic)
edges       75   73.539   -1.948  0.882  -1.656         0.719         7.982
degree0     NA  368.604       NA  1.477      NA         1.453        13.477
degree1    120  116.982   -2.515  1.279  -2.359         1.463        12.310
degree2     NA   13.230       NA  0.299      NA         0.384         3.766
degree3     NA    1.105       NA  0.073      NA         0.132         1.076


Duration and Dissolution Statistics
----------------------- 
Not available when:
- `control$tergmLite == TRUE`
- `control$save.network == FALSE`
- `control$save.diss.stats == FALSE`
- dissolution formula is not `~ offset(edges)`
- `keep.diss.stats == FALSE` (if merging)
plot(sim, network = 2, type = "formation", main = "Layer 2 (casual) formation")
Figure 3: Formation diagnostics during the epidemic, per layer.
Figure 4: Formation diagnostics during the epidemic, per layer.

Now the epidemiological payoff: the disease curve over the two-layer network.

par(mfrow = c(1, 1), mar = c(3, 3, 2, 1), mgp = c(2, 1, 0))
plot(sim, y = c("s.num", "i.num"), legend = TRUE,
     main = "SI Epidemic on Two Independent Layers")

sim <- mutate_epi(sim, prev = i.num / num)
plot(sim, y = "prev", main = "Prevalence", legend = FALSE)
plot(sim, y = "si.flow", main = "Incidence (new infections per step)", legend = FALSE)
Figure 5: SI epidemic over two independent layers: compartment counts, prevalence, and incidence.
Figure 6: SI epidemic over two independent layers: compartment counts, prevalence, and incidence.
Figure 7: SI epidemic over two independent layers: compartment counts, prevalence, and incidence.

The short-duration casual layer tends to drive early spread, while the long-duration main layer sustains transmission over time. Because transmission can occur on either layer, the overall epidemic reflects contributions from both.

Parameters

Parameter Description Value
inf.prob Per-act transmission probability 0.5
act.rate Acts per partnership per time step 2
Layer 1 edges / homophily edges / nodematch("race") 90 / 60
Layer 2 edges / degree-1 edges / degree(1) 75 / 120
Layer 1 / Layer 2 duration mean edge duration 200 / 20

Key EpiModel Functions for Multilayer Models

Function / Argument Purpose
netsim(list(est1, est2), ...) Passing a list of netest objects signals a multilayer model; list order sets layer numbering
resimulate.network = TRUE Redraw the dynamic layers each time step
multilayer() in nwstats.formula Optional per-layer diagnostic statistics
print(sim, network = k) / plot(sim, network = k, ...) Inspect a specific layer

Next Steps

  • Cross-layer dependency: make a person’s activity in one layer reduce their activity in the other (finite relational capacity, negative degree correlation). This adds a cross-layer formation term, a one-time san() bootstrap, and an update callback. See Multilayer Networks (cross-layer dependency).
  • Layer-specific transmission: different inf.prob or act.rate by edge type (e.g., lower transmission on casual ties).
  • More than two layers: add a third layer (e.g., a one-off contact layer with duration = 1) and pass netsim(list(est1, est2, est3), ...).
  • Different disease models: extend to SIR or SEIR using a custom progression module, see Adding an Exposed State.