PSC-190: Temporal and Dynamic Netowks

Author

Keira McBrearty, Nicole Hernandez Vazquez

Temporal networks yay!

“Static” Networks

At the beginning of this course, we learned the most basic form of a graph, the simple undirected graph. It’s the one given by G = (V, E) Where: G: the graph… is given by V: a set of vertices… and E: in plain English, is the subset of {x, y}, pairs of vertices which are connected to one another (they’re edges)

What are their shortcomings?

So far, we’ve been learning about networks as if all connections exist at the same time and have always been present, and this is called a “static” network. But, in real life, connections don’t just start to exist all at once, they form and fade away over time, like synaptic connections in the brain that are pruned or lost to damage and re-routing, or friends who come into our lives and leave.

When we compress a network into one static picture, we ignore the order of events. Not knowing the temporal sequencing (timing) of events can result in erroneous directional interpretations (inaccurate representations of the order stuff happens in). For example:

library(sna)
Loading required package: statnet.common

Attaching package: 'statnet.common'
The following objects are masked from 'package:base':

    attr, order
Loading required package: network

'network' 1.19.0 (2024-12-08), part of the Statnet Project
* 'news(package="network")' for changes since last version
* 'citation("network")' for citation information
* 'https://statnet.org' for help, support, and other information
sna: Tools for Social Network Analysis
Version 2.8 created on 2024-09-07.
copyright (c) 2005, Carter T. Butts, University of California-Irvine
 For citation information, type citation("sna").
 Type help(package="sna") to get started.
library(tsna)
Loading required package: networkDynamic

'networkDynamic' 0.11.5 (2024-11-21), part of the Statnet Project
* 'news(package="networkDynamic")' for changes since last version
* 'citation("networkDynamic")' for citation information
* 'https://statnet.org' for help, support, and other information
library(ndtv)
Loading required package: animation

'ndtv' 0.13.4 (2024-06-30), part of the Statnet Project
* 'news(package="ndtv")' for changes since last version
* 'citation("ndtv")' for citation information
* 'https://statnet.org' for help, support, and other information
#setwd("/Users/victorhernandez/Desktop/")

df = read.csv("./df.csv")
names(df)
[1] "onset"             "terminus"          "tail"             
[4] "head"              "onset.censored"    "terminus.censored"
[7] "duration"          "edge.id"          
 df$tail = df$tail + 1
 df$head = df$head + 1
 df$min_node = pmin(df$tail, df$head)
 df$max_node = pmax(df$tail, df$head)
 df$tail = df$min_node
 df$head = df$max_node
 df$min_node = NULL
 df$max_node = NULL
 df_unique = df[!duplicated(df[, c("onset", "terminus", "tail", "head")]), ]

 #df = read.csv("/User/victorhernandez/Desktop/df.csv")
  head(df)
  onset terminus tail head onset.censored terminus.censored duration edge.id
1    10       42    9   10          FALSE             FALSE       32       1
2    10       39    6    9          FALSE             FALSE       29       2
3    10       43    5   19          FALSE             FALSE       33       3
4    20       57   15   20          FALSE             FALSE       37       4
5    20       48    2    3          FALSE             FALSE       28       5
6    20       48    4    8          FALSE             FALSE       28       6
  n_nodes = max(c(df$tail, df$head))
  base_net = network.initialize(n_nodes, directed = FALSE)
# The dataframe should be turned into a Temporal Network:
  net_dyn = networkDynamic(
    base.net = base_net,
    edge.spells = df[, c("onset", "terminus", "tail", "head")])
Created net.obs.period to describe network
 Network observation period info:
  Number of observation spells: 1 
  Maximal time range observed: 10 until 300 
  Temporal mode: continuous 
  Time unit: unknown 
  Suggested time increment: NA 
# Visualize the Temporal Network
  coords = plot(net_dyn,
               displaylabels=TRUE,
               label.cex = 0.8,
               label.pos = 5,
               vertex.col = 'white',
               vertex.cex = 3,
               col = c("pink4","green4","orchid4", "cadetblue4"),
               edge.label = sapply(get.edge.activity(net_dyn),function(e){
                 paste('(',e[,1],'-',e[,2],')',sep='')
               }),
               edge.label.col = 'blue',
               edge.label.cex = 0.7)

   coord <- network.layout.fruchtermanreingold(network.extract(net_dyn, at = 1), NULL)

Element of Time

The fix? Introducing time.

Instead of asking “are nodes connected?” we want to ask “can information actually travel through these connections?” Only by adding time do we correctly see what sequences of influence are actually possible.

All of this matters because by ignoring time, relationships look more flexible than they are and paths may look possible but not actually be possible. This is why we need to represent networks in a way that keeps track of when things happen, not just whether they happen.

# Plot separate moments in time:
  par(mfrow = c(2, 2))
  times = c(1, 100, 200, 300)
  titles = paste("Network at t =", times)
  invisible(lapply(seq_along(times), function(i) {
    plot(network.extract(net_dyn, at = times[i]),
      main = titles[i],
      displaylabels = TRUE,
      label.cex = 0.6,
      label.pos = 5,
      vertex.col = 'white',
      vertex.cex = 5,
      coord = coord) })) 

Introducing …

Temporal Networks!

G (V, E) Where: G: the graph… is given by V: a set of vertices… and E: the subset of {x, y}, pairs of vertices which are connected to one another but are not the same vertex (x does not = y)

Now, we meet G (V, E, D) Where: D is a “dimension” of the network representing different layers. Each layer is a “slice” of time

D is a dimension that can be added to represent lots of other things like weights, probabilities, and categories. In the specific multilayer network we will be learning about, D represents time.

