The igraph 2.2.1 release for R is now available on CRAN, delivering significant improvements to performance, stability, and API consistency. This update enhances integration with the C core, refines graph manipulation tools, and improves error handling, making it easier for users to build and analyze complex network models at scale.
Overview of the Release
While this is only a minor version bump, igraph 2.2.1 brings many noteworthy changes and improvements to the package. More importantly, this release paves the way for igraph 3.0.0, a major release with many anticipated new features and improvements. If you are interested in our roadmap, you can check out a previous blog post outlining our plans for the near future. But for now, let’s focus on what igraph 2.2.1 has to offer!
library(igraph)
The C library was updated to version 0.10.17. See the changelog for a complete overview of changes on the C side of things.
You can read the complete changelog for the R package. In this post, we want to highlight some of the most important changes.
Tackling the Issues Backlog
igraph has been on CRAN for almost 20 years now, and its current GitHub repository has been public for more than a decade. Over this time, a substantial number of issues have accumulated, some of which have remained unresolved for years.
The plot below illustrates the number of open issues over time for the
igraph/rigraph repository. Two major stages of issue reduction are
evident: the first around mid-2020 to mid-2022, and the second leading
up to the igraph 2.2.1 release in January 2025.
Warning in scale_x_date(date_labels = "%m/%Y", date_breaks = "12 months"): A <numeric> value was passed to a Date scale.
ℹ The value was converted to a <Date> object.
Figure 1: Number of open issues per day for the `igraph/rigraph`
repository. The vertical red line indicates the release date of igraph
2.1.4 on January 23, 2025.
So a big chunk of work for the release was reducing the issue backlog on GitHub. Some of the reported issues were quite old, dating back almost a decade. Others were duplicates or had become irrelevant over time. However, many issues were still valid and important to address. We managed to reduce the backlog from around 225 issues to approximately 130 issues by the time of the release. As an interesting side note, we also merged a PR that was open since 2016!
Performance Enhancements
The release introduces some performance optimizations, notably for
reading/writing graphs from/to matrices and indexing. The functions [,
as_adjacency_matrix() (particularly for attr != NULL and
sparse = FALSE) and graph_from_adjacency_matrix() (for sparse
matrices) benefit the most from these improvements.
The speedups are illustrated in the benchmarks results below, using the following setup.
library(igraph)
set.seed(12)
g <- sample_gnp(5000, 0.1)
g1 <- sample_gnp(1000, 0.1)
g1 <- set_edge_attr(g1, "weight", value = runif(ecount(g1)))
A <- as_adjacency_matrix(g, sparse = TRUE)
The indexing function [ essentially returns a submatrix of the
adjacency matrix. In the old version, the complete matrix was
constructed first, and then the submatrix was extracted. In the new
version, only the relevant parts of the adjacency matrix are
constructed, leading to significant speedups for larger graphs.
# A tibble: 2 × 5
expression version median `itr/sec` mem_alloc
<chr> <chr> <bch:tm> <dbl> <bch:byt>
1 g[1:100,1:100] 2.2.0 2.24ms 429. 3.98MB
2 g[1:100,1:100] 2.1.4 50.95ms 15.3 210.52MB
The as_adjacency_matrix() function has been optimized for the case
when a dense matrix is needed (sparse = FALSE) and edge attributes are
requested. In these cases, the new implementation is significantly
faster than before (factor 100).
# A tibble: 2 × 5
expression version median `itr/sec` mem_alloc
<chr> <chr> <bch:t> <dbl> <bch:byt>
1 "as_adjacency_matrix(g1,attr = \"weight\"… 2.2.0 2.6ms 394. 16.2MB
2 "as_adjacency_matrix(g1,attr = \"weight\"… 2.1.4 303.4ms 3.21 8.3MB
Last but not least, constructing a graph with
graph_from_adjacency_matrix() has been optimized for sparse matrices,
leading to considerable performance improvements when creating graphs
from large sparse adjacency matrices.
# A tibble: 2 × 5
expression version median `itr/sec` mem_alloc
<chr> <chr> <bch:t> <dbl> <bch:byt>
1 "graph_from_adjacency_matrix(A, mode = \"… 2.2.0 58.99ms 13.7 76.8MB
2 "graph_from_adjacency_matrix(A, mode = \"… 2.1.4 2.41s 0.414 162.6MB
In single use case scenarios, these performance improvements might not be very noticeable. However, in workflows where these functions are called repeatedly or on large graphs, the cumulative effect can lead to great reductions in computation time.
Plotting Improvements
The plotting functionalities have not received much love in the past years, but this release brings some improvements in this area as well.
NA values in vertex and edge attributes are now handled more
gracefully during plotting. Instead of causing errors or unexpected
behavior, NA values are now mapped to defaults.
schema <- make_empty_graph() + vertices("c", "p1", "p2",
color = "green", shape = "rectangle",
size = 30, size2 = 30
) + vertices("d", shape = "circle")
as_data_frame(schema, "vertices")
name color shape size size2
c c green rectangle 30 30
p1 p1 green rectangle 30 30
p2 p2 green rectangle 30 30
d d <NA> circle NA NA
plot(schema)
Warning: vertex attribute size contains NAs. Replacing with default value 15
Warning: vertex attribute size2 contains NAs. Replacing with default value 15
Warning: vertex attribute color contains NAs. Replacing with default value 1
vertex attribute color contains NAs. Replacing with default value 1
vertex attribute color contains NAs. Replacing with default value 1
vertex attribute color contains NAs. Replacing with default value 1
Figure 2: Plotting a graph with NA vertex and edge attributes.
Pie shapes for vertices were broken for a while. This has now been fixed.
g <- make_ring(5)
values <- lapply(1:5, function(x) rep(1, 2))
plot(
g,
vertex.shape = "pie",
vertex.pie = values,
vertex.pie.color = list(c("red", "blue")),
vertex.label = NA
)
Figure 3: Plotting a graph with pie-shaped vertices.
The most elaborate plotting improvement was made for plotting loops. Previously, loops where always drawn at the same angle and size, making them overlap and hard to see. Now, loops are drawn around the vertex at different angles, and their size is adapted based on the number of loops at the vertex and the available space around the vertex.
par(mfrow = c(1, 2))
g <- make_graph(
c(1, 2, 2, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1),
directed = FALSE
)
plot(g, margin = 0.6, loop.size = 2, vertex.size = 15)
g2 <- make_graph(
c(
1, 2, 2, 3, 3, 1, 1, 4, 4, 5, 5, 1,
3, 4, 5, 2, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1
),
directed = FALSE
)
plot(g2, loop.size = 2, vertex.shape = "rectangle")
Figure 4: Plotting graphs with loops.
Besides these major improvements, several smaller fixes and improvements were made to the plotting functionalities. For example, it is now possible to customize the angle and adjustment of vertex labels independently.
g <- make_ring(5, directed = FALSE, circular = FALSE)
V(g)$label <- c("AAAAA", "BBBBB", "CCCCC", "DDDDD", "EEEEE")
g$layout <- cbind(1:5, rep(1, 5))
plot(
g,
vertex.label.angle = c(90, 90, 270, 270, 90),
vertex.label.adj = c(1.3, 0.5)
)
Figure 5: Plotting a graph with customized vertex label angles and
adjustments.
Conclusion
The igraph 2.2.1 release marks a significant step forward in the evolution of the igraph package for R. With a focus on performance enhancements, improved plotting capabilities, and addressing longstanding issues, this release sets the stage for future developments in igraph 3.0.0. We are close to be on par with the C core in terms of features. In 3.0.0, we aim to fully upgrade to C/igraph 1.0.0.
If you import igraph in your own package, now is a good time to at least update currently deprecated functions and arguments. A lot will be removed in the next major release! You will be able to track all the breaking changes on Github.
For now, we encourage users to explore the new features and improvements, and as always, we welcome feedback and contributions.