Edit or Exclude Small igraph Modules During Plotting in R - r

Is it possible to exclude or edit the modules being plotted so that modules with only one node do not have a module boundary? In case below, hide the module boundary around 9 and 13.
set.seed(3)
g <- barabasi.game(20, m=2, directed=FALSE)
eb <- cluster_edge_betweenness(g)
plot(eb, g, layout=layout_with_fr)

You can get at this using the mark.groups argument to plot.communities. First create the default grouping, but then eliminate the groups with only one member.
MG = lapply(unique(eb$membership), function(m) { which(eb$membership == m) })
MG[sapply(MG, length) == 1] = NULL
plot(eb, g, mark.groups=MG, layout=layout_with_fr)

Related

Writing a graph and keeping the vertex names

I'm trying to create a graph and then write it with the function write.graph (package igraph). Hence, I create the distance matrix
require(vegan)
data(dune)
dis <- vegdist(dune)
and then I define explicitly the rownames:
x <- c("dune1")
for (i in 1: 20){
n <- paste("dune", i, sep="")
x <- append(x, n)
}
rownames(dune) <- x
With the following procedure I create an undirected graph through the minimum spanning tree algorithm.
gg <- graph.adjacency(as.matrix(dis), weighted=TRUE)
gg_mst <- as.undirected(mst(gg))
At this point I want to represent it such to open it with pajek. In order to do that I use write.graph:
write.graph(gg_mst, "graph.net", format="pajek")
obtaining the following graph:
The names are lost!
Nevertheless, if i use the same function using a different format:
write.graph(gg_mst, "graph.txt", format="ncol")
I obtain a file keeping the rownames:
dune1 dune3 0.448275862068966
dune2 dune3 0.341463414634146
dune2 dune10 0.294117647058824
dune3 dune4 0.270588235294118
... ... ...
Is it a bug related to the use of write.graph with the format "pajek"?
You need to assign id attributes of the vertices in order to be able to have the vertices' names shown in a pajek viewer such as this one http://vlado.fmf.uni-lj.si/pub%20/networks/pajek/default.htm or gephi. Need to modify a few lines of your code like the following:
dis <- vegdist(dune)
x <- c()
for (i in 1: 20){
n <- paste("dune", i, sep="")
x <- append(x, n)
}
gg <- graph.adjacency(as.matrix(dis), weighted=TRUE)
gg_mst <- as.undirected(mst(gg))
V(gg_mst)$id <- x # assign the ids
write.graph(gg_mst, "graph.net", format="pajek")
Opening with pajek shows the vertex ids correctly.

R/Network Analysis - How to create edges by node's attributes

Dear Stackoverflow community,
I am currently using R to compile an affiliation network where nodes are companies/umbrella organisations and ties are defined as "member of". At the moment, my list is still small and I can create edges as follow, based on the position of the nodes (I use igraph):
g <- igraph::add_edges(g, c(1,51,
1,52,
1,53,
1,54))
However, I am adding new nodes and the final network will include at least 500 organisations. This means that the position of a node can change everytime I add a new one. Since I cannot redo the edges everytime I add a new node, is there a way I can add edges knowing the names of the nodes?
The names of the nodes are treated as an attribute, I tried to use the same command as above including names - as opposed to positions - but it did not work:
g <- igraph::add_edges(g, c(V(g)$name=="Company1", V(g)$name == "Umbrella2"))
Any suggestion on how I could create edges by specifying the names and not the position?
I believe you're looking for as.numeric(V(g)["Company1"]).
I would strongly advice against building your network structure in an R-script, though. Even for a small network, I would have inputed my data in an excel-file, create an R-script that reads the data as an edge-list and creates an igraph from it. That way, you can add your companies and organisations as you go with greater oversight of what data has actually gone in to your network, which I guess is what you're looking for in the first place. Doing that here would be out of bounds for the question though.
As for the adding-nodes-by-name, I wrote this example for you which I hope is pedagogical.
library(igraph)
# Make an empty Bipartite graph
g <- make_bipartite_graph(0, NULL, directed=TRUE)
g <- delete_vertices(g, 1)
# Create vertices of two different types: companies and umbrellas
g <- add_vertices(g, 5, color = "red", type=TRUE, name=paste("Company", 1:5, sep="_"))
g <- add_vertices(g, 2, color = "blue", type=FALSE, name=paste("Umbrella", 1:2, sep="_"))
# In a bipartate graph edges may only appear BETWEEN verticies of different types. Companies
# can belong to umbrellas, but not to each other.
# Look at the types:
ifelse(V(g)$type, 'Company', 'Umbrella') # true for companies, false for umbrellas
# Lets add some edges one by one. This is what I believe you're asking for in the question:
g <- add_edges(g, c(as.numeric(V(g)["Company_1"]), as.numeric(V(g)["Umbrella_1"])))
g <- add_edges(g, c(as.numeric(V(g)["Company_1"]), as.numeric(V(g)["Umbrella_2"])))
g <- add_edges(g, c(as.numeric(V(g)["Company_2"]), as.numeric(V(g)["Umbrella_1"])))
g <- add_edges(g, c(as.numeric(V(g)["Company_3"]), as.numeric(V(g)["Umbrella_1"])))
g <- add_edges(g, c(as.numeric(V(g)["Company_4"]), as.numeric(V(g)["Umbrella_2"])))
g <- add_edges(g, c(as.numeric(V(g)["Company_5"]), as.numeric(V(g)["Umbrella_2"])))
# Note that "Company_1" belongs to two umbrella organisations, as I assume your companies can:
plot(g)