Temporal Closeness and Efficiency

# Temporal Closeness and Efficiency
  calc.temporal.closeness = function(net.dyn, start = NULL) {
    n = network.size(net.dyn)
    if (is.null(start)) start = min(df$onset, na.rm = TRUE)
    
    sapply(1:n, function(i) {
      tp = tryCatch(tPath(net.dyn, v = i, direction = "fwd", start = start), error = function(e) NULL)
      if (is.null(tp)) return(0)
      d = tp$tdist[is.finite(tp$tdist) & tp$tdist > 0]
      if (length(d) == 0) return(0)
      mean(1 / d)
    })
  }
  calc.temporal.efficiency = function(net.dyn, start = NULL) {
    mean(calc.temporal.closeness(net.dyn, start = start), na.rm = TRUE)
  }
  calc.temporal.closeness(net_dyn, start = 10)
 [1] 0.00000000 0.05438596 0.05438596 0.05701754 0.05462963 0.05196078
 [7] 0.04912281 0.05701754 0.05196078 0.05196078 0.04912281 0.04912281
[13] 0.04912281 0.04912281 0.05175439 0.04912281 0.03333333 0.05701754
[19] 0.05462963 0.05175439 0.05438596
  calc.temporal.efficiency(net_dyn, start = 1)
[1] 0.03462672

Time-Respecting Paths and Network Metrics

One of the topics we discussed was closeness centrality, which asks how close one node is to all other nodes based on the shortest path. But In temporal networks, we redefine closeness using time-respecting paths.

As we read in the paper by Pan and Saramaki, temporal closeness is based on the earliest time that information can travel from one node to another, instead of the fewest steps. It is about the fastest feasible journeys through time, not the shortest structural path.

Static closeness is still useful as a baseline, telling us who is structurally central, but doesn’t say much about how efficiently things spread in reality.

Another key concept discussed in the Holme & Saramaki paper is reachability, describing how, in a temporal network, node A is reachable from B if there’s a sequence of time ordered interactions that allows information to travel from B to A. This is different from a simple di-graph because if there’s a path A—>B—>C, we say A can reach C but in a temporal network, that path only matters if the edges occur in the correct order in time. Structure alone is not enough because causality requires time-respecting paths.

Temporal Network animation

 # Temporal Network animation
    compute.animation(
    net_dyn,
    animation.mode = "kamadakawai",
    slice.par = list(
      start = 1,
      end = 310,
      interval = 10,
      aggregate.dur = 10,
      rule = "any"  ))
Calculating layout for network slice from time  1 to 11
Calculating layout for network slice from time  11 to 21
Calculating layout for network slice from time  21 to 31
Calculating layout for network slice from time  31 to 41
Calculating layout for network slice from time  41 to 51
Calculating layout for network slice from time  51 to 61
Calculating layout for network slice from time  61 to 71
Calculating layout for network slice from time  71 to 81
Calculating layout for network slice from time  81 to 91
Calculating layout for network slice from time  91 to 101
Calculating layout for network slice from time  101 to 111
Calculating layout for network slice from time  111 to 121
Calculating layout for network slice from time  121 to 131
Calculating layout for network slice from time  131 to 141
Calculating layout for network slice from time  141 to 151
Calculating layout for network slice from time  151 to 161
Calculating layout for network slice from time  161 to 171
Calculating layout for network slice from time  171 to 181
Calculating layout for network slice from time  181 to 191
Calculating layout for network slice from time  191 to 201
Calculating layout for network slice from time  201 to 211
Calculating layout for network slice from time  211 to 221
Calculating layout for network slice from time  221 to 231
Calculating layout for network slice from time  231 to 241
Calculating layout for network slice from time  241 to 251
Calculating layout for network slice from time  251 to 261
Calculating layout for network slice from time  261 to 271
Calculating layout for network slice from time  271 to 281
Calculating layout for network slice from time  281 to 291
Calculating layout for network slice from time  291 to 301
Calculating layout for network slice from time  301 to 311
  render.d3movie(
    net_dyn,
    displaylabels = FALSE,
    vertex.tooltip = function(slice) {
      paste(
        "<b>Name:</b>", (slice %v% "name"),
        "<br>",
        "<b>Region:</b>", (slice %v% "region"))})
caching 10 properties for slice 0
caching 10 properties for slice 1
caching 10 properties for slice 2
caching 10 properties for slice 3
caching 10 properties for slice 4
caching 10 properties for slice 5
caching 10 properties for slice 6
caching 10 properties for slice 7
caching 10 properties for slice 8
caching 10 properties for slice 9
caching 10 properties for slice 10
caching 10 properties for slice 11
caching 10 properties for slice 12
caching 10 properties for slice 13
caching 10 properties for slice 14
caching 10 properties for slice 15
caching 10 properties for slice 16
caching 10 properties for slice 17
caching 10 properties for slice 18
caching 10 properties for slice 19
caching 10 properties for slice 20
caching 10 properties for slice 21
caching 10 properties for slice 22
caching 10 properties for slice 23
caching 10 properties for slice 24
caching 10 properties for slice 25
caching 10 properties for slice 26
caching 10 properties for slice 27
caching 10 properties for slice 28
caching 10 properties for slice 29
caching 10 properties for slice 30
wrote animation HTML file to C:\Users\imjpark\AppData\Local\Temp\Rtmp29BSAj\file13c481ac41051.html
browser launching disabled because R is not in interactive mode

Burstiness

# Burstiness 
   Burstiness = 1 # dense activity
   Burstiness = 0 #Poisson; "coin flip"
  Burstiness < 0 #Periodic; regularity in connections over time
