11 Dynamic Edges
So far we have been drawing static graphs, in this chapter we look at dynamic ones, namely temporal. However, being an introduction we’ll start with dynamic edges only: we’ll plot the nodes and have the edges appear dynamically .We’ll tackle the fully temporal network further down the book.
11.1 Rationale
We’ve been visualising Twitter interactions in a static manner, but they are dynamic when you think of it. Twitter conversations happen over time, thus far, we’ve just been drawing all encompassing snapshots. So let’s take into account the time factor to make a where the edges appear at different time steps.
11.2 Collect
We’ll collect some tweets again, we’ll use retweets this time, so we build the corresponding search.
# TK <- readRDS(file = "token.rds")
tweets <- search_tweets("#rstats filter:retweets", n = 400, token = TK, include_rts = TRUE)
## Searching for tweets...
## Finished collecting tweets!
11.3 Build
Now onto building the graph.
net <- tweets %>%
gt_edges(screen_name, mentions_screen_name, created_at) %>%
gt_nodes() %>%
gt_dyn() %>%
gt_collect()
Quite a few things differ from previous graphs we have built.
- We pass
created_at
ingt_edges
. This in effect adds thecreated_at
column to our edges, so that we know the created time of post in which the edge appears. - We use
gt_dyn
which stands fordynamic
, to essentially compute the time at which edges and nodes should appear on the graph.
head(net$edges)
source | target | created_at | n | end |
---|---|---|---|---|
_alejandro_aa | draftkings | 2019-04-14 03:37:06 | 1 | 2019-04-14 09:56:48 |
_alejandro_aa | fsdeviation | 2019-04-14 03:37:06 | 1 | 2019-04-14 09:56:48 |
_artemisa_v | gp_pulipaka | 2019-04-14 05:25:15 | 1 | 2019-04-14 09:56:48 |
_reactdev | gp_pulipaka | 2019-04-14 05:33:58 | 3 | 2019-04-14 09:56:48 |
sagesharp | takapodigs | 2019-04-14 06:42:54 | 1 | 2019-04-14 09:56:48 |
serverlessbot | gp_pulipaka | 2019-04-14 05:40:45 | 1 | 2019-04-14 09:56:48 |
11.4 Visualise
Now for the visualisation, let’s build it step by step; first we prep the data as we did before: renaming a few columns but also running a few unfamiliar computations.
To explain how we build the visualisation, we first need to tell you how the edges will dynamically appear on the graph. The way this works in sigmajs is by specifying the delay in milliseconds before each respective edge should be added. Therefore, we need to transform the date to milliseconds and rescale them to be within a reasonable range: we don’t want the edges to actually take 15 days to appear on the graph.
- We change the date time column (
POSIXct
actually) to a numeric, which gives the number of milliseconds since 1970. - We rescale between 0 and 1 then multiply by 10,000 (milliseconds) so that the edges are added over 10 seconds.
library(dplyr)
c(edges, nodes) %<-% net # unpack
nodes <- nodes2sg(nodes)
edges <- edges %>%
mutate(
id = 1:n(),
created_at = as.numeric(created_at),
created_at = (created_at - min(created_at)) / (max(created_at) - min(created_at)),
created_at = created_at * 10000
) %>%
select(id, source, target, created_at)
Let’s inspect what we obtain.
head(net$edges)
id | source | target | created_at |
---|---|---|---|
1 | _alejandro_aa | draftkings | 948.7485 |
2 | _alejandro_aa | fsdeviation | 948.7485 |
3 | _artemisa_v | gp_pulipaka | 3526.8176 |
4 | _reactdev | gp_pulipaka | 3734.6047 |
5 | sagesharp | takapodigs | 5377.8308 |
6 | serverlessbot | gp_pulipaka | 3896.3051 |
We see that the column created_at
has changed from Date time (POSIXct
) to a numeric. As mentioned previously, we rescaled it to be between 0 and 10,000 milliseconds, let’s see if that is correct.
range(edges$created_at)
## [1] 0 10000
So for instance the edge at row 25, a tweet where @angelanovari tags @themiserablephd will appear after 8130 milliseconds.
edges[25,]
## # A tibble: 1 x 4
## id source target created_at
## <int> <chr> <chr> <dbl>
## 1 25 angelanovari themiserablephd 8130.
Now, the actual visualisation, as mentioned at the begining to the chapter, we’ll plot the nodes then add edges dynamically. Let’s break it down step by step.
First, we plot the nodes.
sigmajs() %>%
sg_nodes(nodes, id, size, label)
We’ll add the layout as it looks a bit messy with nodes randomly scattered across the canvas. We’ll have to compute the layout differently this time, we cannot simply use sg_layout
as it requires both nodes and edges and we only have nodes on the graph (since edges are to be added later on, dynamically); instead we use sg_get_layout
.
You cannot use sg_cluster
and sg_layout
in dynamic graphs as they require both nodes and edges, use the sg_get_*
alternatives.
This is something that we had not shared with you earlier on, sg_nodes
must have x
and y
coordinates of each node, however, if missing they are generated randomly by the package. sg_get_layout
computes the coordinates of the nodes (x
and y
) and adds them to our nodes data.frame.
nodes <- sg_get_layout(nodes, edges, layout = igraph::layout_components)
head(nodes)
id | label | start | end | type | size | x | y |
---|---|---|---|---|---|---|---|
__m_pereira | __m_pereira | 2019-04-14 04:17:46 | 2019-04-14 09:56:48 | user | 1 | 2.965029 | 23.164393 |
_alejandro_aa | _alejandro_aa | 2019-04-14 03:37:06 | 2019-04-14 09:56:48 | user | 2 | -2.831449 | -2.091483 |
_artemisa_v | _artemisa_v | 2019-04-14 05:25:15 | 2019-04-14 09:56:48 | user | 1 | -7.630594 | -1.214452 |
_colinfay | _colinfay | 2019-04-14 06:07:28 | 2019-04-14 09:56:48 | user | 1 | 19.751258 | 5.504278 |
_reactdev | _reactdev | 2019-04-14 05:33:58 | 2019-04-14 09:56:48 | user | 1 | -6.338099 | -2.438633 |
sagesharp | sagesharp | 2019-04-14 06:42:54 | 2019-04-14 09:56:48 | user | 1 | -9.281873 | 13.438686 |
Now we can simply pass the coordinates x
and y
to sg_nodes
.
sigmajs() %>%
sg_nodes(nodes, id, size, label, x, y)
Let’s beautify the graph a little, this deep black is somewhat unnerving.
sigmajs() %>%
sg_nodes(nodes, id, size, label, x, y) %>%
sg_settings(
defaultNodeColor = "#127ba3",
edgeColor = "default",
defaultEdgeColor = "#d3d3d3",
minNodeSize = 1,
maxNodeSize = 4,
minEdgeSize = 0.3,
maxEdgeSize = 0.3
)
Now we have something that looks like a graph, except it’s missing edges. Let’s add them.
We add the edges almost exactly as we did before, we use sg_add_edges
instead of sg_edges
. Other than the function name, the only difference is that we pass created_at
as delay
. We also set cumsum
to FALSE
otherwise the function computes the cumulative sum on the delay
, which is, here, our created_at
column, and does not require counting the cumulative sum.
sigmajs() %>%
sg_nodes(nodes, id, size, label, x, y) %>%
sg_add_edges(edges, created_at, id, source, target, cumsum = FALSE, refresh = TRUE) %>%
sg_settings(
defaultNodeColor = "#127ba3",
edgeColor = "default",
defaultEdgeColor = "#d3d3d3",
minNodeSize = 1,
maxNodeSize = 4,
minEdgeSize = 0.3,
maxEdgeSize = 0.3
)
Now the edges appear dynamically. However you probably missed that as the animation is triggered when the page is loaded, the edges appear dynamically as you were reading this page. sigmajs provides an easy workaround: we can add a button for the user to trigger the animation themself.
The button is added with sg_button
to which we pass a label (Add edges
) and the event (add_edges
) the button will trigger. The name of the event corresponds to the function it essentially triggers minus the starting sg_
. In our case add_edges
triggers sg_add_edges
. Many events can be triggered by the button, they are listed on sigmajs official website.
Click the button in the top right corner of the visualisation to add the edges, it’ll take 10 seconds for all of them to be on the graph.
sigmajs() %>%
sg_nodes(nodes, id, size, label, x, y) %>%
sg_add_edges(edges, created_at, id, source, target, cumsum = FALSE, refresh = TRUE) %>%
sg_button("add_edges", "Add edges") %>%
sg_settings(
defaultNodeColor = "#127ba3",
edgeColor = "default",
defaultEdgeColor = "#d3d3d3",
minNodeSize = 1,
maxNodeSize = 4,
minEdgeSize = 0.3,
maxEdgeSize = 0.3
)
A sigmajs button can trigger multiple events.
We can see that some nodes are better at diseminating the message (#rstats), as the message reaches them they trigger numerous re-tweets.