Multilevel networks#

A multilevel network has vertices at several levels, such as researchers and the laboratories they belong to, with ties within each level (advice among researchers, collaboration among laboratories) and ties across levels (the affiliations) (Lazega and Snijders 2016). In ergmx it is one network with a vertex attribute for the level. Wang, Robins, Pattison and Lazega (2013) proposed ERGMs for such two-level networks, as their program MPNet fits them: a level A, a level B, the A-ties within A, the B-ties within B, and the X-ties between them, with effects for each network and for how they depend on each other.

linked_sim, from the multinets package, has 100 individuals and 50 organizations:

import ergmx
from ergmx import datasets

linked = datasets.load("linked_sim")
ergmx.summary_stats(linked, "nodemix('level', levels2=TRUE)")
{'mix.level.individual.individual': 304.0,
 'mix.level.individual.organization': 207.0,
 'mix.level.organization.organization': 89.0}

Each level, and the ties between them#

The operator S(), as ergm’s, evaluates terms on a subgraph. With one set of vertices, ~level == 'individual', it is the network within that level, so any ergm term describes it; with two, (level == 'individual') ~ (level == 'organization'), it is the bipartite network of the ties between them, whose b1 terms are about the first set:

ergmx.summary_stats(
    linked,
    "S(~edges + gwesp(0.693147, fixed=TRUE), ~level == 'individual') "
    "+ S(~edges + b1star(2) + b2star(2), (level == 'individual') ~ (level == 'organization'))",
)
{'S(level=="individual")~edges': 304.0,
 'S(level=="individual")~gwesp.fixed.0.693147': 102.1249985780903,
 'S((level=="individual"),(level=="organization"))~edges': 207.0,
 'S((level=="individual"),(level=="organization"))~b1star2': 202.0,
 'S((level=="individual"),(level=="organization"))~b2star2': 429.0}

So MPNet’s effects within each level and within the affiliation network are ergm terms inside S(), with MPNet’s lambda as exp(decay) (MPNet’s default lambda = 2 is a decay of log 2 = 0.693147):

MPNet

ergmx, inside S(~..., ~level == A) (or B)

EdgeA, Star2A, TriangleA

edges, kstar(2), triangle

ATA (alternating triangles)

gwesp(log(lambda), fixed=TRUE), the same statistic

A2PA (alternating two-paths)

gwdsp(log(lambda), fixed=TRUE), the same statistic

ASA (alternating stars)

gwdegree(log(lambda), fixed=TRUE): with edges, the same model

MPNet

ergmx, inside S(~..., (level == A) ~ (level == B))

XEdge, XStar2A, XStar2B

edges, b1star(2), b2star(2)

XASA, XASB (alternating stars)

gwb1degree, gwb2degree: with edges, the same model

XC4 (four-cycles)

cycle(4)

XACA, XACB (alternating two-paths)

gwb1dsp, gwb2dsp

How the levels depend on each other#

The configurations that join ties of different kinds are MPNet’s, written as ergmx terms whose first argument is the level attribute, A and B being its two values in sorted order (or levels=(A, B)). In Wang et al.’s interpretation, with laboratories as A and researchers as B:

Term

Configuration

Interpretation

star2ax, star2bx

a vertex with a within-level tie and an affiliation

affiliation-based popularity: those active within their level have more affiliations

axs1a, aas1x, aaaxs (and b)

the same, alternating in one or both kinds of tie

the same, attenuated

txax, txbx

a within-level tie whose ends share an affiliation

affiliation-based closure: researchers of the same laboratory seek each other’s advice

atxax, atxbx

the same, alternating in the shared affiliations

the same, attenuated

l3xax, l3xbx

an affiliation, a within-level tie, an affiliation

meso-level popularity and within-level activity

l3axb

an A-tie, an affiliation, a B-tie

assortativity of activity across levels: active researchers belong to active laboratories

c4axb

an A-tie, a B-tie and the two affiliations that join their ends

cross-level alignment: members of collaborating laboratories seek each other’s advice

exta, extb

a triangle within a level with an affiliation at one of its vertices

affiliations of the members of closed groups

asaxasb

alternating stars at both ends of an affiliation

assortativity of activity across levels, attenuated

The term reference gives each one’s statistic, and MPNet’s configurations of directed networks (below). A model of the three networks and their interdependence:

mpnet = ergmx.ergm(
    linked,
    "S(~edges + gwesp(0.693147, fixed=TRUE), ~level == 'individual') "
    "+ S(~edges + gwesp(0.693147, fixed=TRUE), ~level == 'organization') "
    "+ S(~edges + gwb1degree(0.693147, fixed=TRUE), (level == 'individual') ~ (level == 'organization')) "
    "+ star2ax('level') + star2bx('level') + txax('level') + txbx('level') + c4axb('level')",
    seed=1,
)
mpnet.summary()
Monte Carlo Maximum Likelihood Results:

                                                                          Estimate  Std. Error  MCMC %  z value  Pr(>|z|)