[1] FALSE
  calc.burstiness = function(net.dyn) {
    acts = get.edge.activity(net.dyn)
    B.vals = sapply(acts, function(sp) {
      if (is.null(sp) || nrow(sp) < 2) return(NA_real_)
      tau = diff(sort(sp[, 1]))
      if (mean(tau) == 0) return(NA_real_)
      (sd(tau) - mean(tau)) / (sd(tau) + mean(tau))
    })
    setNames(B.vals, valid.eids(net.dyn))
  }
  bursty = calc.burstiness(net_dyn)
  hist(bursty)

# Look at Forward Influence of the v^{th} node
 library(tsna)
  v1path <- tPath(net_dyn, v = 2, direction = "fwd")
  print(v1path)
$tdist
 [1] Inf   0  19  29  29  29  29  29  29  29  29  29  29  29  29  29  39  29  29
[20]  29  19

$previous
 [1]  0  0  2  3  4  9  2  4  4 21  9  8 21  4 12  3  4  9  5  8  2

$gsteps
 [1] Inf   0   1   2   3   4   1   3   3   2   4   4   2   3   5   2   3   4   4
[20]   4   1

$start
[1] 1

$end
[1] Inf

$direction
[1] "fwd"

$type
[1] "earliest.arrive"

attr(,"class")
[1] "tPath" "list" 
  # tdist: distance from t = origin for v to affect the i^{th} node
  # previous: The node that immediately preceeded landing on the i^{th} node
  # gsteps: The number of "graph" steps to get to the i^{th} node
 
  plot(v1path, 
      coord = coord,
     displaylabels = TRUE)

# Observing the number of connections as a function of time
  plot(tEdgeFormation(net_dyn, time.interval = 1))

# Observing graph-based density as a function of time
  dynamicdensity = tSnaStats(
    net_dyn,
    snafun = "gden",
    start = 1,
    end = 300,
    time.interval = 1,
    aggregate.dur = 10
  )
  plot(dynamicdensity)

Observing betweenness in the graph over time

# Observing betweenness in the graph over time
  dynamicbtw = tSnaStats(
    net_dyn,
    snafun = "centralization",
    start = 1,
    end = 300,
    time.interval = 1,
    aggregate.dur = 10,
    FUN = "betweenness"
  )
  plot(dynamicbtw)

Introduction to Dynamic Networks

In contrast to the temporal network, where edges appear and disappear over time, a dynamic network is a bit broader. A dynamic network–for this class–is considered some form of model fitted to time-series observations The temporal network focuses on the timing of interactions while the dynamic network focuses on the evolution of the network. The parameters of a dynamic network can describe how the system behaves on an average over time. The parameters represent the steady-state dynamic The nodes can represent the measured variables of individuals such as symptoms moods or personality traits The edges can represent the strength and relationships between nodes Describes how the system fluctuates between its typical state Example: A sucidial individual who normally keeps to themselves. Starts to visit family and friends every Friday night. The family and friends are pleasantly surprised and even say “It’s so great to spend time with you, we typically dont hear from you often.” Until days later wher that individual goes back to spending Friday nights by themselves.

sVAR vs. gVAR

gVAR: - Non-directional, can infer relationships between variables but not give a clear connected relationship - partial correlation relationship

sVAR: - Shows influence on variables and direction varies on influence between variables - Directional(but with certain warnings that depend on the direction of the variable) “The direction of the variables exist”

## AR(1)
  ne = 1
  amat = mxMatrix('Full', ne, ne, TRUE, 0.60, name = 'A')
  bmat = mxMatrix('Zero', ne, ne, name='B')
  cdim = list("AR1", paste0('F', 1:ne))
  cmat = mxMatrix('Diag', ne, ne, FALSE, 1, name = 'C', dimnames = cdim)
  dmat = mxMatrix('Zero', ne, ne, name='D')
  qmat = mxMatrix('Symm', ne, ne, FALSE, diag(1), name='Q', lbound=0)
  rmat = mxMatrix('Symm', ne, ne, FALSE, diag(.2, ne), name='R')
  xmat = mxMatrix('Full', ne, 1, FALSE, 0, name='x0', lbound=-10, ubound=10)
  pmat = mxMatrix('Diag', ne, ne, FALSE, 1, name='P0')
  umat = mxMatrix('Zero', ne, 1, name='u')
  osc = mxModel("OUMod", amat, bmat, cmat, dmat, qmat, rmat, xmat, pmat, umat,
                mxExpectationStateSpace('A', 'B', 'C', 'D', 'Q',
                                        'R', 'x0', 'P0', 'u'))
  sim.data = mxGenerateData(osc, nrows = 10000)
  head(sim.data)
         AR1
1  0.2411899
2  0.5218277
3  0.1601066
4  0.6806131
5  0.3500371
6 -0.3410494
  sim.data$Time = 0:(nrow(sim.data)-1)
  plot(x = (1:nrow(sim.data)), y = sim.data$AR1, type = "l",
       main = "Time-Series of AR1", ylab = "Values", xlab = "Time")

  ar1 = mx.var(dataframe = sim.data, varnames = "AR1")
Running OUMod with 1 parameter

Beginning initial fit attempt
Running OUMod with 1 parameter

 Lowest minimum so far:  30975.4449707563

Solution found

 Solution found!  Final fit=30975.445 (started at 35477.356)  (1 attempt(s): 1 valid, 0 errors)
 Start values from best fit:
0.513115142747104
  summary(ar1)$parameters
          name matrix row col  Estimate   Std.Error lbound ubound lboundMet
1 OUMod.A[1,1]      A   1   1 0.5131151 0.007647385     NA     NA     FALSE
  uboundMet
