Skip to content

Computer Lab 1: Create and Analyze Networks with igraph

Author:
David Martínez Enguita

Updated by:
Alberto Zenere
Sarah Müller
Henrik Podéus Derelöv
Ralph Monte

Last updated: 2026-01-28

Built with R-4.4.1

Contents

0. Before starting
1. How to create a network
2. How to plot a network
3. How to analyze a network
4. Network layouts
5. Homework Exercise
6. References


Before starting

Throughout the lab you will encounter different "admonitions", which is a type of text box. A short legend of the different kinds of admonitions used can be found in the box below:

Different admonitions used (click me)

Background and Introduction

Useful information

Guiding for implementation

Here you are tasked to do something

Reflective questions

Create a R script

Open a new R script file in RStudio and save it, using your name and surname (e.g. Lab1_Name_Surname.R). That is where you will write the R code that you will run during this Lab. Remember to save often, so that you do not lose your progress.

igraph and igraphdata

This Lab is based in the package igraph [1], which provides a wide set of functions for graph and network analysis. It allows to create and visualize graphs, as well as to analyze the properties of networks, among other functionalities.

Complementary to igraph, the igraphdata package supplies multiple datasets that we can work with.

**Figure 1.** Using *igraph* we can create a network and identify communities therein, for example. Figure 1. Using igraph we can create a network and identify communities therein, for example.

Installation

Both packages can be installed and loaded in R using the standard commands for package installation:

# Install packages
install.packages("igraph")
install.packages("igraphdata")

# Load igraph and igraph data packages
library(igraph)
library(igraphdata)
Set seed for reproducibility

Finally, you can set a seed (you can change it to any number that you prefer) to ensure you can reproduce your results in the future.

# Set a seed to produce reproducible results
set.seed(777)
Clear workspace

Sometimes it can be good to clear up your workspace. If you have some objects still left in there from previous projects or before starting a new task, it can be good to use the function below to clear it up.

# Clear the workspace (this will delete all previous objects!)
rm(list = ls())

1 How to create a network

Graphs

A graph - \(G = (V, E)\) - is a combination of a set of vertices V and a set of edges E. Vertices are also referred to as "nodes", whereas edges can also be called "links".

The set of edges can be summarized in an adjacency matrix. The adjacency matrix A of a graph is a square matrix with rows and columns equal to the number of vertices in the graph. The presence of an edge between vertices \(i\) and \(j\) is represented as \(A[ij] = 1\), while the lack of an edge is expressed as a \(0\). For undirected graphs, the adjacency matrix is symmetrical.

**Figure 2.** Example of adjacency matrix for an undirected graph with six vertices and six edges. Figure 2. Example of adjacency matrix for an undirected graph with six vertices and six edges.

In the following sections, we will look at different methods to create networks using the R package igraph.

1.1 Create a network from scratch

Using igraph, it is possible to create graphs from scratch by providing the names of the vertices and their connecting structure. We will go over some tools to connect what you have learned in the lectures to this lab.

As explained in the previous section, graphs are built up of vertices and edges, and so far you have seen them represented in a graphical way or as an adjacency matrix. To plot a graph in R, we need to provide information about what vertices the graph has as well as which vertices are connected in what way. If we have a simple graph of 3 vertices and all of them are connected by edges of an unweighted, undirected graph, we can use the make_graph() function like done below.

Creating simple graphs

To specify edges, you write a list of pairs. Both the two numbers that are part of those pairs, and the different pairs are separated by a comma. For better readability, you can leave a space after the commas that are separating the different pairs.

If you want to test your understanding of graphs and adjacency matrices, try to write out both on paper and control if you got it right by running this code.

# Undirected graph with 3 vertices and 3 edges
g1 <- make_graph(edges = c(1,2, 2,3, 3,1), n = 3, directed = FALSE) 

# The numbers are interpreted as vertex IDs, so the edges are 1-->2, 2-->3, 3-->1
plot(g1) 
class(g1)
g1

as_adjacency_matrix(g1)

Note how you specifically write which edges you have but not which vertices. The specification of that is hidden in n = 3. If you change that number to a higher one, you will have more vertices that will not be connected to anything unless you add them to the edges list as well. The vertices will for now just be called 1, 2, 3, etc.

# Directed graph with 10 vertices
g2 <- make_graph(edges = c(1,2, 2,3, 3,1), n = 10)
plot(g2)   
g2

If we would want to have a directed graph, we can change the directed = FALSE statement to directed = TRUE. R will then take into account in which order you give the names of the vertices in the list of edges. If you do not specify anything, the graph will be directed as well.

