Setup & data

# loadedNamespaces()
# unloadNamespace("ergm")
# unloadNamespace("statnet.common")

master_lib <- "~/R/GHmaster-library"

# #Make lib 1st position in libPath
# defaultPaths <- .libPaths()
# .libPaths(c(master_lib, defaultPaths))
# .libPaths()

#devtools::dev_mode(on=T, path = .libPaths()[1])
#library(network)
#library(ergm)
library(tergm, lib.loc = master_lib)
library(tsna)

sessioninfo::package_info(pkgs = c("statnet.common", "network", "rle",
                                   "ergm", "tergm"),
                          dependencies = FALSE)
 package        * version    date       lib
 ergm           * 4.0-6512   2021-06-22 [1]
 network        * 1.17.1-685 2021-06-16 [1]
 rle              0.9.2-234  2021-06-16 [1]
 statnet.common   4.5.0-362  2021-06-16 [1]
 tergm          * 4.0-2301   2021-06-22 [1]
 source                                 
 Github (statnet/ergm@763ddda)          
 Github (statnet/network@253413a)       
 Github (statnet/rle@d08b185)           
 Github (statnet/statnet.common@e3c23c1)
 Github (statnet/tergm@eac9f3b)         

[1] C:/Users/Martina Morris/Documents/R/GHmaster-library
[2] C:/Users/Martina Morris/Documents/R/win-library/4.0
[3] C:/Program Files/R/R-4.0.5/library

Types of networks

first network

Standard network summary

data(samplk)
samplk1
 Network attributes:
  vertices = 18 
  directed = TRUE 
  hyper = FALSE 
  loops = FALSE 
  multiple = FALSE 
  bipartite = FALSE 
  total edges= 55 
    missing edges= 0 
    non-missing edges= 55 

 Vertex attribute names: 
    cloisterville group vertex.names 

No edge attributes

list

Replicates the standard summary for each network

# summary shows the nodes & edges at each slice
samp.list <- list(samplk1,samplk2,samplk3)
samp.list
[[1]]
 Network attributes:
  vertices = 18 
  directed = TRUE 
  hyper = FALSE 
  loops = FALSE 
  multiple = FALSE 
  bipartite = FALSE 
  total edges= 55 
    missing edges= 0 
    non-missing edges= 55 

 Vertex attribute names: 
    cloisterville group vertex.names 

No edge attributes

[[2]]
 Network attributes:
  vertices = 18 
  directed = TRUE 
  hyper = FALSE 
  loops = FALSE 
  multiple = FALSE 
  bipartite = FALSE 
  total edges= 57 
    missing edges= 0 
    non-missing edges= 57 

 Vertex attribute names: 
    cloisterville group vertex.names 

No edge attributes

[[3]]
 Network attributes:
  vertices = 18 
  directed = TRUE 
  hyper = FALSE 
  loops = FALSE 
  multiple = FALSE 
  bipartite = FALSE 
  total edges= 56 
    missing edges= 0 
    non-missing edges= 56 

 Vertex attribute names: 
    cloisterville group vertex.names 

No edge attributes

network.list

Only shows us that there are 3 network objects in this list

# summary only shows there are 3 network objects as components
samp.netlist <- network.list(samp.list)
samp.netlist
Number of Networks: 3 

NetSeries

Summary shows what tergm will send to ergm for term construction and estimation: the sum of the second and third neworks.

# summary shows nodes(2) + nodes(3), and edges(2) + edges(3)
samp.series <- NetSeries(samp.list)
samp.series
 Combined 2 networks on '.NetworkID':
  1: n = 18, directed = TRUE, bipartite = FALSE, loops = FALSE
  2: n = 18, directed = TRUE, bipartite = FALSE, loops = FALSE

 Network attributes:
  vertices = 36 
  directed = TRUE 
  hyper = FALSE 
  loops = FALSE 
  multiple = FALSE 
  bipartite = FALSE 
  ergm:
    constraints: ~blockdiag(".NetworkID") + discord(.PrevNet)
  total edges= 113 
    missing edges= 0 
    non-missing edges= 113 

 Vertex attribute names: 
    .NetworkID .NetworkName cloisterville group vertex.names 

No edge attributes

networkDynamic

Summary shows the unique total node and edgecount (verified with the collapsed version).