1     FALSE
  # Can compare to different AR:
  osc$A$values = -0.60
  sim.data = mxGenerateData(osc, nrows = 1000)
  plot(x = (1:nrow(sim.data)), y = sim.data$AR1, type = "l",
       main = "Time-Series of AR1", ylab = "Values", xlab = "Time")

  ar1 = mx.var(dataframe = sim.data, varnames = "AR1")
Running OUMod with 1 parameter

Beginning initial fit attempt
Running OUMod with 1 parameter

 Lowest minimum so far:  3075.10551698621

Solution found

 Solution found!  Final fit=3075.1055 (started at 3565.0093)  (1 attempt(s): 1 valid, 0 errors)
 Start values from best fit:
-0.532548073418678
  summary(ar1)$parameters
          name matrix row col   Estimate  Std.Error lbound ubound lboundMet
1 OUMod.A[1,1]      A   1   1 -0.5325481 0.02405986     NA     NA     FALSE
  uboundMet
1     FALSE
## VAR(1)
  ne = 3
  VAR.params = matrix(c(0.60, 0.00, 0.00,
                        0.00, 0.60, -0.25,
                        0.25, 0.00, 0.60), ne, ne)
  amat = mxMatrix('Full', ne, ne, TRUE, VAR.params, name = 'A')
  bmat = mxMatrix('Zero', ne, ne, name='B')
  cdim = list(paste0("V", 1:ne), paste0('F', 1:ne))
  cmat = mxMatrix('Diag', ne, ne, FALSE, 1, name = 'C', dimnames = cdim)
  dmat = mxMatrix('Zero', ne, ne, name='D')
  qmat = mxMatrix('Symm', ne, ne, FALSE, diag(ne), name='Q', lbound=0)
  rmat = mxMatrix('Symm', ne, ne, FALSE, diag(1e-5, ne), name='R')
  xmat = mxMatrix('Full', ne, 1, FALSE, 0, name='x0', lbound=-10, ubound=10)
  pmat = mxMatrix('Diag', ne, ne, FALSE, 1, name='P0')
  umat = mxMatrix('Zero', ne, 1, name='u')
  osc = mxModel("OUMod", amat, bmat, cmat, dmat, qmat, rmat, xmat, pmat, umat,
                mxExpectationStateSpace('A', 'B', 'C', 'D', 'Q',
                                        'R', 'x0', 'P0', 'u'))
  sim.data = mxGenerateData(osc, nrows = 1000)
  head(sim.data)
           V1          V2         V3
1  2.00649595  0.17113509 -0.2682288
2  0.93044167 -1.17112191 -1.7872081
3  0.05590157  0.35164967  0.3078234
4 -0.70618804 -1.51302730  1.7790210
5 -1.53858188 -2.26964334  2.2559516
6  0.16906361 -0.02128064  2.2378042
  VAR1 = mx.var(dataframe = sim.data, varnames = paste0("V", 1:ne))
Running OUMod with 9 parameters

Beginning initial fit attempt
Running OUMod with 9 parameters

 Lowest minimum so far:  8611.86976241829

Solution found

 Solution found!  Final fit=8611.8698 (started at 11401.337)  (1 attempt(s): 1 valid, 0 errors)
 Start values from best fit:
0.651343402432714,0.00564757676124789,-0.0218972173548602,-0.0337835592139425,0.609659613153728,-0.253609956962774,0.230628843493565,-0.0191705417914945,0.596989633070159
  summary(VAR1)$parameters
          name matrix row col     Estimate  Std.Error lbound ubound lboundMet
1 OUMod.A[1,1]      A   1   1  0.651343402 0.02206612     NA     NA     FALSE
2 OUMod.A[2,1]      A   2   1  0.005647577 0.02206266     NA     NA     FALSE
3 OUMod.A[3,1]      A   3   1 -0.021897217 0.02206269     NA     NA     FALSE
4 OUMod.A[1,2]      A   1   2 -0.033783559 0.02500273     NA     NA     FALSE
5 OUMod.A[2,2]      A   2   2  0.609659613 0.02499203     NA     NA     FALSE
6 OUMod.A[3,2]      A   3   2 -0.253609957 0.02499069     NA     NA     FALSE
7 OUMod.A[1,3]      A   1   3  0.230628843 0.02383096     NA     NA     FALSE
8 OUMod.A[2,3]      A   2   3 -0.019170542 0.02381994     NA     NA     FALSE
9 OUMod.A[3,3]      A   3   3  0.596989633 0.02382128     NA     NA     FALSE
  uboundMet
1     FALSE
2     FALSE
3     FALSE
4     FALSE
5     FALSE
6     FALSE
7     FALSE
8     FALSE
9     FALSE
  params = matrix(0, ne, ne)
  for(i in 1:nrow(summary(VAR1)$parameters)){
    params[summary(VAR1)$parameters[i,"row"], summary(VAR1)$parameters[i, "col"]] = 
      ifelse(abs(summary(VAR1)$parameters[i,"Estimate"]) > qnorm(0.975) * summary(VAR1)$parameters[i,"Std.Error"],
             summary(VAR1)$parameters[i,"Estimate"],
             0.00)
  }
  # True
    VAR.params
     [,1]  [,2] [,3]
[1,]  0.6  0.00 0.25
[2,]  0.0  0.60 0.00
[3,]  0.0 -0.25 0.60
  # Estimated
    round(params, 2)
     [,1]  [,2] [,3]
[1,] 0.65  0.00 0.23
[2,] 0.00  0.61 0.00
[3,] 0.00 -0.25 0.60

Murder Mystery

