Skip to Content
Course content

67: Visualizing Networks with igraph

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was tasked with auditing a sprawling set of internal microservices for a fintech client. The documentation was, to put it politely, a fantasy. I spent three days trying to trace a single request path through the system, only to find that a "simple" user profile update was triggering a cascade of fifteen different API calls across four different legacy databases. I remember staring at a spreadsheet of service dependencies and feeling like I was trying to read a map drawn by a toddler. I couldn't see the bottlenecks or the circular dependencies because they were buried in rows of text. That was the day I stopped using spreadsheets for dependency mapping and started using igraph.

At its core, igraph treats your data as a graph—a collection of vertices (nodes) and edges (the connections between them). While you can do basic plotting in R, igraph is the heavy lifter for network analysis. It doesn't just draw lines; it allows you to calculate the "centrality" of a node to find the most important point of failure in your system, or identify "communities" within a network that are tightly knit but isolated from the rest.

Turning Data Frames into Networks

Usually, your network data starts as an edge list—a table where one column is the source and another is the target. I've found that the most intuitive way to handle this is via graph_from_data_frame(). You provide a data frame of edges, and optionally, a data frame of vertex attributes (like the "type" of service or the "region" it's hosted in).

library(igraph)

# Define the connections (edges)
connections <- data.frame(
  from = c("Gateway", "Gateway", "AuthSvc", "AuthSvc", "OrderSvc", "OrderSvc", "PaymentSvc"),
  to = c("AuthSvc", "OrderSvc", "UserDB", "LogSvc", "PaymentSvc", "InventorySvc", "UserDB")
)

# Define the properties of the nodes (vertices)
nodes <- data.frame(
  name = c("Gateway", "AuthSvc", "OrderSvc", "PaymentSvc", "UserDB", "LogSvc", "InventorySvc"),
  type = c("Edge", "Logic", "Logic", "Logic", "Storage", "Utility", "Storage")
)

# Create the graph object
net <- graph_from_data_frame(d = connections, vertices = nodes, directed = TRUE)

The directed = TRUE argument is crucial here. In a microservice architecture, the direction of the call matters. If Service A calls Service B, that's a directed edge. If you're mapping something like "coworkers who have collaborated," you'd set this to FALSE.

Taming the "Hairball" with Layouts

If you just run plot(net), you'll often get what we in the industry call a "hairball"—a chaotic jumble of nodes where the labels overlap and nothing is clear. The secret to a readable network is the layout algorithm. Layouts are essentially mathematical ways of deciding where the X and Y coordinates of your nodes should be to minimize edge crossings.

I personally lean toward the Fruchterman-Reingold layout (layout_with_fr). It treats edges like springs and nodes like electrically charged particles that repel each other, which naturally pushes clusters apart and makes the structure obvious. You can also use layout_in_circle if you just want a clean, symmetric look for a small number of nodes.

# Create a color palette based on the 'type' attribute
node_colors <- c("Edge" = "gold", "Logic" = "skyblue", "Storage" = "tomato", "Utility" = "lightgrey")
V(net)$color <- node_colors[V(net)$type]

# Plot with a specific layout and styling
plot(net, 
     layout = layout_with_fr, 
     vertex.size = 20, 
     vertex.label.cex = 0.8, 
     vertex.label.color = "black", 
     edge.arrow.size = 0.5,
     main = "Microservice Dependency Map")

Notice how I accessed the vertex attributes using V(net)$color. In igraph, V() and E() are special functions used to access the vertices and edges of a graph object. It's a bit of a shorthand that feels strange at first, but it's incredibly powerful once you get used to it.




📋 Practical Task

Exercise: Visualizing a Software Module Dependency Graph

You are analyzing a legacy codebase and have extracted a list of modules and their dependencies. Your goal is to create a visualization that highlights which modules are "storage-heavy" versus "logic-heavy" to identify potential architectural bottlenecks.

Requirements:

  • Create an edge list data frame with the following dependencies:
    • UI depends on Controller
    • UI depends on Validator
    • Controller depends on AuthModule
    • Controller depends on DataMapper
    • Validator depends on AuthModule
    • DataMapper depends on SQLStore
    • AuthModule depends on SQLStore
    • DataMapper depends on CacheStore
  • Create a vertex data frame containing all the modules mentioned above and a category attribute:
    • UI, Controller, Validator, and AuthModule should be categorized as "Logic".
    • SQLStore and CacheStore should be categorized as "Storage".
    • DataMapper should be categorized as "Bridge".
  • Generate a directed graph using graph_from_data_frame.
  • Plot the graph using the layout_with_fr layout.
  • Assign distinct colors to the nodes based on their category (e.g., Logic = green, Storage = red, Bridge = blue).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.