How to assign different images to different vertices in an igraph?

I've looked at this question which seems similar but I am having difficulty getting it to work with my data.
Let's say my edgelist consists of the following:
P1 P2 weight
a b 1
a c 3
a d 2
b c 8
I use read.csv to collect the data, and then I convert it to a matrix. Then I graph it using the following:
g=graph.edgelist(x[,1:2],directed=F)
E(g)$weight=as.numeric(x[,3])
tkplot(g,layout=layout.fruchterman.reingold,edge.width=E(g)$weight)
And this returns a network with vertices and edges. I would like to replace vertex a with one image, vertex b with another and so on. I know how to apply the same image to all, but I want to apply a different image to every vertex. How do I go about doing this?
Edit: Adding reproducible code below as requested by user20650
# loading libraries
library(igraph)
library(rgdal)
# reading data from edgelist
x <- read.csv('edgelist', colClasses = c("character","character","numeric"), header=T)
# however, to replicate the data, use this line instead (Above line included just to show how I get the data)
x <- data.frame(P1 = c("a","a","a","b"), P2 = c("b","c","d","c"), weight = c(1,3,2,8))
# converting x to a matrix
x = as.matrix(x)
# preparing graph (getting rid of arrows, edge colors)
g = graph.edgelist(x[,1:2], directed=F)
E(g)$weight=as.numeric(x[,3])
E(g)[weight<=1]$color='dodgerblue'
E(g)[weight>=2&weight<=3]$color='dodgerblue1'
E(g)[weight>=4&weight<=7]$color='dodgerblue2'
E(g)[weight>=8&weight<=9]$color='dodgerblue3'
E(g)[weight==10]$color='dodgerblue4'
# plot the graph
# beginning of stuff I do not do anymore - the tkplot and adj lines below here I do not do anymore as they have been replaced with suggestions by user20650
tkplot(g, canvas.width=640, canvas.height=640, layout=layout.fruchterman.reingold, edge.width=E(g)$weight)
# just to make sure everything is correct, I was also verifying with this
adj <- get.adjacency(g, attr='weight')
# end of stuff I do not do anymore and I replaced it with what follows
# this is where I started placing user20650's lines (survcont1.png through survcont13.png are local files - 1 is the image for a, 2 for b, and so on)
url <- paste0("survcont", 1:13, ".png")
# my mapply which I guess I don't need anymore (I'm using rgdal because it is a library I already have that can read the images, am willing to use a better method if one exists)
mapply(readGDAL, url)
img <- lapply(url, png::readPNG)
set.seed(1)
adj <- matrix(sample(0:1,3^2,T,prob=c(0.8,0.8)),13,13)
g <- graph.adjacency(adj)
set.seed(1)
l <- layout.fruchterman.reingold(g)
l[,1]=(l[,1]-min(l[,1]))/(max(l[,1])-min(l[,1]))*2-1
l[,2]=(l[,2]-min(l[,2]))/(max(l[,2])-min(l[,2]))*2-1
# I added in the label so I can verify if the right vertices are showing up in the right places, I will remove in final version, also added in the edge weights
plot(g, layout=l, vertex.size=10, vertex.shape="square", vertex.color="#00000000", vertex.frame.color="#00000000", vertex.label="", edge.width=E(g)$weight)
# and finally plotting of the images
for(i in 1:nrow(l)) {
rasterImage(img[[i]], l[i, 1]-0.2, l[i, 2]-0.2, l[i, 1]+0.2, l[i, 2]+0.2)
}
I am guessing something is going wrong with the adj line and I'm somehow not linking my data to the images. I also don't get why I need to set.seed.
The images plot, which is great, but my original edge widths and colors do not and I am not sure image 1 is linking to a, 2 to b, and so on.
You can use Sacha's answer in the question you link to, to do this. If your images are stored in a list, just iterate through it to render the png files. I had to tweak the manual adjustment (from 0.1 to 0.2) to resize the image.
EDIT Using OP's data and adding edge weight and colour (deleted original post as this largely repeats it)
First need some images for the vertices.
# As i dont have access to your images i will download and use the
# images as before. We need four images as there are four vertices
# You dont need to do this bit exactly, all you need to do is read
# in your images into your R session, in a list called img
url <- paste0("http://pngimg.com/upload/cat_PNG", 1632:1635, ".png")
mapply(download.file, url, basename(url))
img <- lapply( basename(url), png::readPNG)
library(igraph)
# data
x <- data.frame(P1 = c("a","a","a","b"),
P2 = c("b","c","d","c"),
weight = c(1,3,2,8))
# this reads in the third column which you can then assign to be weights
g <- graph.data.frame(x, directed=FALSE)
# check
E(g)$weight
# edge colour - you might need to tweak this depending on your
# data, with the right argument etc
E(g)$colour <- as.character(cut(as.numeric(E(g)$weight),
breaks = c(0, 1, 3, 7, 9, 10),
labels=paste0("dodgerblue", c("", 1:4))))
# you need to set the seed as the layout function is an
# iterative process and not deterministic
set.seed(1)
l <- layout.norm(layout.fruchterman.reingold(g),
xmin=-1, xmax=1, ymin=-1, ymax=1)
par(mar=rep(0,4))
plot(g, layout=l, vertex.size=20, vertex.shape="square",
vertex.color="#00000000", vertex.frame.color="#00000000",
vertex.label="", edge.width=E(g)$weight, edge.color=E(g)$colour)
# and finally plotting of the images
for(i in 1:nrow(l)) {
rasterImage(img[[i]], l[i, 1]-0.2, l[i, 2]-0.2, l[i, 1]+0.2, l[i, 2]+0.2)
}

