Order vertices within layers on tripartite igraph - r

I have the following dataframe:
df<-data.frame(consumed= c("level1_plt1", "level1_plt2", "level1_plt3", "level1_plt3","level1_plt2","level1_plt4","level1_plt5","level1_plt5","level1_plt6","level1_plt7","level1_plt8","level1_plt9","level1_plt10","level1_plt10","level1_plt1","level1_plt1","level1_plt6","level1_plt6","level1_plt9","level1_plt9","level1_plt11","level1_plt11","level1_plt11","level2_lep1","level2_lep4","level2_lep3"),consumer=c("level2_lep1","level2_lep2","level2_lep3","level2_lep2","level2_lep4", "level2_lep4","level2_lep5","level2_lep5","level2_lep6","level2_lep7","level2_lep8","level2_lep9","level2_lep10","level2_lep10","level2_lep8","level2_lep8","level2_lep1","level2_lep1","level2_lep3","level2_lep11","level2_lep12","level2_lep13","level2_lep13", "level3_pst1","level3_pst3","level3_pst4"))
And have preformed the following steps to get an igraph tripartite output:
links<-
df%>%
group_by(consumed, consumer) %>%
summarize(freq=n())
g<- graph_from_data_frame(d=links,directed=FALSE)
layer <- rep(2, length(V(g)$name))
layer[grepl("level1_",V(g)$name)]=1
layer[grepl("level3_",V(g)$name)]=3
names<- V(g)$name
names<-sub("level2_","", names)
names<-sub("level3_","", names)
names<-sub("level1_","", names)
V(g)$name = names
layout = layout_with_sugiyama(g, layers=layer)
E(g)$width <- E(g)$freq
V(g)$vertex_degree <- degree(g)*7
plot(g,
layout=cbind(layer,layout$layout[,1]),edge.curved=0,
vertex.shape=c("square","circle","square")[layer],
vertex.frame.color = c("darkolivegreen","darkgoldenrod","orange3")
[layer],
vertex.color=c("olivedrab","goldenrod1","orange1")[layer],
vertex.label.color="white",
vertex.label.font=2,
vertex.size=V(g)$vertex_degree,
vertex.label.dist=c(0,0,0)[layer],
vertex.label.degree=0, vertex.label.cex=0.5)
And I would like to do two things to adjust the picture, if possible:
Order the layers from the largest shape (highest degree) to smallest shape (smallest degree). For example, in the green layer the order could be as follows: plt9, plt3,plt2,plt11,plt6,plt1,plt7,plt5,plt4,plt10,plt8.
Create space between the shapes so that there is no overlap (e.g. lep3 and lep4). I like the current sizes/proportions so I am opposed to making shapes smaller to create space between shapes.
Flip the graph and vertex font 90 degrees counter-clockwise so that from bottom to top it would be in the order green layer-->yellow layer-->orange layer. (I guess it is always an option to rotate vertex text and I can rotate the image in word or ppt.)

I know this question is old, but I hope that the answer will help someone.
Rather than using layout_with_sugiyama, It may be easiest to do this with
a custom layout. It is not very hard to do so. You already constructed the
horizontal position with your layer variable. To get the vertical positions,
we need to order the vertices by size (vertex_degree) and then allow shape proportional to the size, so we will set the height using cumsum on the vertex_degrees within each layer. After I make the layout the complex call to plot is the same as yours except
that I swap my custom layout for your call to sugiyama.
MyLO = matrix(0, nrow=vcount(g), ncol=2)
## Horizontal position is determined by layer
MyLO[,1] = layer
## Vertical position is determined by sum of sorted vertex_degree
for(i in 1:3) {
L = which(layer ==i)
OL = order(V(g)$vertex_degree[L], decreasing=TRUE)
MyLO[L[OL],2] = cumsum(V(g)$vertex_degree[L][OL])
}
plot(g,
layout=MyLO, edge.curved=0,
vertex.shape=c("square","circle","square")[layer],
vertex.frame.color = c("darkolivegreen","darkgoldenrod","orange3")[layer],
vertex.color=c("olivedrab","goldenrod1","orange1")[layer],
vertex.label.color="white",
vertex.label.font=2,
vertex.size=V(g)$vertex_degree,
vertex.label.dist=0,
vertex.label.degree=0, vertex.label.cex=0.5)