# summary shows the unique # nodes and edges in both cases
# we start at 1 to make the series index consistent with the other
# network storage mechanisms

samp.dyn <- networkDynamic(network.list = samp.list, start=1)
Onsets and termini not specified, assuming each network in network.list should have a discrete spell of length 1
Argument base.net not specified, using first element of network.list instead
Created net.obs.period to describe network
 Network observation period info:
  Number of observation spells: 1 
  Maximal time range observed: 1 until 4 
  Temporal mode: discrete 
  Time unit: step 
  Suggested time increment: 1 
samp.dyn
NetworkDynamic properties:
  distinct change times: 4 
  maximal time range: 1 until  4 

Includes optional net.obs.period attribute:
 Network observation period info:
  Number of observation spells: 1 
  Maximal time range observed: 1 until 4 
  Temporal mode: discrete 
  Time unit: step 
  Suggested time increment: 1 

 Network attributes:
  vertices = 18 
  directed = TRUE 
  hyper = FALSE 
  loops = FALSE 
  multiple = FALSE 
  bipartite = FALSE 
  net.obs.period: (not shown)
  total edges= 88 
    missing edges= 0 
    non-missing edges= 88 

 Vertex attribute names: 
    active cloisterville group vertex.names 

 Edge attribute names: 
    active 
samp.collapse <- network.collapse(samp.dyn, onset=1, terminus=4)
samp.collapse
 Network attributes:
  vertices = 18 
  directed = TRUE 
  hyper = FALSE 
  loops = FALSE 
  multiple = FALSE 
  bipartite = FALSE 
  total edges= 88 
    missing edges= 0 
    non-missing edges= 88 

 Vertex attribute names: 
    cloisterville group vertex.names 

No edge attributes

Term counts for 2 panels: using base R

These replicate the edge counts performed by the operators, which makes it easier to verify. But it only works for 2 panels. Show the code for more info.

form = network.edgecount(samplk1 | samplk2)
persist = network.edgecount(samplk1 & samplk2)
diss = -persist
dissolved = network.edgecount(samplk1) - persist
cross = network.edgecount(samplk2)
change = network.edgecount(xor(samplk1, samplk2))

cbind(form=form, persist=persist, diss=diss, dissolved=dissolved,
      cross=cross, change=change)
     form persist diss dissolved cross change
[1,]   77      35  -35        20    57     42

Descriptives

Cross sectional stats and form/diss changes from tsna

samp.stats <- cbind(edges = tErgmStats(samp.dyn, "~edges"),
                    form = tEdgeFormation(samp.dyn),
                    diss = tEdgeDissolution(samp.dyn))

samp.stats
Time Series:
Start = 1 
End = 4 
Frequency = 1 
  edges form diss
1    55   55    0
2    57   22   20
3    56   16   17
4     0    0   56

Netstats and fits

These will be model-specific. So we’ll show results for different specifications.

Form + Diss

summary_formula

Works on NetSeries and nD (but not lists or netlists), and results are very different for these two, because they represent the network differently.

# summary.list <- summary(samp.list ~
#                             Form(~edges) +
#                             Diss(~edges))
# 
# summary.netlist <- summary(samp.netlist ~
#                             Form(~edges) +
#                             Diss(~edges))

summary.fd.series <- summary(samp.series ~
                        Form(~edges) +
                        Diss(~edges))
summary.fd.series
Form~edges Diss~edges 
       150        -75 
summary.fd.nD <- summary(samp.dyn ~
                        Form(~edges) +
                        Diss(~edges),
                      at=0:3)
summary.fd.nD
     Form~edges Diss~edges
[1,]          0          0
[2,]         55          0
[3,]         77        -35
[4,]         73        -40

fit

Works for all data structures

fit.fd.list <- tergm(
  samp.list ~
    Form(~edges) +
    Diss(~edges),
  estimate = "CMLE",  times = c(1:3)
)

fit.fd.netlist <- tergm(
  samp.netlist ~
    Form(~edges) +
    Diss(~edges),
  estimate = "CMLE",  times = c(1:3)
)

fit.fd.series <- tergm(
  samp.series ~
    Form(~edges) +
    Diss(~edges),
  estimate = "CMLE" #,  times = c(1:3)
)

fit.fd.nD <- tergm(
  samp.dyn ~
    Form(~edges) +
    Diss(~edges),
  estimate = "CMLE",  times = c(1:3)
)

