Network Graphs in ggplot2

How to make Network Graphs in ggplot2 with Plotly.


New to Plotly?

Plotly is a free and open-source graphing library for R. We recommend you read our Getting Started guide for the latest installation or upgrade instructions, then move on to our Plotly Fundamentals tutorials or dive straight in to some Basic Charts tutorials.

Random graph

Let’s start with an undirected Bernoulli random graph, with 10 nodes named “a, b, …, i, j”, and a rather high likelihood of an edge to exist between them:

library(plotly)
library(ggnet)
library(network)
library(sna)
library(ggplot2)

# random graph
net = rgraph(10, mode = "graph", tprob = 0.5)
net = network(net, directed = FALSE)

# vertex names
network.vertex.names(net) = letters[1:10]

p <- ggnet2(net)


ggplotly(p)

The net argument is the only compulsory argument of ggnet2. It can be a network object or any object that can be coerced to that class through its edgeset.constructors functions, such as adjacency matrixes, incidence matrixes and edge lists.

If the intergraph package is installed, net can also be an igraph one-mode network object, which is the only type of network that the package can convert from the igraph to the network class.

Node color and size

The most basic properties that one might want to change at that stage are the size and color of the nodes, or the size and color of the edges. Let’s modify each of these properties:

library(plotly)
library(plotly)
library(ggnet)
library(network)
library(sna)
library(ggplot2)

# random graph
net = rgraph(10, mode = "graph", tprob = 0.5)
net = network(net, directed = FALSE)

# vertex names
network.vertex.names(net) = letters[1:10]

p <- ggnet2(net, node.size = 6, node.color = "black", edge.size = 1, edge.color = "grey")

ggplotly(p)

The vertex-related arguments of ggnet2 start with node, and its edge-related arguments start with edge. The node.color and node.size arguments can be abbreviated: ggnet2(net, size = 6, color = "black", edge.size = 1, edge.color = "grey")

It also possible to pass a vector of node colors directly to ggnet2, as long as it has the same number of elements as the network has nodes:

library(plotly)
library(ggnet)
library(network)
library(sna)
library(ggplot2)

# random graph
net = rgraph(10, mode = "graph", tprob = 0.5)
net = network(net, directed = FALSE)

# vertex names
network.vertex.names(net) = letters[1:10]

p <- ggnet2(net, size = 6, color = rep(c("tomato", "steelblue"), 5))

ggplotly(p)

The color, shape, size and transparency of nodes can all be set through these basic methods, or by passing a vertex attribute to them. Let’s first see how to position the nodes.

Node colors

Let’s now assign a vertex attribute called phono, which indicates whether the name of the vertex is a vowel or a consonant. This attribute can be passed to ggnet2 to indicate that the nodes belong to a group. All the user has to do is to pass the name of the vertex attribute to the color argument, which will find it in the list of vertex attributes and use it to map the colors of the nodes:

library(plotly)
library(ggnet)
library(network)
library(sna)
library(ggplot2)

net = rgraph(10, mode = "graph", tprob = 0.5)
net = network(net, directed = FALSE)

net %v% "phono" = ifelse(letters[1:10] %in% c("a", "e", "i"), "vowel", "consonant")

p <- ggnet2(net, color = "phono")

ggplotly(p)

By default, ggnet2 assigns a grayscale color to each group. To modify this behavior, let’s review three different options. The first one consists in “hard-coding” the colors into the graph by assigning them to a vertex attribute, and then in passing this attribute to ggnet2:

library(plotly)
library(ggnet)
library(network)
library(sna)
library(ggplot2)

# random graph
net = rgraph(10, mode = "graph", tprob = 0.5)
net = network(net, directed = FALSE)

net %v% "phono" = ifelse(letters[1:10] %in% c("a", "e", "i"), "vowel", "consonant")
net %v% "color" = ifelse(net %v% "phono" == "vowel", "steelblue", "tomato")

p <- ggnet2(net, color = "color")

ggplotly(p)

ggnet2 returns a ggplot object, so the underlying data can be accessed by requesting the data component of the plot. The structure of that component always contains the following columns, which match the names of ggplot2 arguments.

library(plotly)
library(ggnet)
library(network)
library(sna)
library(ggplot2)

# random graph
net = rgraph(10, mode = "graph", tprob = 0.5)
net = network(net, directed = FALSE)

net %v% "phono" = ifelse(letters[1:10] %in% c("a", "e", "i"), "vowel", "consonant")
net %v% "color" = ifelse(net %v% "phono" == "vowel", "steelblue", "tomato")

ggnet2(net, color = "phono", size = 1:10)$data
##    label alpha     color shape size         x          y
## 1      1     1     vowel    19    1 0.4675789 0.00000000
## 2      2     1 consonant    19    2 0.9257681 0.25820380
## 3      3     1 consonant    19    3 0.1456250 0.00782913
## 4      4     1 consonant    19    4 0.0000000 0.26788417
## 5      5     1     vowel    19    5 1.0000000 0.54504400
## 6      6     1 consonant    19    6 0.5791055 0.63522848
## 7      7     1 consonant    19    7 0.6168145 0.19116387
## 8      8     1 consonant    19    8 0.4056431 0.37765177
## 9      9     1     vowel    19    9 0.3197373 1.00000000
## 10    10     1 consonant    19   10 0.1052882 0.67460486