To name our vertices, we can change the numbers that we used for the vertices so far in the edges list to strings (words). This can make it a little harder to keep your overview of which ones are pairs, especially if you have a lot of vertices.

# We can provide specific names to the vertices
# By default, the graph will have directions for its edges
g3 <- make_graph(c("Pole","Tgfbr1", "Tgfbr1","Lcp2", "Lcp2","Pole")) 
plot(g3)
g3

With this technique, we cannot define isolated vertices that do not have any edges to the other ones. For this, we need to use the argument "isolates".

# Additional vertices can be added, even if they are not connected
g4 <- make_graph(c("Pole","Tgfbr1", "Tgfbr1","Bcl2l1", 
            "Tgfbr1","Bcl2l1", "Pole","Pole"), 
            isolates = c("Lcp2", "Bcas2", "Gtf2", "Smarcb"))  

There are some parameters that can be specified to represent the graph in a more visual pleasing way. If you want to change the appearance of an edge, the arguments normally start with edge., and for vertices they start with vertex.. They are listed in the plot() function, separated by a comma. Note that the first argument needs to be the graph you want to plot.

Table with parameters for plotting

Here is a table of some examples of what you can specify:

Parameter Description
edge.arrow.size = change the size of the arrow head in directed graphs
edge.curved = curve the edges, low numbers (<1) are slight curves, higher numbers (>1) are steeper
vertex.color = "" change the size of the filling color of the vertices
vertex.size = change the size of the vertices
vertex.frame.color = "" change the color of the border of the vertices
vertex.label.color = "" change the color of the names of the vertices
vertex.label.cex = change the size of the names of the vertices
vertex.label.dist = change the distance of the names of the vertices to their center
vertex.label = NA hide the names of the vertices

These are only a fraction of all the available graph visualization parameters provided in igraph. If you are interested in learning about the rest, the complete list of options can be accessed with the command ?igraph.plotting.

Now to the first exercise.

Exercise 1

Plot the graphs g1 - g4 again and change their appearance by playing around with the parameters in the list.

There are other options to plot graphs than the function make_graph(). One of those is graph_from_literal(). The edges are not given as a list but are visually represented as --- for an undirected edge and +--, --+, or +-+ for a directed edge, with the arrow pointing in the direction where the plus appears.

# An undirected edge is represented as "---"
plot(graph_from_literal(a---b, b---c)) 

# A directed edge is represented as "--+" or "+--" depending on the direction
plot(graph_from_literal(a--+b, b+--c))

# Also, the edge can represent a two-way interaction
plot(graph_from_literal(a+-+b, b+-+c))

If there is a group of vertices that are all connected to one or more vertices but are not connected with each other, we can use a shortcut for writing all of these edges with :. In the example below, there are three a vertices and three b vertices, and each a vertex is connected to all three b vertices.

# In this case, every "a" vertex is connected to each "b" vertex
# But no edges are present between "a" or between "b" vertices
plot(graph_from_literal(a1:a2:a3---b1:b2:b3))

Both of these representations with graph_from_literal() can be combined to create more complex graphs. For the rest of this lab and the homework exercise, you can chose whichever representation of graphs fits best for yourself.

# We can combine different edge representations to create a graph
gl <- graph_from_literal(a-b-c-d-e-f, a-g-h-b, h---e:f:i, j)
plot(gl)

As you can see, the g4 graph has two edges going from one node (Tgfbr1) to other (Bcl2l1), and a loop (Pole).

**Figure 3.** Section of the g4 graph, with a duplicated edge and a loop. Figure 3. Section of the g4 graph, with a duplicated edge and a loop.

To do so, we can use the simplify() function, with the graph name as your first parameter and adding whatever you need to specify:

  • remove.multiple = - remove multiple edges between two vertices --> TRUE if not specified
  • remove.loops = - remove loops from one vertex to the same one --> TRUE if not specified

Here, we can also indicate how to combine the attributes of these edges with the edge.attr.comb parameter in case we have a weighted graph. Possible options include "sum", "mean", "prod" (product), "min", "max", "first", or "last". Also, we can decide to remove the attribute by setting the parameter to "ignore".

# Remove multiple edges in g4, combining their weights by "sum" and removing their type
g4s <- igraph::simplify(g4, remove.multiple = TRUE,
                        remove.loops = FALSE)

1.2 Import a network from file

