0

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)
  • Hi Soran, welcome to Stackoverflow (SOF) and thanks for the link to documentation. Looking at last paragraph on pg 19, and also footnote, how is your data organized relative to `?necomb`, which seems to point to a dataset meant to be rendered as you wish. As far as I've gotten...For SOF purposes, it's generally best to present a safe, compact, reproducible example, and as your raw github user csv may inject venom, kill my dog, cancel my phone service, perhaps proceding from `?newcomb` and where you diverge at to data and approach might be better. – Chris Apr 19 '21 at 22:14
  • Thanks! I assure you my data is dog friendly, but point taken. See edit for an example using the newcomb data. – soran hajo dahl Apr 20 '21 at 10:19

2 Answers2

0

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

Chris
  • 1,647
  • 1
  • 18
  • 25
  • When I render the animation using just "rank" as in your example the edge.lwd argument seems to be ignored. All the edges have the same width, and they do not change at all. – soran hajo dahl Apr 20 '21 at 18:06
  • I noticed that you use the .active attributes in the example using my data. This produces the same result as I've had before, with the edge-attributes mapping wrong. When running it using just "weight" and "size" I have the same problem as above. The attributes appear correct when I call "get.edge.value.active" as you suggested, but it does not seem like they are called. Many thanks for helping with this. – soran hajo dahl Apr 20 '21 at 18:13
  • I take your point as to newcomb rank, where I suppose if you could have the rank # be the size, on a per iteration basis (which I couldn't figure out...). And on a per iteration basis for your data as well. Labeling the vertices (on your data) would at least show if arrow are moving forward. – Chris Apr 20 '21 at 18:15
0

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)

skyebend
  • 1,079
  • 6
  • 19
  • Thank you! There are actually meant to be only only 9 vertices active in the beginning (11 vertices in total - the "pid" value in the vertex data is a remnant of an earlier attempt to make this work), so it finally appears to be working. It seems like the vertices and edges align in the sense that the right edges connect with the right vertices in each step, or am I missing something? – soran hajo dahl Apr 20 '21 at 20:07