SOLVED: see my solution, added at the bottom of the post.
I am currently trying to create a circular bipartite graph using r, ideally with ggraph. I have a dataset of mating relationships between male and female frogs; here is a subset of that data:
Mother Father
M1 F1
M2 F2
M3 F2
M4 F3
M5 F4
M6 F4
M7 F4
and my goal is to visualize the number of partners that each organism has, something like this:
concentric bipartite graph showing relationships between mother and father organisms
So far, I've been able to make a linear version of the graph using igraph:
> library(tidyverse)
> library(igraph)
> library(ggraph)
> MyData <- read_csv("Bipartite.csv")
> Visualization <-graph.data.frame(MyData)
> bipartite.mapping(Visualization)
$res
[1] TRUE
$type
[1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
TRUE TRUE TRUE
[14] TRUE TRUE TRUE TRUE TRUE TRUE TRUE
> V(Visualization)$type <- bipartite_mapping(Visualization)$type
> plot(Visualization, layout = layout.bipartite)
Which produces this: Linear bipartite graph showing mating relationships between mothers and fathers; very tangled and hard to read as labels overlap
I'm stuck on converting this to a circular visualization. The two things I've tried are:
First, bringing the igraph object into ggraph and plotting it with the "circle" layout:
> ggraph(Visualization, layout = 'circle')
+ geom_edge_link()
+ geom_node_point()
But instead of two concentric rings, I just get one big circle, which isn't useful for displaying the data (and could have been done more simply just through ggraph, I know).
Single-ring visualization of mating data, with both mothers and fathers in a single ring
Second, I've tried bringing the igraph object into ggraph and setting circular to TRUE:
ggraph(Visualization, layout = 'bipartite', circular = TRUE)
+ geom_edge_link()
+ geom_node_point()
But I get the error message:
Error in layout_igraph_igraph(graph, layout, circular, ...) :
Circular layout only applicable to tree and DAG layout
Any thoughts on how to produce the visualization I'm looking for? I'm very new to both r and stackoverflow, so my apologies for any limits to the explanation of my problem. Thanks!
SOLUTION:
First, load the packages you'll need.
library(tidyverse)
library(tidygraph)
library(igraph)
library(ggraph)
Now, read in your node and edge data, and construct the graph object with ggraph.
Nodes <- read_csv("Nodes.csv")
Edges <- read_csv("Edges.csv")
Graph <- tbl_graph(nodes = Nodes, edges = Edges)
Next, create an object that represents your graph with a bipartite layout.
g <- Graph
V(g)$type <- bipartite_mapping(g)$type
Based on that bipartite graph object, we will find x and y coordinates for each node that arrange the nodes into concentric circles. First, use create_layout to make an object, "coords", that describes the current, bipartite layout. coords contains columns for the x and y coordinates of each node in the linear bipartite graph.
coords <- create_layout(g, layout = "bipartite") %>%
Next, we will use some trig to change the x and y coordinates from those of the linear bipartite graph to x and y coordinates that describe a circular graph. First, select the x and y columns.
select(x, y) %>%
This line creates a column, "theta", which basically divides the unit circle into slices based on the number of nodes we need to place along the edge of the circle, and assigns a slice to each node.
mutate(theta = x / (max(x) + 1) * 2 * pi,
The next lines create r values, the radii of the circles the nodes will be placed on, and find the x and y coordinates on a Cartesian plane that will place each node along the edge of the circles.
r = y + 1,
x = r * cos(theta),
y = r * sin(theta))
Now we create an entirely new graph layout, one that gives the node positions based on those circular coordinates we just created in coords.
my_graph <- create_layout(g, "manual", node.position = coords)
I also want to make sure that the labels for each node are rotated in a logical way, so that there is no label overlapping and all labels are easy to read. To do this, first make an object called "label_data" based on "coords".
label_data <- coords
Add a new column to label_data, called "angle", by converting the theta column from radians to degrees.
label_data$angle <- (label_data$theta)*(180/pi)
If we were to just use this angle value, then the labels in the third and fourth quadrants of the circle would be upside down. So we transform the angle values in these regions to flip them right side up.
label_data$plottingangle<-ifelse(label_data$angle < 270 & label_data$angle > 90, label_data$angle - 180, label_data$angle)
Finally, we just plot the graph object that has the layout we create, specifying the angle of rotation for the labels in the aesthetic of geom_node_text. Tada!
ggraph(my_graph) + geom_edge_link(edge_colour = "gray73") + geom_node_point(size = 0.8) + geom_node_text(aes(label = IndividualID, angle = label_data$plottingangle), size = 3) + scale_shape_manual(values = c(0, 19)) + coord_fixed() + theme_graph()