Compare fit output

All give the same output.

cat("Coefs")
Coefs
rbind(list=coef(fit.fd.list), 
      netlist = coef(fit.fd.netlist),
      netseries = coef(fit.fd.series),
      nD=coef(fit.fd.nD))
          Form~edges Diss~edges
list       -2.497979 -0.7065702
netlist    -2.497979 -0.7065702
netseries  -2.497979 -0.7065702
nD         -2.497979 -0.7065702
cat("nw.stats")
nw.stats
rbind(list=fit.fd.list$nw.stats, 
      netlist = fit.fd.netlist$nw.stats,
      netseries = fit.fd.series$nw.stats,
      nD= fit.fd.nD$nw.stats)
          Form~edges Diss~edges
list             150        -75
netlist          150        -75
netseries        150        -75
nD               150        -75

Form + Persist

summary_formula

Works on NetSeries and nD (but not lists or netlists), and results are very different for these two, because they represent the network differently.

# summary.list <- summary(samp.list ~
#                             Form(~edges) +
#                             Persist(~edges))
# 
# summary.netlist <- summary(samp.netlist ~
#                             Form(~edges) +
#                             Persist(~edges))

summary.fp.series <- summary(samp.series ~
                        Form(~edges) +
                        Persist(~edges))
summary.fp.series
   Form~edges Persist~edges 
          150            75 
summary.fp.nD <- summary(samp.dyn ~
                        Form(~edges) +
                        Persist(~edges),
                      at=0:3)
summary.fp.nD
     Form~edges Persist~edges
[1,]          0             0
[2,]         55             0
[3,]         77            35
[4,]         73            40

fit

Works for all data structures

fit.fp.list <- tergm(
  samp.list ~
    Form(~edges) +
    Persist(~edges),
  estimate = "CMLE",  times = c(1:3)
)

fit.fp.netlist <- tergm(
  samp.netlist ~
    Form(~edges) +
    Persist(~edges),
  estimate = "CMLE",  times = c(1:3)
)

fit.fp.series <- tergm(
  samp.series ~
    Form(~edges) +
    Persist(~edges),
  estimate = "CMLE" #,  times = c(1:3)
)

fit.fp.nD <- tergm(
  samp.dyn ~
    Form(~edges) +
    Persist(~edges),
  estimate = "CMLE",  times = c(1:3)
)

Compare fit output

All give the same output.

cat("Coefs")
Coefs
rbind(list=coef(fit.fp.list), 
      netlist = coef(fit.fp.netlist),
      netseries = coef(fit.fp.series),
      nD=coef(fit.fp.nD))
          Form~edges Persist~edges
list       -2.497979     0.7065702
netlist    -2.497979     0.7065702
netseries  -2.497979     0.7065702
nD         -2.497979     0.7065702
cat("nw.stats")
nw.stats
rbind(list=fit.fp.list$nw.stats, 
      netlist = fit.fp.netlist$nw.stats,
      netseries = fit.fp.series$nw.stats,
      nD= fit.fp.nD$nw.stats)
          Form~edges Persist~edges
list             150            75
netlist          150            75
netseries        150            75
nD               150            75

Cross + Change

summary_formula

Works on NetSeries and nD (but not lists or netlists), and results are very different for these two, because they represent the network differently.

# summary.list <- summary(samp.list ~
#                             Form(~edges) +
#                             Persist(~edges))
# 
# summary.netlist <- summary(samp.netlist ~
#                             Form(~edges) +
#                             Persist(~edges))

summary.cc.series <- summary(samp.series ~
                        Cross(~edges) +
                        Change(~edges))
summary.cc.series
       edges Change~edges 
         113           75 
summary.cc.nD <- summary(samp.dyn ~
                        Cross(~edges) +
                        Change(~edges),
                      at=0:3)
summary.cc.nD
     Passthrough~edges Change~edges
[1,]                 0            0
[2,]                55           55
[3,]                57           42
[4,]                56           33

fit

Works for all data structures

fit.cc.list <- tergm(
  samp.list ~
    Cross(~edges) +
    Change(~edges),
  estimate = "CMLE",  times = c(1:3)
)

fit.cc.netlist <- tergm(
  samp.netlist ~
    Cross(~edges) +
    Change(~edges),
  estimate = "CMLE",  times = c(1:3)
)

