(Igraph) Grouped Layout Based on Attribute

How to make grouped layout in igraph?

To expand on Gabor's suggestion, I have created this function:

weight.community=function(row,membership,weigth.within,weight.between){
if(as.numeric(membership[which(names(membership)==row[1])])==as.numeric(membership[which(names(membership)==row[2])])){
weight=weigth.within
}else{
weight=weight.between
}
return(weight)
}

Simply apply it over the rows of the matrix of edges of your graph (given by get.edgelist(your_graph)) to set the new edge weights (membership is the membership vector from the result of any community detection algorithm):

E(g)$weight=apply(get.edgelist(g),1,weight.community,membership,10,1)

Then, simply use a layout algorithm that accepts edge weights such as the fruchterman.reingold as suggested by Gabor. You can tweak the weights arguments to obtain the graph you want. For instance:

E(g)$weight=apply(get.edgelist(g),1,weight.community,membership,10,1)
g$layout=layout.fruchterman.reingold(g,weights=E(g)$weight)
plot(g)

Sample Image

E(g)$weight=apply(get.edgelist(g),1,weight.community,membership,1000,1)
g$layout=layout.fruchterman.reingold(g,weights=E(g)$weight)
plot(g)

Sample Image

Note 1: the transparency/colors of the edges are other parameters of my graphs. I have colored nodes by community to shows that it indeed works.

Note 2: make sure to use membership(comm) and not comm$membership, where comm is the result of the community detection algorithm (e.g., comm=leading.eigenvector.community(g)). The reason is that in the first case, you get a numeric vector with names (what we want), and in the second case, the same vector without names.

To get consensus of multiple community detection algorithms, see this function.

igraph: layout by node attribute

igraph layouts are simply matrices with 2 columns and N rows, so you can easily re-use one layout with another graph as long as the two graphs share the same number of nodes. You can make use of this here: create a graph where you connect the nodes that you want to be placed close to each other, calculate the layout using this graph, and then plot your original graph with the layout you have calculated.

R, igraph: ordered layout

Since a layout in igraph is just a matrix of {x,y} coordinates, you can set the x-coordinates equal to your levels.

g <- make_graph(~A-C-B, C-D, E-D-F)
V(g)$level <- c(1,2,1,3,4,4)
l <- matrix(c(V(g)$level,1,2,3,2,3,1),nrow=length(V(g)$level),ncol=2)
plot(g, layout=l)

Sample Image

I just did the y-axis by hand, but you can construct it as you see fit.


Using Sugiyama layout

Sugiyama layout works by adding layers. There are a lot of options with the layout, but, basically, it tries to create a hierarchical representation of the graph.

l <- layout_with_sugiyama(g, layers = -V(g)$level)$layout
#note the "-", this ensures that the smaller level values get small x coordinates

plot(g,layout=l[,c(2,1)])

How to create a network representing different clusters with other information?

This is mostly a repetition of the answer to Grouped layout based on attribute.

I think that you want to group the vertices by the Clusters attribute and color them using the type attribute. I will do that in this answer.
Your code creating the network is fine, but a simple plot does not group the vertices by the clusters (I have added coloring the vertices by type).

plot(net, edge.arrow.size=.4,vertex.label=NA, 
vertex.color=as.numeric(factor(nodes$type)))

Ungrouped network plot

What you need is a layout that will emphasize the clusters. The previous answer cited above shows how to do that by generating a different graph with the same vertices but with heavy edge weights between vertices in the same cluster. In your case, it would be

Grouped.net = net
E(Grouped.net)$weight = 1

## Add edges with high weight between all nodes in the same group
for(Clus in unique(nodes$Clusters)) {
GroupV = which(nodes$Clusters == Clus)
Grouped.net = add_edges(Grouped.net, combn(GroupV, 2), attr=list(weight=80))
}

## Now create a layout based on G_Grouped
set.seed(567)
LO = layout_with_fr(Grouped.net)

## Use the layout to plot the original graph
plot(net, layout=LO, edge.arrow.size=.4,vertex.label=NA,
vertex.color=as.numeric(factor(nodes$type)))

network grouped by cluster

If you have a large number of vertices, you may also wish to reduce their size with vertex.size=4

igraph group vertices based on community

I have implemented such a function in my package NetPathMiner, called layoutVertexByAttribute.

library(igraph)
library(NetPathMiner)

g <- graph.data.frame(message)
g <- setAttribute(g, "sender", sender_country)

l = layoutVertexByAttr(g, "sender", cluster.strength=10, layout=layout.kamada.kawai)

plotNetwork(g, vertex.color="sender",layout=l)

You can look at the source code here or view the vignette for more details.

Plot

EDIT:

Since installing the package seems a bit difficult if you don't use Bioconductor, I will write a simpler version of the function here.

layout.by.attr <- function(graph, wc, cluster.strength=1,layout=layout.auto) {  
g <- graph.edgelist(get.edgelist(graph)) # create a lightweight copy of graph w/o the attributes.
E(g)$weight <- 1

attr <- cbind(id=1:vcount(g), val=wc)
g <- g + vertices(unique(attr[,2])) + igraph::edges(unlist(t(attr)), weight=cluster.strength)

l <- layout(g, weights=E(g)$weight)[1:vcount(graph),]
return(l)
}

To use it with your example:

g <- graph.data.frame(message) 

l = layoutVertexByAttr(g, sender_country, cluster.strength=10, layout=layout.kamada.kawai)

plot.igraph(g, vertex.color=sender_country, layout=l)

Change Layout Structure in IGraph Plot based on Community

You have to calculate the Kamada-Kawai layout with an artificial weight vector that assigns a high weight to edges within clusters and a low weight to edges that cross cluster boundaries:

> graph <- grg.game(100, 0.2)                 # example graph
> cl <- fastgreedy.community(graph)
> weights <- ifelse(crossing(cl, graph), 1, 100)
> layout <- layout_with_kk(graph, weights=weights)
> plot(graph, layout=layout)

The trick here is the ifelse(crossing(cl, graph), 1, 100) part -- crossing(cl, graph) takes a clustering and the graph that the clustering belongs to, and returns a Boolean vector that defines for each edge whether the edge is crossing cluster boundaries or not. The ifelse() call then simply replaces TRUE (i.e. edge crossing boundaries) in this vector with 1 and FALSE (i.e. edge stays within the cluster) with 0.



Related Topics



Leave a reply



Submit