Mischa Mischa - 2 months ago 11
R Question

How can I efficiently activate an edge attribute at multiple times in a networkDynamic object in R?

I want to construct a

networkDynamic
object in R from transaction data where each line represents a contribution by a person to a document. Multiple contributions should be represented as an increase in edge weight instead of creating multiple edges.

The following code snippets should be reproducible in RStudio easily to see the problem.

if (!require("pacman")) install.packages("pacman"); library("pacman")
pacman::p_load(network, networkDynamic, ndtv, lubridate)

stTransac <- "
'person', 'document', 'weight', 'instantId'
'A', 'a1', '3', '1'
'A', 'a1', '15', '2'
'A', 'a1', '100', '3'
'B', 'a1', '20', '10'
'C', 'a1', '30', '12'
"
dfTransac <- read.csv(text = stTransac, sep = "," , quote = '\'' , strip.white = TRUE, stringsAsFactors = FALSE)

net <- network.initialize(0, directed = TRUE, bipartite = 3)

add.vertices.networkDynamic(net, 3, vertex.pid = c("A","B","C"))
add.vertices.networkDynamic(net, 1, vertex.pid = "a1")

net %v% "vertex.names" <- c(c("A","B","C"), "a1")
set.network.attribute(net,'vertex.pid','vertex.names')
set.network.attribute(net,'edge.pid','edge.names')

add.edges.networkDynamic(net,
tail = get.vertex.id(net, c("A","B","C")),
head = get.vertex.id(net, "a1"),
edge.pid = paste0(c("A","B","C"), "->a1"))

activate.edges(net,
e = get.edge.id(net, paste0(dfTransac[["person"]], "->a1")),
at = dfTransac$instantId)


Up to now, everything works as expected (if you skip the
activate.edge.attribute
block below and jump directly to the last block, you will see in the animation that edges are activated at times 1,2,3,10,12.) However, when using the
activate.edge.attribute
function intuitively in the same way as the
activate.edges
function, for the first edge the weight attribute is only initialized at
3
with a value of
100
. The earlier two weight values are dropped.

activate.edge.attribute(net,
prefix = "weight",
value = dfTransac$weight,
e = get.edge.id(net, paste0(dfTransac[["person"]], "->a1")),
at = dfTransac$instantId)


I could iterate over the transaction data frame, but I suppose this will not scale too well:

by(dfTransac, 1:nrow(dfTransac), function(row) {
net <<- activate.edge.attribute(net,
prefix = "weight",
value = row[["weight"]],
e = get.edge.id(net, paste0(row[["person"]], "->", row[["document"]])),
at = row[["instantId"]])
})


This last block renders the animation...

reconcile.vertex.activity(net = net, mode = "encompass.edges", edge.active.default = FALSE)

compute.animation(net, slice.par = list(start = 1, end = 13, interval = 1, aggregate.dur = 1, rule = "any"))
render.animation(net)
ani.replay()


What is the correct and efficient way to set the weight attribute
at
multiple different timestamps?

Answer

Partially for efficiency reasons, the attribute activate code cannot activate multiple spells per vertex/edge. As the documentation says:

... it is possible to use one function call to activate multiple values on multiple vertices with a different activity time on each vertex, but it is not possible to activate multiple values at multiple times on a single vertex with one call.

I would recommend the following syntax for creating your network with the networkDynamic() constructor function, which has the option to import attributes at the same time.

# re-arrange the data.frame column order to an edge.spell format, 
# duplicating the time to use for onset and terminus
input<-dfTransac[,c(4,4,1,2,3)]

# convert the ids to numeric
ids<-unique(c(dfTransac$person,dfTransac$document))
input[,3]<-match(input[,3],ids)
input[,4]<-match(input[,4],ids)
input
  instantId instantId.1 person document weight
1         1           1      1        4      3
2         2           2      1        4     15
3         3           3      1        4    100
4        10          10      2        4     20
5        12          12      3        4     30

# initialize a base network with the appropriate characteristics
net<-network.initialize(length(ids),bipartite=3)
# copy in the vertex names
network.vertex.names(net)<-ids

# use the networkDynamic constructor, telling it to create dynamic attributes
netDyn <- networkDynamic(net,edge.spells = input,
+                          create.TEAs = TRUE,edge.TEA.names = 'weight')

Activated TEA edge attributes:  weightCreated net.obs.period to describe network
 Network observation period info:
  Number of observation spells: 1 
  Maximal time range observed: 1 until 12 
  Temporal mode: continuous 
  Time unit: unknown 
  Suggested time increment: NA 

# print out the attributes structure for edge 1 to double check
get.edge.attribute(netDyn,'weight.active',unlist=FALSE)[[1]]
[[1]]
[[1]][[1]]
[1] 3

[[1]][[2]]
[1] 15

[[1]][[3]]
[1] 100


[[2]]
     [,1] [,2]
[1,]    1    1
[2,]    2    2
[3,]    3    3

So we can see that the first edge now has the expected 3 values for 'weight'. Note the networkDynamic() has to do similar looping to appropriately attach the dynamic attributes, but at least it is all under the hood.