S(level=="individual")~edges                                               -2.4175      0.1634       0  -14.798    <1e-04 ***
S(level=="individual")~gwesp.fixed.0.693147                                -0.0125      0.0663       0   -0.188   0.85059 
S(level=="organization")~edges                                             -2.3778      0.3661       0   -6.496    <1e-04 ***
S(level=="organization")~gwesp.fixed.0.693147                               0.1913      0.1229       0    1.556   0.11972 
S((level=="individual"),(level=="organization"))~edges                     -2.7010      0.2582       0  -10.462    <1e-04 ***
S((level=="individual"),(level=="organization"))~gwb1deg.fixed.0.693147     0.3565      0.3991       0    0.893   0.37164 
Star2AX.level                                                              -0.0704      0.0358       0   -1.965   0.04944 *
Star2BX.level                                                              -0.0364      0.0457       0   -0.795   0.42664 
TXAX.level                                                                 -0.0435      0.2216       0   -0.196   0.84437 
TXBX.level                                                                  0.0338      0.2848       0    0.119   0.90557 
C4AXB.level                                                                -0.0355      0.1162       0   -0.306   0.75987 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Log-likelihood: -2318.4725 (MC SE 0.136)   AIC: 4658.9451   BIC: 4739.4809
Converged after 3 iterations (4 chains, 1024 samples).

In this simulated network, the cross-level effects are small: only the individuals’ within-level activity is slightly negatively associated with their affiliations (Star2AX), and there is no cross-level alignment (C4AXB).

Goodness of fit by level#

gof(by=...) compares the distributions of each level’s network (degrees, edgewise shared partners and distances within the level) and each level’s number of affiliations with those of simulated networks, as MPNet’s goodness of fit does for networks A, B and X:

mpnet.gof(by="level", seed=1, stats=["degree", "espartners", "affiliations"]).plot();
../_images/e9f056212b308f7c9ce79031fbf3d34b190c09f62aa38e0f5cc39b3591741d6b.png

The model reproduces the degrees and shared partners within both levels. The numbers of affiliations are close, with a few more individuals with four and organizations with ten than the simulated networks usually have.

Estimating the decays#

MPNet fixes the weights of its alternating configurations; with fixed=FALSE, ergmx estimates the decay along with the coefficients, as ergm’s curved terms, from the start decay (by default log 2):

ergmx.ergm(network, "... + atxax('level', fixed=FALSE) + axs1b('level', fixed=FALSE)")

The statistics are the counts of the histogram each term weights (for atxax, the A-ties with 1, 2… shared B partners), and the parameters the coefficient and its decay, ATXAX.level and ATXAX.level.decay. A decay is only identified if the network has a spread of those counts: in linked_sim, few ties share more than one partner, and the decays are not. Terms with two alternating parts (aaaxs, abaxs, asaxasb and the directed ainasxainbs…) need a fixed decay.

Directed multilevel networks#

In a directed network, the ties within each level are arcs (advice among researchers, partnerships from one laboratory to another), and the affiliations are the arcs from the vertices of level A to those of level B (A and B are the attribute’s values in sorted order, or levels=(A, B)). The dyads from B to A carry no affiliation: fix them with blocks(), whose second mixing type in a directed network is from the second level to the first. S() with two sets takes the arcs from the first set to the second, as in ergm. MPNet’s directed configurations then distinguish incoming and outgoing ties, arcs and reciprocated pairs:

model = ergmx.ergm(
    network,
    "S(~edges + mutual, ~level == 'A') + S(~edges + mutual, ~level == 'B') "
    "+ S(~edges, (level == 'A') ~ (level == 'B')) "
    "+ in2starax('level') + txaxarc('level') + txbxreciprocity('level') + c4axbentrainment('level')",
    constraints="blocks('level', levels2=2)",
)

c4axbentrainment counts the four-cycles where an A-arc and a B-arc go the same way between affiliated vertices (if laboratory u advises v, the researchers of u advise those of v); c4axbexchange those where they go opposite ways. The term reference lists them all.

The tools below model the same undirected network by kinds of tie.

Densities by kind of tie#

nodemix('level', levels2=TRUE) has one statistic per mixing type, so without an edges term each coefficient is the log-odds of a tie of that kind:

densities = ergmx.ergm(linked, "nodemix('level', levels2=TRUE)")
densities.summary()
Maximum Likelihood Results:

                                      Estimate  Std. Error  MCMC %  z value  Pr(>|z|)
