Simulating Phylogenetically Structured Networks*

[This article was first published on R Code – Geekcologist, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

*Contributed by Vinicius Bastazini and Vanderlei Debastiani

“Nothing in biology makes sense except in the light of evolution.” (Dobzhansky 1973)

“Nothing in evolutionary biology makes sense except in the light of ecology.” (Grant and Grant 2008)

“Nothing in evolution or ecology makes sense except in the light of the other. ” (Pelletier et al. 2009)

Understanding the interplay between evolutionary and ecological processes is crucial for describing patterns of species diversity in space and time. Historically, community ecology and evolutionary biology have followed separate paths. However, in the past decades, the inclusion of phylogenetic data has expanded the toolkit for community ecologists, shedding light on large range of biological processes such as species interactions, community assembly and disassembly, species distribution, and ecosystem functioning. Phylogenetic information has also provided a sort of  “temporary” solution for missing data in ecological databases.

This novel and integrative approach has given rise to Ecophylogenetics, an interdisciplinary field of study that combines principles from both ecology and phylogenetics. It focuses on understanding the evolutionary relationships among species in the context of ecological processes and patterns. 

In this context, phylogenetically structured models have thus revolutionized our understanding of ecological phenomena, by providing a framework to explore and interpret the patterns of species evolution, diversity, and interactions. This is especially relevant in Network Ecology, a branch of ecology interested in investigating the structure, function, and evolution of ecological systems using network models and analyses. Phylogenetic approaches have been incorporated into the study of ecological networks formed by interacting species, in order to describe distinct ecological phenomena, such as network assembly and disassembly (see a related post here),  and its ability to cope with cascading effects such as species loss (see a related post here). 

Examples of some of the possible eco-evolutionary dynamics of bipartite mutualistic networks. Species interactions are represented by black cells in the bi-adjacency matrices along with their evolutionary trees and traits ( blue and red circles). Trait values are represented by circle size.

In the following example, we will demonstrate how to simulate phylogenetically structured networks using  R.

In this example we will simulate ultrametric phylogenetic trees for two distinct sets of species, such as consumers ( e.g., predator, pollinator or seed disperser) and resources (prey, flowering plants), following a uniform birth–death process, which reflects the underlying evolutionary dynamics of taxa. Additionally, we will simulate the evolution of a single trait by employing a family of power transformations to the branch lengths of these simulated phylogenetic trees. The choice of this transformation parameter defines the tempo and mode of trait evolution. Networks of interacting species will be generated using a single-trait complementarity model (Santamaria and Rodriguez-Girones 2007). According to the single-trait complementarity model, each species in the network is represented by a mean trait value and its variability, and a pair of species is more likely to interact if their trait values overlap. You can find further information on this sort of simulation  in Debastiani et al. (2021a, 2021 b) and Bastazini et al. (2022).

#### Load R packages
require(ape)
require(geiger)
require(plotrix)

#### Define phylo_web, a function  that generates phylogenetic structured networks
# The inputs are: number of species in each set of interacting species and the level of phylogenetic signal

phylo_web = function(n_spe_H, n_spe_L, power_H, power_L) {
  
  # Generate a phylogenetic tree for consumer species (H)
  tree_H = geiger::sim.bdtree(b = 0.1, d = 0, stop = "taxa", n = n_spe_H, extinct = FALSE)
  for (y in 1:length(tree_H$edge.length)) {
    if (tree_H$edge.length[y] <= 0)
      tree_H$edge.length[y] = 0.01
  }
  
  # Generate a phylogenetic tree for resource species  (L)
  tree_L = geiger::sim.bdtree(b = 0.1, d = 0, stop = "taxa", n = n_spe_L, extinct = FALSE)
  for (y in 1: length(tree_L$edge.length)) {
    if (tree_L$edge.length[y] <= 0)
      tree_L$edge.length[y] = 0.01
  }
  
  # Assign labels to the tips of the trees
  tree_H$tip.label = sprintf("H_%.3d", 1:length(tree_H$tip.label))
  tree_L$tip.label = sprintf("L_%.3d", 1:length(tree_L$tip.label))
  
  # Generate trait data for consumers
  trait_H = matrix(NA, nrow = n_spe_H, ncol = 1)
  for(n in 1:1) {
    trait_H[, n] = ape::rTraitCont(compute.brlen(tree_H, power = power_H), model = "BM")
  }
  trait_H[,1] = plotrix::rescale(trait_H[,1], newrange = c(0, 1))
  rownames(trait_H) = tree_H$tip.label
  colnames(trait_H) = sprintf("Tr_H_%.3d", 1:ncol(trait_H))
  
  # Generate trait data for resources
  trait_L = matrix(NA, nrow = n_spe_L, ncol = 1)
  for(n in 1:1) {
    trait_L[, n] = ape::rTraitCont(compute.brlen(tree_L, power = power_L), model = "BM")
  }
  trait_L[,1] = plotrix::rescale(trait_L[,1], newrange = c(0, 1))
  rownames(trait_L) = tree_L$tip.label
  colnames(trait_L) = sprintf("Tr_L_%.3d", 1:ncol(trait_L))
  
  # Generate bi-adjacency interaction  matrix based on trait differences and niche overlap
  d_H = matrix(runif(n_spe_H, max = 0.25), nrow = n_spe_H, ncol = 1)
  d_L = matrix(runif(n_spe_L, max = 0.25), nrow = n_spe_L, ncol = 1)
  web = matrix(NA, nrow = n_spe_L, ncol = n_spe_H)
  for(i in 1:n_spe_L) {
    for(j in 1:n_spe_H) {
      II = abs(trait_H[j] - trait_L[i])
      III = 0.5 * (d_H[j] + d_L[i])
      web[i,j] = ifelse(II < III, yes = 1, no = 0)
    }
  }
  colnames(web) = tree_H$tip.label
  rownames(web) = tree_L$tip.label
  
  # Ensure there are no species with no interactions
  z_H = which(colSums(web) == 0)
  if (length(z_H) > 0) {
    for(i in 1:length(z_H)) {
      web[sample(1:n_spe_L, size = 1), z_H[i]] = 1
    }
  }
  z_L = which(rowSums(web) == 0)
  if (length(z_L) > 0) {
    for(i in 1:length(z_L)) {
      web[z_L[i], sample(1:n_spe_H, size = 1)] = 1
    }
  }
  
  # Return the results as a list
  RES = list(tree_H = tree_H, tree_L = tree_L, trait_H = trait_H, trait_L = trait_L, web = web)
  return(RES)
}

#### Generate networks  using the phylo_web function

# Here, we are  generating a network formed by 10 resource species (e.g., flowers) , 10 consumers (pollinators),  simulating a Brownian motion process for trait evolution:

n_spe_HA = 10 # Number of consumer species 
n_spe_LA = 10 # Number of resource species 
power_HA = 1 # Grafen's rho of consumer species
power_LA = 1  # Grafen's rho of resource species

web = phylo_web(n_spe_HA, n_spe_LA, power_HA, power_LA)

# The result is list containing  a bi-adjacency matrix for two sets of interacting species,  their phylogenies and trait values.

web

Now we can plot the bi-adjacency matrix along with their evolutionary trees and traits. Trait values are represented by circle size.

# Create and arrange the plot for the phylogenetic trees, species traits, and the network

par(mar = c(0.4, 0.4, 0.4, 0.4))
layout(matrix(c(0, 0, 1, 0, 0, 2, 3, 4, 5), nrow = 3, ncol = 3, byrow = TRUE), 
       widths = c(0.4, 0.4, 1), 
       heights = c(0.4, 0.4, 1))


# Plot
ape::plot.phylo(web$tree_H, show.tip.label = FALSE, 
     direction = "downwards")
plot(web$trait_H[,1], rep(1, times = length(web$trait_H[,1])), 
     xlab = "", ylab = "", 
     xaxt = "n", yaxt = "n", 
     type = "n", bty = "n",
     ylim = c(0.9, 1.1), xlim = c(1, length(web$trait_H[,1])))
points(1:length(web$trait_H[,1]), rep(1, times = length(web$trait_H[,1])),
       cex = web$trait_H[,1]+1, pch = 19)
ape::plot.phylo(web$tree_L, show.tip.label = FALSE,
     direction = "rightwards")
plot(web$trait_L[,1], rep(1, times = length(web$trait_L[,1])),
     xlab = "", ylab = "", 
     xaxt = "n", yaxt = "n", 
     type = "n", bty = "n",
     xlim = c(0.9,1.1), ylim = c(1, length(web$trait_L[,1])))
points(rep(1, times = length(web$trait_L[,1])), 1:length(web$trait_L[,1]),
       cex = web$trait_L[,1]+1, pch = 19)
plotrix::color2D.matplot(web$web,
                cs1 = c(1, 0), cs2 = c(1, 0), cs3 = c(1, 0), 
                yrev = FALSE,
                ylab = "", xlab = "", 
                xaxt = "n", yaxt = "n",
                border="white", axes = FALSE)

Et voilà!!!

To leave a comment for the author, please follow the link and comment on their blog: R Code – Geekcologist.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)