Most of the times, you will be provided a network in the form of a file, which you will then have to import in R and convert into an igraph object (see exercise 2 below). To do this, we need two data.frame objects, one that contains the list of edges and a second that contains the attributes of each node.

  • In the data frame of edges, the first two columns should contain the ID of the source (node where the edge starts) and target (node where the edge ends), respectively. Each row corresponds to one edge of the network.
  • In the data frame of nodes, the first column should contain the ID of all nodes in the networks.

Any additional columns in either data frame are interpreted as attributes that can be used to customize the plot of the network, see Section 2.1.

First, download the following csv files: Nodes and Edges.

Load csv file
# Load vertices (nodes) and edges (links)
edges <- read.csv("./RNASeq_network_EDGES.csv", sep = ";", header = TRUE, as.is = TRUE)
nodes <- read.csv("./RNASeq_network_NODES.csv", sep = ";", header = TRUE, as.is = TRUE)

# Examine the data
head(nodes)
head(edges)

nrow(nodes)# Number of node rows
length(unique(nodes$id)) # Number of unique nodes

nrow(edges) # Number of link rows
nrow(unique(edges[, c("from", "to")])) # Number of unique links

To convert the data to an igraph object, the graph_from_data_frame() function takes two data frames (d and vertices), corresponding to links/edges and nodes.

Convert data to igraph object
# Convert data frame to igraph object (directed)
net <- graph_from_data_frame(d = edges, vertices = nodes, directed = TRUE) 

# Examine the resulting object
class(net)
net 

# We can look at the nodes, edges, and their attributes
E(net)
V(net)
E(net)$weight
V(net)$type

plot(net, vertex.size = 6, 
    vertex.label = NA, 
    vertex.color = "lightblue", 
    edge.arrow.size = 0.5)

In the next step, we can get different other representations of our graph, like an adjacency matrix or a data frame. Sometimes edge lists can also be helpful to create.

Representations of our graph
# If you need them, you can extract an edge list or a matrix from an igraph object
list_edges <- as_edgelist(net, names = TRUE)
head(list_edges)

adj_matrix <- as_adjacency_matrix(net, attr = "weight")
adj_matrix[1:10, 1:10] # Display 10 first rows and columns of the matrix

# Or retrieve the data frames describing nodes and edges
edge_df <- as_data_frame(net, what = "edges")
node_df <- as_data_frame(net, what = "vertices")

Imported graphs can contain self loops and having multiple edges between two vertices. Depending on what you are working with, they might be artifacts that we want to remove. You can remove them, but don't forget to first check if those edges really are not needed.

We can use the simplify() function that we went through in section 1.1 to help out with this:

Remove artifacts from graphs
# Removing self loops from the graph
net <- igraph::simplify(net, remove.multiple = FALSE, remove.loops = TRUE) 

This yields a graph like Figure 4 below.

**Figure 4.** Graph plot of the imported network data. Arrows between nodes indicate the direction of the interaction. Figure 4. Graph plot of the imported network data. Arrows between nodes indicate the direction of the interaction.

Now to the second exercise.

Exercise 2

You should recreate the graph shown below (Figure 5), in two ways:

  1. using the methods in Section 1.1. Feel free to change the appearance to increase readability.
  2. create an adjacency matrix, an edgelist, and dataframes containing the edges and vertices, respectively, like seen in section 1.2.

**Figure 5.** Basic undirected graph formed by five vertices and nine edges. Figure 5. Basic undirected graph formed by five vertices and nine edges.


2 How to plot a network

2.1 Graph attributes

Graph objects use attributes to store information about the network and its vertices and edges, such as the name, vertex type, edge weights, etc. These attributes can be added and modified according to our needs and we can use them to set plot options, such as color and line width. We will go over this more in detail in section 2.2.

The vertices' attributes are stored in V(name_of_graph) and the edges' attributes are stored in E(name_of_graph).

Refine g4 graph
# Let's redefine g4 with the same function we used previously for this exercise 
# to have the not simplified version.
g4 <- make_graph(c("Pole","Tgfbr1", "Tgfbr1","Bcl2l1", 
            "Tgfbr1","Bcl2l1", "Pole","Pole"), 
            isolates = c("Lcp2", "Bcas2", "Gtf2", "Smarcb"))

# Graph attributes
E(g4) # Graph edges
V(g4) # Graph vertices

If a graph is created using the function graph_from_data_frame() (Section 1.2), every column of the nodes' data.frame is considered a vertices' attribute. Alternatively, we can add or modify attributes after the graph is created.

Add attributes to the network
# Add attributes to the network, vertices, or edges
V(g4)$name # Automatically generated when the graph is created since there is a column "name" in the nodes' data.frame
V(g4)$type <- c("Protein", "Protein", "Protein", "Protein", 
                "Transcription Factor", "Transcription Factor", "Protein") # We are adding a new attribute called "type"

