We want to build a species-rich metacommunity and a local, small community that is a sample of the metacommunity, according to the neutral model.
We will use letters to build the species names, e.g. “AAC”. Combining 3 letters, we’ll have \(26^3 = 17576\) species names, i.e. more than the number of species in Amazonia (ter Steege et al. 2013).
To build the names, we use this code:
library("tidyverse")
# LETTERS is the vector of capital letters A:Z
# outer() combines the first two args by FUN
LETTERS %>%
outer(LETTERS, FUN = paste0) %>%
# add a third letter
outer(LETTERS, FUN = paste0) ->
species_names
We’ll draw random log-series distributions.
#' Random draw of a log-series distribution
#' The complete set of functions (including density, distribution function and quantiles)
#' can be found in package _sads_ but this implementation of the random generation is much faster.
#'
#' @param n The Number of species
#' @param size The size of the distribution.
#' @param alpha Fisher's alpha, that links the number of species to that of individuals
#' @param show_progress Show a progress bar during long computations
#'
#' @return The number of individuals per species
rlseries <- function(
n,
size,
alpha,
show_progress = TRUE) {
# adapted from Dan Lunn, http://www.stats.ox.ac.uk/~dlunn/BS1_05/BS1_Rcode.pdf
if (size + 1 == size || size - 1 == size) {
# Too large values to deal with as integers
stop("size is too large to simulate the distribution.")
}
# Fisher's x
x <- size / (size + alpha)
# Draw random numbers between 0 and 1 that are quantiles of the cumulative function
u <- sort(stats::runif(n))
# Prepare the corresponding number of abundances
abd <- numeric(n)
# Prepare a progress bar
if (show_progress & interactive()) {
cli::cli_progress_bar(
total = n,
format = "{cli::pb_spin} Drawing log-series values {cli::pb_current}/{cli::pb_total}"
)
}
# Number of drawn values +1
next_value <- 1
# k is the abundance
k <- 1
# Calculate the probability at k=1
P <- -x / log(1 - x)
# Store it in the cumulative function
F <- P
# Repeat while all values are not drawn
while (next_value <= n) {
if (F > u[next_value]) {
# Retain k as the next value
abd[next_value] <- k
# Increment the next value
next_value <- next_value + 1
# Update the progress bar
if (show_progress & interactive()) cli::cli_progress_update()
} else {
# Probability at k+1 obtained from that at k
P <- P * k * x / (k + 1)
# Increment the cumulated probability
F <- F + P
# Increment k
k <- k + 1
}
}
return(abd)
}
We build a dataset according to BCI characteristics (Hubbell 2001): \(\theta=50\) and \(m=0.1\). The metacommunity follows a log-series distribution (Fisher, Corbet, and Williams 1943). We build it with a billion trees. This size is arbitrary: the important parameter is \(\theta\).
mc_size <- 1E9
# theta is also Fisher's alpha
alpha <- 50
# Number of species (Fisher, 1943)
(-alpha * log(alpha / (mc_size + alpha))) %>%
# integer needed
as.integer() ->
n_species
The metacommunity contains 840 species. We simulate their abundances.
library("cli")
# Prepare a progress bar
mc_abundances <- rlseries(n = n_species, size = mc_size, alpha = alpha)
# Set the species names
names(mc_abundances) <- species_names[seq_along(mc_abundances)]
# Declare it is a vector of abundances (package entropart)
library("entropart")
mc_abundances %>%
as.AbdVector() ->
mc_abundances
# Range
summary(mc_abundances)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1 44 4660 1246385 224242 61304615
# Rank-Abundance Curve
autoplot(mc_abundances, Distribution = "lseries")
We draw a local community from the metacommunity.
# Choose the size of the community
c_size <- 256
# Draw a random community according to the abundances of the metacommunity
rmultinom(1, size = c_size, prob = mc_abundances) %>%
# We need a vector, not a matrix
as.vector() ->
c_abundances
# Give the species a name
names(c_abundances) <- names(mc_abundances)
library("entropart")
# Clean-up the data
c_abundances %>%
# Eliminate species with 0 individual
subset(c_abundances > 0) %>%
# Declare a vector of abundances
as.AbdVector() ->
c_abundances
# Show the RAC
autoplot(c_abundances)
We set an arbitrary position to the trees of the local community just to visualize it.
library("dbmss")
# Build a dataframe for the marks of a point pattern
marks <- data.frame(
# Species. Each line of the dataframe is an individual
PointType = rep(names(c_abundances), times = c_abundances),
# Size of the points
PointWeight = rep(1, c_size)
)
# Draw random locations in a square window to obtain a point pattern
c_size %>%
runifpoint() ->
c_ppp
# Add the marks to the point pattern
marks(c_ppp) <- marks
# Make it a wmppp object (package dbmss) to make nice plots
c_wmppp <- as.wmppp(c_ppp)
# Set the factor levels of the point types identical to those of the metacommunity
# so that species of the metacommunity can be used in the local community
marks(c_wmppp)$PointType <- factor(
marks(c_wmppp)$PointType,
levels = names(mc_abundances)
)
# Map the point pattern
autoplot(c_wmppp) +
labs(colour = "Species", size = "Size")
The drift model is as follows: at each step, a tree dies and is replaced by a new tree from the species of the community, with respect to their probabilities.
#' One-step evolution of a community without migration
#'
#' @param community A wmppp object (package dbmss) where points are trees.
#' @param show If TRUE, a map of the evolved community is produced.
#'
#' @return A wmppp object with the modified community.
evolve_drift <- function(community, show = FALSE) {
# Choose a tree
focus_tree <- sample(seq_len(community$n), size = 1)
# Species of the survivors
survivors_sp <- marks(community)$PointType[-focus_tree]
# Draw the species of the new tree
marks(community)$PointType[focus_tree] <-
survivors_sp[sample(seq_along(survivors_sp), size = 1)]
if (show) {
# Copy the community
community_to_plot <- community
# Increase the size of the modified tree
marks(community_to_plot)$PointWeight[focus_tree] <- 2
# Plot the community (print is mandatory inside a function)
print(autoplot(community_to_plot))
}
# return
return(community)
}
# Test the function
c_wmppp %>%
evolve_drift(show = TRUE)
## Marked planar point pattern: 256 points
## Mark variables: PointWeight, PointType
## window: rectangle = [0, 1] x [0, 1] units
Drift causes the local extinction of all species but one.
library("cli")
# Copy the community to modify it
the_community <- c_wmppp
# Number of species
richness <- length(unique(marks(the_community)$PointType))
# Prepare a long vector to save the richness along time
richness_time <- numeric(1E6)
# Count the steps
step <- 0
# Prepare a progress bar
if (interactive()) cli_progress_bar("Drifting")
while (richness > 1) {
step <- step + 1
# Replace a tree
the_community <- evolve_drift(the_community)
# Update the richness
richness <- length(unique(marks(the_community)$PointType))
# Save it
richness_time[step] <- richness
# Show the progress
if (interactive()) cli_progress_update()
}
# Finalise the progress bar
if (interactive()) cli_progress_update(force = TRUE)
# Save the necessary time to reach a single species
drift_time <- step
# Plot the community
autoplot(the_community)
# Plot the decreasing richness
data.frame(Time = seq_len(step), Richness = richness_time[seq_len(step)]) %>%
ggplot() +
geom_line(aes(x = Time, y = Richness))
Immigration is the ability to replace the dead tree in the local community by a tree of a species of the metacommunity.
The parameter m is the probability for that to happen.
The evolution function is rewritten.
#' One-step evolution of a community with migration
#'
#' @param community A wmppp object (package dbmss) where points are trees.
#' @param metacommunity A named vector of abundances or probability that describes the metacommunity.
#' @param m The migration parameter, that is the probability for a dead tree to be replaced by an immigrant species.
#'
#' @return A wmppp object with the modified community.
evolve_migrate <- function(community, metacommunity, m = 0) {
# Choose a tree
focus_tree <- sample(seq_len(community$n), size = 1)
# Migration or local parent? Draw in a uniform distribution.
u <- runif(1)
if (u > m) {
# Local parent: Species of the survivors
survivors_sp <- marks(community)$PointType[-focus_tree]
# Draw the species of the new tree
marks(community)$PointType[focus_tree] <-
survivors_sp[sample(seq_along(survivors_sp), size = 1)]
} else {
# Migration : Draw the species of the new tree in the metacommunity
marks(community)$PointType[focus_tree] <-
names(metacommunity)[which(rmultinom(1, size = 1, prob = metacommunity) == 1)]
}
# return
return(community)
}
We make the community evolve the same amount of time as needed for complete drift and see what happens.
# Choose the migration probability
m <- 0.1
# Copy the community to modify it
the_community <- c_wmppp
# Number of species
richness <- length(unique(marks(the_community)$PointType))
# Prepare a long vector to save the richness along time
richness_time <- numeric(1E6)
# Prepare a progress bar
if (interactive()) cli_progress_bar(paste("Migration", m, ":"), total = drift_time)
for (step in seq_len(drift_time)) {
# Replace a tree
the_community <- evolve_migrate(the_community, mc_abundances, m)
# Save the richness
richness_time[step] <- length(unique(marks(the_community)$PointType))
# Show the progress
if (interactive()) cli_progress_update()
}
autoplot(the_community)
# Plot richness
data.frame(Time = seq_len(drift_time), Richness = richness_time[seq_len(drift_time)]) %>%
ggplot() +
geom_line(aes(x = Time, y = Richness))
# Plot the rank-abundance curve
the_community %>%
# Consider the dataframe of marks
marks() %>%
# Group by species
group_by(PointType) %>%
# Count the numnber of individuals by species
summarise(Abundance = n()) %>%
# Extract the column
pull(Abundance) %>%
# Make it an abundance vector (package entropart)
as.AbdVector() %>%
# Plot the rank-abundance curve and try to fit a log-normal distribution
autoplot(Distribution = "lnorm")
The average number of species can be calculated from the data, after eliminating the burn-in steps (i.e. before the pattern is stable).
mean(richness_time[10000:drift_time])
## [1] 42.87037
The equation for the expected number of species is in Etienne and Alonso (2007, eq. 4) but it requires knowing the size of the metacommunity.
The minimum richness is 32.
Re-use the code above to run a few experiments about drift time and the shape of the local community.
From the BCI metacommunity, draw a local 1-ha community. Choose two immigration rates in turn: 0.1, as observed at BCI, and 0.01. Run 50,000 steps and observe the final RAC. Compare the number of species with the actual value.
BCI data is available in package vegan.
library("vegan")
data(BCI)
# Number of trees per ha
sum(BCI)/50
## [1] 429.14
# Number of trees in each 1-ha plot
rowSums(BCI)
## 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
## 448 435 463 508 505 412 416 431 409 483 401 366 409 438 462 437 381 347 433 429
## 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
## 408 418 340 392 442 407 417 387 364 475 421 459 436 447 601 430 435 447 424 489
## 41 42 43 44 45 46 47 48 49 50
## 402 414 407 409 444 430 425 415 427 432
# Number of species in each 1-ha plot
rowSums(BCI > 0)
## 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
## 93 84 90 94 101 85 82 88 90 94 87 84 93 98 93 93 93 89 109 100
## 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
## 99 91 99 95 105 91 99 85 86 97 77 88 86 92 83 92 88 82 84 80
## 41 42 43 44 45 46 47 48 49 50
## 102 87 86 81 81 86 102 91 91 93
# RAC of plot 1
BCI[1, ] %>%
as.AbdVector() %>%
autoplot()
Draw small communities with 10 species, in log-normal distributions, ignoring the metacommunity. Make their size vary: 16, 32, 64, 128, 256 individuals. Run the drift model and note the necessary time for complete drift. Compare your results with figure 4.12 (page 106) of Hubbell (2001).
# Hint
rCommunity(1, size = 256) %>%
autoplot()