21  SIS Epidemic Across a Dynamic Network

EpiModel uses temporal exponential-family random graph models (TERGMs) to estimate and simulate complete networks based on individual-level, dyad-level, and network-level patterns of density, degree, assortivity, and other features influencing edge formation and dissolution. Building and simulating network-based epidemic models in EpiModel is a multi-step process, starting with estimation of a temporal ERGM and continuing with simulation of a dynamic network and epidemic processes on top of that network.

In this tutorial, we work through a model of a Susceptible-Infected-Susceptible (SIS) epidemic. One example of an SIS disease would be a bacterial sexually transmitted infection such as Gonorrhea, in which persons may acquire infection from sexual contact with an infected partner, and then recover from infection either through natural clearance or through antibiotic treatment.

We will use a simplifying assumption of a closed population, in which there are no entries or exits from the network; this may be justified by the short time span over which the epidemic will be simulated.

Note

Download the R script to follow along with this tutorial here.

21.1 Network Model Estimation

To get started, load the EpiModel library.

Code
library(EpiModel)

The first step in our network model is to specify a network structure, including features like size and nodal attributes. The network_initialize function creates an object of class network. Below we show an example of initializing a network of 500 nodes, with no edges between them at the start. Edges represent sexual partnerships (mutual person-to-person contact), so this is an undirected network.

Code
nw <- network_initialize(n = 500)

The sizes of the networks represented in this workshop are smaller than what might be used for a research-level model, mostly for computational efficiency. Larger network sizes over longer time intervals are typically used for research purposes.

21.1.1 Model Parameterization

This example will start simple, with a formula that represents the network density and the level of concurrency (overlapping sexual partnerships) in the population. This is a dyad-dependent ERGM, since the probability of edge formation between any two nodes depends on the existence of edges between those nodes and other nodes. The concurrent term is defined as the number of nodes with at least two partners at any time. Following the notation of the tergm package, we specify this using a right-hand side (RHS) formula. In addition to concurrency, we will use a constraint on the degree distribution. This will cap the degree of any person at 3, with no nodes allowed to have 4 or more ongoing partnerships. This type of constraint could reflect a truncated sampling scheme for partnerships within a survey (e.g., persons only asked about their 3 most recent partners), or a model assumption about limits of human activity.

Code
formation <- ~edges + concurrent + degrange(from = 4)

Target statistics will be the input mechanism for formation model terms. The edges term will be a function of mean degree, or the average number of ongoing partnerships. With an arbitrarily specified mean degree of 0.7, the corresponding target statistic is 175: \(edges = mean \ degree \times \frac{N}{2}\).

We will also specify that 22% of persons exhibit concurrency (this is slightly higher than the 16% expected in a Poisson model conditional on that mean degree). The target statistic for the number of persons with a momentary degree of 4 or more is 0, reflecting our assumed constraint.

Code
target.stats <- c(175, 110, 0)

The dissolution model is parameterized from a mean partnership duration estimated from cross-sectional egocentric data. Dissolution models differ from formation models in two respects. First, the dissolution models are not estimated in an ERGM but instead passed in as a fixed coefficient conditional on which the formation model is to be estimated. The dissolution model terms are calculated analytically using the dissolution_coefs function, the output of which is passed into the netest model estimation function. Second, whereas formation models may be arbitrarily complex, dissolution models are limited to a set of dyad-independent models; these are listed in the dissolution_coefs function help page. The model we will use is an edges-only model, implying a homogeneous probability of dissolution for all partnerships in the network. The average duration of these partnerships will be specified at 50 time steps, which will be days in our model.

Code
coef.diss <- dissolution_coefs(dissolution = ~offset(edges), duration = 50)
coef.diss
Dissolution Coefficients
=======================
Dissolution Model: ~offset(edges)
Target Statistics: 50
Crude Coefficient: 3.89182
Mortality/Exit Rate: 0
Adjusted Coefficient: 3.89182

The output from this function indicates both an adjusted and crude coefficient. In this case, they are equivalent. Upcoming workshop material will showcase when they differ as result of exits from the network.

21.1.2 Model Estimation and Diagnostics

In EpiModel, network model estimation is performed with the netest function, which is a wrapper around the estimation functions in the ergm and tergm packages. The function arguments are as follows:

function (nw, formation, target.stats, coef.diss, constraints, 
    coef.form = NULL, edapprox = TRUE, set.control.ergm = control.ergm(), 
    set.control.tergm = control.tergm(), set.control.ergm.ego = control.ergm.ego(), 
    verbose = FALSE, nested.edapprox = TRUE, ...) 
NULL

The four arguments that must be specified with each function call are:

  • nw: an initialized empty network.
  • formation: a RHS formation formula..
  • target.stats: target statistics for the formation model.
  • coef.diss: output object from dissolution_coefs, containing the dissolution coefficients.

Other arguments that may be helpful to understand when getting started are:

  • constraints: this is another way of inputting model constraints (see help("ergm")).

  • coef.form: sets the coefficient values of any offset terms in the formation model (those that are not explicitly estimated but fixed).

  • edapprox: selects the dynamic estimation method. If TRUE, uses the direct method, otherwise the approximation method.

    • Direct method: uses the functionality of the tergm package to estimate the separable formation and dissolution models for the network. This is often not used because of computational time.
    • Approximation method: uses ergm estimation for a cross-sectional network (the prevalence of edges) with an analytic adjustment of the edges coefficient to account for dissolution (i.e., transformation from prevalence to incidence). This approximation method may introduce bias into estimation in certain cases (high density and short durations) but these are typically not a concern for the low density cases in epidemiologically relevant networks.

21.1.2.1 Estimation

Because we have a dyad-dependent model, MCMC will be used to estimate the coefficients of the model given the target statistics.

Code
est <- netest(nw, formation, target.stats, coef.diss)

21.1.2.2 Diagnostics

There are two forms of model diagnostics for a dynamic ERGM fit with netest: static and dynamic diagnostics. When the approximation method has been used, static diagnostics check the fit of the cross-sectional model to target statistics. Dynamic diagnostics check the fit of the model adjusted to account for edge dissolution.

When running a dynamic network simulation, it is good to start with the dynamic diagnostics, and if there are fit problems, work back to the static diagnostics to determine if the problem is due to the cross-sectional fit itself or with the dynamic adjustment (i.e., the approximation method). A proper fitting ERGM using the approximation method does not guarantee well-performing dynamic simulations.

Here we will examine dynamic diagnostics only. These are run with the netdx function, which simulates from the model fit object returned by netest. One must specify the number of simulations from the dynamic model and the number of time steps per simulation. Choice of both simulation parameters depends on the stochasticity in the model, which is a function of network size, model complexity, and other factors. The nwstats.formula contains the network statistics to monitor in the diagnostics: it may contain statistics in the formation model and also others. By default, it is the formation model. Finally, we are keeping the “timed edgelist” with keep.tedgelist.

Code
dx <- netdx(est, nsims = 10, nsteps = 1000,
            nwstats.formula = ~edges + meandeg + degree(0:4) + concurrent,
            keep.tedgelist = TRUE)

We have also built in parallelization into the EpiModel simulation functions, so it is also possible to run multiple simulations at the same time using your computer’s multi-core design. You can find the number of cores in your system with:

Code
parallel::detectCores()

Then you can run the multi-core simulations by specifying ncores (EpiModel will prevent you from specifying more cores than you have available).

Code
dx <- netdx(est, nsims = 10, nsteps = 1000, ncores = 4,
            nwstats.formula = ~edges + meandeg + degree(0:4) + concurrent,
            keep.tedgelist = TRUE)

Printing the object will show the object structure and diagnostics. Both formation and duration diagnostics show a good fit relative to their targets. For the formation diagnostics, the mean statistics are the mean of the cross sectional statistics at each time step across all simulations. The Pct Diff column shows the relative difference between the mean and targets. There are two forms of dissolution diagnostics. The edge duration row shows the mean duration of partnerships across the simulations; it tends to be lower than the target unless the diagnostic simulation interval is very long since its average includes a burn-in period where all edges start at a duration of zero (illustrated below in the plot). The next row shows the percent of current edges dissolving at each time step, and is not subject to bias related to burn-in. The percentage of edges dissolution is the inverse of the expected duration: if the duration is 50 days, then we expect that 1/50 (or 2%) to dissolve each day.