You have already seen some graph attributes in the lectures. These include if a graph is directed or not, its connectivity, and if it is weighted. These are general graph attributes. The type we just assigned is an additional attribute that every vertex will display, similar to coloring vertices, depending on if they are involved in a disease or pathway. We can also add attributes to the edges, weighting them or also add a type.

Add edge attribute
E(g4)$type <- "Regulatory" # Edge attribute, assigned to all edges
E(g4)$weight <- 10 # Edge weight, setting all existing edges to 10

As with most things in R, there are other ways to assign attributes as well. With the functions set_graph_attr(), set_edge_attr(), and set_vertex_attr(), you can add attributes not only to the edges and vertices, but also to the complete graph. In the example below, we are naming the graph.

Additionally, you can see functions to examine the attributes and remove them again.

Alternative way to set attributes
# Another way to set attributes
# (you can similarly use set_edge_attr(), set_vertex_attr(), etc.)
g4 <- set_graph_attr(g4, "name", "PPI Network")
graph_attr(g4) # Now the name of the graph has been added

# Examine attributes
edge_attr(g4)
vertex_attr(g4)
graph_attr(g4)

# Remove attributes
g4 <- delete_graph_attr(g4, "name")
graph_attr(g4) # And it is gone again

After we added weights to our graph g4, we can look at the simplify() function again. We can now combine weighted edges with edge.attr.comb=.

Simplified graph with weighted edges
# Remove multiple edges in g4, combining their weights by "sum" and removing their type
g4s <- igraph::simplify(g4, remove.multiple = TRUE,
                        remove.loops = FALSE, 
                        edge.attr.comb = list(weight = "sum", 
                                            type = "ignore"))

plot(g4s, edge.arrow.size = 0.5, 
    vertex.color = "lightblue", 
    vertex.size = 15, 
    vertex.frame.color = "black",
    vertex.label.color = "black", 
    vertex.label.cex = 1, 
    vertex.label.dist = 2.5) 

2.2 Layout based on graph attributes using the plot function

Here are some examples on how to change the graph's appearance based on its attributes. Note how we changed the plot() function before to influence how the graph looks like. Now we add the attribute $color in order to assign specific colors to each vertex.

Set color based on node type
# Another way of setting visual attributes is to add them to the igraph object

# Generate colors based on node type
V(net)$color <- ifelse(V(net)$type=="Protein", "gray70", "tomato") #simple code when we have two categories

#node_color <- c("gray70", "tomato")
#V(net)$color <- node_color[as.factor(V(net)$type)] #more advanced option, when we have more than two categories

The ifelse() function looks through the type of the vertices and looks if they are "Protein", achieved by the operator ==. If they are, then the first color given is assigned to the color attribute of the vertex, if not, the second one is chosen.

We can also utilize the node size to customize the plot.

Customize the plot with size attributes
# Set node size based on node type
node_size <- c(5, 10)
V(net)$size <- node_size[as.factor(V(net)$type)]

plot(net, vertex.label = V(net)$type, #we are using the attribute type to customize the labels in the plot
    vertex.label.color = "black",
    vertex.label.cex = 0.7,
    edge.arrow.size = 0.4)

# Set edge width based on weight
E(net)$width <- E(net)$weight

# Change arrow size
E(net)$arrow.size <- 0.2

As you can see, we can also specify the graph's appearance based on its attribute using the plot function in a similar way as we did before.

Specify the graph's appearance
# We can also override the attributes explicitly in the plot
plot(net, edge.color = "orange", 
    vertex.color ="gray50") 

# Add a legend explaining the meaning of the colors we used:
plot(net, edge.color = "blue", 
    main = "Network of proteins and TFs")

legend("topleft", # Legend position
    c("Protein", "Transcription Factor"), # Types of nodes
    pch = 21, # Type of box / circle
    pt.bg = node_color, # Attribute described in legend
    pt.cex = 2, # Size of box
    ncol = 1) # Number of columns

Finally, we can add other attributes to the igraph object, such as their node size, the label, the appearance of edges, and much more.

Customize the igraph object
# Generate colors based on node type
node_color <- c("gray70", "tomato")
V(net)$color <- node_color[as.factor(V(net)$type)]

# Set node size based on node type
node_size <- c(5, 10)
V(net)$size <-node_size[as.factor(V(net)$type)]

plot(net, vertex.label = V(net)$type,
    vertex.label.color = "black",
    vertex.label.cex = 0.7,
    edge.arrow.size = 0.4)