fixing multiple node coordinates when carring out clustering algorithm in igraph

Was wondering if the following is possible:
Currently, I have a subset of nodes in a graph, (graph A), which belongs in another separate and larger graph (graph B).
I would like to preserve the layout from graph B pertaining to this subset of nodes when running a layout generation algorithm on graph A. Could be any of the layout algorithms.
layout.circle(graph, params)
layout.sphere(graph, params)
layout.fruchterman.reingold(graph, ..., dim=2, params)
layout.kamada.kawai(graph, ..., dim=2, params)
layout.spring(graph, ..., params)
layout.reingold.tilford(graph, ..., params)
layout.fruchterman.reingold.grid(graph, ..., params)
layout.lgl(graph, ..., params)
layout.graphopt(graph, ..., params=list())
layout.mds(graph, dist=NULL, dim=2, options=igraph.arpack.default)
layout.svd(graph, d=shortest.paths(graph), ...)
You can use the minx, maxx, miny and maxy arguments of layout.fruchterman.reingold() or layout.kamada.kawai() to fix some vertices completely. These arguments specify vertex-specific lower and/or upper limits for the coordinates.
For the vertices you want to fix, set them exactly to the value to fix, and for the other vertices set minx to some small negative values (-Inf might work, too), and set maxx to some large value, (again, maybe Inf works, too).
You might need to use the rescale=FALSE argument in plot.igraph() to avoid rescaling the complete layout, for both the first and second graph.
Edit:
From the manual:
'minx' If not 'NULL', then it must be a numeric vector that gives
lower boundaries for the 'x' coordinates of the vertices. The
length of the vector must match the number of vertices in the
graph.
'maxx' Similar to 'minx', but gives the upper boundaries.
For example:
g <- graph.star(10, center=1)
minx <- rep(-Inf, vcount(g))
maxx <- rep( Inf, vcount(g))
minx[1] <- 0
maxx[1] <- 0
lay <- layout.fruchterman.reingold(g, minx=minx, maxx=maxx, miny=minx, maxy=maxx)
plot(g, layout=lay)
fixes the first vertex into (0,0) (might be modified by rescaling, to avoid rescaling, use rescale=FALSE in plot() and set the plotting limits).
The layout in igraph is defined as a n (number of nodes) by 2 matrix where the first column indicates the x-coordinate (on a arbitrary scale) and the second column the y-coordinate. You can store the result of any of these functions to obtain this matrix, and then pass that to the layout argument when you plot another graph.
library("igraph")
# A 3-node network:
g <- graph.adjacency(matrix(1,3,3))
# Obtain a layout:
l <- layout.circle(g)
# A different 3-node network:
g2 <- graph.adjacency(matrix(0,3,3))
# Plot second network with layout based on first network:
plot(g2,layout=l)
Edit
If you have a subset of a graph you can simply index this matrix. E.g.:
# A 3-node network:
g <- graph.adjacency(matrix(1,3,3))
# Obtain a layout:
l <- layout.circle(g)
# A 2-node subset:
g2 <- graph.adjacency(matrix(1,2,2))
# Plot second network with layout based on first network:
plot(g2,layout=l[1:2,])