You are a highly exclusive detective attending a charitable Gala on Friday the 13th with a total of 450 guests in attendance. Which contains 150 A-list celebrities, 150 politicians, and 150 billionaire tycons who plan to auction off the incredibly expensive and irreplaceable Pink Panther Diamond!

Your comrades are: VAR an undercover detective with sVAR and gVAR, the junior undercover detectives.

Throughout the gala You, VAR and sVAR interact with party guests and gather information on who they are, how they are feeling, and plans for tomorrow. Important side note, VAR is keeping track of time for the numbered interactions, and takes occasional social breaks throughout the Gala. While gVAR sits in the corner unimpresingly observing and analyzing everyone’s emotion and behavior. Overall, every VAR is accounting for time as they attend the gala. As day enters the night, everyone’s mood drops, as a famous billionaire politician gets murdered in the Ballroom! Shocker.
As the crowd huddles around the scene, the Pink Panther Diamond is stolen! Gasp Luckily, the building goes on lockdown and every number of guests is accounted for. The objective for the rest of the Gala is to find the murder and recover the diamond. Throughout the investigations, integrations, and clues are all gathered.

The recap on roles from investigation: VAR (Time tracker):tracks time, emotions, and behaviors from party guest but would take continuous breaks so their data might have a lagged effect sVAR(Social investigator): analyzed influences and connections between party guest, in present time gVAR(Introverted relationship mapper): built a relationship network without any biased claims or assuming causes You: a prime detective connection all data from team All VAR systems measured the time accounted for since the murder, and before the commitment of both crimes. All together you determine that there were 5 strange suspects that display similarly irregular change in behavior and emotional traits, 3 of them you are acquainted with, 2 of them you know more personally, but only 1 of them is guilty.

The emotions displayed are Irritable; set off by small interactions or gave exaggerated reaction Unusually joyous Extremely social Anxious; avoiding eye contact and fidgeting Fatigue; delayed reactions to questions Each appeared suspicious in isolation.

People acquainted with: Beyonce, Kim Kardashian, Bad Bunny Social event friends: Jenifer Coolidge, Billie Eilish

Using gVAR(relationship mapper): It was determined that Kim Kardashian was highly central in the network, and has connections to all 5 suspects, causing a social bridge to unrelated groups. Using VAR(Time tracker) : Showed Kim did not display suspicious behavior overtime, and their emotional patterns remained stable sVAR(Social investigator): Revealed Kim triggered a chain reaction in the crowd, and her actions caused attention away from the diamond, this reveals coordinated actions occurring in real time.

From the observational Statments:

Beyoncé: Throughout the Gala Beyoncé was shown to be unusually composed despite panic in the Ballroom. Multiple gues reported that she carefully observed conversations rather than engaging in them. VAR detected subtle emtotional fluctuations over time, specifically after the murdered occured. Several guests believed her calmness under pressure appearing to be unnatural, as her interactions remained controlled and difficult to interpret.

Bad Bunny: Displayed high implusive and energetic levels.Appearing commonly through the auction hall, dance floor, and outdoor terrance. Guest reported abrupt emotional shifts, moving rapidly between excitment, humor, and irration between conversations. sVAR identified his reations as highly reactive to nearby social interactions, cuasing tention within surronding groups. Despite his behavior bieng so openly chaotic, investigators struggled to determine if it represented guilt or just his behavior from being at the Gala.

Jenifer Coolidge: You notice that she kept wandering around the Gala, looking overwhelmed by everything happening. Throughout the night she kept drifting between conversations, often forgetting where she had left her drink or what she was talking about mid-sentence. VAR noticed small increases of emtoional instability over time, but honestly that could have been easily cuased by the stress of the lockdown and the growing spread of fear.

Kim Kardashian: Kim remained highly vissuable for the Gala. Overall bieng overally joyus, and extreamely sociable. Appearing frequently near the central ballroom, media, and auction areas. gVAR identified her as most socailly central, connecting people from unrelated groups. Following the murder, sVAR revealed that her movements and interactions unitentionally trigered large emotional reactions throughout nearby crowds, creating waves of distraction and confusion across the ballroom. Investigators questioned her influence over social atmospheres, no diresct evidence connected her to either crime.

Billie Eilish: Billie spent alot of the evening floating between social groups near art displays. She natuurally blended into conversations without drawing too much attention to herself. gVAR identified her as socailly connected across multiple groups, with many people describing her behavior as emotionally grounded. As someone you know personally none of her behavior raised an flags.

The Convicted:

Classified Evidence.

Jenifer Coolidge The actual murder was committed by a quieter, less connected suspect, showing an increase in emotional instability over time

Classified Evidence.

Billie Eilish. Captured by sVAR.

Kim Kardashian, not directly or intentionally committing crime, but engaging attention to distract and enable both actions. Identified by gVAR and sVAR The investigation reveals a coordinated but unspoken alignment gVAR: showed key social structure and a central figure VAR: exposed the murder through behavioral patterns overtime sVAR: captured real-time interactions for crime to unfold

Aftermath questions

Is it possible to solve “crime” with one model or would you need all three? What limitations could occur in all three models? Do you have your own preference in between the gVAR or the sVAR model?

Temporal and Dynamic Netorks tutorial

##Temporal Network

library(sna)
library(tsna)
library(ndtv)
#setwd("/Users/victorhernandez/Desktop/")

df = read.csv("./df.csv")
names(df)
[1] "onset"             "terminus"          "tail"             
[4] "head"              "onset.censored"    "terminus.censored"
[7] "duration"          "edge.id"          
 df$tail = df$tail + 1
 df$head = df$head + 1
 df$min_node = pmin(df$tail, df$head)
 df$max_node = pmax(df$tail, df$head)
 df$tail = df$min_node
 df$head = df$max_node
 df$min_node = NULL
 df$max_node = NULL
 df_unique = df[!duplicated(df[, c("onset", "terminus", "tail", "head")]), ]

 #df = read.csv("/User/victorhernandez/Desktop/df.csv")
  head(df)
  onset terminus tail head onset.censored terminus.censored duration edge.id