Code
print(dx)
EpiModel Network Diagnostics
=======================
Diagnostic Method: Dynamic
Simulations: 10
Time Steps per Sim: 1000

Formation Diagnostics
----------------------- 
           Target Sim Mean Pct Diff Sim SE Z Score SD(Sim Means) SD(Statistic)
edges         175  174.148   -0.487  1.192  -0.714         3.316        12.988
meandeg        NA    0.697       NA  0.005      NA         0.013         0.052
degree0        NA  273.630       NA  1.331      NA         3.186        13.723
degree1        NA  117.532       NA  0.483      NA         0.793         9.416
degree2        NA   95.752       NA  0.615      NA         2.360        10.492
degree3        NA   13.087       NA  0.228      NA         0.902         3.972
degree4        NA    0.000       NA    NaN      NA         0.000         0.000
concurrent    110  108.839   -1.056  0.789  -1.472         2.832        11.872

Duration Diagnostics
----------------------- 
      Target Sim Mean Pct Diff Sim SE Z Score SD(Sim Means) SD(Statistic)
edges     50   50.291    0.583  0.414   0.704         1.123         4.327

Dissolution Diagnostics
----------------------- 
      Target Sim Mean Pct Diff Sim SE Z Score SD(Sim Means) SD(Statistic)
edges   0.02     0.02   -0.149      0  -0.288             0         0.011

Plotting the diagnostics object will show the time series of the target statistics against any targets. The other options used here specify to smooth the mean lines, give them a thicker line width, and plot each statistic in a separate panel. The black dashed lines show the value of the target statistics for any terms in the model. Similar to the numeric summaries, the plots show a good fit over the time series.

Code
plot(dx)

The simulated network statistics from diagnostic object may be extracted into a data.frame with get_nwstats.

Code
nwstats1 <- get_nwstats(dx, sim = 1)
head(nwstats1, 20)
   time sim edges meandeg degree0 degree1 degree2 degree3 degree4 concurrent
1     1   1   156   0.624     299     101      89      11       0        100
2     2   1   153   0.612     298     109      82      11       0         93
3     3   1   154   0.616     297     109      83      11       0         94
4     4   1   153   0.612     300     105      84      11       0         95
5     5   1   155   0.620     299     104      85      12       0         97
6     6   1   155   0.620     298     105      86      11       0         97
7     7   1   150   0.600     304     104      80      12       0         92
8     8   1   151   0.604     302     106      80      12       0         92
9     9   1   154   0.616     302     103      80      15       0         95
10   10   1   156   0.624     300     104      80      16       0         96
11   11   1   159   0.636     298     103      82      17       0         99
12   12   1   158   0.632     297     105      83      15       0         98
13   13   1   159   0.636     296     103      88      13       0        101
14   14   1   157   0.628     299     101      87      13       0        100
15   15   1   162   0.648     296      98      92      14       0        106
16   16   1   163   0.652     293     100      95      12       0        107
17   17   1   168   0.672     288     102      96      14       0        110
18   18   1   171   0.684     284     105      96      15       0        111
19   19   1   170   0.680     284     107      94      15       0        109
20   20   1   172   0.688     284     103      98      15       0        113

The dissolution model fit may also be assessed with plots by specifying either the duration or dissolution type, as defined above. The duration diagnostic is based on the average age of edges at each time step, up to that time step. An imputation algorithm is used for left-censored edges (i.e., those that exist at t1); you can turn off this imputation to see the effects of censoring with duration.imputed = FALSE. Both metrics show a good fit of the dissolution model to the target duration of 50 time steps.

Code
par(mfrow = c(1, 2))
plot(dx, type = "duration")
plot(dx, type = "dissolution")

By inspecting the timed edgelist, we can see the burn-in period directly with censoring of onset times. The as.data.frame function is used to extract this edgelist object.

Code
tel <- as.data.frame(dx, sim = 1)
head(tel, 20)
   onset terminus tail head onset.censored terminus.censored duration edge.id