Related

Change edge size in igraph

I want to plot a simple star graph in which the size of the edges depends on a score representing a difference of perception between the central node (e.g.,a leader) and the other nodes (e.g., its employees).
I succeeded in modifying the colors, the size of the node, the width of the edges but not the size of the latter.
How would you do?
library(igraph)
nodes <- read.csv("exemple_nodes.csv", header=T, as.is=T)
links <- read.csv("exemple_edges.csv", header=T, as.is=T)
st <- graph_from_data_frame(d=links, vertices=nodes, directed=T)
plot(st, vertex.color=V(st)$perception.type)
With the ggraph package and one of the geom_edge_ func' (e.g., geom_edge_arc, geom_edge_diagonal), in order to use the edge_width parameter, depending on a numeric value associated with the edges, in the edges-list (hereafter "value"). For example:
ggraph::ggraph(st) +
ggraph::geom_edge_diagonal(aes(edge_width = as.numeric(value)) )
In addition, ggraph allow you to specify other edges-parameters inside the geom_edge_ func', for example edge_alpha = as.numeric(value).
I think that what you want is to position the vertices so that you can control the length of the edges. If that is not what you want, then please explain what you mean by the "size" of the edges.
You do not provide your data so that we cannot use exactly your graph. I will use a generic star graph as an example. In order to control the placement of the vertices, you need to use the layout parameter. The basic function layout_as_star will place the first vertex at the center and the other vertices equally spaced around it at the same distance. Because this layout function places the center vertex at (0,0) and the remaining nodes on a unit circle around the center, it is easy to adjust it so that the distance of the outer vertices is controlled by a parameter. Just multiply the coordinates by the parameter and it will proportionally change the distance. I just make something up for the distances, but you can use your parameter.
## Make up perception parameter
set.seed(271828)
Perception = sample(4, 9, replace=T)
Perception
[1] 2 3 4 4 1 4 2 2 1
Now there is one weight for every outer vertex, but we need a weight for the central vertex. We don't want it to move so we use a weight of 1.
Weight = c(1, Perception)
LO = layout_as_star(S10)
LO = LO*Weight
plot(S10, layout=LO)

Plot two igraph networks using the same coordinates and same placement in the plot frame

