5 Null models
Jordi Bascompte & Fernando Pedraza
session 24/03/2022
5.1 Null models by hand
In this section you will perform a randomization by hand of the provided networks using the null models covered during our morning lecture. Use R to generate random numbers (using the runif()
function) and for your operations.
- Consider a network represented by the following adjacency matrix:
Provide a randomization of the matrix using:
a. the equifrequent null model
In your Rscript file, replace the ’x’s with the numbers you came up with in the randomization for the cell null model.
# -----------------------------------------------------------
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
# -----------------------------------------------------------
b. the probabilistic cell null model
In your Rscript file, replace the ’x’s with the numbers you came up with in the randomization for the cell null model.
# -----------------------------------------------------------
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
# -----------------------------------------------------------
- Consider now a second network represented by this adjacency matrix:
Provide three iterations of the swap algorithm:
first iteration
In your Rscript file, replace the ’x’s with the numbers you came up with in the randomization for the cell null model.
# -----------------------------------------------------------
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
# -----------------------------------------------------------
second iteration
In your Rscript file, replace the ’x’s with the numbers you came up with in the randomization for the cell null model.
# -----------------------------------------------------------
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
# -----------------------------------------------------------
third iteration
In your Rscript file, replace the ’x’s with the numbers you came up with in the randomization for the cell null model.
# -----------------------------------------------------------
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
#|-----------------------------------------------------------|
#| | | | | |
#| x | x | x | x | x |
#| | | | | |
# -----------------------------------------------------------
5.2 Computer part I
In this section we will use R code to run the null models covered during this morning’s lecture. We will focus on using the null models to evaluate the nestedness value of a single network. Ultimately, we want to know if the measured nestedness value of our network is significantly different from the nestedness values obtained from a set of random iterations. To do this, we will first download a network and compute it’s nestedness. Then, we’ll compute nestedness again using the three null models we went over. Finally, we will estimate the significance of nestedness using the z-score.
Before we begin we should load the packages we will use for this sesssion.
# Load packages
library(igraph)
library(data.table)
library(tidyverse)
library(Matrix)
library(rjson)
5.2.1 Downloading a network
First we will download the network called “M_PL_011” as you have done in the previous lectures.
# define the base url
<- "https://www.web-of-life.es/"
base_url # define the network name
<- 'M_PL_011'
net_name # define the full url associated with the endpoint that download networks interactions
<- paste0(base_url,"get_networks.php?network_name=",net_name)
json_url # and run it
<- jsonlite::fromJSON(json_url)
M_PL_011
# select the 3 relevant column and pass and create the igraph object
<- M_PL_011 %>% select(species1, species2, connection_strength) %>%
M_PL_011_graph graph_from_data_frame(directed = FALSE)
# convert network to bipartite
V(M_PL_011_graph)$type <- bipartite_mapping(M_PL_011_graph)$type
# convert network to incidence matrix
<- get.incidence(M_PL_011_graph) M_PL_011_matrix
5.2.2 The nestedness function
Now that we have downloaded our network, we measure its nestedness. We will be using the same function you used in previous sessions:
<- function(B){
compute_nestedness
# Get number of rows and columns
<- nrow(B)
nrows <- ncol(B)
ncols
# Compute nestedness of rows
<- 0
nestedness_rows for(i in 1:(nrows-1)){
for(j in (i+1): nrows){
<- sum(B[i,] * B[j,]) # Number of interactions shared by i and j
c_ij <- sum(B[i,]) # Degree of node i
k_i <- sum(B[j,]) # Degree of node j
k_j
if (k_i == 0 || k_j==0) {next} # Handle case if a node is disconnected
<- c_ij / min(k_i, k_j) # Overlap between i and j
o_ij
<- nestedness_rows + o_ij
nestedness_rows
}
}
# Compute nestedness of columns
<- 0
nestedness_cols for(i in 1: (ncols-1)){
for(j in (i+1): ncols){
<- sum(B[,i] * B[,j]) # Number of interactions shared by i and j
c_ij <- sum(B[,i]) # Degree of node i
k_i <- sum(B[,j]) # Degree of node j
k_j if (k_i == 0 || k_j==0) {next} # Handle case if a node is disconnected.
<- c_ij / min(k_i, k_j) # Overlap between i and j
o_ij
<- nestedness_cols + o_ij
nestedness_cols
}
}
# Compute nestedness of the network
<- (nestedness_rows + nestedness_cols) / ((nrows * (nrows - 1) / 2) + (ncols * (ncols - 1) / 2))
nestedness
return(nestedness)
}
5.2.3 Computing nestedness
Let’s compute the nestedness value for the network we downloaded before, as in previous sessions. Next, we will implement the null models.
# Calculate the nestedness value for our network and store it
<- compute_nestedness(M_PL_011_matrix)
nestedness_M_PL_011 nestedness_M_PL_011
## [1] 0.602071
5.2.4 Equifrequent null model
First, we’ll start with the equifrequent null model. This is the function we will use to run the model. It takes as arguments, the matrix you wish to use (\(d\)) and the number of replicates (\(t_{max}\)). The function performs \(t_{max}\) randomizations of \(d\) and computes the associated nestedness value of each randomisation. The function outputs a vector of length \(t_{max}\) with our nestedness estimates.
<- function(d,t_max){
equifrequent_model
<- nrow(d)
rows
<- ncol(d)
columns
<- 1
t
= rep(NA,t_max)
null_nest
while (t < t_max + 1) {
<- matrix(0, rows, columns)
B
<- nnzero(d)
number_ones
<- 0
count_ones
while (count_ones < number_ones) {
<- ceiling(runif(1, min = 0, max = rows))
x
<- ceiling(runif(1, min = 0, max = columns))
y
while (B[x,y] == 1) {
<- ceiling(runif(1, min = 0, max = rows))
x
<- ceiling(runif(1, min = 0, max = columns))
y
}
<- 1
B[x,y]
<- count_ones + 1;
count_ones
}
# Remove unconected species if present
= trim_network(B)
B = compute_nestedness(B)
null_nest[t]
<-t+1
t
}
# unlist results
<- unlist(null_nest)
null_nest
return(null_nest)
}
= function(network){
trim_network
# Removes any columns or rows without interactions
<- network[,colSums(network != 0) > 0]
network <- network[rowSums(network !=0) > 0,]
network return(network)
}
Let’s run the equifrequent null model with 100 replicates on our downloaded network. Run the command below in your Rscript file, make sure to add the required parameter values.
# Run the equifrequent model for our network with 100 replicates
<- equifrequent_model(d = , t_max = ) nestedness_equifrequent_list
Now that we have our nestedness estimates, we need to estimate the significance of the nested value we initially observed using the z-score. The formula to estimate z-scores is:
\[ z-score = \frac{observed\ nestedness - mean(nestedness)}{sd(nestedness)} \]
To calculate the z-score, we first have to obtain the mean and the standard deviation of the nestedness values we obtain from our null model. The mean and standard deviation are calculated in R with the mean
and sd
functions, respectively. Run the command below in your Rscript file, make sure to add the required parameter values.
# Compute mean for nestedness values estimated by null model
<- mean()
mean_nestedness # Compute sd for nestedness values estimated by null model
<- sd() sd_nestedness
Now we can calculate the z-score for the null model. Run the command below in your Rscript file, make sure to add the required parameter values.
# Compute z score following formula
<- z_score
Finally we can calculate the probability associated to the z-score using the pnorm
and setting the lower.tail
parameter to FALSE
. Run the command below in your Rscript file, make sure to add the required parameter values.
# Compute associated p value
<-pnorm(, lower.tail = FALSE)
p_val
# Print p value
print(p_val)
5.2.5 Probabilistic cell model
Now let’s move to the probabilistic cell model. This is the function we will use to implement it. As before, it takes as arguments, the matrix you wish to use (\(d\)) and the number of replicates (\(t_{max}\)). The function performs \(t_{max}\) randomizations of \(d\) and computes the associated nestedness value of each randomisation. The function outputs a vector of length \(t_{max}\) with our nestedness estimates.
<- function(d,t_max){
cell_model
<- nrow(d)
rows
<- ncol(d)
columns
<-1
t
= rep(NA,t_max)
null_nest
while (t <= t_max + 1){
<- matrix(0, rows, 1)
PR <- matrix(0, columns, 1)
PC <- matrix(0, rows, columns)
B
for (i in 1:rows){
<-0
number_onesfor (j in 1:columns){
if( d[i,j] == 1){
<-number_ones+1
number_ones
}
}<- number_ones/columns
PR[i]
}
for (j in 1:columns){
<-0
number_onesfor (i in 1:rows){
if( d[i,j] == 1){
<-number_ones+1
number_ones
}
}<- number_ones/rows
PC[j]
}
for (i in 1:rows){
for (j in 1:columns){
<- ( PR[i]+PC[j] )/2;
p <- runif(1)
r if( r < p ){
<- 1;
B[i,j]
}
}
}
= trim_network(B)
B
= compute_nestedness(B)
null_nest[t]
<-t+1
t
}
# unlist results
<- unlist(null_nest)
null_nest return(null_nest)
}
Repeat the same process we followed for the equifrequent null model, but now using the probabilistic cell model. You should do the following:
- Run the null model 100 times with using the
M_PL_011_matrix
. - Compute the mean and standard deviation of our null model estimates.
- Calculate the z-score.
- Obtain an associated p-value.
# Run the probabilistic cell model in your own Rscript
5.2.6 Swap model
Lastly, we focus on the swap model. This is the function we will use to compute the null model. As before, it takes as arguments, the matrix you wish to use (\(d\)) and the number of replicates (\(t_{max}\)). The function performs \(t_{max}\) randomizations of \(d\) and computes the associated nestedness value of each randomisation. The function outputs a vector of length \(t_{max}\) with our nestedness estimates.
= function(d,t_max){
swap_model
# Make sure we are working with a matrix
= as.matrix(d)
d
<- nrow(d)
rows
<- ncol(d)
columns
<- 0;
swap
<- t_max;
steps
= rep(NA,t_max)
swap_nest
while (swap < steps) {
<- ceiling(runif(1, min = 0, max = rows))
i <- ceiling(runif(1, min = 0, max = columns))
j
while (d[i,j] == 0) {
# for the first pair of numbers
<- ceiling(runif(1, min = 0, max = rows))
i <- ceiling(runif(1, min = 0, max = columns))
j
}# for the second pair of numbers
<- ceiling(runif(1, min = 0, max = rows))
q <- ceiling(runif(1, min = 0, max = columns))
z
# different row and column
while ((q == i) || (q == j)){
<- ceiling(runif(1, min = 0, max = rows))
q
}
# different row and column
while ((z == i) || (z ==j)){
<- ceiling(runif(1, min = 0, max = columns))
z
}
while (d[q,z] == 0) {
<- ceiling(runif(1, min = 0, max = rows))
q
while ((q == 1) || (q == j)) {
<- ceiling(runif(1, min = 0, max = rows))
q
}
<- ceiling(runif(1, min = 0, max = columns))
z
while ((z == i) || (z ==j)){
<- ceiling(runif(1, min = 0, max = columns))
z
}
}
if (d[i,z] == 0 && d[q,j] == 0) {
<- 0;
d[i,j] <- 0;
d[q,z] <- 1;
d[i,z] <- 1;
d[q,j]
= trim_network(d)
d
+1] = compute_nestedness(d)
swap_nest[swap<- swap + 1;
swap
}
}
# unlist results
<- unlist(swap_nest)
swap_nest return(swap_nest)
}
Repeat the same process we followed for the other two null models, but now using the swap model. You should do the following:
- Run the null model 100 times with using the
M_PL_011_matrix
. - Compute the mean and standard deviation of our null model estimates.
- Calculate the z-score.
- Obtain an associated p-value.
# Run the swap model in your own Rscript
5.3 Computer part II
In this final section, we will use the null models to test whether a set of pollination networks are significantly more or less nested than a set of seed dispersal networks. We will use null models to answer this question.
5.3.1 Code
To save some time, I have already downloaded the necessary networks. The first thing we need to is to load the networks. The seed dispersal networks are stored as entries in the list called seed_networks
which is found in the seed_networks.Rdata
file. The pollination networks are stored as entries in the list called pollination_networks
which is found in the pollination_networks.Rdata
file. We will begin by loading these two files.
# Load pollination networks
load("~/ecological_networks_2022/downloads/Data/03-24_null_models/pollination_networks.Rdata")
# Load seed networks
load("~/ecological_networks_2022/downloads/Data/03-24_null_models/seed_networks.Rdata")
Now that we have our networks, we can continue to run the null models, in this example we will use the cell null model. Let’s outline our worflow:
- First, compute the nestedness value of each of the networks in the
seed_networks
andpollination_networks
lists. - Next, run the cell null model with 20 replicates for each network in each type of network.
- Finally, run a t-test to determine whether there are differences between the two types of networks in relation to their standardized nestedness values.
Below you will find the code to perform the workflow outlined above. However, if you’re up for a challenge, feel free to write your own code. Use the p-value you obtain to answer the question at the bottom of this exercise.
#################################################
# Compute z-score for seed-disperser networks
# Create vector to store z-scores
<- rep(0,length(seed_networks))
z_score_vector_seed_nets
# Loop through the seed-disperser networks
for (network in 1:length(seed_networks)) {
# Set the current network
<- as.matrix(seed_networks[[network]])
current_seed_net # Compute nestedness for current network
<- as.numeric(compute_nestedness(current_seed_net))
current_nestedness # Run cell null model with current network and 20 replicates
<- cell_model(current_seed_net,20)
z_score_vector # Compute mean of zscores
<- mean(z_score_vector)
mean_z_score # Compute standard devation of zscores
<- sd(z_score_vector)
sd_z_score # Compute the zscore for the network
<- (current_nestedness - mean_z_score)/sd_z_score
z_score_seed_nets # Store zscore in vector
<- z_score_seed_nets
z_score_vector_seed_nets[network]
}
#################################################
# Compute z-score for pollination networks
# Create vector to store z-scores
<- rep(0,length(pollination_networks))
z_score_vector_pollination_nets
# Loop through the seed-disperser networks
for (network in 1:length(pollination_networks)) {
# Set the current network
<- as.matrix(seed_networks[[network]])
current_seed_net # Compute nestedness for current network
<- as.numeric(compute_nestedness(current_seed_net))
current_nestedness # Run cell null model with current network and 20 replicates
<- cell_model(current_seed_net,20)
z_score_vector # Compute mean of zscores
<- mean(z_score_vector)
mean_z_score # Compute standard devation of zscores
<- sd(z_score_vector)
sd_z_score # Compute the zscore for the network
<- (current_nestedness - mean_z_score)/sd_z_score
z_score_pollination_nets # Store zscore in vector
<- z_score_pollination_nets
z_score_vector_pollination_nets[network]
}
# Calculate t-test to compare vector of zscores
t.test(z_score_vector_seed_nets,z_score_vector_pollination_nets)