Adding more nodes to the center of "star" layout - igraph - r

I would like to create a net-graph, that will have 2 nodes in the middle and the rest of the nodes surround them. The edges go from nodes "A" and "B" to the rest of nodes but they are not connected with each other.
I found that layout "star" from igraph package will suit me probably the most.
It is theoretically possible to add more nodes in the center of "star" (manual page) but it does not work for me, as regardless specifying two nodes in center parameter there is still only one.
#data
set.seed(1)
name <- LETTERS
a <- data.frame(from = "A", to = name)
b <- data.frame(from = "B", to = name)
sample <- rbind(a,b)
sample <- sample[-c(1,2,27,28), ] #please note removed edges between A-A, A-B, B-B, and B-A
#plot
g <- graph_from_data_frame(sample)
plot(g, layout = layout_as_star(g, center = V(g)[c("A", "B")]) )

I do not think that layout_as_star allows multiple centers.
The center argument on the help page that you refer to only
allows you to specify which one node is the center, not multiple
centers. So to get what you want, you need to do more of the
layout yourself. Here is one way to get a graph in the form that
you wanted. I use layout_as_star to lay out all of the nodes
except for one of the centers. But we do not want both "centers"
at the exact center of the circle or they would overlap. So I move
the center and make a spot for the second "center".
library(igraph)
s1 = make_star(25, mode="out")
V(s1)$name = LETTERS[-2]
s2 = make_star(25, mode="out")
V(s2)$name = LETTERS[-1]
LO1 = layout_as_star(s1)
TwoCenters = union(s1, s2)
LO2 = LO1
LO2[1,] = c(-0.2, 0)
LO2 = rbind(LO2, c(0.2,0))
plot(TwoCenters, layout= LO2)

Related

cluster walktrap returns three communities, but when plotting they are all on top of each other, with no visible clustering

I've been following documentation tutorials and even lecture tutorials step by step. But for some reason the output of my plot is like this:
The output doesn't make any sense to me. There clearly is no structure, or communities in this current plot, as you can see that the bigger circles are all overlapping. Shouldn't this, in this case, return only a single community? Additionally the modularity of my network is ~0.02 which would again, suggest there is no community structure. But why does it return 3 communities?
this is my code: (exactly same as in documentation, with different dataset)
m <- data.matrix(df)
g <- graph_from_adjacency_matrix(m, mode = "undirected")
#el <- get.edgelist(g)
wc <- cluster_walktrap(g)
modularity(wc)
membership(wc)
plot(wc,g)
my data set looks is a 500x500 adjacency matrix in the form of a csv, with a 1-500 column and index names corresponding to a person.
I tried understanding the community class and using different types of variables for the plot, e.g. membership(wc)[2] etc. My thought is that the coloring is simply wrong, but nothing Ive tried so far seems to fix the issue.
You can have inter-community connections. You're working with a graph of 500 nodes and they can have multiple connections. There will be a large number of connections between nodes of different communities, but if you conduct a random walk you're most likely to traverse connections between nodes of the same community.
If you separate the communities in the plot (using #G5W's code (igraph) Grouped layout based on attribute) you can see the different groups.
set.seed(4321)
g <- sample_gnp(500, .25)
plot(g, vertex.label = '', vertex.size = 5)
wc <- cluster_walktrap(g)
V(g)$community <- membership(wc)
E(g)$weight = 1
g_grouped = g
for(i in unique(V(g)$community)){
groupV = which(V(g)$community == i)
g_grouped = add_edges(g_grouped, combn(groupV, 2), attr=list(weight = 2))
}
l <- layout_nicely(g_grouped)
plot( wc,g, layout = l, vertex.label = '', vertex.size = 5, edge.width = .1)
Red edges are intercommunity connections and black edges are intracommunity edges

Order vertices within layers on tripartite igraph

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)

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()

Obtain layout from a graph object

This is a simple question, but I can't seem to find documentation for it. I have a graph object that was created by taking the union of two graphs. I would like to output the layout function that was created during the merge.
a <- barabasi.game(10)
b <- barabasi.game(20)
ab <- union(a,b)
Ideally, I'd like to visualize the union in a way that places subgraphs (a,b) in their own "space." Is there a default function in igraph for outputting the layout of a graph object?
For igraph, layouts are matrices of coordinates. If you call any layout method, you get a matrix:
loa <- layout.fruchterman.reingold(a)
lob <- layout.fruchterman.reingold(a)
If you assign these matrices to the layout graph attribute, igraph will use them automatically at plotting, or you can pass them to the plot method directly:
b$layout <- loa
plot(a)
plot(b, layout = lob)
If you take the union of two graphs, their layout attributes, if they have, won't be merged, but renamed to layout_1 and layout_2. If you want to keep the non-overlapping parts separated, and merge the layouts, I have this idea:
a <- barabasi.game(10)
b <- barabasi.game(20)
a$layout <- layout.norm(layout_with_fr(a), -1, 0, -1, 1) # each subgraph
b$layout <- layout.norm(layout_with_fr(b), 0, 1, -1, 1) # in their own space
V(a)$x <- a$layout[,1]
V(a)$y <- a$layout[,2]
V(b)$x <- b$layout[,1]
V(b)$y <- b$layout[,2]
V(a)$color <- 'blue'
ab <- union(a, b)
V(ab)$x <- vapply(seq(vcount(ab)),
function(vid){
ifelse(is.na(V(ab)$x_1[vid]),
V(ab)$x_2[vid],
V(ab)$x_1[vid])
}, 0.0)
V(ab)$y <- vapply(seq(vcount(ab)),
function(vid){
ifelse(is.na(V(ab)$y_1[vid]),
V(ab)$y_2[vid],
V(ab)$y_1[vid])
}, 0.0)
ab$layout <- cbind(V(ab)$x, V(ab)$y)
V(ab)$color[is.na(V(ab)$color)] <- 'yellow'
plot(ab, rescale = FALSE)
Here I created two layouts, one scaled to the west, other to the east half of the coordinate system. Then I merged the layouts, taking by default the coordinates from a, and from b if the vertex was not part of b. After making a new layout matrix from x and y coordinates, I plotted the graph with rescale = FALSE, so the coordinates remain unchanged.
Note: likely you want to merge your graphs not based on numberic vertex IDs, but by names. For this, create a name vertex attribute, and pass the byname = TRUE parameter to the union method.

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