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)