# The labels are currently the node IDs
# Setting them to NA will render no labels
V(net)$label <- NA

plot(net, vertex.label.color = "black",
    vertex.label.cex = 0.7,
    edge.arrow.size = 0.4)

# Set edge width based on weight
E(net)$width <- E(net)$weight

# Change arrow size
E(net)$arrow.size <- 0.2

# We can also override the attributes explicitly in the plot
plot(net, edge.color = "orange",
    vertex.color = "gray50")

# Add a legend explaining the meaning of the colors we used:
plot(net, edge.color = "blue",
    main = "Network of proteins and TFs")

legend("topleft", # Legend position
    c("Protein", "Transcription Factor"), # Types of nodes
    pch = 21, # Type of box/circle
    pt.bg = node_color, # Attribute described in legend
    pt.cex = 2, # Size of box
    ncol = 1) # Number of columns

This customization results in the right graph plot.

Figure 6: Comparison of graph plot Figure 6. Comparison of graph plot generated with default visualization attributes (left) and graph plot generated with specific attributes (right), using the same initial network data.

Now to the third exercise.

Exercise 3

Assign your graph's (from exercise 2) graph attributes directly in the igraph object. Add weights to its edges and add some attributes to their vertices. Plot them with a visual difference between vertices with differing attributes.


3 How to analyze a network

Networks are present everywhere, from social media, to biological systems and transport routes. When we analyze a network we can either look at its general properties as a whole, or we can quantify the importance of specific nodes in the network.

3.1 Node properties: centrality

Centrality is a property of nodes that indicates their ability to influence the entire network. For instance, in social networks, celebrities have a lot of followers and can propagate information more easily than others.

Figure 7: social network graph Figure 7. Twitter social network graph for a single user (node in the center). Edges represents follows between accounts in the network. Note that areas of interest for the main user are shaped in the form of communities with a common topic. However, this definition of centrality is not unique and depends on the application. For example, in a street network, an urban region is central if it presents traffic jams and is more accessed than other places.

Since there is no consensus about the definition of centrality, several measures have been proposed, where each one considers specific concepts. In this Lab, we will cover the following centrality measures that you have previously seen in your lectures:

  • Degree
  • Closeness
  • Betweenness
Degree centrality

The simplest centrality measure is degree centrality, which is defined by the number of connections attached to each node. The in-degree represents the number of directed connections reaching a node, while the out-degree represents the number of directed edges leaving a node. In an undirected graph, the in-degree equals the out-degree.

With the degree() function with the argument, telling which network we want to look at, we can access the total degree of a vertex, as well as simply in- and out-degrees.

# Degree for each node
degree(net, mode = "all")

degree(net, mode = "in") # In-degree
degree(net, mode = "out") # Out-degree

It is also possible to normalize the degree centrality directly. For this, we can add the attribute normalized = TRUE.

Another function for degree-centrality measure is centr_degree(). It can have the same attributes as degree(), but gives out information differently, showing the degrees of each node in order, a centralization value (theoretical maximum level of centralization of the whole graph), and the maximum possible centralization of the graph (maximum number of edges a node can have).

# Normalized centrality for each node
degree(net, mode = "all", normalized = TRUE)

# Degree, centralization value, and maximum centralization for a graph of that size
centr_degree(net, mode = "in", normalized = TRUE)
Closeness centrality

Node centrality can also be defined in terms of shortest paths. A central node, in this definition, should allow us to reach any other node in the network using a fairly short path.

This idea is enclosed in the closeness centrality measure, which is calculated using the average distance of a node to all others.

If you want to see the closeness centrality of a weighted graph but ignore the weights of the edges, you can add the attribute weights = NA. This can be helpful if you want to find an unexpectedly high weight that increases the centrality of a vertex significantly, for example.

Similar to before, you can use two different functions, closeness() and centr_clo(). Adding weights = NA only works with the first function. There, you will get the centrality of each node similar to the degree centrality. The second function gives you the normalized centrality measure for each node in $res, the centralization (similar to the value in the degree centrality), and the theoretical maximal centrality a vertex could have.

# Closeness
closeness(net, mode = "all", weights = NA)
centr_clo(net, mode = "all", normalized = TRUE)

# If there are disconnected sections in the graph, a warning will show up
# In this case, you can ignore it
Betweenness centrality

Finally, the betweenness centrality of a node is based on the number of shortest paths (between other nodes of the network) that pass through that node.

