0

Aloha guys!

I am trying to create a Minimum-Spanning-Tree with ggplot, because I want to make use of the ggplot2 and especially ggnetwork functions like geom_edgelabel() to receive a sophisticated, modifiable tree plot. What I couldn't accomplish is to annotate the distance values, from the distance matrix that underlies the Minimum-Spanning-Tree, to the edges.

First I have the distance matrix based on a matrix containing integers.

set.seed(1)
matrix <- matrix(sample(1:20, 10 * 20, replace = TRUE), nrow = 10, ncol = 20)
dist <- dist(matrix)

I then enter the distance matrix to the ape::mst() function to get a minimum-spanning-tree.

if (!require(ape))
  install.packages('ape')
library(ape)

ape_mst <- ape::mst(dist)

To convert the resulting mst object in a format that is accepted by ggplot() I passed it to igraph::graph.adjacency() to get an igraph object that contains edge information.

if (!require(igraph))
  install.packages('igraph')
library(igraph)

gr_adj <- graph.adjacency(ape_mst, "undirected")

As a last step I used ggnetwork() from the ggnetwork package to get a data frame that I can finally hand over to ggplot() and receive my tree.

if (!require(ggnetwork))
  install.packages('ggnetwork')
library(ggnetwork)

gg <- ggnetwork(gr_adj, arrow.gap = 0, layout = layout_with_fr(gr_adj))

ggplot(gg, aes(x = x, y = y, xend = xend, yend = yend)) + geom_edges() + geom_nodelabel(aes(label = name)) + geom_edgelabel(aes(label = name), size = 2)

Unfortunately the data frame doesn't save any edge information so I cant annotate, e.g. my desired distances. Using the package Claddis I can easily retrieve the distances corresponding to each edge of a minimum-spanning tree given a distance matrix, but I was not able to annotate them due to the structure of the objects that I created beforehand.

if (!require(Claddis))
  install.packages('Claddis')
library(Claddis)

find_minimum_spanning_edges(dist)

Would be happy if someone has a suggestion on how to solve this!

EDIT: With respect to @allan-cameron 's answer I modified my code as follows but it still doesn't allow to annotate the edge lengths in the way I like when using ape:mst instead igraph::mst (see image link below)

set.seed(1)

    dist <- dist(matrix(sample(1:20, 10 * 20, replace = TRUE), nrow = 10, ncol = 20))

    ape_mst <- ape::mst(dist) 

    gr_adj <- graph.adjacency(ape_mst, weighted = TRUE)

    ggraph(gr_adj, layout = "igraph", algorithm = "nicely") +
    geom_edge_link(aes(label = round(weight, 3)),
                 angle_calc = "along", vjust = -0.5) +
    geom_node_label(aes(label = name), size = 2) +
    scale_edge_width_continuous(range = c(0.5, 1.5), guide = "none") +
    theme_graph()

    find_minimum_spanning_edges(dist)

tree plot

edges by find_minimum_spanning_edges

The edge annotations should correspond to the edge length like in this plot which I am trying to reproduce:

target plot

1 Answer 1

1

You can do all of this in igraph and ggraph

library(igraph)
library(ggraph)

set.seed(2)

matrix(sample(1:20, 10 * 20, replace = TRUE), nrow = 10, ncol = 20) |>
  dist() |>
  as.matrix() |>
  graph.adjacency(weighted = TRUE) |>
  mst() |>
  ggraph(layout = "igraph", algorithm = "kk", weights = weight) +
  geom_edge_link(aes(width = weight, label = round(weight, 1)),
                 angle_calc = "along", vjust = -0.5) +
  geom_node_point(size = 10, fill = "white", shape = 21) +
  geom_node_text(aes(label = name)) +
  scale_edge_width_continuous(range = c(0.5, 1.5), guide = "none") +
  theme_graph() +
  coord_equal()

enter image description here

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for the suggestion, that definitely opens up new possibilities for me. But look at this: I want to use the ape::mst() function rather than igraph::mst because it produces a more desired look. ape::mst() has no inherent weight attributes in the mst object. Claddis::find_minimum_spanning_edges() wrappes the ape::mst() function and returns the desired edge values, which I wanted to annotate to the tree in a workaround. Hence, when I hand the result of ape::mst() to ggraph(), all the weights are equal to 1 and annotated accordingly.
I don't understand @infinity-a11y. All ape::mst does is creates an unweighted adjacency matrix of the mst. This is equivalent to creating the correctly weighted graph in igraph, finding its mst, then calling igraph::as_adjacency_matrix, which drops all the weights. I can't figure out why you want to do this. Similarly, find_minimum_spanning_edges just gives a vector of the weights of the mst that are already included on my diagram above (except I have rounded them to 1 decimal place). Can you explain what is wrong with my approach, and what you mean by ape::mst has a better look?
Actually I am trying to reproduce i.sstatic.net/1oPFV.png and I was not aware that the weight of an edge might not be the same as the length. In the picture you see that the annotated numbers on the edges correspond to the length which is not given in your approach.
@infinity-a11y it's not always possible to specify arbitrary edge lengths on a given graph, since it may not be geometrically possible. In this case, where the graph is acyclic it is possible, by specifying the kk layout and passing the weights. See my update showing the edge lengths proportional to the weights. (It's not obvious because all your lengths are very similar). Is that what you wanted?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.