Plotting collocation networks with R and ggraph
In a previous post, I showed how to use the igraph package for R to plot a graph of the nominal collocates of the verbs hoard and stockpile in the Coronavirus Corpus (Davies, 2020). In this post, I show how to do the same with the ggraph package.
ggraph
On February 2017, Thomas Lin Pedersen announced the release of ggraph on CRAN. The motivation for ggraphis the following:
The grammar of graphics as implemented in
https://cran.r-project.org/web/packages/ggraph/index.htmlggplot2is a poor fit for graph and network visualizations due to its reliance on tabular data input.ggraphis an extension of theggplot2API tailored to graph visualizations and provides the same flexible approach to building up plots layer by layer.
Although presented as an improvement upon ggplot2, ggraph also plays the role of an igraph add-on to bring the flexibility of the tidyverse grammar and more graphics settings into it. Naturally, because of its native tidyverse coloration, ggraph is well suited to the logic of tidygraph.
Walkthrough
We download, install, and load three packages: igraph, ggraph, and tidygraph.
rm(list=ls(all=TRUE))
install.library("igraph")
install.library("ggraph")
install.library("tidygraph")
library(igraph)
library(ggraph)
library(tidygraph)
The data is the same as used previously. It consists of two files: a table of vertex attributes (v.attr) and an edge list (edges_data). We load them.
v.attr <- read.table("https://www.nakala.fr/data/11280/1f0d5c4c", header=T, sep="\t")
edges_data <- read.table("https://www.nakala.fr/data/11280/cfd3793e", header=T, sep="\t")
We make a graph object with the graph_from_data_frame function from igraph. The function takes three arguments:
d, the data frame with theedges_data;vertices, a data frame withv.attrin the first column;directedwhich, set to FALSE, specifies that we want an undirected graph.
ig <- graph_from_data_frame(d=edges_data, vertices=v.attr, directed = FALSE)
Let us inspect the graph object:
> ig IGRAPH 4bca3fb UN-- 154 202 -- + attr: name (v/c), coll_freq (v/n), MI (e/n) + edges from 4bca3fb (vertex names): [1] food --hoard hoard --masks hoard --supplies hoard --toilet [5] hoard --paper cash --hoard hoard --items goods --hoard [9] groceries --hoard hoard --ventilators hoard --price hoard --profiteering [13] face --hoard hoard --wealth equipment --hoard hoard --products [17] commodities--hoard hoard --profiteers hoard --things hoard --panic [21] hoard --misuse essentials --hoard hand --hoard bottles --hoard [25] drugs --hoard drug --hoard behaviour --hoard devices --hoard [29] hoard --overpricing amounts --hoard hoard --sanitizer dollars --hoard + ... omitted several edges
The output says that we have a graph that is undirected (U) and named (N) with 154 nodes and 202 edges. The vertices have coll_freq (i.e. collocation frequency) as a quantitative attribute, and the edges are weighted with MI (i.e. mutual information).
For the sake of illustration, let us compare the igraph object to its tidygraph counterpart:
tg <- tidygraph::as_tbl_graph(ig) %>% activate(nodes) %>% mutate(label=name)
R outputs the following:
> tg # A tbl_graph: 154 nodes and 202 edges # # An undirected simple graph with 1 component # # Node Data: 154 x 3 (active) name coll_freq label <chr> <int> <chr> 1 alcohol 17 alcohol 2 allocation 4 allocation 3 ammunition 3 ammunition 4 amounts 24 amounts 5 antibiotics 5 antibiotics 6 anticipation 8 anticipation # … with 148 more rows # # Edge Data: 202 x 3 from to MI <int> <int> <dbl> 1 45 60 6.41 2 60 76 5.59 3 60 137 6.72 # … with 199 more rows
tg is a tibble graph (tbl_graph). As the tidygraph documentation indicates:
The
https://rdrr.io/cran/tidygraph/man/tbl_graph.htmltbl_graphclass is a thin wrapper around anigraphobject that provides methods for manipulating the graph using the tidy API. As it is just a subclass ofigraphevery igraph method will work as expected.
tidygraph bridges the gap between igraph and the tidyverse, which means that we can rely on igraph while benefiting from the intuitive grammar of the tidyverse.
We index the size of each node on collocation frequency,
v.size <- V(tg)$coll_freq
and specify that mutual information is used to weight the edges of the graph.
E(tg)$weight <- E(tg)$MI
As previously, we use eigenvector centrality to spot the most influential nodes by:
- rearranging the centrality scores into quantized buckets known as ‘bins;
- assigning a color to each bin along a heat continuum skewed to the reds (for higher centrality scores) and yellows (for lower centrality scores).
eigenCent <- evcent(tg)$vector bins <- unique(quantile(eigenCent, seq(0,1,length.out=30))) vals <- cut(eigenCent, bins, labels=FALSE, include.lowest=TRUE) colorVals <- rev(heat.colors(length(bins)))[vals]
Finally, we plot the graph with the desired parameters. Note that the grammar is typical of the tidyverse, as shown by the pipe operator (%>%).
tg %>% ggraph(layout="stress") + geom_edge_diagonal(alpha = .2, color='white') + geom_node_point(size=log(v.size)*2, color=colorVals) + geom_node_text(aes(label = name), repel = TRUE, point.padding = unit(0.2, "lines"), size=log(v.size), colour='white') + theme_graph(background = 'grey20')
Here is what each line means:
ggraph(layout="stress"): “This layout is related to the stress-minimization algorithm known as Kamada-Kawai (available as the ‘kk’ layout), but uses another optimization strategy.”(https://rdrr.io/cran/ggraph/man/layout_tbl_graph_stress.html);geom_edge_diagonal(alpha = .2, color='white'): this draws edges as diagonal bezier curves rather than straight lines. A level of transparency is added (alpha = .2) and the edges are printed in white (color='white');geom_node_point(size=log(v.size)*2, color=colorVals): the size of each node is indexed on the log of collocation frequency times two (size=log(v.size)*2) and the color of each node is indexed on its centrality score (color=colorVals).geom_node_text(aes(label = name), repel = TRUE, point.padding = unit(0.2, "lines"), size=log(v.size), colour='white'): each node is assigned a label (aes(label = name)). The position of labels is optimized to avoid overlapping text (repel = TRUE). Labels are in white (colour='white‘). Padding is added around the labeled data points (point.padding = unit(0.2, "lines"));theme_graph(background = 'grey20'): the background of the graph is colored in a dark shade of grey.
The results is an elegant graph (Fig. 1). See this previous post for a short analysis.

Note that we have not indexed edge width on MI. To do so, remove the geom_edge_diagonal parameter and replace it with: geom_edge_link(alpha = .25, colour='white, aes(width = weight)). The code is now:
tg %>% ggraph(layout="stress") + geom_edge_link(alpha = .25, colour='white', aes(width = weight)) + geom_node_point(size=log(v.size)*2, color=colorVals) + geom_node_text(aes(label = name), repel = TRUE, point.padding = unit(0.2, "lines"), size=log(v.size), colour="white") + theme_graph(background = 'grey20')

In a future post, I plan to conduct a diachronic study of collocation shifts to see how panick-buying has evolved along the whole COVID-19 crisis.
References
Davies, Mark. 2020. The Coronavirus Corpus. https://www.english-corpora.org/corona/ (last accessed 05.17.2020).
Pedersen, Thomas Lin. 2020. ggraph: An Implementation of Grammar of Graphics for Graphs and Networks. R package version 2.0.2. https://CRAN.R-project.org/package=ggraph
OpenEdition suggests that you cite this post as follows:
Guillaume Desagulier (May 19, 2020). Plotting collocation networks with R and ggraph. Around the word. Retrieved May 20, 2026 from https://doi.org/10.58079/n4v1


orcid.org/0000-0003-4895-0788