Similar to the other centrality measures, you can use the betweenness() or the centr_betw() functions. Both of these functions will check the centrality of the vertices of a graph. Additionally, we can check the betweenness centrality of edges (defined as number of shortest paths between any pair of nodes of the network that include that edge) using the function edge_betweenness(). For both the betweenness() and the edge_betweenness() functions, it is possible to add the argument weights = NA to ignore the weights of the edges of a graph.

# Node betweenness
betweenness(net, directed = FALSE, weights = NA)

# Edge betweenness (geodesics going through edges)
edge_betweenness(net, directed = FALSE, weights = NA)

centr_betw(net, directed = FALSE, normalized = TRUE)

This figure gives an example of node closeness centrality.

Figure 8: Node closeness centrality Figure 8. Node closeness centrality for the map of streets and intersections of the town of Piedmont, California. The nodes are colored by their relative centrality, from lowest in dark purple to highest in bright yellow.

We can summarize network centralities in a single data frame, for easy calculation and access. The row names will be the vertex names.

# Summary of graph centrality metrics
metrics <- data.frame(deg = degree(net),
                      clo = closeness(net),
                      bet = betweenness(net))
View(metrics)

3.2 Network properties: density, transitivity and diameter

We will also explore som network properties.

Density

Network density represents the ratio between the number of edges in the network compared to the maximum number of edges that could theoretically exist (i.e. if all nodes were connected to each other).

# Create a random network with 10 nodes and 30 edges and plot it
net_random <- sample_gnm(n = 10, m = 30)
plot(net_random)

# Density: proportion of present edges compared to all possible edges
edge_density(net_random, loops = FALSE) # If the network is undirected
ecount(net.random)/(vcount(net_random)*(vcount(net_random)-1)) # If the network is directed
Transitivity

Network transitivity is a measure to indicate the presence of tightly connected groups of nodes. It is based on the notion of closed triplets and open triplets.

A closed triplet indicates three nodes that are all connected to each other, while an open triplet indicates that at least one edge between the three nodes is missing. The transitivity is then calculated as the ratio between the observed number of closed triplets and the sum between closed and open triplets (i.e., the maximum number of triplets we could theoretically observe).

Global transitivity (for an entire graph) is also referred to as global clustering coefficient.

Figure 9: global transitivity Figure 9. Representation of global transitivity. The graph contains a total of 12 closed triplets and six open triplets. Each triangle counts as three closed triplets. This is because closed triplets are counted for each of the three nodes of the triangle. For example, the top left triangle includes the closed triplets [A,B,C], [B,C,A], and [C,A,B]. Note that [A,B,C] and [C,B,A] are the same triplet, and only count as one.

# Transitivity
# Global: ratio of closed triplets to closed + open triplets (direction does not matter)
transitivity(net_random, type = "global") 
transitivity(as.undirected(net_random, mode = "collapse")) # Same value

# Local: transitivity value for each specific ´vertex
transitivity(net_random, type = "local")
Diameter

Network diameter is defined as the maximum shortest path between any two nodes in the network.

# Diameter
# Edge weights are used by default, unless set to NA
diameter(net_random, directed = FALSE, weights = NA)
diameter(net_random, directed = FALSE)

# Show the nodes that form the maximum shortest path
diam <- get_diameter(net_random, directed = TRUE)
diam

Figure 10: graph diameter Figure 10. Representation of graph diameter in a network of social interactions between 62 bottlenose dolphins, in which ties correspond to preferred companionships. Nodes connected by red lines form the maximum shortest path of the graph (diameter) = longest connection between any two dolphins, following the shortest route through their friends, and the friends of their friends.


4 Network layouts

4.1 Using pre-existing graphs in R

Some networks can be created automatically. This includes simple networks (like empty or connected), as well as special classes of networks that have received a lot of attention in the literature.

Simple networks

Here are some examples of simple networks, where we set the number of vertices to 40. Notice that some cases, such as tree graphs, require to specify additional parameters.

Examples of simple networks
# Empty graph (40 nodes)
eg <- make_empty_graph(40)
plot(eg, vertex.size = 10, vertex.label = NA, main = "Empty graph")

# Full graph (completely connected)
fg <- make_full_graph(40)
plot(fg, vertex.size = 10, vertex.label = NA, vertex.color = "lightblue", main = "Full graph")

# Star graph (directed towards a central node)
st <- make_star(40)
plot(st, vertex.size = 10, vertex.label = NA, vertex.color = "red",  main = "Star graph")

