cynkra


Performance improvements and more: igraph 2.2.1!

From the Blog
R
igraph

Authors

David Schoch

Maëlle Salmon

Kirill Müller

Published

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.
Line plot showing the number of open issues per day for the `igraph/rigraph` repository from January 2015 to October 2025. A dashed red vertical line marks the release date of igraph 2.1.4 on January 23, 2025. 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
A plot of a graph with four vertices and no edges. One vertex has NA color and shape attributes, and one edge has NA width attribute. The graph is displayed with default settings for the NA attributes. 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
)
A plot of a ring graph with five vertices, each represented as a pie shape divided into two equal parts colored red and blue. 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")
Two plots of graphs with loops. The first plot shows a graph with three vertices and multiple loops on one vertex, drawn at different angles and sizes. The second plot shows a graph with five vertices and several loops on one vertex, fitted within the available space around the vertex. 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)
)
A plot of a path graph with five vertices, each labeled with a five-letter string. The vertex labels are displayed at different angles and adjustments around the vertices. 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.