#' Metaweb building
#'
#'
#' @param data a dataframe containing species and size variable
#' @param diet_shift data.frame containing species and the predation window.
#' @inheritParams compute_prey_size
#' @inheritParams compute_classes
#' @param fish_resource_method character Either "overlap", "willem" or "midpoint"
#'
#' @details class_method = "quantile" use the `quantile` function to create size
#' class in nb_class . When class_method = "percentile", the range of the size distribution
#' is divided by nb_class. The difference is that `quantile` start the first
#' class at the minimum value of the size distribution while percentile begins
#' at 0. It therefore gives slightly different results.
#' When the predation window definition is defined as overlap or midpoint, there
#' will be possibility of overlap between size class of a predator and a prey.
#' It means that for trophic link established between a prey and a predator, the
#' smallest predator of a given size class is smaller than the biggest prey
#' fish. When pred_win_method is set to "no_overlap", the upper limit of the predation window of
#' a predator size class is defined by the smallest predator of the size class,
#' so there is no overlap between predator and prey. With midpoint option, the
#' existence of a trophic link is defined by the middle of the predator size
#' class.
#'
#' @return matrix
#'
#' @export
build_metaweb <- function(data, species, size, pred_win, beta_min, beta_max,
fish_diet_shift, low_bound, upper_bound, fish,
resource_diet_shift, class_method = "quantile",
nb_class = 9, pred_win_method = "midpoint", na.rm = FALSE,
replace_min_by_one = FALSE, fish_resource_method = "overlap") {
#Capture var:
species <- rlang::enquo(species)
size <- rlang::enquo(size)
low_bound <- rlang::enquo(low_bound)
upper_bound <- rlang::enquo(upper_bound)
fish <- rlang::enquo(fish)
beta_min <- rlang::enquo(beta_min)
beta_max <- rlang::enquo(beta_max)
# Check
data <- sanatize_metaweb(
data = data,
species = !!species,
fish_diet_shift = fish_diet_shift,
resource_diet_shift = resource_diet_shift,
nb_class = nb_class
)
# TODO: Check concordance of column names between datasets
## eg. species, (life_stage), low_bound, upper_bound, fish
# Build the size classes
size_class <- compute_classes(data, group_var = !!species, var = !!size,
class_method = class_method, na.rm = na.rm, replace_min_by_one =
replace_min_by_one)
## Compute the th_prey size min & max + mid-point
th_prey_size <- compute_prey_size(size_class, pred_win, !!species,
!!beta_min, !!beta_max, pred_win_method = pred_win_method)
## Get piscivory index for each size class
piscivory_index <- compute_piscivory(size_class, fish_diet_shift,
species = !!species, low_bound = !!low_bound, upper_bound = !!upper_bound,
fish = !!fish)
## Fill the matrix
int_matrices <- compute_links(size_class, th_prey_size, piscivory_index,
fish_diet_shift, resource_diet_shift, !!species, !!fish, !!low_bound, !!upper_bound,
fish_resource_method = fish_resource_method, pred_win_method = pred_win_method
)
fish_fish_int <- int_matrices$fish_fish_int
fish_resource_int <- int_matrices$fish_resource_int
# Resource-Resource matrix
resource_list <- int_matrices$resource_list
resource_resource_int <- t(resource_diet_shift[, resource_list])
# Resource-Fish matrix
resource_fish_int <- matrix(0, ncol = ncol(resource_resource_int),
nrow = nrow(fish_fish_int)) #0 because no resources are eating fishes
colnames(resource_resource_int) <- colnames(resource_fish_int) <- resource_list
# Merge the matrix
metaweb <- rbind(
cbind(fish_fish_int, resource_fish_int),
cbind(fish_resource_int, resource_resource_int)
)
list(
metaweb = metaweb,
species = int_matrices$species_list,
resource = resource_list,
nb_class = nb_class,
size_class = size_class,
piscivory_index = piscivory_index,
th_prey_size = th_prey_size
)
}
#' Compute species links
#'
#' Compute feeding links between species based on their size
#'
#' @inheritParams compute_piscivory
#' @param th_prey_size data.frame generated by compute_prey_size
#' @param piscivory_index data.frame generated by compute_piscivory
#' @param resource_diet_shift data.frame generated by compute_prey_size
#'
#' @details
#'
#' @return list containing the species-species and the
#' species-resource interaction matrix
#' @export
compute_links <- function(size_class, th_prey_size, piscivory_index,
fish_diet_shift, resource_diet_shift, species, fish, low_bound, upper_bound,
fish_resource_method = "overlap", pred_win_method = "midpoint") {
species <- rlang::enquo(species)
low_bound <- rlang::enquo(low_bound)
upper_bound <- rlang::enquo(upper_bound)
fish <- rlang::enquo(fish)
#Check
stopifnot(fish_resource_method %in% c("overlap", "midpoint", "willem"))
# Build the empty matrices
## Fish-Fish
species_list <- unique(dplyr::select(size_class, !!species)) %>% unlist
nb_species <- length(species_list)
nb_class <- length(unique(size_class$class_id))
fish_fish_int <- matrix(rep(0, (nb_species * nb_class) ^ 2), ncol = nb_species * nb_class)
fish_class_names <- rep(species_list, each = nb_class) %>%
paste(., seq(1, nb_class), sep = "_")
rownames(fish_fish_int) <- colnames(fish_fish_int) <- fish_class_names
## Fish-Resource
resource_list <- dplyr::select(resource_diet_shift, !!species) %>% unlist
names(resource_list) <- NULL
nb_resource <- length(resource_list)
fish_resource_int <- matrix(rep(0, nb_resource * nb_class * nb_species), ncol = nb_class * nb_species)
rownames(fish_resource_int) <- resource_list
colnames(fish_resource_int) <- fish_class_names
# Set method of resource assignement
if (fish_resource_method == "midpoint") {
condition <- rlang::quo(
#size class midpoint in stage interval
midpoint >= !!low_bound & midpoint <= !!upper_bound
)
} else if (fish_resource_method == "willem") {
condition <- rlang::quo(
# min stage in size class: ]min_pred;max_pred]
(min_pred < !!low_bound & !!low_bound < max_pred) |#<-diff with overlap
# max stage in size class:]min_pred;max_pred]
(min_pred < !!upper_bound & !!upper_bound <= max_pred) |
#stage class strictly around size class:]min_pred;max_pred]
## stage class
(!!low_bound < min_pred & max_pred <= !!upper_bound) |#<-diff with overlap
#size class strictly in stage class:]low_bound;upper_bound]
(!!low_bound > min_pred & max_pred >= !!upper_bound)
)
} else {
condition <- rlang::quo(
# min stage in size class: ]min_pred;max_pred]
(min_pred < !!low_bound & !!low_bound <= max_pred) |
# max stage in size class:]min_pred;max_pred]
(min_pred < !!upper_bound & !!upper_bound <= max_pred) |
#stage class strictly around size class:]min_pred;max_pred]
## stage class
(!!low_bound <= min_pred & max_pred < !!upper_bound) |
#size class strictly in stage class:]low_bound;upper_bound]
(!!low_bound > min_pred & max_pred >= !!upper_bound)
)
}
# merge dataset
trophic_data <- dplyr::left_join(size_class, th_prey_size,
by = c(rlang::quo_name(species), "class_id")) %>%
dplyr::left_join(., piscivory_index, by = c(rlang::quo_name(species), "class_id")) %>%
tidyr::unite(sp_class, !!species, class_id)
pred_classes <- colnames(fish_fish_int)
trophic_data <- trophic_data[order(match(unlist(trophic_data[, "sp_class"]), rownames(fish_fish_int))), ]
prey_data <- dplyr::select(trophic_data, sp_class, lower, upper)
for (j in 1:ncol(fish_fish_int)) {#Predators
pred_data <- dplyr::filter(trophic_data, sp_class == pred_classes[j]) %>%
unlist
if (pred_data["pisc_index"] != 0) {
min_prey <- as.numeric(pred_data["min_prey"])
max_prey <- as.numeric(pred_data["max_prey"])
pisc_index <- as.integer(pred_data["pisc_index"])
## Check if the middle of the prey size class is in the range of prey of the predator
if (pred_win_method == "midpoint") {
troph_link <- dplyr::mutate(prey_data,
prey_mid = (lower + upper) / 2,
troph_link = (min_prey <= prey_mid & max_prey >= prey_mid) * pisc_index)
} else {
stop("pred_win_method is different from midpoint. Other methods are not yet implemented")
}
fish_int_values <- troph_link %>%
dplyr::select(troph_link) %>% unlist
fish_fish_int[, j] <- fish_int_values
}
#Fish-Resource matrix
## What the predator size class is eating:
### Get predator size:
min_pred <- as.numeric(pred_data["lower"])
max_pred <- as.numeric(pred_data["upper"])
### Filter diet:
#### get species name
pred_diet <- fish_diet_shift %>%
dplyr::filter(!!species ==
stringr::str_remove(pred_classes[j], "_\\d+")
) %>%
dplyr::select(-!!fish)
if (fish_resource_method == "midpoint") {
midpoint <- (min_pred + max_pred) / 2
}
pred_diet_class_match <- pred_diet %>%
dplyr::filter(!!condition)
if (nrow(pred_diet_class_match) == 0) {
message(paste("Species/class", pred_classes[j], "had no matches with life stages."))
resource_int_values <- rep(0, length(resource_list))
} else {
resource_int_values <- pred_diet_class_match %>%
dplyr::select(!! resource_list) %>%
tidyr::gather(resource, troph_index) %>%
dplyr::group_by(resource) %>%
dplyr::summarise(troph_index = (sum(troph_index) > 0) * 1) %>%
tidyr::spread(resource, troph_index) %>%
unlist
good_order <- match(names(resource_int_values), resource_list)
resource_int_values <- resource_int_values[good_order]
}
fish_resource_int[, j] <- resource_int_values
}
list(
fish_fish_int = fish_fish_int,
fish_resource_int = fish_resource_int,
species_list = species_list,
resource_list = resource_list
)
}
#' Compute piscivory link
#'
#' Determine which size class is piscivorous
#'
#' @param size_class a data.frame generated by compute_classes
#' @param fish_diet_shift a data.frame containing species, life stage, lower and
#' upper bound size for each stage
#' @param species variable name
#' @param low_bound variable containing lower limit of the stage
#' @param upper_bound variable containing upper limit of the stage. Not used.
#' @param fish name of logical piscivory variable. 0 = no piscivory; 1 = piscivory
#'
#' @details If given species class is considered as piscivor if the upper bound
#' of the class is greater than the mininum size to reach piscivory.
#'
#' @return a data.frame
#' @export
compute_piscivory <- function (size_class, fish_diet_shift, species, low_bound, upper_bound, fish) {
species <- rlang::enquo(species)
lower_stage_bound <- rlang::enquo(low_bound)
upper_stage_bound <- rlang::enquo(upper_bound) #not use until now
fish <- rlang::enquo(fish)
# Check variables
type_variable <- fish_diet_shift %>%
#dplyr::select(!!lower_stage_bound) %>%#, !!upper_stage_bound
dplyr::summarise(type = typeof(!!lower_stage_bound)) %>%
dplyr::select(type) %>% unlist(.)
if (any(!(type_variable %in% c("double", "integer")))) {
stop("In fish_diet_shift, the low_bound variable is not numeric")
}
get_piscivory <- function(fish, lower) {
if (sum(fish) > 0) { # Is there at least one picivory life stage?
# Get min size at piscivory
min(lower[which(fish != 0)])
} else {
NA
}
}
## Get minimum size for each species where there is piscivory:
min_pisc <- fish_diet_shift %>%
dplyr::group_by(!!species) %>%
dplyr::summarise(min_pisc = get_piscivory(!!fish, !!lower_stage_bound)) %>%
dplyr::select(!!species, min_pisc)
## Piscivory if upper bound of the size class is sup to the size min at which
## the species begins to be piscivorous (if it does)
dplyr::left_join(size_class, min_pisc, by = rlang::quo_name(species)) %>%
dplyr::mutate(pisc_index = ifelse(is.na(min_pisc), 0, (upper > min_pisc) * 1)) %>%
dplyr::select(-min_pisc, - lower, - upper)
}
#' Compute prey size
#'
#' Compute prey size for class size species can eat. It is theoretical, indeep,
#' the information about real diet of the fish is not taken in account here.
#'
#' @param class_size a data.frame generated by compute_classes
#' @param pred_win data.frame containing species and the predation window
#' parameters (\alpha and \beta).
#' @param pred_win_method character midpoint, overlap or no_overlap. Default to midpoint.
#' @param species blabla
#' @param beta_min column name containing beta_min parameters
#' @param beta_max column name contains beta_max parameters
#'
#' @details For now, the function compute the mininum prey size as follow:
#' beta_min * min_predator_size. But the general formula is in hte form:
#' alpha_min + beta_min * min_predator_size. But here, we do not have data about
#' alpha. We will add it if necessary.
#'
#' @return matrix
#'
#' @export
compute_prey_size <- function(class_size, pred_win, species, beta_min, beta_max, pred_win_method = "midpoint") {
#Get var:
species <- rlang::enquo(species)
beta_min <- rlang::enquo(beta_min)
beta_max <- rlang::enquo(beta_max)
if (pred_win_method != "midpoint") {
message("Other methods are not yet implemented")
}
# check species concondance
class_sp <- dplyr::select(class_size, !!species)[[1]] %>% unique()
pred_sp <- dplyr::select(pred_win, !!species)[[1]] %>% unique()
missing_sp <- which(!class_sp %in% pred_sp)
if (length(missing_sp) > 0) {
msg <- paste0(
"The following species ",
class_sp[missing_sp],
" have no predation windows defined.", collapse = "\n")
stop(msg)
}
dplyr::left_join(class_size, pred_win, by = rlang::quo_name(species)) %>%
dplyr::mutate(
min_prey = !!beta_min * ( (lower + upper) / 2),
max_prey = !!beta_max * ( (lower + upper) / 2)
) %>%
dplyr::select(!!species, class_id, min_prey, max_prey)
}
#' Compute classes
#'
#' @param size data.frame containing species and length.
#' @param group_var variable to group the data. E.g. species.
#' @param var variable containing the values to class.
#' @param na.rm logical. Should the NAs be removed ?
#' @param replace_min_by_one logical. In the first class, replace low bound by
#' 1.
#' @inheritParams split_in_classes
#' @details The replace_min_by_one option is to reflect the choice made by
#' Bonnafé et al. to replace the low bound of the first class (= lowest value)
#' by one. It leads to slighty different results (env. 0.6% of the links).
#'
#' @return blabla
#' @export
compute_classes <- function(size, group_var, var, class_method = "quantile",
nb_class = 9, na.rm = FALSE, replace_min_by_one = FALSE) {
stopifnot(is.numeric(nb_class))
#Capture variables:
group_var <- rlang::enquo(group_var)
var <- rlang::enquo(var)
if (na.rm) {
size %<>% na.omit
message("NAs has been removed with na.omit")
} else {
if (length(which(is.na(size))) > 0) {
stop("There are NAs in your dataset. Please set na.rm = TRUE")
}
}
size %<>%
dplyr::group_by(!!group_var) %>%
dplyr::select(!!group_var, !!var) # ensure that there is only the variable
#of interest
nested_size <- size %>%
tidyr::nest()
## Check that there are at least 2 different values
check_nb_data <- size %>%
dplyr::summarise(nb = length(unique(!!var))) %>%
dplyr::mutate(good = ifelse(nb >= 2, TRUE, FALSE)) %>%
dplyr::select(-nb)
## Warn which species species had been removed
if (!all(check_nb_data$good)) {
species_not_good <- dplyr::filter(check_nb_data, good == FALSE) %>%
dplyr::select(!!group_var) %>% unlist(.)
nested_size %<>% dplyr::filter(!(!!group_var %in% species_not_good))
if (nrow(nested_size) == 0) {
stop("None of the species got more of two unique values. Check your dataset.")
}
warning("The following species had less than two unique size values, so we got rid of them:", species_not_good)
}
## Compute classes
nested_size %<>%
dplyr::mutate(
classes = purrr::map(data, split_in_classes, nb_class = nb_class,
class_method = class_method)
) %>%
dplyr::select(-data)
output <- nested_size %>%
tidyr::unnest(classes) %>%
dplyr::ungroup()
if (replace_min_by_one) {
output[output$class_id == 1, "lower"] <- 1
}
return(output)
}
#' Split in classes
#'
#' @param to_class a numeric vector
#' @param class_method character percentile or quantile. Default to percentile.
#' @param nb_class integer number of size class to create. Default to 9.
#' @param round_limits logical Does the class limits should be rounded ?.
#' @export
split_in_classes <- function(to_class, class_method = "quantile",
nb_class = 9, round_limits = TRUE) {
stopifnot(class_method %in% c("percentile", "quantile"))
if (class_method == "quantile") {
if (is.list(to_class)) {
to_class %<>% unlist
stopifnot(is.numeric(to_class))
}
classified <- quantile(to_class, probs = seq(0, 1, by = 1 / nb_class), names = FALSE)
} else {
classified <- seq(min(to_class), max(to_class),
by = (max(to_class) - min(to_class)) / nb_class)
}
if (round_limits) {
classified %<>% round
}
# Determine the lower and upper limit of each size class:
tibble::tibble(
class_id = seq(1, nb_class),
lower = classified[-length(classified)],
upper = classified[-1]
)
}
#' Sanatize fish length for metaweb
#'
#' @param data blabla
#' @param species blabla
#' @param fish_diet_shift blabla
#' @param nb_class blabla
#'
#' @export
sanatize_metaweb <- function(
data = NULL,
species = NULL,
fish_diet_shift = NULL,
resource_diet_shift = NULL,
nb_class = NULL) {
#Capture var:
species <- rlang::enquo(species)
sp_chr <- rlang::quo_name(species)
data_sp <- unique(data[[sp_chr]])
onto_sp <- unique(fish_diet_shift[[sp_chr]])
# Stop if resource species are not found in fish diet data
mask_res_missing <-
!resource_diet_shift[[sp_chr]] %in% colnames(fish_diet_shift)
if (any(mask_res_missing)) {
res_missing <- resource_diet_shift[[sp_chr]][mask_res_missing]
msg <- c(
"The following resources are not found in fish_diet_shift: ",
paste(res_missing, "\n", sep = ", "),
"They should be present to build the metaweb !")
stop(msg)
}
## Del species for which life traits are not filled
sp_missing <- data_sp[!data_sp %in% onto_sp]
if (length(sp_missing) != 0) {
msg <- c(
"For the following species, ",
"the life stage are not found in fish_diet_shift are absent: ",
paste(sp_missing, "\n", sep = ", "),
"They have been removed from data")
message(msg)
data <- data[! data[[sp_chr]] %in% sp_missing, ]
}
# Del species for which the number of individuals is inferior to the number of
# classes:
nb_ind_sp <- data %>%
dplyr::group_by(!!species) %>%
dplyr::summarise(nind = dplyr::n())
species_chr <- rlang::quo_name(species)
mask <- nb_ind_sp$nind <= nb_class
sp_to_low <- nb_ind_sp[mask, ][[species_chr]]
if (length(sp_to_low) != 0) {
msg <- c(
"For the following species, ",
"# obs is not superior to nb_class of the metaweb: ",
paste(sp_to_low, "\n", sep = ", "),
"They have been removed from data")
message(msg)
data <- data[! data[[sp_chr]] %in% sp_to_low, ]
}
data
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.