I am trying to plot a network that changes in time. The network starts with a certain number of nodes and edges and each time step some of the nodes and edges are removed.
I want to be able to plot the network so that the nodes are in the same place in each. However when I try this. sometimes the nodes shift position in the plot frame even if the relation to each other is the same.
I am making the network change into a gif so even small changes are annoying. I think the change may occur when a large fraction of the nodes are removed but I am not sure.
The code below illustrates this using an ER graph.
library(igraph); library(dplyr)
#generate random graph
set.seed(500)
RandomGraph <- sample_gnm(1000, 2500)
#name nodes
V(RandomGraph)$name <- paste0("Node", 1:1000)
#Get the coordinates of the Nodes
Coords <- layout_with_fr(RandomGraph) %>%
as_tibble %>%
bind_cols(data_frame(names = names(V(RandomGraph))))
#Delete random vertices
deletevertex <-sample( V(RandomGraph)$name, 400)
RandomGraph2 <-delete.vertices(RandomGraph, deletevertex)
#get the coordinates of the remaining Nodes
NetCoords <- data_frame(names = names(V(RandomGraph2))) %>%
left_join(Coords, by= "names")
#plot both graphs
RandomGraph%>%
plot(.,vertex.size=.8, edge.arrow.size=.4, vertex.label = NA, layout = as.matrix(Coords[,1:2]))
RandomGraph2%>%
plot(.,vertex.size=.8, edge.arrow.size=.4, vertex.label = NA, layout = as.matrix(NetCoords[,2:3]))
#They nodes have the same relationship to each other but are not laid out in the same position in the frame
As you can see the plots have placed nodes in the same place relative to each other but not relative to the frame.
How can I have the plot position fixed.
plot.igraph rescales each axis by default (from -1 to +1 on both x and y).
You just need to turn that off: rescale = F and then explicitly set appropriate xlim and ylim values.
For your example code..
RandomGraph%>%
plot(.,vertex.size=.8, edge.arrow.size=.4, vertex.label = NA, layout = as.matrix(Coords[,1:2]),rescale=F,xlim=c(-25,30),ylim=c(-20,35))
RandomGraph2%>%
plot(.,vertex.size=.8, edge.arrow.size=.4, vertex.label = NA, layout = as.matrix(NetCoords[,2:3]),rescale=F,xlim=c(-25,30),ylim=c(-20,35))
The problem is that
identical(range(Coords[1]), range(NetCoords[2]))
# [1] FALSE
Since igraph normalizes the coordinates on a range between -1 and 1 before plotting, this leads to slightly different coordinates for NetCoords compared to Coords. I'd just calculate the normalized coordinates for all nodes beforehand:
coords_rescaled <- sapply(Coords[-3], function(x) -1+((x-min(x))*2)/diff(range(x)))
rownames(coords_rescaled) <- Coords$names
And then assign the normalized coordinates (or the required subset) and set rescale=FALSE (as #jul) already suggested:
par(mfrow=c(1,2), mar=c(1,.5,1,.5))
RandomGraph%>%
plot(.,edge.arrow.size=.4, layout = coords_rescaled, rescale=F);box()
RandomGraph2%>%
plot(.,edge.arrow.size=.4, layout = coords_rescaled[NetCoords$names, ], rescale=F);box()

igraph using two layouts for different nodes

Is there a way to plot a graph using two different layouts, one for a set of nodes and another for all other nodes?
E.g., define that nodes 1-10 are plotted with circular layout and all other nodes are drawn with force-directed layout.
Yes you can. You just need to hack together two different layouts.
library(igraph)
gr <- random.graph.game(100, p.or.m = 0.25, type = "gnp")
lay1 <- layout_in_circle(induced_subgraph(gr, 1:20)) ##layouts are just matrices with x, y coordinates
lay2 <- layout_with_fr(induced_subgraph(gr, 21:100)) #I used Fruchterman-Reingold on the subgraph excluding the nodes in the circle but you could include them and then overwrite their layout coordinates with the coordinates for the circle
lay3 <- rbind(lay1+2, lay2) ## I added a scalar to shift the circlular nodes out of the middle of the force-directed layout to make it more obvious.
plot(gr, layout=lay3, vertex.size=8)

how to make concentric circles layout in igraph (R)

I'm trying to create a special graph layout where 2 different types of nodes (based on their attribute) are placed on 2 different circles with different radius (concentric circles layout).
Here's a toy example where a graph with 10 nodes have an attribute (size). The goal is to place the nodes with size less than 5 on an inner circle, and the nodes with size greater than 5 on an outer circle:
g <- make_full_graph(10)
V(g)$size = V(g)
I couldn't find any such layout supported by igraph library. Does anyone know how to achieve this?
There is the layout_in_circle option if you only wanted one circle. You could apply that separately to each of your groups with something like this
layout_in_circles <- function(g, group=1) {
layout <- lapply(split(V(g), group), function(x) {
layout_in_circle(induced_subgraph(g,x))
})
layout <- Map(`*`, layout, seq_along(layout))
x <- matrix(0, nrow=vcount(g), ncol=2)
split(x, group) <- layout
x
}
Then you could plot with
plot(g, layout=layout_in_circles(g, group=V(g)>5))
It doesn't do anything special to try to make the edges pretty. But I guess the point is you can define whatever function you want to control the layout by returning a matrix of coordinates.

Plot tree with R

from a data.frame (or any other R object type), with 3 Columns: "Node, Parent and text", I'd like to plot a tree with rows from "Node" to "Parent" and "text" as label.
Can anyone suggest a good library to use and example code, if possible.
I've been looking at the igraph library, but all examples I could find plot trees with sequential numbers or letters as nodes and its not simple to set the tree layout.
Any help would be greatly appreciated
Thanks
EDIT:
Thanks guys for all your help, I really appreciate it.
Some extra comments, if you can help further
#md1630, I tried your suggestion but that's not what I'm looking for. The fist code plots the tree with the root on top and the arrows from root to leaf and the second corrects the arrows but inverts the tree. What I'd like is root on top and arrow from leafs to root (I understand that may not be a tree per say - but that's the requirement
#user20650 your solution looks correct but the image starts to get crowded as the number of nodes increase. Any idea on how to add more space between them?
#math Am I using the function you provided correctly? I called plot(layout.binary(g)) and got the result on the left. The one on the right is the output of plot(g)
upgrade comment
library(igraph)
# some example data
dat <- data.frame(parent=rep(letters[1:3], each=2),
node=letters[2:7],
text=paste0("lab", 1:6))
# create graph
g <- graph.data.frame(dat)
# plot
# layout.reingold.tilford gives a tree structure
# edge and vertx labels can be defined in the plot command or alternatively
# you can add them to the graph via V(g)$name and E(g($label assignments
plot(g, layout = layout.reingold.tilford,
edge.label=E(g)$text, vertex.label=paste0("v_lab",1:7))
EDIT re comment
If you want the direction to go from the leaves towards the root; you can first, get the tree layout coordinates from the more standard tree structure, and then reverse the edges.
# get tree layout coords
g <- graph.data.frame(dat)
lay = layout.reingold.tilford(g)
# redraw graph with edges reversed
g2 <- graph.data.frame(dat[2:1], vertices = get.data.frame(g, what="vertices"))
par(mar=rep(0,4), mfrow=c(1,2))
plot(g, layout=lay)
plot(g2, layout=lay)
You can use rgraphviz. Here's the code to plot the tree from a dataframe df with columns "Node, Parent and text". I didn't run this on my computer so there may be bugs. But roughly this is the idea:
source("http://bioconductor.org/biocLite.R")
biocLite("Rgraphviz")
library("Rgraphviz")
#first set up the graph with just the nodes
nodes<- unique(df['Node'])
gR <- new("graphNEL", nodes = nodes, edgemode = "directed")
#add edges for each row in df
for (j in (1:nrow(df))) {
gR <- addEdge(df[j,2], df[j,1], gR, 1)
}
#add text labels
nAttrs <- list()
z <- df['text']
nAttrs$label <- z
#plot
plot(gR, nodeAttrs = nAttrs) #you can specify more attributes here
You can use igraph to get a network with your data (supposing your dataframe is dd):
g = graph(t(dd[,2:1]))
V(g)$label = as.character(dd$text)
plot(g, layout=layout.binary)
I supposed your root (with no parents) is not in the dataframe, otherwise use dd[-1,2:1] instead.
If you want to have a tree, you can easily produce a layout, it is simply a function that takes a graph and return a matrix. For a binary tree :
layout.binary = function(graph) {
layout = c()
r_vertex = length(V(graph))
depth = ceiling(log2(r_vertex+1))
for (ii in 0:(depth-1)) {
for (jj in 1:min(2^ii, r_vertex)) {
layout = rbind(layout, c(ii, (2*(jj-1)+1)/(2^(ii+1))))
}
r_vertex = r_vertex - 2^ii
}
return(layout)
}
It will plot an horizontal tree, use c((2*(jj-1)+1)/(2^(ii+1)), ii) if you want it to be vertical.

Resources