Using igraph: community membership of components built by decompose.graph()

I would appreciate help with using decompose.graph, community detection functions from igraph and lapply.
I have an igraph object G with vertex attribute "label" and edge attribute "weight". I want to calculate community memberships using different functions from igraph, for simplicity let it be walktrap.community.
This graph is not connected, that is why I decided to decompose it
into connected components and run walktrap.community on each component, and afterwards add a community membership vertex attribute to the original graph G.
I am doing currently the following
comps <- decompose.graph(G,min.vertices=2)
communities <- lapply(comps,walktrap.community)
At this point I get stuck since I get the list object with the structure I cannot figure out. The documentation on decompose.graph tells only that it returns list object, and when I use lapply on the result I get completely confused. Moreover, the communities are numbered from 0 in each component, and I don't know how to supply weights parameter into walktrap.community function.
If it were not for the components, I would have done the following:
wt <- walktrap.community(G, modularity=TRUE, weights=E(G)$weight)
wmemb <- community.to.membership(G, wt$merges,steps=which.max(wt$modularity)-1)
V(G)$"walktrap" <- wmemb$membership
Could anyone please help me solve this issue? Or provide some
information/links which could help?
You could use a loop:
library(igraph)
set.seed(2)
G <- erdos.renyi.game(100, 1/50)
comps <- decompose.graph(G,min.vertices=2)
length(comps) # 2 components, in this example
for(i in seq_along(comps)) { # For each subgraph comps[[i]]
wt <- walktrap.community(comps[[i]], modularity=TRUE, weights=E(comps[[i]])$weight)
wmemb <- community.to.membership(comps[[i]], wt$merges,steps=which.max(wt$modularity)-1)
V(comps[[i]])$"walktrap" <- wmemb$membership
}
It is possible to do it with lapply and mapply, but it is less readable.
comps <- decompose.graph(G,min.vertices=2)
wt <- lapply( comps, function(u)
walktrap.community(u, modularity=TRUE, weights=E(u)$weight)
)
wmemb <- mapply(
function(u,v) community.to.membership(u, v$merges,steps=which.max(v$modularity)-1),
comps, wt,
SIMPLIFY=FALSE
)
comps <- mapply(
function(u,v) { V(u)$"walktrap" <- v$membership; u },
comps, wmemb,
SIMPLIFY=FALSE
)

Resources