0

I'm using igraph function to create a tripartite graph linking an instructor (A) to a list of students (B to R) who participate in the 3 clubs mentored by this instructor. Student have crossed memberships in 2 classes and may have the same gender. Finally, the width of the edges represents the amount of time the instructor and each student dedicate, on average, to each club in a given week.

Creating the graph is straightforward (my code is provided at below), but given that my list contains a total of 17 students (B to R), it would be better to present the graph in a horizontal way by placing the instructor (A) on top, the 3 club in the middle, with the 17 students (B to R) on the bottom. I suspect this is because I used layout_with_sugiyama() for my graph, but could anyone suggest an alternative to achieve my desired horizontal layout?

Below is my current R code for this graph:

rm(list=ls())
library(foreign)
library(igraph)
library(dplyr)

### create tripartite node list and pairwise attributes
time <- data.frame(student = c("A", "A", "A", "B", "B", "B", "C", "C", "C", "D", "D", "D", "E", "E", "E", "F", "F", "F", "G", "G", "G", "H", "H", "H", "I", "I", "I", "J", "J", "J", "K", "K", "K", "L", "L", "L", "M", "M", "M", "N", "N", "N", "O", "O", "O", "P", "P", "P", "Q", "Q", "Q", "R", "R", "R"),
club = c("club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3", "club 1", "club 2", "club 3"), 
hours = c(10, 3, 6, 5, 2, 1, 3, 3, 2, 7, 5, 11, 1, 0, 3, 8, 2, 2, 2, 2, 0, 5, 7, 11, 1, 0, 1, 0, 1, 3, 8, 9, 2, 0, 0, 3, 4, 3, 6, 3, 1, 0, 3, 1, 7, 0, 0, 1, 0, 1, 5, 1, 3, 3))

### convert time dataframe into a graph object
df <- time[!time$hours == 0, ]
g <- graph_from_data_frame(df, directed = FALSE)
E(g)$width <- log(E(g)$hours)

### parse the data into three disjoint sets, use different node shapes to distinguish them

A <- "A"
club <- c("club 1", "club 2", "club 3")

V(g)$type <- 1
V(g)[name %in% club]$type <- 2
V(g)[name %in% "A"]$type <- 3
shape <- c("circle", "square", "circle")
size <- c(12, 15, 12)

### label class affiliation (except node A; G, K, L, Q do not belong to any classes)
Class1 <- c("B", "C", "E", "H", "J", "O")
Class2 <- c("D", "F", "M", "P", "I", "N", "R")

V(g)$color[V(g)$name] = "white"
V(g)$color[V(g)$name %in% Class1] = "red"
V(g)$color[V(g)$name %in% Class2] = "orange"
V(g)$color[V(g)$name == "A"] = "olivedrab1"

### highlight same sex nodes
s <- c("B", "D", "F", "G", "H", "K", "M", "P", "Q")
s_col = ifelse(V(g)$name %in% s,'black','grey80')

layout = layout_with_sugiyama(g, layers=V(g)$type)
V(g)$vertex_degree <-  igraph::degree(g)



plot(g,
     layout=cbind(V(g)$type, layout$layout[,1]), edge.curved=0,
     vertex.color = V(g)$color,
     vertex.label.color = "black",
     vertex.label.cex = 0.45,
     vertex.size = size[V(g)$type],
     vertex.shape = shape[V(g)$type],
     vertex.frame.color = s_col,
     edge.color= "grey30",
     asp = 1.3,
     edge.width = E(g)$width
)

The above code generates this graph.

enter image description here

Yet my desired output should look something like this

enter image description here

Chris T.
  • 1,699
  • 7
  • 23
  • 45
  • It might be helpful to add an image of the desired output. My idea would be to switch columns a̶n̶d̶ ̶r̶e̶v̶e̶r̶s̶e̶ ̶t̶h̶e̶ ̶o̶r̶d̶e̶r̶ : `cbind(layout$layout[,1], V(g)$type)`. But that's not what you are looking for, is it? – Ben Nutzer Jul 19 '19 at 09:39
  • @Ben, thanks for the head up, I will add a picture of my desired output. – Chris T. Jul 19 '19 at 10:01

1 Answers1

1

Thanks for the clarification. This is more meant as a comment than an answer. If I run the code as proposed by yourself and commenters here:

plot(g,
     layout=cbind(V(g)$type, layout$layout[,1])[,2:1], edge.curved=0,
     vertex.color = V(g)$color,
     vertex.label.color = "black",
     vertex.label.cex = 0.45,
     vertex.size = size[V(g)$type],
     vertex.shape = shape[V(g)$type],
     vertex.frame.color = s_col,
     edge.color= "grey30",
     asp = 1.3,
     edge.width = E(g)$width
)

I get the following output:enter image description here

How is that different from what you trying to accomplish?

EDIT: One way to find a nicer looking distribution of the vertices on the x-axes is to use the cut function:

idx <- which(layout$layout[,2] == 2)  # find "club"-vertices
cuts <- layout$layout[idx, 1]         # find x-coords of vertices
cut(cuts, length(idx))                # cut into 3 intervals
layout$layout[idx,1] <- c(6,7.5,9)    # manually calculated even spans between x-coords

However, I am sure that there are better ways to do this.

Ben Nutzer
  • 1,082
  • 7
  • 15
  • Wow, thanks so much for this. With a slight modification of the code, your recommended solution gets exactly what I want. If you don't mind me asking, is there a way to increase the space between the second-level nodes (`club`) and the third-level nodes (`student`), because `club 2` and `club 3` overlap a bit. – Chris T. Jul 19 '19 at 13:30
  • Glad it worked! You could try to evenly spread out the vertices on the x-axes (see edit) – Ben Nutzer Jul 19 '19 at 14:14
  • Hi Ben, following your recommended approach, the student-level nodes are shrunk down to three nodes, corresponding to the positions of the three equally-spaced club nodes. I am thinking of reducing the length of edges, but this might change the layout of the graph in some unexpected ways. – Chris T. Jul 19 '19 at 14:51
  • Sorry, I did not intend to shrink/modify the positions of the nodes at the bottom at all. I am not sure what happened there. I wanted to have the same amount of space between nodes of the "club-type" and giving you a rough idea how to get there. Maybe the other arguments of the `layout_with_sugiyama` function could be helpful to you (e.g. `hgap`). Keep in mind that igraph.plotting is [rescaling](https://stackoverflow.com/questions/51194653/r-is-not-taking-the-parameter-hgap-in-layout-with-sugiyama) by default – Ben Nutzer Jul 19 '19 at 15:12
  • thanks again for your reply. I found that many users suggested using `layout.fruchterman.reingold()` function in lieu of other layouts to scale edge length by a user-specified scaling factor, but the `layout.fruchterman.reingold` does not have the `layers` argument, which might not fit my case. I will look for other layouts or syntax, particularly those that will help reduce the excessively long edge length in my graph. – Chris T. Jul 19 '19 at 16:27
  • In the end it depends on what you want to convey with your plot. That said, one could always start from this and tweak/scale the layout-matrix oneself: `plot(g,layout=cbind(V(g)$type*.2, layout$layout[,1]*-.05)[,2:1], rescale = F)` Happy coding! – Ben Nutzer Jul 19 '19 at 17:13
  • Thanks again @Ben, sorry I didn't notice this update earlier. With scaling method and subsetting tricks you recommended, I can now fine-tune the height as well as span of the tripartite graph and adjust them accordingly. Would you mind explaining (in the edit) what the subsetting code block `[,2:1]` really means? Thanks. – Chris T. Jul 21 '19 at 16:31