1      0      106    6  371          FALSE             FALSE      106       1
2      0       40    6  469          FALSE             FALSE       40       2
3      0       72    8  171          FALSE             FALSE       72       3
4      0      163    9  172          FALSE             FALSE      163       4
5      0        7    9  299          FALSE             FALSE        7       5
6      0        7   11  287          FALSE             FALSE        7       6
7      0        2   11  365          FALSE             FALSE        2       7
8      0       51   13   85          FALSE             FALSE       51       8
9    822      885   13   85          FALSE             FALSE       63       8
10     0       14   14   97          FALSE             FALSE       14       9
11     0       27   17  216          FALSE             FALSE       27      10
12     0       41   17  340          FALSE             FALSE       41      11
13     0        3   28  483          FALSE             FALSE        3      12
14     0       99   29   49          FALSE             FALSE       99      13
15     0       14   31   42          FALSE             FALSE       14      14
16     0       70   31  276          FALSE             FALSE       70      15
17     0       55   31  344          FALSE             FALSE       55      16
18     0        4   35  246          FALSE             FALSE        4      17
19     0       88   37  436          FALSE             FALSE       88      18
20     0       66   41  193          FALSE             FALSE       66      19

If the model diagnostics had suggested a poor fit, then additional diagnostics and fitting would be necessary. If using the approximation method, one should first start by running the cross-sectional diagnostics (setting dynamic to FALSE in netdx). Note that the number of simulations may be very large here and there are no time steps specified because each simulation is a cross-sectional network.

Code
dx.static <- netdx(est, nsims = 10000, dynamic = FALSE)
print(dx.static)

The plots now represent individual simulations from an MCMC chain, rather than time steps.

Code
par(mfrow = c(1,1))
plot(dx.static, sim.lines = TRUE, sim.lwd = 0.1)

This lack of temporality is now evident when looking at the raw data.

Code
nwstats2 <- get_nwstats(dx.static)
head(nwstats2, 20)
   sim edges concurrent deg4+
1    1   175        106     0
2    2   155         87     0
3    3   186        121     0
4    4   190        123     0
5    5   177        116     0
6    6   172        100     0
7    7   167        107     0
8    8   192        121     0
9    9   191        121     0
10  10   148         95     0
11  11   156         88     0
12  12   152         90     0
13  13   157         94     0
14  14   165        107     0
15  15   178        106     0
16  16   177        112     0
17  17   174        116     0
18  18   179        109     0
19  19   171        110     0
20  20   190        120     0

If the cross-sectional model fits well but the dynamic model does not, then a full STERGM estimation may be necessary (using edapprox = TRUE). If the cross-sectional model does not fit well, different control parameters for the ERGM estimation may be necessary (see the help file for netdx for instructions).

21.2 Epidemic Simulation

EpiModel simulates disease epidemics over dynamic networks by integrating dynamic model simulations with the simulation of other epidemiological processes such as disease transmission and recovery. Like the network model simulations, these processes are also simulated stochastically so that the range of potential outcomes under the model specifications is estimated.

The specification of epidemiological processes to model may be arbitrarily complex, but EpiModel includes a number of “built-in” model types within the software. Additional components will be programmed and plugged into the simulation API (just like any epidemic model); we will start to cover that tomorrow. Here, we will start simple with an SIS epidemic using this built-in functionality. This is starting point to what you can do in EpiModel!

21.2.1 Epidemic Model Parameters

Our SIS model will rely on three parameters. The act rate is the number of sexual acts that occur within a partnership each time unit. The overall frequency of acts per person per unit time is a function of the incidence rate of partnerships and this act rate parameter. The infection probability is the risk of transmission given contact with an infected person. The recovery rate for an SIS epidemic is the speed at which infected persons become susceptible again. For a bacterial STI like gonorrhea, this may be a function of biological attributes like sex or use of therapeutic agents like antibiotics.

EpiModel uses three helper functions to input epidemic parameters, initial conditions, and other control settings for the epidemic model. First, we use the param.net function to input the per-act transmission probability in inf.prob and the number of acts per partnership per unit time in act.rate. The recovery rate implies that the average duration of disease is 10 days (1/rec.rate).

Code
param <- param.net(inf.prob = 0.4, act.rate = 2, rec.rate = 0.1)

For initial conditions in this model, we only need to specify the number of infected persons at the outset of the epidemic. The remaining persons in the network will be classified as disease susceptible.

