rggplot2ggraph

Color edge links in ggraph based on specific nodes


I am trying to generate a bipartite graph where I color specific links based on certain nodes (i.e., plant species names). So far, I have been able to color the nodes of interest, but not their corresponding links.

Below, I provide a minimal reproducible example where I show some species in yellow, and I would like all the links stemming from those species to be colored as well (for instance, also in yellow). Up to now, I have only been able to color the links based on their weights, but not according to any other kind of attribute.

I am finding it quite difficult to access this type of information — possibly because the way I’m manipulating the ggraph object isn’t appropriate for this purpose.

Below is a minimal reproducible example:

library(dplyr)
library(ggraph)

# Set seed for reproducibility
set.seed(123)

# Number of plant and pollinator species
n_plants <- 10
n_pollinators <- 10

# Create a random binary matrix (1 = interaction, 0 = no interaction)
net <- matrix(sample(0:1, n_plants * n_pollinators, replace = TRUE, prob = c(0.7, 0.3)),
                             nrow = n_plants, ncol = n_pollinators)

# Name rows and columns
rownames(net) <- paste0("Plant_", 1:n_plants)
colnames(net) <- paste0("Pollinator_", 1:n_pollinators)


#Network 1
# Calculate row and column degrees
row_degrees <- rowSums(net > 0)
col_degrees <- colSums(net > 0)

# Order rows and columns
ordered_rows <- order(row_degrees, decreasing = TRUE)
ordered_columns <- order(col_degrees, decreasing = TRUE)

# Reorder matrix based on degrees
ordered_net <- net[ordered_rows, ordered_columns]
rownames(ordered_net) <- rownames(net)[ordered_rows]
colnames(ordered_net) <- colnames(net)[ordered_columns]

# Create weights tibble
weights <- tibble(
  weight = c(col_degrees[ordered_columns], row_degrees[ordered_rows]),
  name = c(colnames(ordered_net), rownames(ordered_net)),
  trophic = c(rep("2", length(col_degrees[ordered_columns])), rep("1", length(row_degrees[ordered_rows]))) # Trophic levels
)

# Create layout for the bipartite graph
layout <- create_layout(ordered_net, "bipartite") %>%
  mutate(position = ifelse(y > 0, "upper", "lower"))


layout <- left_join(layout, weights, by = "name")


# Organize data for better visualization
layout_false = layout %>%
  filter(type=="FALSE")
layout_true = layout %>%
  filter(type=="TRUE")

x_ordered_false = arrange(layout_false,x)
x_ordered_true = arrange(layout_true, x)
n_polls=layout_false %>% nrow(.)
n_plants=layout_true %>% nrow(.)
max_counts = max(c(n_polls, n_plants))

result_sequence_polls <- seq(from = 1, to = max_counts, length.out = n_polls)
result_sequence_plants <- seq(from = 1, to = max_counts, length.out = n_plants)

layout_false$x = result_sequence_polls
layout_true$x = result_sequence_plants

layout = bind_rows(layout_false, layout_true)

#Create species vector to color code those
spp_vector = c("Plant_9", "Plant_7", "Plant_8")


#Create new col with colors
layout <- layout %>%
  mutate(node_color = case_when(
    name %in% spp_vector ~ "yellow",
   TRUE ~ "black"))

# Plot the ggraph
p1 = ggraph(layout) +
  geom_edge_link2(aes(edge_width = weight, color= from)) +
  geom_node_point(aes(shape = trophic, size = weight, color= node_color)) +  # Use 'weight' for size
  geom_node_text(aes(label = name,
                     hjust = ifelse(position == "upper", 0, 1)),
                 size = 1.5, angle = 90, vjust = 0.4, colour = "black") +
  scale_colour_identity() +
  scale_shape_manual(values = c(19, 19)) +
  scale_size_continuous(range = c(0.5, 4)) +
  scale_edge_width(range = c(0.5, 2.5)) +
  theme(
    panel.grid = element_blank(),
    panel.background = element_blank(),
    panel.border = element_blank(),
    plot.background = element_blank(),
    legend.position = "none",
    plot.margin = margin(t = 80, r = 10, b = 80, l = 10),
    plot.title = element_text(vjust=25)
  ) +
  coord_cartesian(expand = FALSE, clip = "off") 
  