mix.level.individual.individual        -2.7267      0.0592       0  -46.059    <1e-04 ***
mix.level.individual.organization      -3.1422      0.0710       0  -44.263    <1e-04 ***
mix.level.organization.organization    -2.5466      0.1101       0  -23.136    <1e-04 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Log-likelihood: -2323.5528   AIC: 4653.1055   BIC: 4675.0698

Affiliation ties are rarer, relative to the number of pairs, than ties within either level: about 4% of individual–organization pairs are tied, against 6% of pairs of individuals and 7% of pairs of organizations.

Closure within levels#

Do ties within a level close triangles? F(~gwesp(0.5, fixed=TRUE), ~nodematch('level')) computes gwesp on the network of the ties within levels only:

closure = ergmx.ergm(
    linked,
    "nodemix('level', levels2=TRUE) + F(~gwesp(0.5, fixed=TRUE), ~nodematch('level'))",
    seed=1,
)
closure.summary()
Monte Carlo Maximum Likelihood Results:

                                        Estimate  Std. Error  MCMC %  z value  Pr(>|z|)
mix.level.individual.individual          -2.7639      0.0816       0  -33.883    <1e-04 ***
mix.level.individual.organization        -3.1434      0.0727       0  -43.262    <1e-04 ***
mix.level.organization.organization      -2.5769      0.1151       0  -22.387    <1e-04 ***
F(nodematch("level"))~gwesp.fixed.0.5     0.0415      0.0596       0    0.696   0.48652 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Log-likelihood: -2323.3112 (MC SE 0.010)   AIC: 4654.6225   BIC: 4683.9082
Converged after 3 iterations (4 chains, 1024 samples).
ergmx.compare(densities, closure)
Model comparison:

      df        log-likelihood         AIC         BIC     dAIC   LR chi2   df  Pr(>chi2)
  1    3             -2323.553     4653.11     4675.07     0.00
  2    4     -2323.311 (0.010)     4654.62     4683.91     1.52      0.48    1      0.487

  1: nodemix('level', levels2=True)
  2: nodemix('level', levels2=True) + F(gwesp(0.5, fixed=True), nodematch('level'))

Log-likelihoods of dyad-dependent models are Monte Carlo estimates (standard errors in parentheses).

Not in this simulated network: the coefficient is close to 0, and the comparison prefers the model without it.

The filter is any dyad-independent term with one statistic; a tie passes if adding it would change that statistic. ~!nodematch('level') keeps the other ties, here the affiliations.

Given the affiliations#

To model the ties within levels taking the affiliations as given, fix the individual–organization dyads with blocks. Mixing types are numbered as nodemix orders them: (individual, individual), (individual, organization), (organization, organization), so the affiliations are type 2:

within = ergmx.ergm(
    linked,
    "nodemix('level', levels2=c(1, 3)) + F(~gwesp(0.5, fixed=TRUE), ~nodematch('level'))",
    constraints="blocks('level', levels2=2)",
    seed=1,
)
within.summary()
Monte Carlo Maximum Likelihood Results:

                                        Estimate  Std. Error  MCMC %  z value  Pr(>|z|)
mix.level.individual.individual          -2.7667      0.0825       0  -33.545    <1e-04 ***
mix.level.organization.organization      -2.5781      0.1149       0  -22.444    <1e-04 ***
F(nodematch("level"))~gwesp.fixed.0.5     0.0414      0.0601       0    0.688   0.49156 
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Log-likelihood: -1461.4597 (MC SE 0.009)   AIC: 2928.9193   BIC: 2949.1041
Constraints: blocks('level', levels2=2).
Converged after 3 iterations (4 chains, 1024 samples).

blocks is dyad-independent, so the log-likelihood is still absolute, over the free dyads, and BIC counts only those.

Fixed coefficients#

offset() fixes a coefficient rather than estimating it, for example to compare networks of different sizes with a density adjusted for the number of vertices (Krivitsky, Handcock and Morris 2011), or to forbid a kind of tie with a coefficient of -inf:

# The same network without ties between organizations.
organizations = set(linked.vs.select(level="organization").indices)
without = linked.copy()
without.delete_edges([e for e in without.es if {e.source, e.target} <= organizations])

forbidden = ergmx.ergm(
    without,
    "nodemix('level', levels2=c(1, 2)) + offset(nodemix('level', levels2=3))",
    offset_coef=[float("-inf")],
)
forbidden.summary()
Maximum Likelihood Results:

                                              Estimate  Std. Error  MCMC %  z value  Pr(>|z|)
mix.level.individual.individual                -2.7267      0.0592       0  -46.059    <1e-04 ***
mix.level.individual.organization              -3.1422      0.0710       0  -44.263    <1e-04 ***
offset(mix.level.organization.organization)       -inf                                         (offset)
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Log-likelihood: -2004.5038   AIC: 4013.0076   BIC: 4027.4182

With the -inf offset, ties between organizations are impossible: the model is fitted on the other dyads, and its simulated networks never have them.