## ----setup, include=FALSE-----------------------------------------------------
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.width = 7,
fig.height = 5
)
## ----packages-----------------------------------------------------------------
library(mpmaggregate)
library(kableExtra)
library(rphylopic)
## ----fetch-comadre------------------------------------------------------------
# Matrices for Asian elephant populations included in this package were
# originally retrieved from COMADRE using the Rcompadre package.
# Not run: requires Rcompadre and internet access
# library(Rcompadre)
# comadre <- cdb_fetch("comadre",version = "4.23.3.1")
# mpm_elephant1 <- comadre[comadre$MatrixID == 249273, ]
# matA_elephant1 <- matA(mpm_elephant1)[[1]] # Projection interval is 5 years
# matA_elephant1 <- matA_elephant1[1:12,1:12] # Confine to females
# mpm_elephant2 <- comadre[comadre$MatrixID == 249274, ]
# matA_elephant2 <- matA(mpm_elephant2)[[1]] # Projection interval is 1 year
# matA_elephant2[60, 60] <- 0 # Remove statis to form a true Leslie Matrix
# usethis::use_data(matA_elephant1, matA_elephant2, # Write data (development)
# overwrite = TRUE)
# Load data locally
data(matA_elephant1, matA_elephant2, package="mpmaggregate")
matA_coarse <- matA_elephant1 # 12x12 5-year projection interval
matA_fine <- matA_elephant2 # 60x60 1-year projection interval
## ----leslie-aggregation-------------------------------------------------------
res <- leslie_aggregate(
matA_fine,
ngroups = 12,
framework = "lambda",
criterion = "standard"
)
matB_lambda_stand <- res$matAk_agg
matB_lambda_stand_eff <- res$effectiveness
res <- leslie_aggregate(
matA_fine,
ngroups = 12,
framework = "lambda",
criterion = "elasticity"
)
matB_lambda_elast <- res$matAk_agg
matB_lambda_elast_eff <- res$effectiveness
res <- leslie_aggregate(
matA_fine,
ngroups = 12,
framework = "R0",
criterion = "standard"
)
matB_R0_stand <- res$matAk_agg
matB_R0_stand_eff<- res$effectiveness
res <- leslie_aggregate(
matA_fine,
ngroups = 12,
framework = "R0",
criterion = "elasticity"
)
matB_R0_elast <- res$matAk_agg
matB_R0_elast_eff <- res$effectiveness
## ----leslie-metrics-----------------------------------------------------------
# Decompose a Leslie matrix into its components U and F
leslie_UF <- function(A) {
F <- matrix(0, nrow(A), ncol(A))
F[1, ] <- A[1, ]
U <- A
U[1, ] <- 0
list(U = U, F = F)
}
# Return R0, the net reproductive rate
get_R0_leslie <- function(A) {
uf <- leslie_UF(A)
U <- uf$U
F <- uf$F
I <- diag(nrow(U))
N <- solve(I - U)
K <- F %*% N
spectral_radius(K)
}
# Return generation time in the lambda framework
get_generation_time_lambda <- function(A) {
uf <- leslie_UF(A)
U <- uf$U
F <- uf$F
I <- diag(nrow(U))
N <- solve(I - U)
gt = generation_time(F, U, framework = "lambda")$generation_time
return(gt)
}
# Return cohort generation time
get_generation_time_R0 <- function(A) {
uf <- leslie_UF(A)
U <- uf$U
F <- uf$F
I <- diag(nrow(U))
N <- solve(I - U)
gt = generation_time(F, U, framework = "R0")$generation_time
return(gt)
}
## ----define-models------------------------------------------------------------
models <- list(
"Orig. coarse" = matA_coarse,
"Agg λ + standard" = matB_lambda_stand,
"Agg λ + elasticity" = matB_lambda_elast,
"Agg R0 + standard" = matB_R0_stand,
"Agg R0 + elasticity" = matB_R0_elast
)
## ----lambda - r0 - table------------------------------------------------------
#Form the data frame that contains the table information
results <- data.frame(
Model = names(models),
lambda = sapply(models, spectral_radius),
R0 = sapply(models, get_R0_leslie),
Ta = sapply(models, get_generation_time_lambda)*5, #measure in years
Tc = sapply(models, get_generation_time_R0)*5, #measure in years
Eff = c(NA,matB_lambda_stand_eff,matB_lambda_elast_eff,
matB_R0_stand_eff,matB_R0_elast_eff),
row.names = NULL,
stringsAsFactors = FALSE
)
n_rows <- nrow(results)
#Construct a table using function kable
knitr::kable(
results,
col.names = c(
"Model",
"λ",
"R0",
"Ta (years)",
"Tc (years)",
"ρ2"
),
digits = 3,
caption = paste0(
"Table 1. Demographic comparison between two Asian elephant populations, ",
"represented by an original coarse-stage Leslie model from Nagarhole National Park ",
"(MatrixID 249273) and aggregated versions of a Leslie model from Periyar Reserve ",
"(MatrixID 249274). Metrics include population growth rate (λ), ",
"net reproductive rate (R0), generation time (years; ",
"Ta), cohort generation time (years; ",
"Tc), and aggregation effectiveness (ρ2). ",
"“Standard” denotes use of a standard aggregator and ",
"“elasticity” denotes elasticity-consistent aggregation."
),
format = "html",
escape = FALSE
) |>
kable_styling(full_width = TRUE) |>
row_spec(0, extra_css = "border-bottom: 2px solid black;") |>
row_spec(nrow(results), extra_css = "border-bottom: 2px solid black;") |>
footnote(
general_title = "",
general = paste0(
"",
"Note. The projection interval associated with the population ",
"growth rate (λ) is 5 years."
),
footnote_as_chunk = TRUE,
escape = FALSE
)
## ----vital-rates-barplot, fig.height=8, fig.width=10--------------------------
#Asian elephant
#"Elephas maximus" by T. Michael Keesey (2011; CC BY 3.0).
# Not run: requires internet access
# uuid <- "7c9ab182-175d-4f02-96d0-09c1e5212bff"
# img <- get_phylopic(uuid = uuid, format = "raster", height = 691)
# Write image
# png::writePNG(unclass(img), "inst/extdata/elephant.png")
#Load the image file that is stored locally
img_file <- system.file("extdata", "elephant.png", package = "mpmaggregate")
if (!nzchar(img_file)) {
img_file <- "../inst/extdata/elephant.png"
}
img <- png::readPNG(img_file)
# ---- Helpers to extract Leslie vital rates from A ----
extract_fertility <- function(A) {
as.numeric(A[1, ])
}
extract_survival <- function(A) {
n <- nrow(A)
s_sub <- A[cbind(2:n, 1:(n - 1))] # A[i+1, i]
c(s_sub)
}
model_names <- names(models)
# Choose distinct colors for the 5 models (base R friendly)
cols <- c("#1b9e77", "#d95f02", "#7570b3", "#e7298a", "#66a61e")
# Build rate-by-model matrices so:
# rows = models (5 bars), cols = vital rates (groupings)
F_by_model <- t(sapply(models, extract_fertility)) # 5 x n
S_by_model <- t(sapply(models, extract_survival)) # 5 x n-1
# Name columns (vital rates)
n <- nrow(models[[1]])
colnames(F_by_model) <- paste0("F", 1:n)
colnames(S_by_model) <- c(paste0("S", 1:(n - 1)))
# Keep only vital rates that are nonzero in at least one model
keep_F <- colSums(abs(F_by_model) > 0) > 0
keep_S <- colSums(abs(S_by_model) > 0) > 0
F_plot <- F_by_model[, keep_F, drop = FALSE]
S_plot <- S_by_model[, keep_S, drop = FALSE]
# Leave extra space on the right for a legend that won't overlap bars
op <- par(
mfrow = c(2, 1),
mar = c(7, 4, 3, 7),
oma = c(0, 0, 0, 0)
)
op <- par(
mfrow = c(2, 1),
mar = c(5, 4, 1.5, 7),
oma = c(0, 0, 0, 0)
)
ylim = c(0, max(F_plot))
mydiff = ylim[2] - ylim[1]
ylim[2] = ylim[2] + .3 * mydiff
# Panel (a): Fertility rates
barplot(
F_plot,
ylim = ylim,
beside = TRUE,
col = cols,
border = "white",
las = 2,
ylab = "Fertility rate",
main = "Fertility rates"
)
mtext("(a)",
side = 3,
adj = 0,
line = 0.2)
lims <- par("usr")
mydiff <- lims[4] - lims[3]
add_phylopic_base(
img = img,
x = NULL,
y = mydiff * .9,
height = .3 * mydiff * dim(img)[1] / 1024
)
# One legend for both panels, placed in the right margin of the top panel
par(xpd = NA)
legend(
"topright",
inset = c(-0.18, 0.00),
legend = model_names,
fill = cols,
bty = "n",
cex = 0.9
)
par(xpd = FALSE)
ylim <- c(0, max(S_plot))
mydiff <- ylim[2] - ylim[1]
ylim[2] <- ylim[2] + .3 * mydiff
# Panel (b): Survival probs
barplot(
S_plot,
ylim = ylim,
beside = TRUE,
col = cols,
border = "white",
las = 2,
ylab = "Survival probability",
main = "Survival probabilities"
)
mtext("(b)",
side = 3,
adj = 0,
line = 0.2)
lims <- par("usr")
mydiff <- lims[4] - lims[3]
add_phylopic_base(
img = img,
x = NULL,
y = mydiff * .9,
height = .3 * mydiff * dim(img)[1] / 1024
)
# Legend for bottom panel (same placement logic)
par(xpd = NA)
legend(
"topright",
inset = c(-0.18, 0.00),
legend = model_names,
fill = cols,
bty = "n",
cex = 0.9
)
par(xpd = FALSE)
par(op)
## ----lambda-elasticities-barplot, fig.height=8, fig.width=10------------------
#Asian elephant
#"Elephas maximus" by T. Michael Keesey (2011; CC BY 3.0).
# Not run: requires internet access
# uuid <- "7c9ab182-175d-4f02-96d0-09c1e5212bff"
# img <- get_phylopic(uuid = uuid, format = "raster", height = 691)
# Write image
# png::writePNG(unclass(img), "inst/extdata/elephant.png")
#Load a local copy of the image file
img_file <- system.file("extdata", "elephant.png", package = "mpmaggregate")
if (!nzchar(img_file)) {
img_file <- "../inst/extdata/elephant.png"
}
img <- png::readPNG(img_file)
# Compute elasticity matrices for each model (same dimensions as A)
E_models <- lapply(models, function(A) {
E <- mpm_elasticity(A, framework = "lambda")$elasticity
as.matrix(E)
})
# ---- Helpers to extract Leslie elasticities from an elasticity matrix ----
extract_fertility_elast <- function(E) {
as.numeric(E[1, ])
}
extract_survival_elast <- function(E) {
n <- nrow(E)
e_sub <- E[cbind(2:n, 1:(n - 1))] # elasticity of A[i+1, i]
as.numeric(e_sub)
}
model_names <- names(models)
# Same colors as the vital-rate plot for consistency
cols <- c("#1b9e77", "#d95f02", "#7570b3", "#e7298a", "#66a61e")
# Build elasticity-by-model matrices so:
# rows = models (5 bars), cols = vital rates (groupings)
F_el_by_model <- t(sapply(E_models, extract_fertility_elast)) # 5 x n
S_el_by_model <- t(sapply(E_models, extract_survival_elast)) # 5 x n-1
# Name columns (vital rates)
n <- nrow(models[[1]])
colnames(F_el_by_model) <- paste0("F", 1:n)
colnames(S_el_by_model) <- c(paste0("S", 1:(n - 1)))
# Keep only elasticities that are nonzero in at least one model
keep_F <- colSums(abs(F_el_by_model) > 0) > 0
keep_S <- colSums(abs(S_el_by_model) > 0) > 0
F_el_plot <- F_el_by_model[, keep_F, drop = FALSE]
S_el_plot <- S_el_by_model[, keep_S, drop = FALSE]
# Leave extra space on the right for legends that won't overlap bars
op <- par(
mfrow = c(2, 1),
mar = c(7, 4, 3, 7),
oma = c(0, 0, 0, 0)
)
op <- par(
mfrow = c(2, 1),
mar = c(5, 4, 1.5, 7),
oma = c(0, 0, 0, 0)
)
# Panel (a): Elasticities with respect to fertility rates
barplot(
F_el_plot,
beside = TRUE,
ylim = c(0, 0.20),
col = cols,
border = "white",
las = 2,
ylab = "Elasticity of λ",
main = "Elasticities with respect to fertility rates"
)
mtext("(a)",
side = 3,
adj = 0,
line = 0.2)
lims <- par("usr")
mydiff <- lims[4] - lims[3]
add_phylopic_base(
img = img,
x = NULL,
y = mydiff * .75,
height = .3 * mydiff * dim(img)[1] / 1024
)
par(xpd = NA)
legend(
"topright",
inset = c(-0.15, -0.05),
legend = model_names,
fill = cols,
bty = "n",
cex = 0.9
)
par(xpd = FALSE)
# Panel (b): Elasticities with respect to survival probabilities
barplot(
S_el_plot,
beside = TRUE,
ylim = c(0, .20),
col = cols,
border = "white",
las = 2,
ylab = "Elasticity of λ",
main = "Elasticities with respect to survival probabilities"
)
mtext("(b)",
side = 3,
adj = 0,
line = 0.2)
lims <- par("usr")
mydiff <- lims[4] - lims[3]
add_phylopic_base(
img = img,
x = NULL,
y = mydiff * .75,
height = .3 * mydiff * dim(img)[1] / 1024
)
par(xpd = NA)
legend(
"topright",
inset = c(-0.15, -0.05),
legend = model_names,
fill = cols,
bty = "n",
cex = 0.9
)
par(xpd = FALSE)
par(op)
## ----R0-elasticities-barplot, fig.height=8, fig.width=10----------------------
#Asian elephant
#"Elephas maximus" by T. Michael Keesey (2011; CC BY 3.0).
# Not run: requires internet access
# uuid <- "7c9ab182-175d-4f02-96d0-09c1e5212bff"
# img <- get_phylopic(uuid = uuid, format = "raster", height = 691)
# Write image
# png::writePNG(unclass(img), "inst/extdata/elephant.png")
img_file <- system.file("extdata", "elephant.png", package = "mpmaggregate")
if (!nzchar(img_file)) {
img_file <- "../inst/extdata/elephant.png"
}
img <- png::readPNG(img_file)
# Compute elasticity matrices for each model (same dimensions as A)
# These are elasticities of R0 wrt to matrix entries
# normalized to sum to 1.
E_models <- lapply(models, function(A) {
n <- nrow(A)
matU <- A
matU[1,] <- 0
matF <- matrix(0,n,n)
matF[1,] <- A[1,]
E <- mpm_elasticity(matA=NULL, matF = matF, matU = matU,
framework = "R0", normalize = TRUE)$elasticity
as.matrix(E)
})
# ---- Helpers to extract Leslie elasticities from an elasticity matrix ----
extract_fertility_elast <- function(E) {
as.numeric(E[1, ])
}
extract_survival_elast <- function(E) {
n <- nrow(E)
e_sub <- E[cbind(2:n, 1:(n - 1))] # elasticity of A[i+1, i]
as.numeric(e_sub)
}
model_names <- names(models)
# Same colors as the vital-rate plot for consistency
cols <- c("#1b9e77", "#d95f02", "#7570b3", "#e7298a", "#66a61e")
# Build elasticity-by-model matrices so:
# rows = models (5 bars), cols = vital rates (groupings)
F_el_by_model <- t(sapply(E_models, extract_fertility_elast)) # 5 x n
S_el_by_model <- t(sapply(E_models, extract_survival_elast)) # 5 x n-1
# Name columns (vital rates)
n <- nrow(models[[1]])
colnames(F_el_by_model) <- paste0("F", 1:n)
colnames(S_el_by_model) <- c(paste0("S", 1:(n - 1)))
# Keep only elasticities that are nonzero in at least one model
keep_F <- colSums(abs(F_el_by_model) > 0) > 0
keep_S <- colSums(abs(S_el_by_model) > 0) > 0
F_el_plot2 <- F_el_by_model[, keep_F, drop = FALSE]
S_el_plot2 <- S_el_by_model[, keep_S, drop = FALSE]
# Leave extra space on the right for legends that won't overlap bars
op <- par(
mfrow = c(2, 1),
mar = c(7, 4, 3, 7),
oma = c(0, 0, 0, 0)
)
op <- par(
mfrow = c(2, 1),
mar = c(5, 4, 1.5, 7),
oma = c(0, 0, 0, 0)
)
# Panel (a): Elasticities with respect to fertility rates
barplot(
F_el_plot2,
beside = TRUE,
ylim = c(0, 0.20),
col = cols,
border = "white",
las = 2,
ylab = expression("Normalized elasticity of " * R[0] * ""),
main = "Elasticities with respect to fertility rates"
)
mtext("(a)",
side = 3,
adj = 0,
line = 0.2)
lims <- par("usr")
mydiff <- lims[4] - lims[3]
add_phylopic_base(
img = img,
x = NULL,
y = mydiff * .75,
height = .3 * mydiff * dim(img)[1] / 1024
)
par(xpd = NA)
legend(
"topright",
inset = c(-0.15, -0.05),
legend = model_names,
fill = cols,
bty = "n",
cex = 0.9
)
par(xpd = FALSE)
# Panel (b): Elasticities with respect to survival probabilities
barplot(
S_el_plot2,
beside = TRUE,
ylim = c(0, .20),
col = cols,
border = "white",
las = 2,
ylab = expression("Normalized elasticity of " * R[0] * ""),
main = "Elasticities with respect to survival probabilities"
)
mtext("(b)",
side = 3,
adj = 0,
line = 0.2)
lims <- par("usr")
mydiff <- lims[4] - lims[3]
add_phylopic_base(
img = img,
x = NULL,
y = mydiff * .75,
height = .3 * mydiff * dim(img)[1] / 1024
)
par(xpd = NA)
legend(
"topright",
inset = c(-0.15, -0.05),
legend = model_names,
fill = cols,
bty = "n",
cex = 0.9
)
par(xpd = FALSE)
par(op)
## ----lambda-vs.-R0-elasticities-----------------------------------------------
#compare elasticities with respect to fertility rates between frameworks
#fertility rates in lambda framework
sum(F_el_plot)/5
#fertility rates in R0 framework
sum(F_el_plot2)/5