1    10       42    9   10          FALSE             FALSE       32       1
2    10       39    6    9          FALSE             FALSE       29       2
3    10       43    5   19          FALSE             FALSE       33       3
4    20       57   15   20          FALSE             FALSE       37       4
5    20       48    2    3          FALSE             FALSE       28       5
6    20       48    4    8          FALSE             FALSE       28       6
  n_nodes = max(c(df$tail, df$head))
  base_net = network.initialize(n_nodes, directed = FALSE)
# The dataframe should be turned into a Temporal Network:
  net_dyn = networkDynamic(
    base.net = base_net,
    edge.spells = df[, c("onset", "terminus", "tail", "head")])
Created net.obs.period to describe network
 Network observation period info:
  Number of observation spells: 1 
  Maximal time range observed: 10 until 300 
  Temporal mode: continuous 
  Time unit: unknown 
  Suggested time increment: NA 
# Visualize the Temporal Network
  # Realize that visualization is lame
  coords = plot(net_dyn,
               displaylabels=TRUE,
               label.cex = 0.8,
               label.pos = 5,
               vertex.col = 'white',
               vertex.cex = 3,
               edge.label = sapply(get.edge.activity(net_dyn),function(e){
                 paste('(',e[,1],'-',e[,2],')',sep='')
               }),
               edge.label.col = 'blue',
               edge.label.cex = 0.7)

   coord <- network.layout.fruchtermanreingold(network.extract(net_dyn, at = 1), NULL)
# Plot separate moments in time:
  par(mfrow = c(2, 2))
  times = c(1, 100, 200, 300)
  titles = paste("Network at t =", times)
  invisible(lapply(seq_along(times), function(i) {
    plot(network.extract(net_dyn, at = times[i]),
      main = titles[i],
      displaylabels = TRUE,
      label.cex = 0.6,
      label.pos = 5,
      vertex.col = 'white',
      vertex.cex = 5,
      coord = coord) }))

# Create a controllable animation
  compute.animation(
    net_dyn,
    animation.mode = "kamadakawai",
    slice.par = list(
      start = 1,
      end = 310,
      interval = 10,
      aggregate.dur = 10,
      rule = "any"  ))
Calculating layout for network slice from time  1 to 11
Calculating layout for network slice from time  11 to 21
Calculating layout for network slice from time  21 to 31
Calculating layout for network slice from time  31 to 41
Calculating layout for network slice from time  41 to 51
Calculating layout for network slice from time  51 to 61
Calculating layout for network slice from time  61 to 71
Calculating layout for network slice from time  71 to 81
Calculating layout for network slice from time  81 to 91
Calculating layout for network slice from time  91 to 101
Calculating layout for network slice from time  101 to 111
Calculating layout for network slice from time  111 to 121
Calculating layout for network slice from time  121 to 131
Calculating layout for network slice from time  131 to 141
Calculating layout for network slice from time  141 to 151
Calculating layout for network slice from time  151 to 161
Calculating layout for network slice from time  161 to 171
Calculating layout for network slice from time  171 to 181
Calculating layout for network slice from time  181 to 191
Calculating layout for network slice from time  191 to 201
Calculating layout for network slice from time  201 to 211
Calculating layout for network slice from time  211 to 221
Calculating layout for network slice from time  221 to 231
Calculating layout for network slice from time  231 to 241
Calculating layout for network slice from time  241 to 251
Calculating layout for network slice from time  251 to 261
Calculating layout for network slice from time  261 to 271
Calculating layout for network slice from time  271 to 281
Calculating layout for network slice from time  281 to 291
Calculating layout for network slice from time  291 to 301
Calculating layout for network slice from time  301 to 311
  render.d3movie(
    net_dyn,
    displaylabels = FALSE,
    vertex.tooltip = function(slice) {
      paste(
        "<b>Name:</b>", (slice %v% "name"),
        "<br>",
        "<b>Region:</b>", (slice %v% "region"))})
caching 10 properties for slice 0
caching 10 properties for slice 1
caching 10 properties for slice 2
caching 10 properties for slice 3
caching 10 properties for slice 4
caching 10 properties for slice 5
caching 10 properties for slice 6
caching 10 properties for slice 7
caching 10 properties for slice 8
caching 10 properties for slice 9
caching 10 properties for slice 10
caching 10 properties for slice 11
caching 10 properties for slice 12
caching 10 properties for slice 13
caching 10 properties for slice 14
caching 10 properties for slice 15
caching 10 properties for slice 16
caching 10 properties for slice 17
caching 10 properties for slice 18
caching 10 properties for slice 19
caching 10 properties for slice 20
caching 10 properties for slice 21
caching 10 properties for slice 22
caching 10 properties for slice 23
caching 10 properties for slice 24
caching 10 properties for slice 25
caching 10 properties for slice 26
caching 10 properties for slice 27
caching 10 properties for slice 28
caching 10 properties for slice 29
caching 10 properties for slice 30
wrote animation HTML file to C:\Users\imjpark\AppData\Local\Temp\Rtmp29BSAj\file13c485d261c68.html
browser launching disabled because R is not in interactive mode
# Look at Forward Influence of the v^{th} node
 library(tsna)
  v1path <- tPath(net_dyn, v = 2, direction = "fwd")
  print(v1path)
$tdist
 [1] Inf   0  19  29  29  29  29  29  29  29  29  29  29  29  29  29  39  29  29
