SAOM_ABM

Aims

We are going to use RSiena to simulate a stochastic actor-oriented model as an agent-based model.

A more extensive script for simulating with RSiena is given in NetworkSimulation.R. That script also illustrates how to simulate multiple waves, somethign that might be useful if you want to track the trajectory over time.

Model assumptions

In a time-window \(t \in [0,1)\), \(n\) actors are assumed to make changes to their behaviour and/or network. Once a decision to make a change, or to consider the opportunity to change, is made by an actor, the actor makes a decision based on the current state of the system, picking the course of action that maximises a stochastic utility function.

Rates

For each behaviour \(r \in \mathcal{R}=\{1,\ldots, R\}\), each actor has a rate of change \(\lambda_{i,r}(X(t))\), where \(X(t)\) is the current state of the system at time \(t\). The time until actor \(i \in V\) gets an opportuntiy to make a change to behavior \(r\) is exponentially distributed with rate \(\lambda_{i,r}(X(t))\). This means that we can pick the actor and behaviour that might cahnge in a number of ways. One way is to draw \(n \times R\) variables \(\langle S_{i,r} \rangle_{i \in V,r \in \mathcal{R}}\) from exponential distributions with rates \(\langle \lambda_{i,r}(X(t)) \rangle_{i \in V,r \in \mathcal{R}}\), and then pick the actor \(i\) and behaviour \(r\) that has the smallest value \(S_{i,r}\), i.e. the shortest wating time. Another way of picking actor \(i\) and behaviour \(r\), is by using the property of the exponential distribution that the probability that \(S_{i,r}\) is minimum, is \[\frac{\lambda_{i,r}}{\sum_i \sum_r \lambda_{i,r}} .\] Once the actor \(i\) and behaviour \(r\) have been chosen, and a potential change has happened, time is incremented \(t := t+S_{i,r}\).

Decision process

Given that actor \(i\) is given the opportunity to make a change to the behaviour \(r\), the actor chooses the make the change that maximises \[ U_i(r,\Delta_{i,r,k}X(t))=f_i(r,\Delta_{i,r,k}X(t))+\epsilon_{i,r,k,t} \] where

  • \(\Delta_{i,r,k}X(t)\) is the state that would result from \(i\) making change \(k\) to behaviour \(r\),
  • \(U_i(r,X)\) is the utility of state \(X\) to actor \(i\) with respect to behaviour \(r\)
  • \(f_i(r,X)\) is the objective function of the utility of state \(X\) to actor \(i\) with respect to behaviour \(r\)
  • \(\epsilon_{i,r,k,t}\) is an extreme value type one variable

Typically, for a network change, \(\Delta_{i,r,k}X(t)\) is the network that would result from \(i\) making a change to their tie to \(k\) by toggling the current state. This means that \(\Delta_{i,r,k}X(t)\) would be an adjacency matrix that is identidal to that of \(X(t)\), with the exception that you set the cell \((i,j)\) to \(1-X(t)_{i,j}\). The universe of changes to the network avaible to \(i\) is \((\Delta_{i,k}: k=1,\ldots,i-1,i+1,\ldots,n)\) as well as \(\Delta_{i,i}\) which is defined as a non-change.

A simple example of a diffusion and network model