This means that you can append any ggplot2 component to the graph by passing additional aesthetics to it, which allows for a fair amount of “plot hacking”. In this example, we use ggnet2 to get the basic data structure in place, while sizing the nodes to 0. The nodes are then plotted manually, by overlaying several geom objects:

library(plotly)
library(ggnet)
library(network)
library(sna)
library(ggplot2)

# random graph
net = rgraph(10, mode = "graph", tprob = 0.5)
net = network(net, directed = FALSE)

net %v% "phono" = ifelse(letters[1:10] %in% c("a", "e", "i"), "vowel", "consonant")
net %v% "color" = ifelse(net %v% "phono" == "vowel", "steelblue", "tomato")

p <- ggnet2(net, color = "phono", palette = "Set1", size = 0) +
  geom_point(aes(color = color), size = 12, color = "white") +
  geom_point(aes(color = color), size = 12, alpha = 0.5) +
  geom_point(aes(color = color), size = 9) +
  geom_text(aes(label = toupper(substr(color, 1, 1))), color = "white", fontface = "bold") +
  guides(color = FALSE)

ggplotly(p)

Node size

It is common to size the nodes of a network by their centrality or by some other indicator of interest. Just like its color argument, the size argument of ggnet2 can take a single numeric value, a vector of values, or a vertex attribute:

library(plotly)
library(ggnet)
library(network)
library(sna)
library(ggplot2)

# random graph
net = rgraph(10, mode = "graph", tprob = 0.5)
net = network(net, directed = FALSE)

net %v% "phono" = ifelse(letters[1:10] %in% c("a", "e", "i"), "vowel", "consonant")
net %v% "color" = ifelse(net %v% "phono" == "vowel", "steelblue", "tomato")

p <- ggnet2(net, size = "phono", size.palette = c("vowel" = 10, "consonant" = 1))

ggplotly(p)

When the size attribute is not a single numeric value, the maximum size of the nodes is determined by the max_size argument, just like in the scale_size_area controller of ggplot2, which ggnet2 emulates to compute the relative size of the nodes:

ggnet2(net, size = sample(0:2, 10, replace = TRUE), max_size = 9)

ggnet2 can also size nodes by calculating their in-degree, out-degree, or total (Freeman) degree, using the degree function of the sna package. All the user has to do is to pass the indegree, outdegree, or freeman option to the weight argument (degree is also understood, and is equivalent to freeman).

ggnet2 gives the user further control over the node size by providing a quick way to cut the node sizes into quantiles, using the size.cut argument. If set to TRUE, it defaults to quartiles, but any numeric value above 1 is acceptable:

library(plotly)
library(ggnet)
library(network)
library(sna)
library(ggplot2)

# random graph
net = rgraph(10, mode = "graph", tprob = 0.5)
net = network(net, directed = FALSE)

net %v% "phono" = ifelse(letters[1:10] %in% c("a", "e", "i"), "vowel", "consonant")
net %v% "color" = ifelse(net %v% "phono" == "vowel", "steelblue", "tomato")

p <- ggnet2(net, size = "degree", size.cut = 3)

ggplotly(p)

Adding labels

Through the label argument, ggnet2 can label the nodes of a network by using their vertex names, another vertex attribute, or any other vector of labels.

The size of the labels, which is automatically set to half of the node size, is controlled by the label.size argument, their color by the label.color argument, and their level of transparency by the label.alpha argument:

library(plotly)
library(ggnet)
library(network)
library(sna)
library(ggplot2)

# random graph
net = rgraph(10, mode = "graph", tprob = 0.5)
net = network(net, directed = FALSE)

p <-ggnet2(net, size = 12, label = TRUE, label.alpha = 0.75, label.size = 5, color = "black", label.color = "white")

ggplotly(p)

Changing shapes

The shapes and transparency of the nodes can be set exactly like the color and size of the nodes, either through a single value, a vector of (numeric) values, or a vertex attribute. This allows to create nodes that can be distinguished even in the plot loses its colors:

library(plotly)
library(ggnet)
library(network)
library(sna)
library(ggplot2)

# random graph
net = rgraph(10, mode = "graph", tprob = 0.5)
net = network(net, directed = FALSE)

net %v% "phono" = ifelse(letters[1:10] %in% c("a", "e", "i"), "vowel", "consonant")
net %v% "color" = ifelse(net %v% "phono" == "vowel", "steelblue", "tomato")


p <-ggnet2(net, color = "phono", shape = "phono")

ggplotly(p)

What About Dash?

Dash for R is an open-source framework for building analytical applications, with no Javascript required, and it is tightly integrated with the Plotly graphing library.

Learn about how to install Dash for R at https://dashr.plot.ly/installation.

Everywhere in this page that you see fig, you can display the same figure in a Dash for R application by passing it to the figure argument of the Graph component from the built-in dashCoreComponents package like this:

library(plotly)

fig <- plot_ly() 
# fig <- fig %>% add_trace( ... )
# fig <- fig %>% layout( ... ) 

library(dash)
library(dashCoreComponents)
library(dashHtmlComponents)

app <- Dash$new()
app$layout(
    htmlDiv(
        list(
            dccGraph(figure=fig) 
        )
     )
)

app$run_server(debug=TRUE, dev_tools_hot_reload=FALSE)