[20]  29  19

$previous
 [1]  0  0  2  3  4  9  2  4  4 21  9  8 21  4 12  3  4  9  5  8  2

$gsteps
 [1] Inf   0   1   2   3   4   1   3   3   2   4   4   2   3   5   2   3   4   4
[20]   4   1

$start
[1] 1

$end
[1] Inf

$direction
[1] "fwd"

$type
[1] "earliest.arrive"

attr(,"class")
[1] "tPath" "list" 
  # tdist: distance from t = origin for v to affect the i^{th} node
  # previous: The node that immediately preceeded landing on the i^{th} node
  # gsteps: The number of "graph" steps to get to the i^{th} node
 
  plot(v1path, 
      coord = coord,
     displaylabels = TRUE)
  
# Observing the number of connections as a function of time
  plot(tEdgeFormation(net_dyn, time.interval = 1))
# Observing graph-based density as a function of time
  dynamicdensity = tSnaStats(
    net_dyn,
    snafun = "gden",
    start = 1,
    end = 300,
    time.interval = 1,
    aggregate.dur = 10
  )
  plot(dynamicdensity)

# Observing betweenness in the graph over time
  dynamicbtw = tSnaStats(
    net_dyn,
    snafun = "centralization",
    start = 1,
    end = 300,
    time.interval = 1,
    aggregate.dur = 10,
    FUN = "betweenness"
  )
  plot(dynamicbtw)

# Burstiness 
   Burstiness = 1 # dense activity
   Burstiness = 0 #Poisson; "coin flip"
  Burstiness < 0 #Periodic; regularity in connections over time
[1] FALSE
  calc.burstiness = function(net.dyn) {
    acts = get.edge.activity(net.dyn)
    B.vals = sapply(acts, function(sp) {
      if (is.null(sp) || nrow(sp) < 2) return(NA_real_)
      tau = diff(sort(sp[, 1]))
      if (mean(tau) == 0) return(NA_real_)
      (sd(tau) - mean(tau)) / (sd(tau) + mean(tau))
    })
    setNames(B.vals, valid.eids(net.dyn))
  }
  bursty = calc.burstiness(net_dyn)
  hist(bursty)
  
# Temporal Closeness and Efficiency
  calc.temporal.closeness = function(net.dyn, start = NULL) {
    n = network.size(net.dyn)
    if (is.null(start)) start = min(df$onset, na.rm = TRUE)
    
    sapply(1:n, function(i) {
      tp = tryCatch(tPath(net.dyn, v = i, direction = "fwd", start = start), error = function(e) NULL)
      if (is.null(tp)) return(0)
      d = tp$tdist[is.finite(tp$tdist) & tp$tdist > 0]
      if (length(d) == 0) return(0)
      mean(1 / d)
    })
  }
  calc.temporal.efficiency = function(net.dyn, start = NULL) {
    mean(calc.temporal.closeness(net.dyn, start = start), na.rm = TRUE)
  }
  calc.temporal.closeness(net_dyn, start = 10)
 [1] 0.00000000 0.05438596 0.05438596 0.05701754 0.05462963 0.05196078
 [7] 0.04912281 0.05701754 0.05196078 0.05196078 0.04912281 0.04912281
[13] 0.04912281 0.04912281 0.05175439 0.04912281 0.03333333 0.05701754
[19] 0.05462963 0.05175439 0.05438596
  calc.temporal.efficiency(net_dyn, start = 1)
[1] 0.03462672

Dynamic Networks Tutorial

## AR(1)
  ne = 1
  amat = mxMatrix('Full', ne, ne, TRUE, 0.60, name = 'A')
  bmat = mxMatrix('Zero', ne, ne, name='B')
  cdim = list("AR1", paste0('F', 1:ne))
  cmat = mxMatrix('Diag', ne, ne, FALSE, 1, name = 'C', dimnames = cdim)
  dmat = mxMatrix('Zero', ne, ne, name='D')
  qmat = mxMatrix('Symm', ne, ne, FALSE, diag(1), name='Q', lbound=0)
  rmat = mxMatrix('Symm', ne, ne, FALSE, diag(.2, ne), name='R')
  xmat = mxMatrix('Full', ne, 1, FALSE, 0, name='x0', lbound=-10, ubound=10)
  pmat = mxMatrix('Diag', ne, ne, FALSE, 1, name='P0')
  umat = mxMatrix('Zero', ne, 1, name='u')
  osc = mxModel("OUMod", amat, bmat, cmat, dmat, qmat, rmat, xmat, pmat, umat,
                mxExpectationStateSpace('A', 'B', 'C', 'D', 'Q',
                                        'R', 'x0', 'P0', 'u'))
  sim.data = mxGenerateData(osc, nrows = 10000)
  head(sim.data)
        AR1
1 -1.292089
2 -3.042656
3 -1.501645
4 -2.053060
5 -3.131747
6 -1.665145
  sim.data$Time = 0:(nrow(sim.data)-1)
  plot(x = (1:nrow(sim.data)), y = sim.data$AR1, type = "l",
       main = "Time-Series of AR1", ylab = "Values", xlab = "Time")

  ar1 = mx.var(dataframe = sim.data, varnames = "AR1")
Running OUMod with 1 parameter

Beginning initial fit attempt
Running OUMod with 1 parameter

 Lowest minimum so far:  31007.8202174047

Solution found

 Solution found!  Final fit=31007.82 (started at 36132.713)  (1 attempt(s): 1 valid, 0 errors)
 Start values from best fit:
0.53730320219866
  summary(ar1)$parameters
          name matrix row col  Estimate   Std.Error lbound ubound lboundMet