Code
init <- init.net(i.num = 10)

The control settings specify the structural elements of the model. These include the disease type, number of simulations, and number of time steps per simulation. (Here again we could use the model multi-core functionality by specifying an ncores value, but these models run so quickly that it’s not necessary.)

Code
control <- control.net(type = "SIS", nsims = 5, nsteps = 500)

21.2.2 Simulating the Epidemic Model

Once the model has been parameterized, simulating the model is straightforward. One must pass the fitted network model object from netest along with the parameters, initial conditions, and control settings to the netsim function. With a no-feedback model like this (i.e., there are no vital dynamics parameters), the full dynamic network time series is simulated at the start of each epidemic simulation, and then the epidemiological processes are simulated over that structure.

Code
sim <- netsim(est, param, init, control)

Printing the model output lists the inputs and outputs of the model. The output includes the sizes of the compartments (s.num is the number susceptible and i.num is the number infected) and flows (si.flow is the number of infections and is.flow is the number of recoveries). Methods for extracting this output is discussed below.

Code
print(sim)
EpiModel Simulation
=======================
Model class: netsim

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

Fixed Parameters
---------------------------
inf.prob = 0.4
act.rate = 2
rec.rate = 0.1
groups = 1

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

Formation Statistics
----------------------- 
           Target Sim Mean Pct Diff Sim SE Z Score SD(Sim Means) SD(Statistic)
edges         175  170.135   -2.780  2.213  -2.198         3.060        12.826
concurrent    110  105.572   -4.026  1.547  -2.862         2.749        11.444
deg4+           0    0.000      NaN    NaN     NaN         0.000         0.000


Duration Statistics
----------------------- 
      Target Sim Mean Pct Diff Sim SE Z Score SD(Sim Means) SD(Statistic)
edges     50   49.099   -1.801  0.676  -1.333         0.899         3.931

Dissolution Statistics
----------------------- 
      Target Sim Mean Pct Diff Sim SE Z Score SD(Sim Means) SD(Statistic)
edges   0.02     0.02    1.989      0   1.749             0         0.011

21.2.3 Model Analysis

Now the the model has been simulated, the next step is to analyze the data. This includes plotting the epidemiological output, the networks over time, and extracting other raw data.

21.2.3.1 Epidemic Plots

Plotting the output from the epidemic model using the default arguments will display the size of the compartments in the model across simulations. The means across simulations at each time step are plotted with lines, and the polygon band shows the inter-quartile range across simulations.

Code
par(mfrow = c(1, 1))
plot(sim)

Note

The inclusion of the sim.num outcome in the plots is a software bug that will be fixed in the next release of EpiModel.

Graphical elements may be toggled on and off. The popfrac argument specifies whether to use the absolute size of compartments versus proportions.

Code
par(mfrow = c(1, 2))
plot(sim, sim.lines = TRUE, mean.line = FALSE, qnts = FALSE, popfrac = TRUE)
plot(sim, mean.smooth = FALSE, qnts = 1, qnts.smooth = FALSE, popfrac = TRUE)

Whereas the default will print the compartment proportions, other elements of the simulation may be plotted by name with the y argument. Here we plot both flow sizes using smoothed means, which converge at model equilibrium by the end of the time series.

Code
par(mfrow = c(1,1))
plot(sim, y = c("si.flow", "is.flow"), qnts = FALSE, 
     ylim = c(0, 25), legend = TRUE, main = "Flow Sizes")

21.2.3.2 Network Plots

Another available plot type is a network plot to visualize the individual nodes and edges at a specific time point. Network plots are output by setting the type parameter to "network". To plot the disease infection status on the nodes, use the col.status argument: blue indicates susceptible and red infected. It is necessary to specify both a time step and a simulation number to plot these networks.

Code
par(mfrow = c(1, 2), mar = c(0, 0, 0, 0))
plot(sim, type = "network", col.status = TRUE, at = 1, sims = 1)
plot(sim, type = "network", col.status = TRUE, at = 500, sims = 1)

21.2.3.3 Time-Specific Model Summaries

The summary function with the output of netsim will show the model statistics at a specific time step. Here we output the statistics at the final time step, where roughly two-thirds of the population are infected.

