Networks

A network is a series of connections. We get networks when things (nodes) relate to each other (edges).

Examples

Using the visNetwork package

This is the visNetwork package:

library(visNetwork)

It allows us to

An example with the package

Let’s make a random network. We will have

# Set seed for reproducibility
set.seed(1234567890)
# The sixteen nodes
population <- 1:16
# Network relationships
relate_fr  <- rep(population, each = 2) # from
relate_to  <- sample(population, 32, T) # to

nodes <- data.frame(
  id = population,
  label = paste0("N", population)
)
edges <- data.frame(
  from = relate_fr,
  to   = relate_to
)
visNetwork(nodes, edges) %>% 
  visNodes(shape = "ellipse") %>% 
  visEdges(color = "black")

Directed graphs

Note that edges and the relationships they describe can have direction. Edges with a direction are called directed edges.

Example: a love triangle

For example, think of a social network where

Life is very complicated.

nodes <- data.frame(
  id = c(1, 2, 3),
  label = c(":(", ":(", ":("),
  value = c(10, 10, 10)
)
edges <- data.frame(
  from = c(1, 2, 3),
  to = c(2, 3, 1),
  label = "fancies"
)
visNetwork(nodes, edges) %>% 
  visNodes(shape = "ellipse") %>% 
  visEdges(arrows = "to", smooth = list(enabled = FALSE))

Making more complicated network plots

Consider this figure from a recently published paper that I was involved in (Froese et al., 2019):

Bayesian network for risk of invasive weed spread

Bayesian network for risk of invasive weed spread

We’re going to make this plot. (Only the first one (a), though).

First note the structure of the network

The main structure for the network is given by the nodes

We will set this up first.

basic_network <- data.frame(
  from = c(1, 2, 3, 4),
  to = c(3, 3, 5, 5),
  arrows = "to"
)
vertex_info <- data.frame(
  id = 1:5, 
  label = c(
    "Establishment", 
    "Persistence",
    "Suitability", 
    "Propagule\npressure", 
    "Susceptibility"
  ),
  value = 1:5,
  group = "Not user-specified", # for the legend
  shape = rep("box", 5), # shape
  color = "black", # color
  font.color = "white", # text color in boxes when labelling
  shadow = rep(TRUE, 5) 
)

Now for a vibe check:

visNetwork(vertex_info, basic_network) %>% 
  visNodes(shape = "ellipse") %>% 
  visEdges(arrows = "to", smooth = list(enabled = FALSE))

The basic structure is fine though the random layout of the graph can be a bit annoying.

Now we can add in the parent nodes. This part gets a bit messy, so hold on.

Firstly, note that the colour of the nodes in the original plot can be red, yellow or green. This is a traffic-light system that weights the contribution of each parent node to the child node. The following functions are used to automate the process of assigning colours to numbers. This won’t be a part of every set up.

colour_labeller <- function(wt) switch(
  wt, "1" = "forestgreen", "2" = "orange", "3" = "red"
)
colour_labeller_vectorised <- function(wts){
  if(!all(wts %in% 1:3)){
    stop("All weights must be 1, 2, or 3")
  }
  unlist(
    lapply(wts, colour_labeller)
  )
}

Secondly, we set up the new parent nodes in a different data.frame object. We will join them later.

# The weights and names of nodes
establishment <- c("SoilMoisture", "CanopyCover", "VegetationLandUse")
establishment_wts <- c(1, 2, 3)
persistence   <- c("Elevation", "RainDriestMonth")
persistence_wts <- c(1, 2)
propagule <- c("PropaguleSupply", "Hydrochory", "Zoochory")
propagule_wts <- c(3, 2, 3)
# Assign ids 
n_est <- length(establishment)
n_per <- length(persistence)
n_prg <- length(propagule)
est_id <- 6:(5 + n_est)
per_id <- (max(est_id) + 1):(max(est_id) + n_per)
prg_id <- (max(per_id) + 1):(max(per_id) + n_prg)
# Set up new data frame of edges
new_connections <- data.frame(
  from = c(est_id, per_id, prg_id),
  to   = c(rep(1, n_est), rep(2, n_per), rep(4, n_prg)),
  arrows = "to"
)
# Set up new frame for vertex information
n_elem <- length(c(establishment, persistence, propagule))
new_vertex_info <- data.frame(
  id = c(est_id, per_id, prg_id), 
  label = c(establishment, persistence, propagule),
  value = c(est_id, per_id, prg_id),
  group = paste("Weight =", 
                c(establishment_wts, persistence_wts, propagule_wts)
  ), # for the legend
  shape = rep("ellipse", n_elem),
  color = colour_labeller_vectorised(
    c(establishment_wts, persistence_wts, propagule_wts)
  ), # colors!
  font.color = "white",
  shadow = rep(FALSE, n_elem)
)

Thirdly, we join the new node/edge data.frame objects to the old ones.

all_network <- rbind(
  basic_network,
  new_connections
)
all_vertex <- rbind(
  vertex_info,
  new_vertex_info
)

Finally, we create the plot:

the_graph <- visNetwork(
  all_vertex, 
  all_network
) %>% 
  # Constrain edges to be straight
  visEdges(physics = FALSE, smooth = FALSE) %>%
  # Set up legend entries for the groups
  visGroups(groupname = "Weight = 1", color = "forestgreen", font = list(color = "white")) %>%
  visGroups(groupname = "Weight = 2", color = "orange", font = list(color = "white")) %>%
  visGroups(groupname = "Weight = 3", color = "red", font = list(color = "white")) %>%
  visGroups(groupname = "Not user-specified", color = "black", shape = "box", font = list(color = "white")) %>%
  # Create legend with title 'Legend'
  visLegend(main = "Legend") %>%
  visPhysics(enabled = FALSE) %>% 
  visLayout(randomSeed = 7000)
the_graph

References

Froese, J.G., Pearse, A.R. & Hamilton, G.S. (2019). Rapid spatial risk modelling for management of early weed invasions: Balancing ecological complexity and operational needs. Methods in Ecology and Evolution, 0(0),1-13.