fit.cc.series <- tergm(
  samp.series ~
    Cross(~edges) +
    Change(~edges),
  estimate = "CMLE" #,  times = c(1:3)
)

fit.cc.nD <- tergm(
  samp.dyn ~
    Cross(~edges) +
    Change(~edges),
  estimate = "CMLE",  times = c(1:3)
)

Compare fit output

All give the same output.

cat("Coefs")
Coefs
rbind(list=coef(fit.cc.list), 
      netlist = coef(fit.cc.netlist),
      netseries = coef(fit.cc.series),
      nD=coef(fit.cc.nD))
               edges Change~edges
list      -0.8957043    -1.602274
netlist   -0.8957043    -1.602274
netseries -0.8957043    -1.602274
nD        -0.8957043    -1.602274
cat("nw.stats")
nw.stats
rbind(list=fit.cc.list$nw.stats, 
      netlist = fit.cc.netlist$nw.stats,
      netseries = fit.cc.series$nw.stats,
      nD= fit.cc.nD$nw.stats)
          edges Change~edges
list        113           75
netlist     113           75
netseries   113           75
nD          113           75

No operators

summary_formula

Works on NetSeries and nD (but not lists or netlists), and results are very different for these two, because they represent the network differently.

# summary.list <- summary(samp.list ~
#                             Form(~edges) +
#                             Persist(~edges))
# 
# summary.netlist <- summary(samp.netlist ~
#                             Form(~edges) +
#                             Persist(~edges))

summary.none.series <- summary(samp.series ~ edges)
summary.none.series
edges 
  113 
summary.none.nD <- summary(samp.dyn ~ edges,
                          at=0:3)
summary.none.nD
      [,1]
edges    0
edges   55
edges   57
edges   56

fit

Works for all data structures

fit.none.list <- tergm(
  samp.list ~edges,
  estimate = "CMLE",  times = c(1:3)
)

fit.none.netlist <- tergm(
  samp.netlist ~edges,
  estimate = "CMLE",  times = c(1:3)
)

fit.none.series <- tergm(
  samp.series ~ edges,
  estimate = "CMLE" #,  times = c(1:3)
)

fit.none.nD <- tergm(
  samp.dyn ~ edges,
  estimate = "CMLE",  times = c(1:3)
)

Compare fit output

All give the same output.

cat("Coefs")
Coefs
rbind(list=coef(fit.none.list), 
      netlist = coef(fit.none.netlist),
      netseries = coef(fit.none.series),
      nD=coef(fit.none.nD))
              edges
list      -1.485218
netlist   -1.485218
netseries -1.485218
nD        -1.485218
cat("nw.stats")
nw.stats
rbind(list=fit.none.list$nw.stats, 
      netlist = fit.none.netlist$nw.stats,
      netseries = fit.none.series$nw.stats,
      nD= fit.none.nD$nw.stats)
          edges
list        113
netlist     113
netseries   113
nD          113

Summary

The simple descriptives:

samp.stats
Time Series:
Start = 1 
End = 4 
Frequency = 1 
  edges form diss
1    55   55    0
2    57   22   20
3    56   16   17
4     0    0   56
test <- rbind(names(coef(fit.cc.nD)), round(coef(fit.cc.nD), 3))

The model stats for each model

model names nw.stats
Form + Diss: Form~edges, Diss~edges 150, -75
Form + Persist: Form~edges, Persist~edges 150, 75
Cross + Change: edges, Change~edges 113, 75
no operators: edges 113

The model coefs for each model

model names coefs
Form + Diss: Form~edges, Diss~edges -2.498, -0.707
Form + Persist: Form~edges, Persist~edges -2.498, 0.707
Cross + Change: edges, Change~edges -0.896, -1.602
no operators: edges -1.485

Note that:

The Form + Persist coefs are: -2.498, 0.707

The Cross + Change coefs are: -0.896, -1.602

-2.498 = -1.602 + (-0.896), so Form = Cross + Change (Form operates on the union)

0.707 = -0.896 - (-1.602), so Persist = Change - Cross (Persist operates on the intersection)

And from Pavel:

When there are 3 time points, both Cross() and bare terms will not “see” the first network. t1 network will only affect Change(), Form(), and Diss()/Persist().