p1

enter image description here


Solution

  • The original data passed to ggraph gets transformed by geom_edge_link, i.e. by default get_edges() is called to extract the edges data from the graph object or the data frame in your case. As a result the columns get renamed but the information is still available, e.g. the info on the from node are stored in columns with prefix node1. and with prefix node2. for the to node.

    Hence, you can use these column names to map on aesthetics. Also note the use of I() or AsIs (instead of scale_edge_colour_identity).

    library(ggraph)
    
    head(get_edges()(layout))
    #>   from to weight direction circular x y xend yend node1.x node1.y node1.type
    #> 1    1 12      1     right    FALSE 1 1    2    0       1       1      FALSE
    #> 2    1 15      1      left    FALSE 1 1    5    0       1       1      FALSE
    #> 3    1 16      1     right    FALSE 1 1    6    0       1       1      FALSE
    #> 4    1 18      1      left    FALSE 1 1    8    0       1       1      FALSE
    #> 5    2 11      1     right    FALSE 2 1    1    0       2       1      FALSE
    #> 6    2 12      1     right    FALSE 2 1    2    0       2       1      FALSE
    #>   node1.name node1..ggraph.orig_index node1.circular node1..ggraph.index
    #> 1    Plant_1                        1          FALSE                   1
    #> 2    Plant_1                        1          FALSE                   1
    #> 3    Plant_1                        1          FALSE                   1
    #> 4    Plant_1                        1          FALSE                   1
    #> 5    Plant_4                        2          FALSE                   2
    #> 6    Plant_4                        2          FALSE                   2
    #>   node1.position node1.weight node1.trophic node1.node_color node2.x node2.y
    #> 1          upper            4             1            black       2       0
    #> 2          upper            4             1            black       5       0
    #> 3          upper            4             1            black       6       0
    #> 4          upper            4             1            black       8       0
    #> 5          upper            4             1            black       1       0
    #> 6          upper            4             1            black       2       0
    #>   node2.type   node2.name node2..ggraph.orig_index node2.circular
    #> 1       TRUE Pollinator_4                       12          FALSE
    #> 2       TRUE Pollinator_2                       15          FALSE
    #> 3       TRUE Pollinator_3                       16          FALSE
    #> 4       TRUE Pollinator_8                       18          FALSE
    #> 5       TRUE Pollinator_1                       11          FALSE
    #> 6       TRUE Pollinator_4                       12          FALSE
    #>   node2..ggraph.index node2.position node2.weight node2.trophic
    #> 1                  12          lower            4             2
    #> 2                  15          lower            3             2
    #> 3                  16          lower            3             2
    #> 4                  18          lower            2             2
    #> 5                  11          lower            4             2
    #> 6                  12          lower            4             2
    #>   node2.node_color edge.id
    #> 1            black       1
    #> 2            black       2
    #> 3            black       3
    #> 4            black       4
    #> 5            black       5
    #> 6            black       6
    
    p1 <- ggraph(layout) +
      geom_edge_link(aes(edge_width = weight, color = I(node1.node_color))) +
      geom_node_point(aes(shape = trophic, size = weight, color = node_color)) + # Use 'weight' for size
      geom_node_text(
        aes(
          label = name,
          hjust = ifelse(position == "upper", 0, 1)
        ),
        size = 1.5, angle = 90, vjust = 0.4, colour = "black"
      ) +
      scale_colour_identity() +
      scale_shape_manual(values = c(19, 19)) +
      scale_size_continuous(range = c(0.5, 4)) +
      scale_edge_width(range = c(0.5, 2.5)) +
      theme(
        panel.grid = element_blank(),
        panel.background = element_blank(),
        panel.border = element_blank(),
        plot.background = element_blank(),
        legend.position = "none",
        plot.margin = margin(t = 80, r = 10, b = 80, l = 10),
        plot.title = element_text(vjust = 25)
      ) +
      coord_cartesian(expand = FALSE, clip = "off")
    
    p1