# Tree graph (undirected, multiple "branches")
tr <- make_tree(40, children = 3, mode = "undirected")
plot(tr, vertex.size = 10, vertex.label = NA, vertex.color = "lightgreen", main = "Tree graph")
/
# Ring graph (undirected, connects all vertices in a circular pattern)
rn <- make_ring(40)
plot(rn, vertex.size = 10, vertex.label = NA, vertex.color = "purple", main = "Ring graph")

**Figure 11.** From top left to bottom right: a full graph, star graph, tree graph, and ring graph. Figure 11. From top left to bottom right: a full graph, star graph, tree graph, and ring graph.

Once we have created a network, it is possible to rewire (i.e., change the edges). This can be done randomly, if you want to change the pre-made graphs for example.

Rewiring
# Rewiring a graph
# 'each_edge()' rewires edges by setting an edge probability between 0 and 1
rn.rewired <- rewire(rn, each_edge(prob = 0.1))
plot(rn.rewired, vertex.size = 10, vertex.label = NA)
# The higher the probability, the higher the chance of edge rewiring

# Rewire to connect vertices to other vertices at a certain order of distance
# Order 0 is the vertex itself, order 1 is the vertex plus the immediate neighbors, etc
rn.neigh <- connect.neighborhood(rn, order = 5)
plot(rn.neigh, vertex.size = 8, vertex.label = NA)

Combining two graphs does not merge their vertices by default (disjoint union), it only displays them in the same plot. The function %du% is used to combine igraph objects.

Combining igraph objects
# First graph to be combined
plot(rn, vertex.size = 10, vertex.label = NA, vertex.color = "lightgreen")

# Second graph to be combined
plot(tr, vertex.size = 10, vertex.label = NA, vertex.color = "red")  

# Combine graphs with %du% ("du": disjoint union for non-overlapping sets of vertices)
plot(rn %du% tr, vertex.size = 10, vertex.label = NA, vertex.color = "orange")

**Figure 12.** Combination of a tree graph (top left) and a ring graph (bottom right). Figure 12. Combination of a tree graph (top left) and a ring graph (bottom right).

4.2 Special classes of networks

Some special classes of networks have been studied in detail, and it is possible to generate them with specific functions. Typical graph models include the Barabási-Albert model (also called "scale-free network"), the Erdös-Rényi model (or "random"), and the Watts-Strogatz model (or "small world").

**Figure 13.** Examples of random graph models. Figure 13. Examples of random graph models.

Barabási-Albert model (scale-free)

The Barabási-Albert model [3] represents scale-free networks, meaning the degree distribution follows a power law. In other words, in these networks we find a small subset of nodes that are characterized by a very large number of edges. For example, biological networks tend to be scale-free.

  • Examples: social networks, gene regulatory networks.
# Create Barabási-Albert model 
# 50 nodes, directed
barabasi = sample_pa(n = 50, directed = TRUE)
plot.igraph(barabasi)
plot(barabasi)
Erdös-Rényi model (random)

Erdös-Rényi graphs [4] are formed by completely random interactions between the nodes. Each node chooses its neighbors at random, using either the total number of relationships that might be assigned in the graph, or the probability of connecting to a certain neighbor.

  • Examples: emergence of the crystalline structure of ice, emergence of magnetic properties in ferromagnetic materials.
# Create Erdos-Renyi model 
# 20 nodes, 30 edges, undirected
random <- sample_gnm(n = 20, m = 30)
plot.igraph(random)

# Create Erdos-Renyi model
# 20 nodes, 0.5 probability that an edge is present in the graph, undirected
random <- sample_gnp(n = 20, p = 0.5)
plot.igraph(random)
Watts-Strogatz model (small world)

A small-world, or Watts-Strogatz graph [5] is a type of graph in which most nodes are not neighbors of one another, but the neighbors of any given node are likely to be neighbors of each other. Most nodes can be reached from every other node by a small number of steps.

  • Examples: electrical power grids, brain neuron networks, social influence networks.
# Create Watts-Strogatz model 
# Dimension 1, size 50, neighborhood 0.5, rewiring probability 0.05
smallworld <- sample_smallworld(dim = 1, size = 50, 
                                nei = 5, p = 0.05)
plot(smallworld)

4.3 Network layouts

A key factor in the comprehensibility of a graph representation is its layout. Network layouts are algorithms that return coordinates for each node in a network, allowing them to be displayed in multiple spatial structures.

By default, igraph uses a layout called "layout_nicely" which selects an appropriate layout algorithm based on the properties of the graph. This normally includes that it is avoided to have two edges cross each other if possible for increased readability.

Note that network layouts do not alter the attributes and properties of the graph, they only affect the way the network is displayed.

