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)
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)
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)
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)))
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)))
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.
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
Can the Value.Var in Dcast Be a List or Have Multiple Value Variables
Subsetting a Matrix by Row.Names
What Does the Function Invisible() Do
Make a Rectangular Legend, with Rows and Columns Labeled, in Grid
How to Repeat the Grubbs Test and Flag the Outliers
Select Along One of N Dimensions in Array
Dt[!(X == .)] and Dt[X != .] Treat Na in X Inconsistently
"Set Difference" Between Two Vectors with Duplicate Values
Compute the Minimum of a Pair of Vectors
Calculate Total Miles Traveled from Vectors of Lat/Lon
How to Align Multiple Ggplot2 Plots and Add Shadows Over All of Them
Normalizing Y-Axis in Histograms in R Ggplot to Proportion by Group
Risks of Using Setwd() in a Script