All posts

Network Visualisation with visNetwork

22 Dec 2018
Hello, welcome to this post! Today we are talking about networks (also called graphs)!

I will show how to visualise a network using visNetwork, which is an R package designed specifically for network visualisation using the vis.js JavaScript library.

visNetwork package is a great R package for network visualisation, and can be used with Shiny and R Markdown, and is fully interactive within the RStudio viewer (more on that later!). We’ll start off building a simple visualisation, and build up to something more complex where we style parts of our network using CSS, add more interactivity and create groupings of nodes. This post assumes no knowledge of visNetwork, though knowledge of R, RStudio and networks is useful! You can find all the code, output and more at the post GitHub repository and you can find me on Twitter @stevo_marky.

Part 1: A Simple Network


A network is a collection of nodes (a point of intersection) and edges (links between the nodes).
The visNetwork() function requires a data frame for the nodes of the network and a data frame for the edges between the nodes.

We’re going to start off by building a simple eight node data frame with 15 edges.

We start by creating a simple nodes data frame. To do this we run the following code:
# create nodes dataframe
# eight nodes where nodes have the id 1 to 8
# the node labels are Node i where i is between 1 and 8
nodes <- data.frame(id = 1:8,
label = paste("Node", 1:8),
# make the nodes circle
shape = "circle")


We now want to create our edges data frame. To generate the edges, I am going to randomly select 15 connections between the eight nodes. To do this, we perform a neat little trick of getting all the possible combinations of node connections (with the exception of a node connection to itself) and then randomly selecting 15 rows from the resulting data frame.

library(dplyr)
# create two dataframes, one to eight in the first column and a column of 1's in the second column
# the second column is for the join
x <- data.frame(edges = 1:8, z = 1)
y <- data.frame(edges = 1:8, z = 1)

# set seed so the output is reproducible
set.seed(123)

# perform a cross join using dplyr::inner_join() and join by z which always matches
sample <- x %>%
inner_join(., y, by = 'z') %>%
select(-z) %>%
# filter out any occurrences where nodes are joined to themselves
filter(edges.x != edges.y) %>%
# randomly sample 15 rows from the dataframe
sample_n(., 15) %>%
# these will be the edges in our network
# add additional column for length of edge
mutate(length = round(rnorm(15, mean = 10, sd = 3), 0))


The last line of code above adds an extra column to the data frame and fills it with values from a normal distribution (using rnorm()). For now, we will not use this column, but it will come in useful later on!

These fifteen rows are our edges in our network. To create our edges in our network visualisation we first create a data frame using the following code:
# create edges dataframe
edges <- data.frame(from = sample$edges.x, to = sample$edges.y)
# from and to use a line of the dataframe
# an edge is created on a row basis which means that an edge is created from the node number in the edges.x column to the node number in the edges.y column


Edges are created on a row basis. This means that we have an edge from node 3 to node 4, from node 7 to node 2, from node 4 to node 2 and so on.

head(sample)


edges.x edges.y length
1 3 4 14
2 7 2 5
3 4 2 15
4 7 5 12
5 7 8 18
6 1 4 12


Now we have our nodes and edges data frame, we are ready to create our first network.

library(visNetwork)
# create network
visNetwork(nodes = nodes, edges = edges) %>%
# use visLayout randomSeed so we always have the same network
visLayout(randomSeed = 123)


We now have our network!



Part 2: Adding Detail to Our Network


We now build on the simple network we created in Part 1. We are going to make our network more aesthetically pleasing. We will build up our network visualisation bit by bit, starting with the nodes.

For our nodes data frame, we remove the shape argument (that will be moved elsewhere), but instead add a font.color argument, where we use the hex code for white.

# nodes
nodes <- data.frame(id = 1:8,
label = paste("Node", 1:8),
font.color = "#ffffff") # make font color white


The only change to our edges data frame is adding the length of the edges. Remember the length column we created in our sample data frame? Now we specify this column as the label.

# edges
edges <- data.frame(from = sample$edges.x,
to = sample$edges.y,
# add the length of the edge to the network
label = sample$length)


The next stage is where we do the bulk of the work, so let’s break it down. We start with what we had in Part 1:

library(visNetwork)
# create network
visNetwork(nodes = nodes, edges = edges) %>%
visLayout(randomSeed = 123)


The first thing we do is add a title to our network. To do this we use main = and pass a list. This list contains our title (text =) and some styling (style =). Here we use CSS to style the title as we wish. If you're not familiar with CSS, I recommend looking at W3Schools, which is a fantastic resource.

At the moment our code looks like:

library(visNetwork)
# create network
visNetwork(nodes = nodes, edges = edges,
main = list(text = "Simple Network with Eight Nodes",
# style the title using inline css
style = 'font-family: "Lucida Sans Unicode",
"Lucida Grande", sans-serif;
font-weight: bold;
font-size: 15px;
text-align:center')) %>%
# for styling see https://www.w3schools.com/css/default.asp
visLayout(randomSeed = 123)


We now add what are termed as Global Options in the visNetwork package. Global Options are a simple way to set a configuration for all nodes and/or edges.

For nodes, we want to ensure they are all circle (which we moved from our nodes data frame) and make the nodes a dark blue. Therefore, we add visNodes():

library(visNetwork)
# create network
visNetwork(nodes = nodes, edges = edges,
main = list(text = "Simple Network with Eight Nodes",
style = 'font-family: "Lucida Sans Unicode",
"Lucida Grande", sans-serif;
font-weight: bold;
font-size: 15px;
text-align:center')) %>%
# use global option for nodes
visNodes(shape = "circle", # move circle to a global option
color = "#0000FF") %>% # make nodes blue
visLayout(randomSeed = 123)


For edges we want to:

  1. make all our edges dashes.

  2. make all the edges directional “to” arrows.

  3. colour our edges light blue



Adding this to our code block gives us:

library(visNetwork)
# create network
visNetwork(nodes = nodes, edges = edges,
main = list(text = "Simple Network with Eight Nodes",
style = 'font-family: "Lucida Sans Unicode",
"Lucida Grande", sans-serif;
font-weight: bold;
font-size: 15px;
text-align:center')) %>%
visNodes(shape = "circle",
color = "#0000FF") %>%
# use global option for edges
visEdges(dashes = TRUE, # add dashes to network to reduce ink
arrows = c("to"), # add directional arrows
color = "#7ec0ee") %>% # make edges light blue
visLayout(randomSeed = 123)


Our Network now looks much better!



Now, we can take advantage of visNetwork’s interactivity (I am using RStudio to do this).

We want to add the more interactivity to our network using the RStudio Viewer. We want to make our network visualisation such that when a user clicks on a node, the selected node and the nearest nodes are highlighted. By nearest nodes, we mean those nodes which are connected to our selected node. We also want to highlight only the connections between our selected node and the nodes one connection (one degree ) away. To do this, we add the following code:

library(visNetwork)
# create network
visNetwork(nodes = nodes, edges = edges,
main = list(text = "Simple Network with Eight Nodes",
style = 'font-family: "Lucida Sans Unicode",
"Lucida Grande", sans-serif;
font-weight: bold;
font-size: 15px;
text-align:center')) %>%
visNodes(shape = "circle",
color = "#0000FF") %>%
visEdges(dashes = TRUE,
arrows = c("to"),
color = "#7ec0ee") %>%
# on click highlight the nearest nodes
visOptions(highlightNearest = list(enabled = TRUE,
# highlight nodes connected to chosen node
degree = 1,
# only look at input/output nodes of the clicked node
algorithm = "hierarchical")) %>%
visLayout(randomSeed = 123)


You’ll notice here that part of the beauty of the visNetwork package is being able to use the magrittr pipe (%>%). We now have a network that is aesthetically pleasing and interactive.



Part 3: Creating Groups of Nodes


If you look carefully at the network we have created, you’ll see that node 2 and node 6 have no outwards edges, only inwards edges. This is a sink node. If we were travelling around our network and arrived at either node 2 or node 6, we would be stuck, as there is no outward edge. This differentiates these two nodes from all other nodes in our network. We can therefore group our nodes into two distinct groups:


  1. Group S: nodes 2 and nodes 6 (S stands for sink).

  2. Group A: every remaining node.



The first thing we need to do is amend the nodes data frame and add a column which specifies which group each node is in.

# nodes
nodes <- data.frame(id = 1:8,
# add a column specifying the group
group = c("A", "S", "A", "A", "A", "S", "A", "A"),
label = paste("Node", 1:8),
font.color = "#ffffff")


Our edges data frame remains the same as before, which is:

# edges
edges <- data.frame(from = sample$edges.x,
to = sample$edges.y,
label = sample$length)


We then need to make some changes to our visNetwork() call. We firstly remove our visNodes() function, as this will be superseded by visGroups(). We add a visGroups() function for each of our desired groups.

For Group S, we want a special icon for the node. As the fantastic team behind visNetwork kindly built visNetwork so that it supports Font Awesome , let’s use a Font Awesome icon. To do this, we specify shape = icon, and then we pass a list of options:


  1. the code of the icon we want (this can be found on the Font Awesome website).

  2. the colour, here burnt orange.

  3. the size, here let’s make it slightly smaller than the standard node size.



We also need to add addFontAwesome() at the bottom of our code for the Font Awesome icons to render.

# creates groups of nodes
# group s
visGroups(groupname = "S", # specify group name
shape = "icon", # use a font awesome icon (https://fontawesome.com/)
# specify icon and colour
icon = list(code = "f057", color = "#cc5500", size = 40)) %>%
addFontAwesome() # add to ensure font awesome icons render


The code for the Group A nodes is:

# group a
visGroups(groupname = "A",
color = "#0000FF",
shape = "circle")


Putting all this together gives us:

library(visNetwork)
# create network
visNetwork(nodes = nodes, edges = edges,
main = list(text = "Simple Network with Eight Nodes",
style = 'font-family: "Lucida Sans Unicode",
"Lucida Grande", sans-serif;
font-weight: bold;
font-size: 15px;
text-align:center')) %>%
visGroups(groupname = "S",
shape = "icon",
icon = list(code = "f057", color = "#cc5500", size = 40)) %>%
visGroups(groupname = "A",
color = "#0000FF",
shape = "circle") %>%
visEdges(dashes = TRUE,
arrows = c("to"),
color = "#7ec0ee") %>%
visOptions(highlightNearest = list(enabled = TRUE,
degree = 1,
algorithm = "hierarchical")) %>%
visLayout(randomSeed = 123) %>%
addFontAwesome()




Conclusion


Hopefully you have found this post useful! We have shown how to create a simple network, visualise it using visNetwork, style the visualisation, add further interactivity, create groups of nodes and add icons to your network. Network visualisation is a fantastic area and an area that is going to grow; we’ve only scratched the surface here using visNetwork! The visNetwork documentation shows the breadth of capability of visNetwork. If you want advanced network visualisation, I recommend delving into the docs.

If you see any errors in the code, please let me know. If you have any comments, then please leave them below, and don't forget that you can find all the code for this post (and more!) at the post Github repository.

Session and Package Information


The details of my R session and packages installed are:

sessionInfo()


R version 3.5.2 (2018-12-20)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS High Sierra 10.13.6

attached base packages:
[1] stats graphics grDevices utils datasets methods base

other attached packages:
[1] bindrcpp_0.2.2 dplyr_0.7.8 visNetwork_2.0.5

Leave a comment