Using the layout_randomly
# Let's generate a random 80-node graph (power: preferential connections)
net.bg <- sample_pa(n = 80, power = 1.2)
V(net.bg)$size <- 8
V(net.bg)$frame.color <- "white"
V(net.bg)$color <- "orange"
V(net.bg)$label <- "" 
E(net.bg)$arrow.mode <- 0
plot(net.bg)

# You can change the layout in the plot function
plot(net.bg, layout = layout_randomly) # Random layout

# Or calculate the vertex coordinates in advance
l <- layout_in_circle(net.bg) # Circle layout
plot(net.bg, layout = l)
# l is simply a matrix of x,y coordinates (N x 2) for the N nodes in the graph. 

If you'd like, you can try out multiple layouts of the graph with the random layout. Additionally, here are some layouts that are included in igraph:

More layouts
# igraph has a number of built-in layouts, including:

# Randomly placed vertices
l <- layout_randomly(net.bg)
plot(net.bg, layout = l)

# Circle layout
l <- layout_in_circle(net.bg)
plot(net.bg, layout = l)

# 3D sphere layout
l <- layout_on_sphere(net.bg)
plot(net.bg, layout = l)

# The Fruchterman-Reingold force-directed algorithm 
# Nice but slow, most often used in graphs smaller than ~1000 vertices
l <- layout_with_fr(net.bg)
plot(net.bg, layout = l)

# The Kamada Kawai force-directed algorithm
# Attempts to minimize the energy in a spring system
l <- layout_with_kk(net.bg)
plot(net.bg, layout = l)

# The LGL algorithm is for large connected graphs
# You can specify a root (node that will be placed in the middle)
# By default, the root is random
plot(net.bg, layout = layout_with_lgl, root = 10)

You can explore these and other layouts by running ?layout in your RStudio console and selecting "Graph layouts" for the igraph package.

**Figure 14.** Four different graph layouts available in igraph. Figure 14. Four different graph layouts available in igraph.

Now, try to identify which layout was applied for each of the graphs in Figure 14 above. Hint: none of them are from the previous code.


5 Homework Exercise

A cytokine storm is a severe immune reaction in which too many inflammatory cytokines are released simultaneously. The goal of this exercise is to use a network-based approach to identify possible therapeutic targets.

Homework exercise

A. Download the file Cytokines_homework.txt and go to https://string-db.org/.

  • Go to Multiple proteins and upload the aforementioned file, which contains a list of cytokines; as organism select Homo sapiens.

Proceed to Search; all proteins should be mapped so you can continue to the network.

  • In Settings, remove interactions retrieved from text mining.
  • In Settings, consider only interactions with a score of at least 0.9.
  • Add more proteins by clicking the option "More" under the network twice. You should obtain a network with 23 proteins.
  • Download the network in text format.

B. Plot the same network in R. Make sure that all 23 proteins are plotted. Use a different color to highlight the initial cytokines (i.e., those in Cytokines_homework.txt).

C. Compute a centrality measure of the nodes in the network and use a lollipop plot (https://r-graph-gallery.com/lollipop-plot.html) or bar plot to show the centrality measure of the top 10 nodes. Based on this figure, which cytokine would you choose as your first target? Motivate your answer.

D. Simulate what would happen if you silenced this protein by removing its connecting edges from the network (but not the protein itself). Plot the new network, any major changes? Compare the density of the network, before and after removing these edges, did you expect the observed changes? Why?

E. Reflect on the homework. What would be the rationale in targeting cytokines with high centrality? On the other hand, what unintended consequences may this approach bring in a real biological system?

Gather the plots you generated and your answers to the homework in a text file (Word or similar). Upload the file, as well as your R script, in Lisam.

The deadline is Wednesday 18th of February 2026 at 17:00. Note: late submissions will not be graded. You are then only allowed to hand-in during the resubmission opportunity (resubmission deadline: Friday 27th of March 2026 at 23:30).


References

  1. Csardi, G., & Nepusz, T. (2006). The igraph software package for complex network research. InterJournal, Complex Systems, 1695(5), 1-9.

  2. Kleinberg, J. M. (1999). Hubs, authorities, and communities. ACM computing surveys (CSUR), 31(4es), 5.

  3. Barabási, A. L., & Albert, R. (1999). Emergence of scaling in random networks. Science, 286(5439), 509-512.

  4. Erdős, P., & Rényi, A. (1960). On the evolution of random graphs. Publ. Math. Inst. Hung. Acad. Sci., 5(1), 17-60.

  5. Watts, D. J., & Strogatz, S. H. (1998). Collective dynamics of 'small-world'networks. Nature, 393(6684), 440.