First load the RSiena, RSiena, andRSiena` packages:

library(sna)
library(network)
library(RSiena)
library(ggplot2)

We will use a network of \(n=32\) actors as a starting network (but you could use any network you like)

tmp4[is.na(tmp4)] <- 0 # remove missing
mynet1 <- sienaDependent(array(c(tmp4, tmp4), dim=c(32, 32,2)),allowOnly = FALSE)# note that we want to simulate both creation and deletion of ties

Create a binary starting behaviour

mybeh.1 <- matrix(runif(2*32)<.2,32,2)+0 # remove missing
mybeh <- sienaDependent( mybeh.1, type = "behavior" ,allowOnly = TRUE)# we only want to model an absorbing state

Create the data object

mydata <- sienaDataCreate(mynet1,mybeh)

Simulate model

Decide what effects to include and set parameter values

myeffects <- getEffects( mydata )
myeffects <- setEffect( myeffects, name = "mynet1",density,initialValue = -1.4163)# set the density paramter
##   effectName          include fix   test  initialValue parm
## 1 outdegree (density) TRUE    FALSE FALSE    -1.4163   0
myeffects <- setEffect( myeffects,recip,initialValue = 1.1383 )# set the reciprocity paramter
##   effectName  include fix   test  initialValue parm
## 1 reciprocity TRUE    FALSE FALSE     1.1383   0
myeffects <- includeEffects( myeffects, transTrip, transRecTrip )# adding two transitivity effects
##   effectName                  include fix   test  initialValue parm
## 1 transitive triplets         TRUE    FALSE FALSE          0   0   
## 2 transitive recipr. triplets TRUE    FALSE FALSE          0   0
myeffects <- setEffect( myeffects, egoX, 
                         interaction1 = "mybeh" ,initialValue = -.5 )# social selection effects
##   effectName include fix   test  initialValue parm
## 1 mybeh ego  TRUE    FALSE FALSE       -0.5   0
myeffects <- setEffect( myeffects,  altX, 
                         interaction1 = "mybeh" ,initialValue = .25 )# social selection effects
##   effectName  include fix   test  initialValue parm
## 1 mybeh alter TRUE    FALSE FALSE       0.25   0
myeffects <- setEffect( myeffects, egoXaltX,
                         interaction1 = "mybeh",initialValue = .5 )# social selection homophilye
##   effectName              include fix   test  initialValue parm
## 1 mybeh ego x mybeh alter TRUE    FALSE FALSE        0.5   0
myeffects <- setEffect( myeffects, avAlt, name="mybeh",
                         interaction1 = "mynet1",   initialValue = 1.2 ) # a positive influence effect
##   effectName          include fix   test  initialValue parm
## 1 mybeh average alter TRUE    FALSE FALSE        1.2   0
myeffects <- setEffect(myeffects, name = "mynet1", Rate, type="rate",
                            initialValue =  4.2525)
##   effectName                  include fix   test  initialValue parm
## 1 basic rate parameter mynet1 TRUE    FALSE FALSE     4.2525   0
myeffects <- setEffect(myeffects, name = "mybeh", Rate, type="rate",
                            initialValue = 3)
##   effectName          include fix   test  initialValue parm
## 1 rate mybeh period 1 TRUE    FALSE FALSE          3   0

Set up the simulation settings:

sim_model  <-  sienaAlgorithmCreate( 
                          projname = 'sim_model',
                          cond = FALSE, 
                          useStdInits = FALSE, nsub = 0 ,
                          n3 = 100,# run 100 simulations
                          simOnly = TRUE)   # by setting simOnly to TRUE RSiena won't estimate      

Run simulations

sim_ans <- siena07( sim_model,#simulation settings
                    data = mydata,# our starting data
                    effects = myeffects,# the effects and paramter values we have set for model
                    returnDeps = TRUE,# this is to actually return the simulated networks and behaviours
                    batch=TRUE )    
## 
## Start phase 0 
## theta:  4.25 -1.42  1.14  0.00  0.00  0.25 -0.50  0.50  3.00  0.00  1.20 
## 
## Start phase 3

Extract simulated data

Selection and influence tables

Selection and influence tables plot the part of the utility function that corresponds to the selection and influence effects. Note that these tables are the theoretical effects and not based on what is actually simulated

Read in the script from a website

source('http://www.stats.ox.ac.uk/~snijders/siena/SelectionTables.r')

Calculate the social selection table:

vname <- "mybeh"
name <- "mynet1"
levls <- 0:1
vselect <- selectionTable(sim_ans, mydata, name, vname, levls)
## Dependent network mynet1 ; actor variable mybeh .
## Parameters found are
##     altX     egoX egoXaltX 
##     0.25    -0.50     0.50 
## Check that these parameter values are correct!
## The subtracted mean value of mybeh is 0.15625 .
vselect
##   ego vego valter      select
## 1   0    0      0  0.05126953
## 2   0    0      1  0.22314453
## 3   1    1      0 -0.52685547
## 4   1    1      1  0.14501953
sp <- ggplot(vselect, aes(valter, select, group=ego, colour=ego))
sp +geom_point() + geom_smooth(size=1.2, span=3) +
scale_colour_hue() +
scale_x_continuous(breaks=levls) +
theme(legend.key=element_blank())+
labs(x=paste(vname),
y=paste("selection function"),
title=paste("Effect",vname,"on",name),
colour=paste(vname)) +
theme_grey(base_size=26, base_family="") +
theme(legend.key.width=unit(1, "cm")) +
theme(plot.title=element_text(hjust=0.5))
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

Read in the Influence script from a website

source('http://www.stats.ox.ac.uk/~snijders/siena/InfluenceTables.r')

Calculate the social influence table:

name <- "mybeh"
zname <- "mynet1"
levls <- 0:1
zselect <- influenceMatrix(sim_ans, mydata,zname,  name, levls)
## Network mynet1 ; dependent behavior mybeh .
## Parameters found are
## avAlt 
##   1.2 
## 
## Levels of alter refer to average values of alter's behavior.
zselect
##             0          1
## 0  0.02929688 -0.1582031
## 1 -0.15820312  0.8542969
zselect <- influenceTable(sim_ans, mydata,zname,  name, levls)
## Network mynet1 ; dependent behavior mybeh .
## Parameters found are
## avAlt 
##   1.2 
## 
## Levels of alter refer to average values of alter's behavior.
sp <- ggplot(zselect, aes(zego, select, group=alter, colour=alter))
sp + geom_point() + geom_smooth(size=1.2, span=3) +
scale_colour_hue() +scale_x_continuous(breaks=levls) +
theme(legend.key=element_blank())+labs(x=paste(name),
                                       y=paste('evaluation function'),
                                       title=paste('Influence effect',zname,'on',name),
                                       colour=paste(name,'\nalter')) +
theme_grey(base_size=26, base_family="") +
theme(legend.key.width=unit(1, "cm")) +
theme(plot.title=element_text(hjust=0.5))
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

Extracting networks

The function networkExtraction returns the network as an edge list of class “network” according to the network package (used for package sna). Unfortunately there is a bug that I can’t get around so here we do it the hard way

#attr(sim_ans$f[['Data1']]$depvars[["mynet1"]],  "type")
simnets <- vector('list',100)
for (i in c(1:100)){

emptyNetwork <- network::network.initialize(32, 
            bipartite = NULL)
matrixNetwork <- sparseMatrixExtraction(i, sim_ans$f, sim_ans$sims, 
        1, 'Data1', "mynet1")
    sparseMatrixNetwork <- as(matrixNetwork, "dgTMatrix")
simnets[[i]] <- network::network.edgelist(cbind(sparseMatrixNetwork@i + 
            1, sparseMatrixNetwork@j +1, 1), emptyNetwork)
}
hist(gden(simnets))# chart density of simulated networks

For simulated behaviour, extracting the simulated values is straightforward (once you know or guess the structure of the ‘sims’ object; see ‘9.1 Accessing the generated networks’ in the RSiena Manual Manual)

#attr(sim_ans$f[['Data1']]$depvars[["mybeh"]],  "type")

simbeh <- matrix(NA,32,100)
for (i in c(1:100)){
simbeh[,i] <- sim_ans$sims[[i]][[1]][[2]][[1]]
# [[iteration]][[period]][[dependent variable]][[]]
}
 hist(colMeans(simbeh))# proportion of adopters

Let’s look at the number of homophilous and heterophilour ties

mat <- as.matrix.network(simnets[[1]])# convert edgelist to matrix
sum( mat[simbeh[,1]==1, simbeh[,1]==1] )# number 1-> 1
## [1] 60
sum( mat[simbeh[,1]==1, simbeh[,1]==0] )# number 1-> 0
## [1] 35
sum( mat[simbeh[,1]==0, simbeh[,1]==1] )# number 0-> 1
## [1] 44
sum( mat[simbeh[,1]==0, simbeh[,1]==0] )# number 0-> 0
## [1] 20

Calculate a function that calculates homophilous ties across simulations or just loop

hom.tab <- matrix(NA,100,4)
colnames(hom.tab) <- c('1=>1','1=>0','0=>1','0=>0')
for (i in c(1:100))
{
mat <- as.matrix.network(simnets[[i]])# convert edgelist to matrix
hom.tab[i,1] <-  sum( mat[simbeh[,i]==1, simbeh[,i]==1] )# number 1-> 1
hom.tab[i,2] <- sum( mat[simbeh[,i]==1, simbeh[,i]==0] )# number 1-> 0
hom.tab[i,3] <- sum( mat[simbeh[,i]==0, simbeh[,i]==1] )# number 0-> 1
hom.tab[i,4] <- sum( mat[simbeh[,i]==0, simbeh[,i]==0] )# number 0-> 0
}
plot(ts(hom.tab))