Code
summary(sim, at = 500)

EpiModel Summary
=======================
Model class: netsim

Simulation Details
-----------------------
Model type: SIS
No. simulations: 5
No. time steps: 500
No. NW groups: 1

Model Statistics
------------------------------
Time: 500 
------------------------------ 
           mean      sd    pct
Suscept.  324.2  13.330  0.648
Infect.   175.8  13.330  0.352
Total     500.0   0.000  1.000
S -> I     21.0   3.873     NA
I -> S     21.2   4.382     NA
------------------------------ 

21.2.3.4 Data Extraction

The as.data.frame function may be used to extract the model output into a data frame object for easy analysis outside of the built-in EpiModel functions. The function default will output the raw data for all simulations for each time step.

Code
df <- as.data.frame(sim)
head(df, 10)
   sim time sim.num s.num i.num num si.flow is.flow
1    1    1     500   490    10 500      NA      NA
2    1    2     500   492     8 500       2       4
3    1    3     500   493     7 500       1       2
4    1    4     500   491     9 500       2       0
5    1    5     500   491     9 500       1       1
6    1    6     500   492     8 500       1       2
7    1    7     500   493     7 500       1       2
8    1    8     500   490    10 500       3       0
9    1    9     500   489    11 500       2       1
10   1   10     500   491     9 500       2       4
Code
tail(df, 10)
     sim time sim.num s.num i.num num si.flow is.flow
2491   5  491     500   309   191 500      18      20
2492   5  492     500   311   189 500      17      19
2493   5  493     500   308   192 500      23      20
2494   5  494     500   313   187 500      18      23
2495   5  495     500   304   196 500      28      19
2496   5  496     500   311   189 500      18      25
2497   5  497     500   313   187 500      30      32
2498   5  498     500   301   199 500      30      18
2499   5  499     500   302   198 500      25      26
2500   5  500     500   309   191 500      18      25

The out argument may be changed to specify the output of means across the models (with out = "mean"). The output below shows all compartment and flow sizes as integers, reinforcing this as an individual-level model.

Code
df <- as.data.frame(sim, out = "mean")
head(df, 10)
   time sim.num s.num i.num num si.flow is.flow
1     1     500 490.0  10.0 500     NaN     NaN
2     2     500 488.8  11.2 500     3.4     2.2
3     3     500 488.0  12.0 500     2.6     1.8
4     4     500 485.8  14.2 500     3.0     0.8
5     5     500 483.8  16.2 500     3.2     1.2
6     6     500 483.4  16.6 500     1.8     1.4
7     7     500 482.6  17.4 500     2.6     1.8
8     8     500 481.8  18.2 500     3.4     2.6
9     9     500 480.2  19.8 500     3.2     1.6
10   10     500 480.2  19.8 500     3.2     3.2
Code
tail(df, 10)
    time sim.num s.num i.num num si.flow is.flow
491  491     500 324.0 176.0 500    22.0    18.2
492  492     500 322.8 177.2 500    20.8    19.6
493  493     500 323.2 176.8 500    20.2    20.6
494  494     500 323.4 176.6 500    20.8    21.0
495  495     500 320.4 179.6 500    20.8    17.8
496  496     500 322.0 178.0 500    17.8    19.4
497  497     500 323.4 176.6 500    22.2    23.6
498  498     500 323.2 176.8 500    21.6    21.4
499  499     500 324.0 176.0 500    23.0    23.8
500  500     500 324.2 175.8 500    21.0    21.2

The networkDynamic objects are stored in the netsim object, and may be extracted with the get_network function. By default the dynamic networks are saved, and contain the full edge history for every node that has existed in the network, along with the disease status history of those nodes.

Code
nw1 <- get_network(sim, sim = 1)
nw1
NetworkDynamic properties:
  distinct change times: 502 
  maximal time range: 0 until  Inf 

 Dynamic (TEA) attributes:
  Vertex TEAs:    testatus.active 

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

 Network attributes:
  vertices = 500 
  directed = FALSE 
  hyper = FALSE 
  loops = FALSE 
  multiple = FALSE 
  bipartite = FALSE 
  net.obs.period: (not shown)
  vertex.pid = tergm_pid 
  total edges= 1873 
    missing edges= 0 
    non-missing edges= 1873 

 Vertex attribute names: 
    active status tergm_pid testatus.active vertex.names 

 Edge attribute names not shown 

