Edge and vertex attributes not mapping properly when constructing networkDynamic object

357 Views Asked by At

My aim is to make a dynamic network visualization using the ndtv and networkDynamic packages in R. I constuct the networkDynamic object using edge spells and vertex spells as described in section 7.3 here. The data frames also contain temporal edge and vertex attributes (TEAs).

library(networkDynamic)
edges <- read.csv("https://raw.githubusercontent.com/SoranHD/Rstuff/main/edges.csv")
edges$X<- NULL
nodes <- read.csv("https://raw.githubusercontent.com/SoranHD/Rstuff/main/nodes.csv")
nodes$X <- NULL
net <- networkDynamic(edge.spells = edges, vertex.spells = nodes, create.TEAs = TRUE)

I want all edges and vertices to remain active throughout, but to vary based on their TEAs. I want "weight" to determine the width of the edges and "size" to determine the size of the nodes.

However, when I render the animation, it is clear that neither map onto their object properly.

compute.animation(net, 
              slice.par = list(start = 1, end = 8, interval = 1, aggregate.dur = 1, rule = "any"))
render.d3movie(net, usearrows = T,
           edge.lwd = (net %e% "weight.active")/100,
           vertex.cex = (net %v% "size.active"),
           output.mode = "HTML",
           launchBrowser = FALSE)

For some reason, only the edges directed from "node 1" have different widths, and even these do not appear as expected. The node sizes are also wrong, and remain static throughout.

Clearly I am doing something wrong. I have tried to construct the data set in several different ways, including as a list of network objects representing each wave, but I keep running into the same issue. Any help would be greatly appreciated.

Edit

I have had the same issue when constructing the dynamicNetwork object using the network.list option, and it persists when running the same code on the newcomb data, as suggested in the comments. The documentation linked above mentions that the data set contains the edge attribute "weights" it doesn't seem to, so I use the "rank" attribute from the newcom.rank data set instead.

library(networkDynamic)
library(networkDynamic)
data(newcomb)
newcombDyn <- networkDynamic(network.list = newcomb.rank, create.TEAs = TRUE)

When comparing the "rank" attribute in the first network in the newcomb.rank network list with the "rank.active" attribute at time 1, we see that they do not correspond. Moreover, the "rank.active" attribute is much larger than the number of active edges.

get.edge.attribute(newcomb.rank[[1]], "rank")
get.edge.attribute.active(newcombDyn, "rank.active", at = 1)

Moreover, "rank.active" looks the same at t = 2 as t = 1.

get.edge.attribute.active(newcombDyn, "rank.active", at = 2)

Finally, when rendering the animation with edge width based on the "rank.active" attribute, the edge width is not dynamic but remains static throughout.

    compute.animation(newcombDyn, slice.par = list(start = 1, end = 14, interval = 1, aggregate.dur = 1, rule = "any"))
render.d3movie(newcombDyn, usearrows = T, edge.lwd = (newcombDyn %e% "rank.active")/2, output.mode = "HTML", launchBrowser = TRUE)
2

There are 2 best solutions below

3
On

I think it is just a matter of changing from rank.active to rank, but the results may not be what you desire as all relationships through time are present as there are no ties allowed. But,

library(networkDynamic)
library(ndtv)
data(newcomb)
newcombDyn <- networkDynamic(network.list = newcomb.rank, create.TEAs = TRUE)

Looking at the difference between

get.edge.value.active(newcombDyn, 'rank', onset=0, terminus=1,  dynamic.only=TRUE)

and

get.edge.value.active(newcombDyn, 'rank', onset=1, terminus=2,  dynamic.only=TRUE) 

we see the values are changing so should map as expected (perhaps)

render.d3movie(newcombDyn, usearrows = T, edge.lwd = (newcombDyn %e% "rank")/2, output.mode = "HTML", launchBrowser = TRUE)

makes a movie of sorts. Alternatively,using your data from above (now that it has been certified as safe by the author), changing your

render.d3movie(net, usearrows = T,
          edge.lwd = (net %e% "weight.active")/100,
          vertex.cex = (net %v% "size.active"),
          output.mode = "HTML",
          launchBrowser = FALSE)

to

render.d3movie(net, usearrows = T,
           edge.lwd = (net %e% "weight.active")/25, #divide by 100 makes invisible arrows
           vertex.cex = (net %v% "size.active")*2, # perhaps too large, but...
           output.mode = "HTML",
           launchBrowser = FALSE)

And it seems to be working(ish), for a remarkably tricky data structure. HTH

1
On

Because the rendering function already computes a static 'slice' network at each time step (with network.collapse) it is possible to directly pass in the name of the static version of the attribute ('size' vs 'size.active') to be used in the rendering function:

> render.d3movie(net, usearrows = T,
                                 edge.lwd = 'weight',
                                 vertex.cex = 'size',
                                 output.mode = "HTML",
                                 launchBrowser = FALSE)

But in this case, you also want to transform the the attribute for rendering, you can pass in a function to be applied (see examples in ?render.animation) The function can use the variable named slice that corresponds to the appropriate network for that time slice:

> render.d3movie(net, usearrows = T,
                 edge.lwd = function(slice){slice%e%'weight'/100},
                 vertex.cex = function(slice){slice%v%'size'},
                 output.mode = "HTML",
                 launchBrowser = FALSE)

It looks like you have defined edges to be active between all vertices at all time steps, but the vertices are not defined to active at all time steps. For example there are only 9 vertices active at time step one. You could change your input file to align vertex and edge activity. You could also use reconcile.vertex.activity() to expand the activity of the vertices (but it won't be able to infer the TEA attributes for the missing time ranges)