1 OUMod.A[1,1]      A   1   1 0.5373032 0.007505239     NA     NA     FALSE
  uboundMet
1     FALSE
  # Can compare to different AR:
  osc$A$values = -0.60
  sim.data = mxGenerateData(osc, nrows = 1000)
  plot(x = (1:nrow(sim.data)), y = sim.data$AR1, type = "l",
       main = "Time-Series of AR1", ylab = "Values", xlab = "Time")

  ar1 = mx.var(dataframe = sim.data, varnames = "AR1")
Running OUMod with 1 parameter

Beginning initial fit attempt
Running OUMod with 1 parameter

 Lowest minimum so far:  3069.82756446222

Solution found

 Solution found!  Final fit=3069.8276 (started at 3605.5064)  (1 attempt(s): 1 valid, 0 errors)
 Start values from best fit:
-0.55117442006225
  summary(ar1)$parameters
          name matrix row col   Estimate  Std.Error lbound ubound lboundMet
1 OUMod.A[1,1]      A   1   1 -0.5511744 0.02381471     NA     NA     FALSE
  uboundMet
1     FALSE
## VAR(1)
  ne = 3
  VAR.params = matrix(c(0.60, 0.00, 0.00,
                        0.00, 0.60, -0.25,
                        0.25, 0.00, 0.60), ne, ne)
  amat = mxMatrix('Full', ne, ne, TRUE, VAR.params, name = 'A')
  bmat = mxMatrix('Zero', ne, ne, name='B')
  cdim = list(paste0("V", 1:ne), paste0('F', 1:ne))
  cmat = mxMatrix('Diag', ne, ne, FALSE, 1, name = 'C', dimnames = cdim)
  dmat = mxMatrix('Zero', ne, ne, name='D')
  qmat = mxMatrix('Symm', ne, ne, FALSE, diag(ne), name='Q', lbound=0)
  rmat = mxMatrix('Symm', ne, ne, FALSE, diag(1e-5, ne), name='R')
  xmat = mxMatrix('Full', ne, 1, FALSE, 0, name='x0', lbound=-10, ubound=10)
  pmat = mxMatrix('Diag', ne, ne, FALSE, 1, name='P0')
  umat = mxMatrix('Zero', ne, 1, name='u')
  osc = mxModel("OUMod", amat, bmat, cmat, dmat, qmat, rmat, xmat, pmat, umat,
                mxExpectationStateSpace('A', 'B', 'C', 'D', 'Q',
                                        'R', 'x0', 'P0', 'u'))
  sim.data = mxGenerateData(osc, nrows = 1000)
  head(sim.data)
           V1         V2         V3
1 -1.00872210  0.5408650 0.36028776
2 -0.16939515  1.1610310 0.11583928
3  0.65356965  0.8541743 1.03274049
4 -0.42906753 -0.3474605 0.63863866
5 -0.02543786 -0.7628920 0.09690779
6 -0.65408742 -0.4922264 1.24874470
  VAR1 = mx.var(dataframe = sim.data, varnames = paste0("V", 1:ne))
Running OUMod with 9 parameters

Beginning initial fit attempt
Running OUMod with 9 parameters

 Lowest minimum so far:  8530.95938991316

Solution found

 Solution found!  Final fit=8530.9594 (started at 10861.984)  (1 attempt(s): 1 valid, 0 errors)
 Start values from best fit:
0.630953903541439,0.0350803259133233,-0.0135996597895703,0.00934694238556144,0.586945983141843,-0.240467095389706,0.218753850879668,0.0147853441942145,0.590535564949616
  summary(VAR1)$parameters
          name matrix row col     Estimate  Std.Error lbound ubound lboundMet
1 OUMod.A[1,1]      A   1   1  0.630953904 0.02320130     NA     NA     FALSE
2 OUMod.A[2,1]      A   2   1  0.035080326 0.02319930     NA     NA     FALSE
3 OUMod.A[3,1]      A   3   1 -0.013599660 0.02319972     NA     NA     FALSE
4 OUMod.A[1,2]      A   1   2  0.009346942 0.02594547     NA     NA     FALSE
5 OUMod.A[2,2]      A   2   2  0.586945983 0.02594305     NA     NA     FALSE
6 OUMod.A[3,2]      A   3   2 -0.240467095 0.02594225     NA     NA     FALSE
7 OUMod.A[1,3]      A   1   3  0.218753851 0.02432811     NA     NA     FALSE
8 OUMod.A[2,3]      A   2   3  0.014785344 0.02432635     NA     NA     FALSE
9 OUMod.A[3,3]      A   3   3  0.590535565 0.02432607     NA     NA     FALSE
  uboundMet
1     FALSE
2     FALSE
3     FALSE
4     FALSE
5     FALSE
6     FALSE
7     FALSE
8     FALSE
9     FALSE
  params = matrix(0, ne, ne)
  for(i in 1:nrow(summary(VAR1)$parameters)){
    params[summary(VAR1)$parameters[i,"row"], summary(VAR1)$parameters[i, "col"]] = 
      ifelse(abs(summary(VAR1)$parameters[i,"Estimate"]) > qnorm(0.975) * summary(VAR1)$parameters[i,"Std.Error"],
             summary(VAR1)$parameters[i,"Estimate"],
             0.00)
  }
  # True
    VAR.params
     [,1]  [,2] [,3]
[1,]  0.6  0.00 0.25
[2,]  0.0  0.60 0.00
[3,]  0.0 -0.25 0.60
  # Estimated
    round(params, 2)
     [,1]  [,2] [,3]
[1,] 0.63  0.00 0.22
[2,] 0.00  0.59 0.00
[3,] 0.00 -0.24 0.59