One thing you can do with that network dynamic object is to extract the timed edgelist of all ties that existed for that simulation.

Code
nwdf <- as.data.frame(nw1)
head(nwdf, 25)
   onset terminus tail head onset.censored terminus.censored duration edge.id
1      0       33    1  101          FALSE             FALSE       33       1
2      0       34    1  203          FALSE             FALSE       34       2
3      0       45    4  238          FALSE             FALSE       45       3
4      0       33    7  204          FALSE             FALSE       33       4
5      0       24    8  317          FALSE             FALSE       24       5
6      0      253    8  347          FALSE             FALSE      253       6
7      0       29    9  163          FALSE             FALSE       29       7
8      0       28    9  480          FALSE             FALSE       28       8
9      0      112   11  263          FALSE             FALSE      112       9
10     0       49   13  422          FALSE             FALSE       49      10
11     0       36   16  333          FALSE             FALSE       36      11
12     0       45   17  170          FALSE             FALSE       45      12
13     0        4   23  117          FALSE             FALSE        4      13
14     0       12   24  127          FALSE             FALSE       12      14
15     0      266   28  352          FALSE             FALSE      266      15
16     0       31   28  378          FALSE             FALSE       31      16
17     0       40   29  173          FALSE             FALSE       40      17
18     0        2   29  500          FALSE             FALSE        2      18
19     0       14   35  119          FALSE             FALSE       14      19
20     0       42   35  163          FALSE             FALSE       42      20
21     0      120   40  456          FALSE             FALSE      120      21
22     0        6   41  479          FALSE             FALSE        6      22
23     0       49   45  346          FALSE             FALSE       49      23
24     0       15   52  279          FALSE             FALSE       15      24
25     0       18   54  131          FALSE             FALSE       18      25

A matrix is stored that records some key details about each transmission event that occurred. Shown below are the first 10 transmission events for simulation number 1. The sus column shows the unique ID of the previously susceptible, newly infected node in the event. The inf column shows the ID of the transmitting node. The other columns show the duration of the transmitting node’s infection at the time of transmission, the per-act transmission probability, act rate during the transmission, and final per-partnership transmission rate (which is the per-act probability raised to the number of acts).

Code
tm1 <- get_transmat(sim, sim = 1)
head(tm1, 10)
# A tibble: 10 × 8
# Groups:   at, sus [10]
      at   sus   inf network infDur transProb actRate finalProb
   <dbl> <int> <int>   <int>  <dbl>     <dbl>   <dbl>     <dbl>
 1     2   142   203       1      9       0.4       2      0.64
 2     2   303   297       1     19       0.4       2      0.64
 3     3   143   142       1      1       0.4       2      0.64
 4     4   142   143       1      1       0.4       2      0.64
 5     4   422   303       1      2       0.4       2      0.64
 6     5   203   142       1      1       0.4       2      0.64
 7     6     1   203       1      1       0.4       2      0.64
 8     7    13   422       1      3       0.4       2      0.64
 9     8    13   422       1      4       0.4       2      0.64
10     8   170   297       1     25       0.4       2      0.64

21.2.3.5 Data Exporting and Plotting with ggplot

We built in plotting methods directly for netsim class objects so you can easily plot multiple types of summary statistics from the simulated model object. However, if you prefer an external plotting tool in R, such as ggplot, it is easy to extract the data in tidy format for analysis and plotting. Here is an example how to do so for out model above. See the help for the ggplot if you are unfamiliar with this syntax.

Code
df <- as.data.frame(sim)
df.mean <- as.data.frame(sim, out = "mean")

library(ggplot2)
ggplot() +
  geom_line(data = df, mapping = aes(time, i.num, group = sim), alpha = 0.25,
            lwd = 0.25, color = "firebrick") +
  geom_bands(data = df, mapping = aes(time, i.num),
             lower = 0.1, upper = 0.9, fill = "firebrick") +
  geom_line(data = df.mean, mapping = aes(time, i.num)) +
  theme_minimal()