First, let’s load the packages in R
needed to run this code
# R functions and datasets to support "Modern Applied Statistics with S",
#a book from W.N. Venables and B.D. Ripley
library(MASS);
# R functions for Data Cloning (maximum likelihood estimation using Bayesian MCMC)
library(dclone);
# Create plots for MCMC output
library(mcmcplots)
# Data manipulation and elegant figures
library(tidyverse)
# Palettes inspired by works at the Metropolitan Museum of Art in New York
library(MetBrewer)
We will need initial or “naive” guesses of the parameters to fit the state-space model. This could be roughly computing by providing the vector of (log)abundance observations Yobs
(\(\mathbf{N}\)) and the vector of observation times Tvec
with the function guess.calc()
:
guess.calc <- function(Yobs,Tvec){
# For calculations, time starts at zero.
T.t <-Tvec-Tvec[1];
# Number of time series transitions, q.
q <- length(Yobs)-1;
# q+1 gives the length of the time series.
qp1 <- q+1;
# Time intervals
S.t <- T.t[2:qp1]-T.t[1:q];
# Mean of the observations
Ybar <- mean(Yobs);
# Variance of the observations
Yvar <- sum((Yobs-Ybar)*(Yobs-Ybar))/q;
# Initial mu estimate (at stationary distribution)
mu1 <- Ybar;
# Kludge an initial value for theta based on mean of Y(t+s) given Y(t).
th1 <- -mean(log(abs((Yobs[2:qp1]-mu1)/(Yobs[1:q]-mu1)))/S.t);
# Moment estimate using stationary
bsq1 <- 2*th1*Yvar/(1+2*th1);
# Observation error variance, assumed as first guess as betasq=tausq.
tsq1 <- bsq1;
# What to do if initial guesses is three 0's (or NAs)? Assume arbitrary values
three0s <- sum(c(th1,bsq1,tsq1))
if(three0s==0|is.na(three0s)){
th1 <- 0.5;
bsq1 <- 0.09;
tsq1 <- 0.23;
}
out1 <- c(th1,bsq1,tsq1);
# What to do if initial guesses are too little? Assume arbitrary values
if(sum(out1<1e-7)>=1){
out1 <- c(0.5,0.09,0.23)
}
out <- c(mu1,out1);
return(abs(out))
}
To take into account possible NAs in the counts, we can wrap these initial guess estimator function guess.calc()
into another function guess.calc2.0()
:
guess.calc2.0<- function(TimeAndNs){
newmat <- TimeAndNs
isnas <- sum(is.na(TimeAndNs))
if(isnas >= 1){
isnaind <- which(is.na(TimeAndNs[,2]), arr.ind=TRUE)
newmat <- TimeAndNs[-isnaind,]
newmat[,1] <- newmat[,1] - newmat[1,1]
}
init.guess <- guess.calc(Yobs = log(newmat[,2]), Tvec=newmat[,1])
mu1 <- init.guess[1]
th1 <- init.guess[2]
bsq1 <- init.guess[3]
sigsq1<- ((1-exp(-2*th1))*bsq1)/(2*th1)
out <- c(mu=mu1, theta=th1, sigmasq = sigsq1)
return(out)
}
We also need a Multivariate Normal random generator function randmvn()
, which needs:
n
mu.vec
cov.mat
randmvn <- function(n, mu.vec, cov.mat){
# Save the length of the mean vector of the multivariate normal distribution to sample
p <- length(mu.vec);
# The Cholesky decomposition
#(factorization of a real symmetric positive-definite sqr matrix)
Tau <- chol(cov.mat, pivot=TRUE);
# generate normal deviates outside loop
Zmat <- matrix(rnorm(n=p*n,mean=0,sd=1),nrow=p,ncol=n);
# empty matrix
out <- matrix(0,nrow=p,ncol=n);
# iterate
for(i in 1:n){
Z <- Zmat[,i];
out[,i] <- t(Tau)%*%Z + mu.vec
}
return(out)
}
We have four datasets: \
1) wetlands and demes.csv
\
2) survey_dates_1997_2025.csv
\
3) daily stage in each wetland.csv
\
4) snailkite counts by wetland 1997_2025.csv
\
The first serve as a relationship of the names of each wetland and the regions
# relationship of wetlands and regions ####
wetland_region <- read.csv("Code/wetlands and demes.csv") |>
# adjust name "regions"
rename(region = deme) |>
# unify names (spaces and dash as points)
mutate(wetland = gsub("[- ]", ".", wetland)) |>
# remove the name of "CATCHMENT" which is "Grassy.Waters.Preserve"
# and "Fellsmere" which is "SJM"
filter(!wetland %in% c("CATCHMENT","Fellsmere"))
# count of number of wetlands per region
table(wetland_region$region)
##
## EAST EVER KRV OKEE OTHER PP SJM
## 5 13 18 3 1 1 3
The first file provide the range of dates for each survey. We can use this to extract the water level for the week before each survey started. The functions in lubridate allow to deal with dates.
# surveys dates to link water level data ####
survey_date <- read.csv("Code/survey_dates_1997_2025.csv") |>
mutate(start = ymd(start),
end = ymd(end),
start.w = floor_date(start-6,"week"),
end.w = floor_date(start, "week"))
Then, we have the water level for each day in each wetland.
# daily water level ####
daily_wetland_water <- read.csv("Code/daily stage in each wetland.csv") |>
mutate(date = dmy(Daily.Date)) |>
pivot_longer(cols = !c("date", "Daily.Date"),
names_to = "wetland",
values_to = "daily_water") |>
mutate(wetland = ifelse(wetland == "Fellsmere", "SJM", wetland))
nrow(daily_wetland_water)
## [1] 322800
We can add the region. Some wetlands were not included, but we can assign them easily
daily_wetland_water |>
left_join(wetland_region) |>
filter(is.na(region)) |>
group_by(wetland) |>
count()
## Joining with `by = join_by(wetland)`
daily_wetland_water <- daily_wetland_water |>
left_join(wetland_region) |>
mutate(region = ifelse(wetland == "Kissimmee.Restoration.Area.N", "KRV",
ifelse(wetland == "Kissimmee.Restoration.Area.S", "KRV",
ifelse(wetland == "STA1W", "EVER",
ifelse(wetland == "Three.Forks.Marsh", "SJM",
region)))),
region.wetland = paste0(region, "_", wetland)) |>
group_by(region.wetland) |>
mutate(mean_stage = mean(daily_water, na.rm = TRUE)) |>
ungroup() |>
mutate(center_water = daily_water - mean_stage) |>
as.data.frame()
## Joining with `by = join_by(wetland)`
table(daily_wetland_water$region)
##
## EAST EVER KRV OKEE PP SJM
## 21520 107600 118360 21520 10760 43040
table(daily_wetland_water$region.wetland)
##
## EAST_Grassy.Waters.Preserve EAST_Loxahatchee.Slough
## 10760 10760
## EVER_Big.Cypress.NP EVER_Everglades.NP
## 10760 10760
## EVER_Loxahatchee.NWR EVER_Rotenberger.WMA
## 10760 10760
## EVER_STA1E EVER_STA1W
## 10760 10760
## EVER_STA5 EVER_WCA.2B
## 10760 10760
## EVER_WCA.3A EVER_WCA.3B
## 10760 10760
## KRV_East.Lake.Tohopekaliga KRV_Kissimmee.Restoration.Area.C
## 10760 10760
## KRV_Kissimmee.Restoration.Area.N KRV_Kissimmee.Restoration.Area.S
## 10760 10760
## KRV_Lake.Hatchineha KRV_Lake.Istokpoga
## 10760 10760
## KRV_Lake.Jackson KRV_Lake.Kissimmee
## 10760 10760
## KRV_Lake.Marian KRV_Lake.Parker
## 10760 10760
## KRV_Lake.Tohopekaliga OKEE_Lake.Hicpochee.Impoundments
## 10760 10760
## OKEE_Lake.Okeechobee PP_Paynes.Prairie
## 10760 10760
## SJM_Kenansville.Lake SJM_SJM
## 10760 21520
## SJM_Three.Forks.Marsh
## 10760
summary(daily_wetland_water)
## Daily.Date date wetland daily_water
## Length:322800 Min. :1996-01-01 Length:322800 Min. :-48.70
## Class :character 1st Qu.:2003-05-13 Class :character 1st Qu.: 11.71
## Mode :character Median :2010-09-23 Mode :character Median : 18.68
## Mean :2010-09-23 Mean : 27.35
## 3rd Qu.:2018-02-03 3rd Qu.: 49.51
## Max. :2025-06-16 Max. :378.25
## NA's :79946
## region region.wetland mean_stage center_water
## Length:322800 Length:322800 Min. : 2.865 Min. :-59.50
## Class :character Class :character 1st Qu.: 12.766 1st Qu.: -0.57
## Mode :character Mode :character Median : 19.945 Median : 0.09
## Mean : 29.551 Mean : 0.00
## 3rd Qu.: 50.521 3rd Qu.: 0.70
## Max. :130.384 Max. :367.45
## NA's :79946
We can filter the dates for the week before each survey
# Filter water level at the weekly scale for the dates of surveys
www_filt <- pmap(survey_date, function(start.w, end.w, ...) {
daily_wetland_water |>
filter(date >= start.w & date <= end.w)
})
www_filt_df <- bind_rows(www_filt, .id = "survey_id")
www_filt_df$X <- as.integer(www_filt_df$survey_id)
www_filt_df <- www_filt_df |>
arrange(wetland, date)
head(www_filt_df)
There are NAs in the daily_water
value for the different wetlands. We can assume that the data missing is at random and conduct an imputation using JAGS (Kery & Royle, 2016). In other words, we can model the relative water level as latent variable, using as predictors the region, wetland, and date. We can include an autoregressive effect (AR1), because each day’s relative water level depends on the previous day’s value when the previous observation belongs to the same wetland.
\[ \text{W}_{d} \sim \text{Normal}(\mu_{d},\sigma^{2}) \\ \mu_{d} = \beta_{1} * \text{Date}_{d} + \beta_{2}[\text{Region}_d] + \beta_{3}[\text{Wetland}_{d}] + \phi\times(W_{d-1}-\mu_{d-1}) \] This model adds random intercepts for regions \(\beta_{2}[Region_d]\sim\text{Normal}(0,\tau_{\text{region}}^{-1})\), and wetland-specific deviation centered on their region’s effect \(\beta_{3}[ Wetland_{d}]\sim\text{Normal}(\beta_2[\text{region}(w)],\tau_{wetland}^{-1})\), where \(\text{region}(w)\) maps each wetland \(w\) to its parent region. Finally, the AR1 effect \(\phi \sim \text{Uniform}(-1,1)\) is added if the previous observation belongs to the same wetland as the current observation. This represents the deviation of the previous observation \(W_{d-1}\) from its expected mean \(\mu_{d-1}\) to correct the prediction of the current expected value \(\mu_{d}\) (error correction model).
Impute.model.w <- function(){
# Likelihood
for (d in 2:D) {
is_same_wetland[d] <- equals(wetland[d], wetland[d - 1])
mu_w_prev[d] <- beta1_w * date[d-1] + beta2_w[region[d-1]] + beta3_w[wetland[d-1]]
mu_w[d] <- beta1_w * date[d] + beta2_w[region[d]] + beta3_w[wetland[d]] +
phi * is_same_wetland[d] * (water[d-1] - mu_w_prev[d])
water[d] ~ dnorm(mu_w[d], tau_w)
}
# Prior for first observation
water[1] ~ dnorm(mu_w[1], tau_w)
mu_w[1] <- beta1_w * date[1] + beta2_w[region[1]] + beta3_w[wetland[1]]
# Priors
tau_w ~ dgamma(0.01, 0.01)
beta1_w ~ dnorm(0, 0.01)
phi ~ dunif(-1, 1)
for (r in 1:n_regions) {
beta2_w[r] ~ dnorm(0, tau_region_w)
}
tau_region_w ~ dgamma(0.01, 0.01)
for (w in 1:n_wetlands) {
beta3_w[w] ~ dnorm(beta2_w[region_for_wetland[w]], tau_wetland_w)
}
tau_wetland_w ~ dgamma(0.01, 0.01)
}
We need to adjust the data for this model.
data_jags_w <- www_filt_df |>
dplyr::select(date, region, wetland, center_water) |>
mutate(
# generate numeric index for categorical variables
region = as.numeric(as.factor(region)),
wetland = as.numeric(as.factor(wetland))
)
n_wetlands = length(unique(data_jags_w$wetland))
region_for_wetland <- rep(NA, n_wetlands)
for (w in 1:n_wetlands) {
region_for_wetland[w] <- unique(data_jags_w$region[data_jags_w$wetland == w])
}
dat_w <- list(
D = nrow(data_jags_w),
water = ifelse(is.na(data_jags_w$center_water), NA, data_jags_w$center_water),
date = as.numeric(data_jags_w$date),
region = data_jags_w$region,
wetland = data_jags_w$wetland,
n_regions = length(unique(data_jags_w$region)),
n_wetlands = length(unique(data_jags_w$wetland)),
region_for_wetland = region_for_wetland
)
Fit the model
Impute.w.fit <- jags.fit(data = dat_w,
params = c("water","phi"),
model = Impute.model.w)
saveRDS(Impute.w.fit, "Code/Impute_Relative_Water_fit_AR1_noBeta0.rds")
Extract posterior means and add to original data to see the missing values
# Convert to matrix
mcmc_mat <- as.matrix(Impute.w.fit)
# Extract posterior means
water_means <- colMeans(mcmc_mat[, grep("^water\\[", colnames(mcmc_mat))])
# Add new columns with imputed values
www_filt_df$water_imputed <- www_filt_df$center_water
www_filt_df$water_imputed[is.na(www_filt_df$center_water)] <- water_means[is.na(www_filt_df$center_water)]
saveRDS(www_filt_df, "data/www_center_filtered_df_impute_AR1_noBeta0.RDS")
And we can explore how this imputation performed.
www_filt_df <- readRDS("data/www_center_filtered_df_impute_AR1_noBeta0.RDS") |>
filter(wetland != "Fellsmere")
www_filt_df_pre <- www_filt_df |>
mutate(wet.reg = paste0(region, "_", wetland)) |>
ggplot(aes(x = date, y = center_water, color = region)) +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray")+
facet_wrap(~wetland, scales = "free_y", ncol = 4) +
geom_point(alpha = 0.5)+
scale_color_manual(values=met.brewer("Homer2",n = 6)) +
scale_fill_manual(values=met.brewer("Homer2"),n = 6) +
labs(title = "Data not imputed",
y = "Relative water (stage)")+
theme_classic()+
theme(legend.position = "bottom")+
guides(color = guide_legend(nrow = 1),
fill = guide_legend(nrow = 1))
ggsave(filename = "ww_pre_impute.jpg", www_filt_df_pre, dpi = 300,
width = 230, height = 200, units = "mm")
## Warning: Removed 7704 rows containing missing values or values outside the scale range
## (`geom_point()`).
www_filt_df_post <- www_filt_df |>
mutate(wet.reg = paste0(region, "_", wetland)) |>
ggplot(aes(x = date, y = center_water, color = region)) +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray")+
facet_wrap(~wetland, scales = "free_y", ncol = 4) +
geom_line(aes(y = water_imputed))+
geom_point(alpha = 0.05)+
labs(title = "Data imputed (line)",
y = "Relative water (stage)")+
scale_color_manual(values=met.brewer("Homer2",n = 6)) +
scale_fill_manual(values=met.brewer("Homer2"),n = 6) +
theme_classic()+
theme(legend.position = "bottom")+
guides(color = guide_legend(nrow = 1),
fill = guide_legend(nrow = 1))
ggsave(filename = "ww_post_impute.jpg", www_filt_df_post, dpi = 300,
width = 230, height = 200, units = "mm")
## Warning: Removed 7704 rows containing missing values or values outside the scale range
## (`geom_point()`).
Pre
Post
This dataset include the counts per survey
# Counts per wetland in 6 regions ####
snki_N_wetland <- read.csv("Code/snailkite counts by wetland 1997_2025.csv")
names(snki_N_wetland)
## [1] "X" "year"
## [3] "survey_num" "A.1.FEB"
## [5] "Arbuckle" "Big.Cypress.NP"
## [7] "C.23.Reservoir" "C.44.Impoundment"
## [9] "CATCHMENT" "East.Lake.Tohopekaliga"
## [11] "Everglades.NP" "Fellsmere"
## [13] "Grassy.Waters.Preserve" "Hungryland"
## [15] "Johns.Lake" "Kenansville.Lake"
## [17] "Kissimmee.Restoration.Area.C" "Lake.Arbuckle"
## [19] "Lake.Cypress" "Lake.Hatchineha"
## [21] "Lake.Hicpochee.Impoundments" "Lake.Istokpoga"
## [23] "Lake.Jackson" "Lake.Kissimmee"
## [25] "Lake.Marian" "Lake.Monroe"
## [27] "Lake.Okeechobee" "Lake.Parker"
## [29] "Lake.Rosalie" "Lake.Tohopekaliga"
## [31] "Lake.Weohyakapka" "Loxahatchee.NWR"
## [33] "Loxahatchee.Slough" "Mary.A..Mitigation.Bank"
## [35] "Paynes.Prairie" "Rotenberger.WMA"
## [37] "SJM" "STA.Lakeside.Ranch"
## [39] "STA1E" "STA2"
## [41] "STA3" "STA5"
## [43] "Shingle.Marsh" "Tiger.Lake"
## [45] "WCA.2A" "WCA.2B"
## [47] "WCA.3A" "WCA.3B"
## [49] "OTHER"
Converting in a long format matches the organization of the water level, which we can add and then extract the mean and standard deviation value for each week before each survey (immediate conditions pre-survey might affect the counts).
# long format with water level per survey
snki_N_wetland_region <- snki_N_wetland |>
# move wetland columns to a single, having the count reported
pivot_longer(cols = !c(X, year, survey_num),
names_to = "wetland",
values_to = "count") |>
mutate(wetland = ifelse(wetland == "CATCHMENT", "Grassy.Waters.Preserve",
wetland),
wetland = ifelse(wetland == "Fellsmere", "SJM",
wetland),
wetland = ifelse(wetland == "Arbuckle", "Lake.Arbuckle",
wetland)) |>
# merge with the daily water wetland level
left_join(www_filt_df |>
select(X, wetland, water_imputed)) |>
filter(!is.na(water_imputed) | !is.na(count)) |>
# merge the region
left_join(wetland_region) |>
rename(survey_id = X) |>
# extract survey mean and CV
group_by(region, wetland, year, survey_id, survey_num) |>
summarise(
count = round(mean(count, na.rm = TRUE),0),
mu_water = mean(water_imputed, na.rm = TRUE),
sd_water = sd(water_imputed, na.rm = TRUE)) |>
arrange(region, wetland, year) |>
as.data.frame()
## Joining with `by = join_by(X, wetland)`
## Joining with `by = join_by(wetland)`
## `summarise()` has grouped output by 'region', 'wetland', 'year', 'survey_id'.
## You can override using the `.groups` argument.
We can explore which wetlands has more data
snki_N_wetland_region |>
mutate(region = ifelse(wetland == "STA.Lakeside.Ranch","OTHER",
region)) |>
# to summarize, `group_by()`
group_by(region, wetland) |>
# remove NA surveys
drop_na(count) |>
# how many surveys per wetland?
count() |>
# sort the information (for Table 1 in the report)
arrange(region, -n) |>
filter(region != "OTHER") |>
as.data.frame()
And we select to work with those wetlands with surveys ≥75% of the data in relation with the maximum per region. For example, OKEE include two wetlands, the maximum number of surveys is for Lake Okeechobee (148), the 75% is 111; thus, the second wetland (Lake Hicpochee Impoundments) with only 15 surveys does not have enough surveys.
snki_N_water <- snki_N_wetland_region |>
# here we can select the wetlands with more data to represent each region
filter(wetland %in% c(
"Grassy.Waters.Preserve", # EAST
"WCA.3A", # EVER
"WCA.2B", # EVER
"Loxahatchee.NWR", # EVER
# "WCA.2A", # EVER - but does not have hydrology gauge data
"WCA.3B", # EVER
"Everglades.NP", # EVER
"STA1E", # EVER
"East.Lake.Tohopekaliga", # KRV
"Lake.Kissimmee", # KRV
"Lake.Istokpoga", # KRV
"Lake.Hatchineha", # KRV - below threshold of surveys, but added for managers interest
"Lake.Tohopekaliga", # KRV - below threshold of surveys, but added for managers interest
"Lake.Okeechobee", # OKEE
"Paynes.Prairie", # PP
"SJM" # SJM
)) |>
# unify in a single name
mutate(region.wetland = paste(region, wetland, sep = "_")) |>
filter(year != 2025) |>
arrange(region.wetland,year) |>
as.data.frame()
Data frame for the counts
snki_counts <- snki_N_water |>
dplyr::select(year, survey_num, region.wetland, count) |>
pivot_wider(names_from = region.wetland, values_from = count) |>
as.data.frame()
Data frame for the water level mean (of the week before starting the survey)
snki_water <- snki_N_water |>
dplyr::select(year, survey_num, region.wetland, mu_water) |>
pivot_wider(names_from = region.wetland, values_from = mu_water) |>
as.data.frame()
Data frame for the water level standard deviation (of the week before starting the survey)
snki_water_SD <- snki_N_water |>
dplyr::select(year, survey_num, region.wetland, sd_water) |>
pivot_wider(names_from = region.wetland, values_from = sd_water) |>
as.data.frame()
And generate the three matrices
names.snkicounts <- names(snki_counts)
wet.names <- c(
"EAST_Grassy.Waters.Preserve", # EAST
"EVER_WCA.3A", # EVER
"EVER_WCA.2B", # EVER
"EVER_Loxahatchee.NWR", # EVER
# "WCA.2A", # EVER - but does not have hydrology gauge data
"EVER_WCA.3B", # EVER
"EVER_Everglades.NP", # EVER
"EVER_STA1E", # EVER
"KRV_East.Lake.Tohopekaliga", # KRV
"KRV_Lake.Kissimmee", # KRV
"KRV_Lake.Istokpoga", # KRV
"KRV_Lake.Hatchineha", # KRV - below threshold of surveys, but added for managers interest
"KRV_Lake.Tohopekaliga", # KRV - below threshold of surveys, but added for managers interest
"OKEE_Lake.Okeechobee", # OKEE
"PP_Paynes.Prairie", # PP
"SJM_SJM" # SJM
)
cols.with.counts <- match(wet.names, names.snkicounts)
years <- unique(snki_counts$year)
nyears <- length(years)
year.col <- which(names.snkicounts=="year",arr.in=TRUE)
surv.num <- unique(snki_counts$survey_num)
tt <- years - years[1]
S4mat <- S5mat <- S6mat <- matrix(0, nrow=length(wet.names), ncol=nyears)
Counts.list <- list(S4mat = S4mat, S5mat=S5mat, S6mat=S6mat)
Water.list <- list(W4mat = S4mat, W5mat=S5mat, W6mat=S6mat)
WaterSD.list <- list(W4matSD = S4mat, W5matSD=S5mat, W6matSD=S6mat)
for(i in 1:3){
SN <- surv.num[(i+3)]
ith.rows <- which(snki_counts$survey_num==SN, arr.ind=TRUE)
ith.datmat <- t(snki_counts[ith.rows, cols.with.counts])
ith.watmat <- t(snki_water[ith.rows, cols.with.counts])
ith.watSDmat <- t(snki_water_SD[ith.rows, cols.with.counts])
colnames(ith.datmat) <- colnames(ith.watmat) <- colnames(ith.watSDmat) <- tt
Counts.list[[i]] <- ith.datmat
Water.list[[i]] <- ith.watmat
WaterSD.list[[i]] <- ith.watSDmat
}
The model in full matrix format including water is: \[ \mathbf{X}_{t+1} = \mathbf{A} + \beta\mathbf{W}^2_t+ (\mathbf{I} + \mathbf{B})\mathbf{X}_t + \mathbf{E}_t \]
and the State-space model is completed by including the observation process, as a Poisson error with detection probability:
\[ \mathbf{N}_{t} \sim \text{Poisson}(\mathbf{p}_{t} \times e^{\mathbf{X}_{t}}) \]
MGSS_DC_w4.0 <- function(){
##### Priors (no cloning)
lmuvec ~ dmnorm(mupmean, muprec)
lthetavec ~ dmnorm(lthetamean, lthetaprec)
lsigmasq ~ dnorm(0, 1)
betaw ~ dnorm(0,1)
#mig ~ dbeta(2,2)
mig.near ~ dnorm(0,1)
mig.far ~ dnorm(0,1)
sigmasq <- exp(lsigmasq)
for(pp in 1:p){
muvec[pp] <- exp(lmuvec[pp])
thetavec[pp] <- exp(lthetavec[pp])
cvec[pp] <- exp(-thetavec[pp])
}
Sigma <- (1/sigmasq)*Ip
for(m in 1:p){
for(n in 1:p){
B[m,n] <- ifelse((m==n),cvec[m],ifelse(
(m==step.stone[m,1])&&(n==step.stone[m,2]),
dinvmat[m,n]*mig.near,dinvmat[m,n]*mig.far)
)
}
}
A[1:p] <- (Ip - B[1:p,1:p])%*%muvec
for(k in 1:K){
##### ---- Likelihood (data cloning) ---- #####
X[1:p,1,k] ~ dmnorm(muvec + betaw*W[1:p,1], Sigma) #muvec + beta*water[,1]
for(h in 1:p){
N4[h,1,k] ~ dpois(detectp[h,1]*exp(X[h,1,k]))
N5[h,1,k] ~ dpois(detectp[h,1]*exp(X[h,1,k]))
N6[h,1,k] ~ dpois(detectp[h,1]*exp(X[h,1,k]))
}
for( i in 2:qp1){
## Process error
X[1:p,i,k] ~ dmnorm(A[1:p] + betaw*W[1:p,i] + B[1:p,1:p]%*%X[1:p,(i-1),k], Sigma);
for(j in 1:p){
N4[j,i,k] ~ dpois(detectp[j,i]*exp(X[j,i,k])) ## Observation error
N5[j,i,k] ~ dpois(detectp[j,i]*exp(X[j,i,k])) ## Observation error
N6[j,i,k] ~ dpois(detectp[j,i]*exp(X[j,i,k])) ## Observation error
}
}
}
}
Bring back the counts and water level of the 11 wetlands representing 6 regions.
S4mat <- Counts.list$S4mat[1:15,1:28] # Cut the last time step: no data anywhere
S5mat <- Counts.list$S5mat[1:15,1:28]
S6mat <- Counts.list$S6mat[1:15,1:28]
S4mat[is.na(S4mat)] <- NA
S5mat[is.na(S5mat)] <- NA
S6mat[is.na(S6mat)] <- NA
But for now, lets combine a single value of mean water level across the three surveys (first approach)
W4mat <- Water.list$W4mat[1:15,1:28] # Cut the last time step: no data anywhere
W5mat <- Water.list$W5mat[1:15,1:28]
W6mat <- Water.list$W6mat[1:15,1:28]
W <- (W4mat + W5mat + W6mat) / 3
W[is.na(W)] <- NA
Again, we pick a single time series of counts with no NA
s, and “cbind()
it” to a column of time, thus getting a matrix with time as a first column and counts as a second column. We use this matrix to compute a first guess of the model parameters to be used in all regions.
ts.4guess <- S4mat[8,]+1 # some 0s and NAs bring the first guess to Inf, thus add 1 only for this first guess
tvec4guess <- as.numeric(colnames(S4mat))
onets4guess <- cbind(tvec4guess, ts.4guess)
naive.guess <- guess.calc2.0(TimeAndNs = onets4guess)
naive.guess
## mu theta sigmasq
## 3.39035023 0.50000000 0.05689085
We can bring the distance between regions, using the centroid and distance from the edge of a convex polygon of the historical geolocation of nests in each region. The data was provided in meters, but it could be convenient numerically to convert it to kilometers. In addition, we assign the row names that matches with our survey matrices.
# mean distance from the centroid
wet.dists <- read.csv("Code/Wetlands_centroid distance in meters.csv")[,-c(1:3)]
wet.dists <- as.matrix(wet.dists)/1000
rownames(wet.dists) <- rownames(S4mat)
wet.dists
## EAST_Grassy.Waters.Preserve EVER_WCA.3A EVER_WCA.2B
## EAST_Grassy.Waters.Preserve 0.00000 91.57120 68.56550
## EVER_WCA.3A 91.57120 0.00000 31.03748
## EVER_WCA.2B 68.56550 31.03748 0.00000
## EVER_Loxahatchee.NWR 33.89899 57.76494 36.04266
## EVER_WCA.3B 105.97951 24.70743 37.75726
## EVER_Everglades.NP 170.71000 80.67577 103.23222
## EVER_STA1E 19.73185 73.21239 53.41472
## KRV_East.Lake.Tohopekaliga 201.04187 254.23837 251.10297
## KRV_Lake.Kissimmee 165.51762 212.05397 210.65460
## KRV_Lake.Istokpoga 128.43998 157.22698 160.22437
## KRV_Lake.Hatchineha 184.65786 228.87984 228.62388
## KRV_Lake.Tohopekaliga 198.39992 247.13030 245.54828
## OKEE_Lake.Okeechobee 68.19836 97.69908 95.75480
## PP_Paynes.Prairie 374.63064 421.28001 421.88426
## SJM_SJM 125.16025 191.68028 182.38917
## EVER_Loxahatchee.NWR EVER_WCA.3B EVER_Everglades.NP
## EAST_Grassy.Waters.Preserve 33.89899 105.97951 170.71000
## EVER_WCA.3A 57.76494 24.70743 80.67577
## EVER_WCA.2B 36.04266 37.75726 103.23222
## EVER_Loxahatchee.NWR 0.00000 72.60433 136.90505
## EVER_WCA.3B 72.60433 0.00000 65.58909
## EVER_Everglades.NP 136.90505 65.58909 0.00000
## EVER_STA1E 17.38741 89.60308 153.26979
## KRV_East.Lake.Tohopekaliga 219.41006 278.19424 326.49465
## KRV_Lake.Kissimmee 180.46457 236.25061 283.31056
## KRV_Lake.Istokpoga 134.52612 181.83199 225.44513
## KRV_Lake.Hatchineha 199.02179 253.25463 298.49135
## KRV_Lake.Tohopekaliga 214.87366 271.34095 317.86029
## OKEE_Lake.Okeechobee 69.06911 121.40467 173.58732
## PP_Paynes.Prairie 391.75654 445.83561 487.01483
## SJM_SJM 148.05062 213.90972 269.68855
## EVER_STA1E KRV_East.Lake.Tohopekaliga
## EAST_Grassy.Waters.Preserve 19.73185 201.04187
## EVER_WCA.3A 73.21239 254.23837
## EVER_WCA.2B 53.41472 251.10297
## EVER_Loxahatchee.NWR 17.38741 219.41006
## EVER_WCA.3B 89.60308 278.19424
## EVER_Everglades.NP 153.26979 326.49465
## EVER_STA1E 0.00000 204.17158
## KRV_East.Lake.Tohopekaliga 204.17158 0.00000
## KRV_Lake.Kissimmee 166.18566 43.39006
## KRV_Lake.Istokpoga 123.26315 102.19746
## KRV_Lake.Hatchineha 184.99393 32.76343
## KRV_Lake.Tohopekaliga 200.20058 14.03755
## OKEE_Lake.Okeechobee 59.02138 156.82982
## PP_Paynes.Prairie 377.08714 173.62144
## SJM_SJM 131.49810 79.15795
## KRV_Lake.Kissimmee KRV_Lake.Istokpoga
## EAST_Grassy.Waters.Preserve 165.51762 128.43998
## EVER_WCA.3A 212.05397 157.22698
## EVER_WCA.2B 210.65460 160.22437
## EVER_Loxahatchee.NWR 180.46457 134.52612
## EVER_WCA.3B 236.25061 181.83199
## EVER_Everglades.NP 283.31056 225.44513
## EVER_STA1E 166.18566 123.26315
## KRV_East.Lake.Tohopekaliga 43.39006 102.19746
## KRV_Lake.Kissimmee 0.00000 58.85019
## KRV_Lake.Istokpoga 58.85019 0.00000
## KRV_Lake.Hatchineha 19.23150 73.04775
## KRV_Lake.Tohopekaliga 35.09177 92.60176
## OKEE_Lake.Okeechobee 115.33524 65.52911
## PP_Paynes.Prairie 211.39254 264.08573
## SJM_SJM 56.82928 74.23761
## KRV_Lake.Hatchineha KRV_Lake.Tohopekaliga
## EAST_Grassy.Waters.Preserve 184.65786 198.39992
## EVER_WCA.3A 228.87984 247.13030
## EVER_WCA.2B 228.62388 245.54828
## EVER_Loxahatchee.NWR 199.02179 214.87366
## EVER_WCA.3B 253.25463 271.34095
## EVER_Everglades.NP 298.49135 317.86029
## EVER_STA1E 184.99393 200.20058
## KRV_East.Lake.Tohopekaliga 32.76343 14.03755
## KRV_Lake.Kissimmee 19.23150 35.09177
## KRV_Lake.Istokpoga 73.04775 92.60176
## KRV_Lake.Hatchineha 0.00000 20.33703
## KRV_Lake.Tohopekaliga 20.33703 0.00000
## OKEE_Lake.Okeechobee 132.99615 150.38440
## PP_Paynes.Prairie 193.26326 176.90891
## SJM_SJM 73.92349 80.42423
## OKEE_Lake.Okeechobee PP_Paynes.Prairie SJM_SJM
## EAST_Grassy.Waters.Preserve 68.19836 374.6306 125.16025
## EVER_WCA.3A 97.69908 421.2800 191.68028
## EVER_WCA.2B 95.75480 421.8843 182.38917
## EVER_Loxahatchee.NWR 69.06911 391.7565 148.05062
## EVER_WCA.3B 121.40467 445.8356 213.90972
## EVER_Everglades.NP 173.58732 487.0148 269.68855
## EVER_STA1E 59.02138 377.0871 131.49810
## KRV_East.Lake.Tohopekaliga 156.82982 173.6214 79.15795
## KRV_Lake.Kissimmee 115.33524 211.3925 56.82928
## KRV_Lake.Istokpoga 65.52911 264.0857 74.23761
## KRV_Lake.Hatchineha 132.99615 193.2633 73.92349
## KRV_Lake.Tohopekaliga 150.38440 176.9089 80.42423
## OKEE_Lake.Okeechobee 0.00000 326.2423 96.48053
## PP_Paynes.Prairie 326.24226 0.0000 251.69849
## SJM_SJM 96.48053 251.6985 0.00000
And the annual detection probability estimated from the CMR model
all.ps <- read.csv(file="Code/p.estimates.wt.7.24.25.csv")
names(all.ps)
## [1] "X" "time" "stratum" "estimate" "se" "lcl" "ucl"
## [8] "wt"
# I am going to leave it as a matrix of ps in case we want to make them
# time dependent later
phats <- matrix(nrow=15, ncol=28)
row.names(phats) <- wet.names
for(i in 1:15){
subdat <- all.ps[all.ps$stratum==sub("_.*", "", wet.names)[i],]
phats[i,] <- subdat$estimate
}
The number of (sub)populations p
now is defined as the 15 wetlands, and included in the initial values lists. This is a trick to have the priors in the positive domain of the lognormal distribution.
p <- nrow(S4mat)
myinits <- list(lmuvec = log(rep(naive.guess[1],p)), # mu ≈ mean(log(ts.4guess)),
lthetavec = log(rep(naive.guess[2],p)), # why theta in log?
lsigmasq = log(naive.guess[3])) # why sigmasq in log?
We next define the “dinvmat” choosing one of the distance matrices. This is the inverse matrix of distances, because in our model, the effect of one region on the growth rate of another region will be made inversely proportional to their mutual geographical distance.
dmat <- wet.dists
dinvmat <- (dmat/dmat^2)
dinvmat[is.na(dinvmat)] <- 0
dinvmat
## EAST_Grassy.Waters.Preserve EVER_WCA.3A EVER_WCA.2B
## EAST_Grassy.Waters.Preserve 0.000000000 0.010920464 0.014584595
## EVER_WCA.3A 0.010920464 0.000000000 0.032219110
## EVER_WCA.2B 0.014584595 0.032219110 0.000000000
## EVER_Loxahatchee.NWR 0.029499407 0.017311540 0.027744902
## EVER_WCA.3B 0.009435786 0.040473654 0.026484974
## EVER_Everglades.NP 0.005857888 0.012395295 0.009686898
## EVER_STA1E 0.050679480 0.013658891 0.018721432
## KRV_East.Lake.Tohopekaliga 0.004974088 0.003933317 0.003982430
## KRV_Lake.Kissimmee 0.006041653 0.004715781 0.004747107
## KRV_Lake.Istokpoga 0.007785738 0.006360232 0.006241248
## KRV_Lake.Hatchineha 0.005415421 0.004369105 0.004373996
## KRV_Lake.Tohopekaliga 0.005040325 0.004046448 0.004072519
## OKEE_Lake.Okeechobee 0.014663110 0.010235511 0.010443341
## PP_Paynes.Prairie 0.002669296 0.002373718 0.002370318
## SJM_SJM 0.007989757 0.005217021 0.005482782
## EVER_Loxahatchee.NWR EVER_WCA.3B EVER_Everglades.NP
## EAST_Grassy.Waters.Preserve 0.029499407 0.009435786 0.005857888
## EVER_WCA.3A 0.017311540 0.040473654 0.012395295
## EVER_WCA.2B 0.027744902 0.026484974 0.009686898
## EVER_Loxahatchee.NWR 0.000000000 0.013773283 0.007304332
## EVER_WCA.3B 0.013773283 0.000000000 0.015246439
## EVER_Everglades.NP 0.007304332 0.015246439 0.000000000
## EVER_STA1E 0.057512888 0.011160331 0.006524443
## KRV_East.Lake.Tohopekaliga 0.004557676 0.003594611 0.003062837
## KRV_Lake.Kissimmee 0.005541254 0.004232793 0.003529695
## KRV_Lake.Istokpoga 0.007433501 0.005499582 0.004435669
## KRV_Lake.Hatchineha 0.005024575 0.003948595 0.003350181
## KRV_Lake.Tohopekaliga 0.004653898 0.003685400 0.003146036
## OKEE_Lake.Okeechobee 0.014478253 0.008236915 0.005760789
## PP_Paynes.Prairie 0.002552606 0.002242979 0.002053326
## SJM_SJM 0.006754447 0.004674869 0.003707981
## EVER_STA1E KRV_East.Lake.Tohopekaliga
## EAST_Grassy.Waters.Preserve 0.050679480 0.004974088
## EVER_WCA.3A 0.013658891 0.003933317
## EVER_WCA.2B 0.018721432 0.003982430
## EVER_Loxahatchee.NWR 0.057512888 0.004557676
## EVER_WCA.3B 0.011160331 0.003594611
## EVER_Everglades.NP 0.006524443 0.003062837
## EVER_STA1E 0.000000000 0.004897841
## KRV_East.Lake.Tohopekaliga 0.004897841 0.000000000
## KRV_Lake.Kissimmee 0.006017366 0.023046755
## KRV_Lake.Istokpoga 0.008112725 0.009784979
## KRV_Lake.Hatchineha 0.005405583 0.030521831
## KRV_Lake.Tohopekaliga 0.004994991 0.071237501
## OKEE_Lake.Okeechobee 0.016943013 0.006376338
## PP_Paynes.Prairie 0.002651907 0.005759657
## SJM_SJM 0.007604672 0.012632970
## KRV_Lake.Kissimmee KRV_Lake.Istokpoga
## EAST_Grassy.Waters.Preserve 0.006041653 0.007785738
## EVER_WCA.3A 0.004715781 0.006360232
## EVER_WCA.2B 0.004747107 0.006241248
## EVER_Loxahatchee.NWR 0.005541254 0.007433501
## EVER_WCA.3B 0.004232793 0.005499582
## EVER_Everglades.NP 0.003529695 0.004435669
## EVER_STA1E 0.006017366 0.008112725
## KRV_East.Lake.Tohopekaliga 0.023046755 0.009784979
## KRV_Lake.Kissimmee 0.000000000 0.016992300
## KRV_Lake.Istokpoga 0.016992300 0.000000000
## KRV_Lake.Hatchineha 0.051998032 0.013689675
## KRV_Lake.Tohopekaliga 0.028496709 0.010798931
## OKEE_Lake.Okeechobee 0.008670377 0.015260393
## PP_Paynes.Prairie 0.004730536 0.003786649
## SJM_SJM 0.017596562 0.013470261
## KRV_Lake.Hatchineha KRV_Lake.Tohopekaliga
## EAST_Grassy.Waters.Preserve 0.005415421 0.005040325
## EVER_WCA.3A 0.004369105 0.004046448
## EVER_WCA.2B 0.004373996 0.004072519
## EVER_Loxahatchee.NWR 0.005024575 0.004653898
## EVER_WCA.3B 0.003948595 0.003685400
## EVER_Everglades.NP 0.003350181 0.003146036
## EVER_STA1E 0.005405583 0.004994991
## KRV_East.Lake.Tohopekaliga 0.030521831 0.071237501
## KRV_Lake.Kissimmee 0.051998032 0.028496709
## KRV_Lake.Istokpoga 0.013689675 0.010798931
## KRV_Lake.Hatchineha 0.000000000 0.049171386
## KRV_Lake.Tohopekaliga 0.049171386 0.000000000
## OKEE_Lake.Okeechobee 0.007519014 0.006649626
## PP_Paynes.Prairie 0.005174289 0.005652626
## SJM_SJM 0.013527501 0.012434064
## OKEE_Lake.Okeechobee PP_Paynes.Prairie SJM_SJM
## EAST_Grassy.Waters.Preserve 0.014663110 0.002669296 0.007989757
## EVER_WCA.3A 0.010235511 0.002373718 0.005217021
## EVER_WCA.2B 0.010443341 0.002370318 0.005482782
## EVER_Loxahatchee.NWR 0.014478253 0.002552606 0.006754447
## EVER_WCA.3B 0.008236915 0.002242979 0.004674869
## EVER_Everglades.NP 0.005760789 0.002053326 0.003707981
## EVER_STA1E 0.016943013 0.002651907 0.007604672
## KRV_East.Lake.Tohopekaliga 0.006376338 0.005759657 0.012632970
## KRV_Lake.Kissimmee 0.008670377 0.004730536 0.017596562
## KRV_Lake.Istokpoga 0.015260393 0.003786649 0.013470261
## KRV_Lake.Hatchineha 0.007519014 0.005174289 0.013527501
## KRV_Lake.Tohopekaliga 0.006649626 0.005652626 0.012434064
## OKEE_Lake.Okeechobee 0.000000000 0.003065207 0.010364785
## PP_Paynes.Prairie 0.003065207 0.000000000 0.003973007
## SJM_SJM 0.010364785 0.003973007 0.000000000
Bundle the data cloning fit
data4dclone <- list(K=1,
N4=dcdim(array(S4mat,dim=c(p,ncol(S4mat),1))),
N5=dcdim(array(S5mat,dim=c(p,ncol(S5mat),1))),
N6=dcdim(array(S6mat,dim=c(p,ncol(S6mat),1))),
W = W^2, # testing quadratic
detectp = phats,
qp1=ncol(S4mat),
p=p,
mupmean=rep(6,p),
muprec=(1/3)*diag(p),
lthetamean = rep(0.5,p),
lthetaprec = 10*diag(p),
Ip = diag(p),
dinvmat = dinvmat,
step.stone = matrix(c(1,7,
2,5, 3,2, 4,7, 5,2, 6,5, 7,4,
8,12, 9,11, 10,9, 11,9, 12,8,
13,7,
14,8,
15,9),
nrow=15,ncol=2,byrow=TRUE)
)
Setup the data cloning model fit with wather and annual detection probability
cl.seq <- c(1,20);
n.iter<-100000;n.adapt<-50000;n.update<-100;thin<-10;n.chains<-3;
out.parms <- c("muvec","cvec","mig.near","mig.far","sigmasq","A",
"betaw")
mgssdclone_w <- dc.fit(data4dclone,
params=out.parms,
model=MGSS_DC_w4.0,
n.clones=cl.seq,
multiply="K",
unchanged = c("qp1","p","mupmean","muprec",
"step.stone", "lthetamean","lthetaprec", "Ip","dinvmat",
"W","detectp"),
n.chains = n.chains,
n.adapt=n.adapt,
n.update=n.update,
n.iter = n.iter,
thin=thin,
inits=myinits)
saveRDS(mgssdclone_w, "Code/mgssdclone_w_phat_quadr.RDS")
We can save the MLEs and predict the dynamic of the population for each wetland within six regions with the Kalman filter estimates
mgssdclone_w <- readRDS("Code/mgssdclone_w_phat_quadr.RDS")
mlescentrdist <- summary(mgssdclone_w)[[1]]
mles4kalman <- mlescentrdist[,1]
mles4kalman
## A[1] A[2] A[3] A[4] A[5] A[6]
## 0.60757243 1.32751552 -0.86056937 -0.19839954 -1.17129705 1.53197463
## A[7] A[8] A[9] A[10] A[11] A[12]
## 0.98936060 0.49808510 1.47346846 0.53887603 -0.50488937 2.42643880
## A[13] A[14] A[15] betaw cvec[1] cvec[2]
## 1.11956117 5.64536892 1.31364856 -0.14450645 0.55156395 0.63359713
## cvec[3] cvec[4] cvec[5] cvec[6] cvec[7] cvec[8]
## 0.90440938 0.54713589 0.89818017 0.19088037 0.42034280 0.49360172
## cvec[9] cvec[10] cvec[11] cvec[12] cvec[13] cvec[14]
## 0.44733524 0.51470405 0.15005180 0.09497199 0.58657155 0.01995905
## cvec[15] mig.far mig.near muvec[1] muvec[2] muvec[3]
## 0.57037571 1.35027795 3.14734912 4.42749217 6.53948276 5.08978137
## muvec[4] muvec[5] muvec[6] muvec[7] muvec[8] muvec[9]
## 3.23879094 2.75504423 2.60333081 4.33187089 4.05403721 4.44985769
## muvec[10] muvec[11] muvec[12] muvec[13] muvec[14] muvec[15]
## 2.89008460 1.29369016 4.39433954 4.86026823 6.06836616 4.92852128
## sigmasq
## 1.19707273
avec4kalman <- mles4kalman[1:15]
beta.4kalman <- mles4kalman[16]
cvec4kalman <- mles4kalman[17:31]
mig.far4kalman <- mles4kalman[32]
mig.near4kalman <- mles4kalman[33]
muvec4kalman <- mles4kalman[34:48]
sigsq4kalman <- mles4kalman[49]
MGSS_KALMANw4.0 <- function(){
A <- (Ip - B)%*%muvec
Sigma <- (1/sigmasq)*Ip
##### ---- Likelihood ---- #####
X[1:p,1] ~ dmnorm(muvec + betaw*W[1:p,1], Sigma)
for(h in 1:p){
N4[h,1] ~ dpois(detectp[h,1] * exp(X[h,1]))
N5[h,1] ~ dpois(detectp[h,1] * exp(X[h,1]))
N6[h,1] ~ dpois(detectp[h,1] * exp(X[h,1]))
}
for( i in 2:qp1){
## Process error
X[1:p,i] ~ dmnorm(A + betaw*W[1:p,i] + B%*%X[1:p,(i-1)], Sigma);
for(j in 1:p){
N4[j,i] ~ dpois(detectp[j,i] * exp(X[j,i])) ## Observation error
N5[j,i] ~ dpois(detectp[j,i] * exp(X[j,i])) ## Observation error
N6[j,i] ~ dpois(detectp[j,i] * exp(X[j,i])) ## Observation error
}
}
}
Data for Kalman estimates
step.stone <- matrix(c(1,7,
2,5, 3,2, 4,7, 5,2, 6,5, 7,4,
8,12, 9,11, 10,9, 11,9, 12,8,
13,7,
14,8,
15,9),
nrow=15,
ncol=2,
byrow=TRUE)
step.stone
## [,1] [,2]
## [1,] 1 7
## [2,] 2 5
## [3,] 3 2
## [4,] 4 7
## [5,] 5 2
## [6,] 6 5
## [7,] 7 4
## [8,] 8 12
## [9,] 9 11
## [10,] 10 9
## [11,] 11 9
## [12,] 12 8
## [13,] 13 7
## [14,] 14 8
## [15,] 15 9
B <- matrix(0,p,p)
for(m in 1:p){
for(n in 1:p){
B[m,n] <- ifelse((m==n),cvec4kalman[m],
ifelse(
(m==step.stone[m,1])&&(n==step.stone[m,2]),
dinvmat[m,n]*mig.near4kalman,dinvmat[m,n]*mig.far4kalman)
) #
}
}
print(B) # this is \mathcal{B}, because the diagonal is the `cvec4kalman` (intra-region strength of density dependence)
## [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] 0.551563954 0.014745661 0.019693257 0.039832398 0.012740934 0.007909777
## [2,] 0.014745661 0.633597130 0.043504754 0.023375391 0.127384719 0.016737093
## [3,] 0.019693257 0.101404788 0.904409376 0.037463329 0.035762076 0.013080004
## [4,] 0.039832398 0.023375391 0.037463329 0.547135892 0.018597760 0.009862879
## [5,] 0.012740934 0.127384719 0.035762076 0.018597760 0.898180168 0.020586930
## [6,] 0.007909777 0.016737093 0.013080004 0.009862879 0.047985865 0.190880368
## [7,] 0.068431384 0.018443300 0.025279137 0.181013137 0.015069549 0.008809811
## [8,] 0.006716402 0.005311071 0.005377387 0.006154130 0.004853724 0.004135682
## [9,] 0.008157911 0.006367615 0.006409914 0.007482233 0.005715447 0.004766070
## [10,] 0.010512910 0.008588081 0.008427419 0.010037292 0.007425965 0.005989386
## [11,] 0.007312323 0.005899506 0.005906111 0.006784573 0.005331701 0.004523675
## [12,] 0.006805839 0.005463830 0.005499032 0.006284055 0.004976315 0.004248023
## [13,] 0.019799274 0.013820784 0.014101413 0.019549666 0.011122125 0.007778667
## [14,] 0.003604291 0.003205179 0.003200589 0.003446727 0.003028645 0.002772560
## [15,] 0.010788393 0.007044428 0.007403279 0.009120381 0.006312373 0.005006805
## [,7] [,8] [,9] [,10] [,11] [,12]
## [1,] 0.159506016 0.006716402 0.008157911 0.010512910 0.007312323 0.006805839
## [2,] 0.018443300 0.005311071 0.006367615 0.008588081 0.005899506 0.005463830
## [3,] 0.025279137 0.005377387 0.006409914 0.008427419 0.005906111 0.005499032
## [4,] 0.181013137 0.006154130 0.007482233 0.010037292 0.006784573 0.006284055
## [5,] 0.015069549 0.004853724 0.005715447 0.007425965 0.005331701 0.004976315
## [6,] 0.008809811 0.004135682 0.004766070 0.005989386 0.004523675 0.004248023
## [7,] 0.420342803 0.006613447 0.008125117 0.010954433 0.007299039 0.006744626
## [8,] 0.006613447 0.493601719 0.031119526 0.013212441 0.041212955 0.224209285
## [9,] 0.008125117 0.031119526 0.447335235 0.022944327 0.163655962 0.038478478
## [10,] 0.010954433 0.013212441 0.053480699 0.514704052 0.018484867 0.014581559
## [11,] 0.007299039 0.041212955 0.163655962 0.018484867 0.150051802 0.066395039
## [12,] 0.006744626 0.224209285 0.038478478 0.014581559 0.066395039 0.094971993
## [13,] 0.053325578 0.008609829 0.011707419 0.020605772 0.010152759 0.008978843
## [14,] 0.003580811 0.018127652 0.006387538 0.005113029 0.006986728 0.007632617
## [15,] 0.010268421 0.017058020 0.055382523 0.018188596 0.018265886 0.016789443
## [,13] [,14] [,15]
## [1,] 0.019799274 0.003604291 0.010788393
## [2,] 0.013820784 0.003205179 0.007044428
## [3,] 0.014101413 0.003200589 0.007403279
## [4,] 0.019549666 0.003446727 0.009120381
## [5,] 0.011122125 0.003028645 0.006312373
## [6,] 0.007778667 0.002772560 0.005006805
## [7,] 0.022877777 0.003580811 0.010268421
## [8,] 0.008609829 0.007777138 0.017058020
## [9,] 0.011707419 0.006387538 0.023760249
## [10,] 0.020605772 0.005113029 0.018188596
## [11,] 0.010152759 0.006986728 0.018265886
## [12,] 0.008978843 0.007632617 0.016789443
## [13,] 0.586571547 0.004138881 0.013995341
## [14,] 0.004138881 0.019959047 0.005364664
## [15,] 0.013995341 0.005364664 0.570375706
data4kalman <- list(N4=S4mat,
N5=S5mat,
N6=S6mat,
W = W^2,
qp1=ncol(S4mat),
p=p,
detectp = phats,
Ip = diag(p),
betaw = beta.4kalman,
muvec = muvec4kalman,
sigmasq = sigsq4kalman,
B=B
)
str(data4kalman)
## List of 12
## $ N4 : num [1:15, 1:28] 3 56 36 3 9 NA NA 27 NA NA ...
## ..- attr(*, "dimnames")=List of 2
## .. ..$ : chr [1:15] "EAST_Grassy.Waters.Preserve" "EVER_WCA.3A" "EVER_WCA.2B" "EVER_Loxahatchee.NWR" ...
## .. ..$ : chr [1:28] "0" "1" "2" "3" ...
## $ N5 : num [1:15, 1:28] 14 184 47 1 3 NA NA 36 NA NA ...
## ..- attr(*, "dimnames")=List of 2
## .. ..$ : chr [1:15] "EAST_Grassy.Waters.Preserve" "EVER_WCA.3A" "EVER_WCA.2B" "EVER_Loxahatchee.NWR" ...
## .. ..$ : chr [1:28] "0" "1" "2" "3" ...
## $ N6 : num [1:15, 1:28] 7 93 30 1 0 NA NA 28 NA NA ...
## ..- attr(*, "dimnames")=List of 2
## .. ..$ : chr [1:15] "EAST_Grassy.Waters.Preserve" "EVER_WCA.3A" "EVER_WCA.2B" "EVER_Loxahatchee.NWR" ...
## .. ..$ : chr [1:28] "0" "1" "2" "3" ...
## $ W : num [1:15, 1:28] 1.556 0.424 3.21 0.132 0.126 ...
## ..- attr(*, "dimnames")=List of 2
## .. ..$ : chr [1:15] "EAST_Grassy.Waters.Preserve" "EVER_WCA.3A" "EVER_WCA.2B" "EVER_Loxahatchee.NWR" ...
## .. ..$ : chr [1:28] "0" "1" "2" "3" ...
## $ qp1 : int 28
## $ p : int 15
## $ detectp: num [1:15, 1:28] 0.236 0.236 0.236 0.236 0.236 ...
## ..- attr(*, "dimnames")=List of 2
## .. ..$ : chr [1:15] "EAST_Grassy.Waters.Preserve" "EVER_WCA.3A" "EVER_WCA.2B" "EVER_Loxahatchee.NWR" ...
## .. ..$ : NULL
## $ Ip : num [1:15, 1:15] 1 0 0 0 0 0 0 0 0 0 ...
## $ betaw : Named num -0.145
## ..- attr(*, "names")= chr "betaw"
## $ muvec : Named num [1:15] 4.43 6.54 5.09 3.24 2.76 ...
## ..- attr(*, "names")= chr [1:15] "muvec[1]" "muvec[2]" "muvec[3]" "muvec[4]" ...
## $ sigmasq: Named num 1.2
## ..- attr(*, "names")= chr "sigmasq"
## $ B : num [1:15, 1:15] 0.5516 0.0147 0.0197 0.0398 0.0127 ...
And fit the Kalman estimate model
n.iter<-100000
n.adapt<-1000
n.update<-100
thin<-10
n.chains<-3
out.parms <- c("X")
kalman.mcmc.w <- jags.fit(data4kalman,
params=out.parms,
model=MGSS_KALMANw4.0,
n.chains = n.chains,
n.adapt=n.adapt,
n.update=n.update,
n.iter = n.iter,
thin=thin)
saveRDS(kalman.mcmc.w, "Code/Kalman_w_quad_phat_OUSS.RDS")
And then, bring back the data to see the population trajectories in each region.
kalman.mcmc.w <- readRDS("Code/Kalman_w_quad_phat_OUSS.RDS")
Xkalman.w <- do.call(rbind, kalman.mcmc.w)
Kalman.quants.w <- apply(Xkalman.w, 2, FUN=function(x){quantile(x, probs=c(0.025,0.5,0.975))})
Converting the matrix form to a data frame and extracting the values
kalman_df_w <- as.data.frame(Kalman.quants.w) |>
mutate(quantile = rownames(Kalman.quants.w)) |>
pivot_longer(-quantile,
names_to = "state_time",
values_to = "value") |>
extract(state_time,
into = c("wetland", "time"),
regex = "X\\[(\\d+),(\\d+)\\]", convert = TRUE)
And then plotting with the names of the regions and in real scale of counts, adding the three surveys per year
kalman_wide_w <- kalman_df_w |>
mutate(value = exp(value),
wetland = ifelse(wetland == 1, "EAST_Grassy.Waters.Preserve",
ifelse(wetland == 2, "EVER_WCA.3A",
ifelse(wetland == 3, "EVER_WCA.2B",
ifelse(wetland == 4, "EVER_Loxahatchee.NWR",
ifelse(wetland == 5, "EVER_WCA.3B",
ifelse(wetland == 6, "EVER_Everglades.NP",
ifelse(wetland == 7, "EVER_STA1E",
ifelse(wetland == 8, "KRV_East.Lake.Tohopekaliga",
ifelse(wetland == 9, "KRV_Lake.Kissimmee",
ifelse(wetland == 10, "KRV_Lake.Istokpoga",
ifelse(wetland == 11, "KRV_Lake.Hatchineha",
ifelse(wetland == 12, "KRV_Lake.Tohopekaliga",
ifelse(wetland == 13, "OKEE_Lake.Okeechobee",
ifelse(wetland == 14, "PP_Paynes.Prairie",
ifelse(wetland == 15, "SJM_SJM",
wetland)))))))))))))))) |>
pivot_wider(names_from = quantile,
values_from = value)
#counts
S4df <- as.data.frame(S4mat) |>
mutate(wetland = rownames(S4mat)) |>
pivot_longer(-wetland,
names_to = "time",
values_to = "Count4")
S5df <- as.data.frame(S5mat) |>
mutate(wetland = rownames(S5mat)) |>
pivot_longer(-wetland,
names_to = "time",
values_to = "Count5")
S6df <- as.data.frame(S6mat) |>
mutate(wetland = rownames(S6mat)) |>
pivot_longer(-wetland,
names_to = "time",
values_to = "Count6")
Countsdf <- S4df |>
left_join(S5df) |>
left_join(S6df) |>
mutate(time = as.numeric(time) + 1)
## Joining with `by = join_by(wetland, time)`
## Joining with `by = join_by(wetland, time)`
kalman_wide <- kalman_wide_w |>
left_join(Countsdf) |>
mutate(time = time + 1996,
region = str_extract(wetland, "[^_]+"))
## Joining with `by = join_by(wetland, time)`
kalman_wide$wetland <- factor(kalman_wide$wetland,
levels = c("EAST_Grassy.Waters.Preserve", # EAST
"EVER_WCA.3A", # EVER
"EVER_WCA.2B", # EVER
"EVER_Loxahatchee.NWR", # EVER
"EVER_WCA.3B", # EVER
"EVER_Everglades.NP", # EVER
"EVER_STA1E", # EVER
"KRV_East.Lake.Tohopekaliga", # KRV
"KRV_Lake.Kissimmee", # KRV
"KRV_Lake.Istokpoga", # KRV
"KRV_Lake.Hatchineha", # KRV
"KRV_Lake.Tohopekaliga", # KRV
"OKEE_Lake.Okeechobee", # OKEE
"PP_Paynes.Prairie", # PP
"SJM_SJM" # SJM
))
# Plot using ggplot2
Wetland_water_detectp <- ggplot(kalman_wide, aes(x = time, y = `50%`,
color = factor(region),
fill = factor(region))) +
facet_wrap(~factor(wetland,
levels = c(
"EVER_Everglades.NP","EVER_WCA.3B","EVER_Loxahatchee.NWR",
"EVER_STA1E","EVER_WCA.2B","EVER_WCA.3A",
"OKEE_Lake.Okeechobee",
"KRV_Lake.Hatchineha","KRV_Lake.Istokpoga",
"KRV_East.Lake.Tohopekaliga",
"KRV_Lake.Tohopekaliga",
"KRV_Lake.Kissimmee",
"EAST_Grassy.Waters.Preserve",
"SJM_SJM",
"PP_Paynes.Prairie"
)),
ncol = 3, scales = "free_y") +
geom_line() +
geom_ribbon(aes(ymin = `2.5%`, ymax = `97.5%`),
alpha = 0.2, color = NA) +
geom_point(aes(y = Count4), shape = 25, fill = NA)+
geom_point(aes(y = Count5), shape = 21, fill = NA)+
geom_point(aes(y = Count6), shape = 24, fill = NA)+
labs(
x = "Year",
y = "Local resident abundance",
color = "",
fill = ""
) +
scale_color_manual(values=met.brewer("Homer2", 6)) +
scale_fill_manual(values=met.brewer("Homer2", 6)) +
theme_classic() +
theme(legend.position = "bottom",
legend.direction = "horizontal",
plot.margin = margin(0.5, 1, 0.5, 0.5, "cm")) +
guides(color=guide_legend(nrow =1),
fill=guide_legend(nrow = 1))
ggsave(Wetland_water_detectp, filename = "Wetland_15_water_phat.pdf",dpi = 300,
width = 200, height = 150, units = "mm")
ggsave(Wetland_water_detectp, filename = "Wetland_15_water_phat.jpg",dpi = 300,
width = 200, height = 150, units = "mm")
In this plot, the open points represent three surveys per year: down-facing triangles are survey 4, circles are survey 5, and up-facing triangles are survey 6. This dynamic includes the extra parameter for detectability and the influence of relative water sage as a quadratic effect. Using quadratic effect of water reduces the confidence intervals of predicted trajectories of the population.
Our modified Gompertz model in full matrix format that includes standardized quadratic water level effects is:
\[ \mathbf{X}_{t+1} = \mathbf{A} + \beta\mathbf{W}^2_t+ (\mathbf{I} + \mathbf{B})\mathbf{X}_t + \mathbf{E}_t \] where \(\mathbf{W}\) denotes the standardized water level as explained in the main text. The State-space model is completed by including the observation process, as a Poisson error with detection probability:
\[ \mathbf{N}_{t} \sim \text{Poisson}(p_{t} \times e^{\mathbf{X}_{t}}). \]
The idea of including the effect of water levels in the growth rate is to be able to compare the estimated growth rate after accounting for the effect of water with the standard growth rate, a little bit like one would compare the reduction of the residual sum of squares brought about by an extra factor added to a regression model. From the initial model we have that
\[ \begin{array}{lll} \mathbf{X}_{t+1} &=& \mathbf{X}_t + \mathbf{A} + \mathbf{BX}_t + \mathbf{E}_t\\ &=& \mathbf{A} + (\mathbf{I} + \mathbf{B})\mathbf{X}_t + \mathbf{E}_t\quad\text{where}\quad\mathcal{B} = \mathbf{I}+ \mathbf{B}. \end{array} \] Hence, the growth rate \(\text{R}_t\) is written as: \[ \begin{array}{lll} \text{R}_t&=& \mathbf{X}_{t+1} - \mathbf{X}_t \\ &=& \mathbf{A} + \mathbf{BX}_t+\mathbf{E}_t \\ &=& \mathbf{A} + (\mathcal{B}-\mathbf{I})\mathbf{X}_t + \mathbf{E}_t. \end{array} \]
With a water covariate effect, the growth rate becomes:
\[ \text{R}_{t}^{W}= \mathbf{X}_{t+1} - \mathbf{X}_t = \mathbf{A} + \mathbf{BX}_t + \beta W_t^2 + \mathbf{E}_t. \]
%%%%%%%%%%%% CONTINUE HERE
Thus, to achieve a benchmark comparison with the model without a water covariate, we can simply subtract both growth rates which simply yields \(\beta W^2\). Because that coefficient is assumed to be the same for all populations, scaling it by the vector of means \(\boldsymbol{\mu} = \mathbf{(I-B)^{-1}A}\) allows one to compare visually how the water term affects the growth rate. Thus, we set the estimate of the water effects as:
\[ \boldsymbol{\beta_W} = \frac{\widehat{\beta}W_t^{2}}{\widehat{\boldsymbol{\mu}}}. \]
where we abuse a bit of the vector notation and remark that the division in the above fraction represents element-wise division. Thus, \(\boldsymbol{\beta_W}\) is a vector of estimated water effects per wetland studied. We emphasize that adopting this statistic to estimate the water effects is useful because it directly shows how the maximum growth rates, the strength of density dependence and the inter-demes effects \(b_{ij}\) regulate the effect of water level changes in every deme. Finally, note that using the value of \(\boldsymbol{\mu}\) to scale the water effect is also justified because it is the value of the process that renders the growth rate zero on average. To see why, recall that in the long-run, \(\mathbf{X}_t = \mathbf{X}_{t+1} = \mathbf{X}_{\infty}\) is invariant over time so that the average growth rate is \(\text{E}[\text{R}_t] = \mathbf{0} = \text{E}[\mathbf{A} + (\mathcal{B}-\mathbf{I})\mathbf{X}_\infty + \mathbf{E}_t]\). To check that this is true, note that the right hand side (RHS) of this last equation is \(\mathbf{A} + (\mathcal{B}-\mathbf{I})\text{E}[\mathbf{X}_\infty] = \mathbf{A} + (\mathcal{B}-\mathbf{I})\boldsymbol{\mu}\) and since \(\boldsymbol{\mu} = (\mathbf{I-B})^{-1}\mathbf{A}\) the last equation becomes \(\mathbf{A} -\mathbf{IA} = \mathbf{0}\).
# standardize growth rate changes per population
# ((A + BX - ßW)-(A + BX)) / (A + BX)
# ßW/(A+BX) # fraction of reduction in growth rate
# average growth rate (A+(B-I)X)
avg.gr <- muvec4kalman #avec4kalman + (B-diag(p)) %*% muvec4kalman
# Get a range of Water level (centered) for plotting
W.sim = seq(min(W),max(W),by = 0.01)
W.sim.sqr = W.sim^2
std.gr.hat <- matrix(0, nrow = length(W.sim), ncol = 15)
for (i in 1:15){
#std.gr.hat[,i] <- (beta.4kalman * W.sim.sqr) / (avg.gr[i])
std.gr.hat[,i] <- (beta.4kalman * W.sim.sqr) / (avg.gr[i]^2)
}
cool.palette <- as.vector(met.brewer("Signac",15))
wet.names
## [1] "EAST_Grassy.Waters.Preserve" "EVER_WCA.3A"
## [3] "EVER_WCA.2B" "EVER_Loxahatchee.NWR"
## [5] "EVER_WCA.3B" "EVER_Everglades.NP"
## [7] "EVER_STA1E" "KRV_East.Lake.Tohopekaliga"
## [9] "KRV_Lake.Kissimmee" "KRV_Lake.Istokpoga"
## [11] "KRV_Lake.Hatchineha" "KRV_Lake.Tohopekaliga"
## [13] "OKEE_Lake.Okeechobee" "PP_Paynes.Prairie"
## [15] "SJM_SJM"
#std.gr.hat <- mat.forlist
colnames(std.gr.hat) <- wet.names
mean_W_wetland <- daily_wetland_water |>
group_by(region.wetland) |>
summarize(mean_stage = mean(mean_stage, na.rm = TRUE)) |>
as.data.frame()
mean_W4wetland <- matrix(NA,nrow = length(wet.names),ncol = 1)
for(i in 1:15){
mean_W4wetland[i] <- mean_W_wetland$mean_stage[mean_W_wetland$region.wetland == wet.names[i]]
}
par(mfrow=c(2,3), oma=c(2,2,2,2), mar=c(4,4,4,2))
plot(W.sim+mean_W4wetland[1], std.gr.hat[,1], col=cool.palette[1],
type = "l",
xlab="", ylab="",
xlim = c(5,30),
ylim=c(-1,0),
bty="l", main="EAST", cex.main=1.25,lwd=2)
for(i in 2:7){
if(i==2){
plot(W.sim+mean_W4wetland[i],std.gr.hat[,i], type="l", col=cool.palette[i],
xlim = c(5,30),
ylim=c(-1,0),
xlab="", ylab="",
bty="l", main="EVER", cex.main=1.25,lwd=2)
}
else{
points(W.sim+mean_W4wetland[i],std.gr.hat[,i], type="l", col=cool.palette[i],lwd=2)
}
}
for(i in 8:12){
if(i==8){
plot(W.sim+mean_W4wetland[i],std.gr.hat[,i], type="l", col=cool.palette[i],
xlim = c(35,60),
ylim=c(-1,0),
xlab="", ylab="",
bty="l", main="KRV", cex.main=1.25,lwd=2)
}
else{
points(W.sim+mean_W4wetland[i],std.gr.hat[,i], type="l", col=cool.palette[i],lwd=2)
}
}
plot(W.sim+mean_W4wetland[13], std.gr.hat[,13], col=cool.palette[13],lwd=2,
type = "l",
xlim = c(1,26),
xlab="", ylab="", ylim=c(-1,0),
bty="l", main="OKEE", cex.main=1.25)
plot(W.sim+mean_W4wetland[14], std.gr.hat[,14], col=cool.palette[14],lwd=2,
type = "l",
xlim = c(40,65),
xlab="", ylab="", ylim=c(-1,0),
bty="l", main ="PP", cex.main=1.25)
plot(W.sim+mean_W4wetland[15], std.gr.hat[,15], col=cool.palette[15],lwd=2,
type = "l",
xlim = c(8,33),
xlab="", ylab="",
ylim=c(-1,0),
bty="l", main="SJM", cex.main=1.25)
mtext("Wetland stage", side=1, cex=1.25, outer=TRUE)
mtext("Std. ∆ in growth rate",side=2, cex=1.25, outer=TRUE)
Next, we show the code to compute the confidence intervals for the parabolic curves shown in the previous plot:
# create a Function to compute the
# standardized growth rate changes per population
# ((A + BX - ßW)-(A + BX)) / (A + BX)
# ßW/(A+BX) # fraction of reduction in growth rate
one.boot.quad.effect <- function(p, muvec4kalman, beta.4kalman){
# average growth rate (A+(B-I)X)
avg.gr <- muvec4kalman
#avg.gr[avg.gr==0] <- .Machine$double.xmin
#print(avg.gr)
# Get a range of Water level (centered) for plotting
W.sim = seq(min(W),max(W),by = 0.01)
W.sim.sqr = W.sim^2
std.gr.hat <- matrix(0, nrow = length(W.sim), ncol = 15)
for (i in 1:15){
std.gr.hat[,i] <- (beta.4kalman * W.sim.sqr) / (avg.gr[i]^2)
}
return(std.gr.hat)
}
# Pull back the MCMC chain only with the parameters I need
# Print these three lines to recall the structure of the MCMC chain
length(mgssdclone_w)
dim(mgssdclone_w[[1]])
colnames(mgssdclone_w[[1]])
NMCMC <- dim(mgssdclone_w[[1]])[1]
st.gr.boot.list <- list() #Here I will save 10000 std.gr.hats
inftest <- rep(0, NMCMC)
for(i in 1:NMCMC){
# Retrieve the model parameters:
beta.star <- mgssdclone_w[[1]][i,16]
muvec.star <- mgssdclone_w[[1]][i,34:48]
mat.forlist <- one.boot.quad.effect(p=p,
muvec4kalman=muvec.star,
beta.4kalman=beta.star)
st.gr.boot.list[[i]] <- mat.forlist
notinf <- sum(is.infinite(mat.forlist))<1
inftest[i] <- notinf
}
# Test plot
# par(mfrow=c(1,1), oma=c(2,2,2,2), mar=c(4,4,4,2))
# plot(W.sim, std.gr.hat[,1], col=cool.palette[1],
# type = "l",
# xlab="", ylab="",
# xlim = c(min(W),max(W)),ylim=c(-2,0),
# bty="l", main="EAST", cex.main=1.25,lwd=2)
# for(i in 1:NMCMC){
# points(W.sim, st.gr.boot.list[[i]][,1],col=scales::alpha(cool.palette[1],0.01),
# type = "l")
# }
saveRDS(st.gr.boot.list, file="Code/stgrbootlist_15wetlands.RDS")
st.gr.boot.list <- readRDS(file="Code/stgrbootlist_15wetlands.RDS")
# Re-organizing the 10,000 list into 11 matrices each with 10,000 columns
NMCMC <- dim(mgssdclone_w[[1]])[1]
std.gr.15.mats <- list()
for(i in 1:15){
ith.of15.bootmat <- matrix(0,nrow=length(W.sim), ncol=NMCMC)
for(j in 1:NMCMC){
ith.of15.bootmat[,j] <- st.gr.boot.list[[j]][,i]
}
std.gr.15.mats[[i]] <- ith.of15.bootmat
}
# Maybe it's better to save the 15 matrices of quantiles as well:
quants4.15.mats <- list()
for(i in 1:15){
quants4.15.mats[[i]] <- apply(std.gr.15.mats[[i]], 1,
FUN=function(x){quantile(x, probs=c(0.025,0.5,0.975))})
}
par(mfrow=c(3,5), oma=c(2,2,2,2), mar=c(4,3,3,2))
for(i in 1:15){
plot(W.sim+mean_W4wetland[i], std.gr.hat[,i], col=cool.palette[i],
type = "l",
xlab="", ylab="",
xlim = c(mean_W4wetland[i]-15,mean_W4wetland[i]+15),ylim=c(-1,0),
bty="l", main=wet.names[i], cex.main=1.25,lwd=2)
polygon(c(W.sim+mean_W4wetland[i],rev(W.sim+mean_W4wetland[i])),
c(quants4.15.mats[[i]][1,],
rev(quants4.15.mats[[i]][3,])),
col=scales::alpha(cool.palette[i],0.5), border=NA)
points(W.sim+mean_W4wetland[i], std.gr.hat[,i], col=cool.palette[i],
type = "l", lwd=2)
}
mtext("Relative water (stage)", side=1, cex=1.25, outer=TRUE)
mtext("Std. ∆ in growth rate",side=2, cex=1.25, outer=TRUE)
Organizing the figure form the more sensible (lower \(\hat{\mu}\)) to the less sensible (biggest \(\hat{\mu}\))
sensible <- c(11,6,5,10,4,8,7,12,1,9,13,15,3,14,2)
sensi.palette <- as.vector(met.brewer("Homer2",15))
counter <- 1
par(mfrow=c(5,3), oma=c(2,2,2,2), mar=c(4,3,3,2))
for(i in sensible){
plot(W.sim+mean_W4wetland[i], std.gr.hat[,i], col=sensi.palette[counter],
type = "l",
xlab="", ylab="",
xlim = c(mean_W4wetland[i]-7.5,mean_W4wetland[i]+7.5),
ylim=c(-1,0),
bty="l", main=wet.names[i], cex.main=1.25,lwd=2)
polygon(c(W.sim+mean_W4wetland[i],rev(W.sim+mean_W4wetland[i])),
c(quants4.15.mats[[i]][1,],
rev(quants4.15.mats[[i]][3,])),
col=scales::alpha(sensi.palette[counter],0.5), border=NA)
points(W.sim+mean_W4wetland[i], std.gr.hat[,i], col=sensi.palette[counter],
type = "l", lwd=2)
counter <- counter + 1
}
mtext("Wetland water stage", side=1, cex=1.25, outer=TRUE)
mtext("Std. ∆ in growth rate",side=2, cex=1.25, outer=TRUE)
We cannot project to many years ahead the model, because the covariates messed with the stationary definition. But we can model the next step and the changes in relative water level.
Let’s bring the last point estimation per wetland:
len <- ncol(S4mat)
names.quants.1 <- substr(colnames(Kalman.quants.w), start=1, stop=3)
names.quants.2 <- substr(colnames(Kalman.quants.w), start=1, stop=4)
X1s <- Kalman.quants.w[,which(names.quants.1=="X[1", arr.ind=TRUE)]
X2s <- Kalman.quants.w[,which(names.quants.1=="X[2", arr.ind=TRUE)]
X3s <- Kalman.quants.w[,which(names.quants.1=="X[3", arr.ind=TRUE)]
X4s <- Kalman.quants.w[,which(names.quants.1=="X[4", arr.ind=TRUE)]
X5s <- Kalman.quants.w[,which(names.quants.1=="X[5", arr.ind=TRUE)]
X6s <- Kalman.quants.w[,which(names.quants.1=="X[6", arr.ind=TRUE)]
X7s <- Kalman.quants.w[,which(names.quants.1=="X[7", arr.ind=TRUE)]
X8s <- Kalman.quants.w[,which(names.quants.1=="X[8", arr.ind=TRUE)]
X9s <- Kalman.quants.w[,which(names.quants.1=="X[9", arr.ind=TRUE)]
X10s <- Kalman.quants.w[,which(names.quants.2=="X[10", arr.ind=TRUE)]
X11s <- Kalman.quants.w[,which(names.quants.2=="X[11", arr.ind=TRUE)]
X12s <- Kalman.quants.w[,which(names.quants.2=="X[12", arr.ind=TRUE)]
X13s <- Kalman.quants.w[,which(names.quants.2=="X[13", arr.ind=TRUE)]
X14s <- Kalman.quants.w[,which(names.quants.2=="X[14", arr.ind=TRUE)]
X15s <- Kalman.quants.w[,which(names.quants.2=="X[15", arr.ind=TRUE)]
We can estimate the change in abundance distribution with change in relative water. Given that it is a relative value centered around 0, multiply by a percentage of change change mainly the spread of the data, but little change in mean value (from green
to red
or blue
:
par(mfrow=c(2,3), oma=c(2,2,2,2), mar=c(4,4,4,2))
hist(W, breaks = 50, xlim = c(-10,10), col = "green")
abline(v = mean(W), col = "green")
hist(W*0.5, breaks = 50, xlim = c(-10,10), col = "red")
abline(v = mean(W), col = "green")
abline(v = mean(W*0.5), col = "red")
hist(W*1.5, breaks = 50, xlim = c(-10,10), col = "blue")
abline(v = mean(W), col = "green")
abline(v = mean(W*0.5), col = "red")
abline(v = mean(W*1.5), col = "blue")
hist(W, breaks = 50, xlim = c(-10,10), col = "green")
abline(v = mean(W), col = "green")
hist(W+2, breaks = 50, xlim = c(-10,10), col = "red")
abline(v = mean(W), col = "green")
abline(v = mean(W+2), col = "red")
hist(W-2, breaks = 50, xlim = c(-10,10), col = "blue")
abline(v = mean(W), col = "green")
abline(v = mean(W+2), col = "red")
abline(v = mean(W-2), col = "blue")
But we can add specific values of change in relative water, assuming change in values but not in spread
Thus, we can simulate the next point with the quadratic relationship of Relative water (stage)
MVX.sim <- function(
xo.vec = c(X1s[2,28],
X2s[2,28],X3s[2,28],X4s[2,28],X5s[2,28],X6s[2,28],X7s[2,28],
X8s[2,28],X9s[2,28],X10s[2,28],X11s[2,28],X12s[2,28],
X13s[2,28],
X14s[2,28],
X15s[2,28]),
W = W[,28],
theta.list = list(p=p,
Ip = diag(p),
muvec = muvec4kalman,
sigmasq = sigsq4kalman,
B = B,
beta = beta.4kalman),
rnames = row.names(S4mat),
Nsims = 10,
pt.change = 1
){
W <- (W+pt.change)^2 #change in value of Water, but quadratic effect
p <- theta.list$p
Ip <- theta.list$Ip
muvec <- theta.list$muvec
sigmasq <- theta.list$sigmasq
B <- theta.list$B
beta <- theta.list$beta
A <- (Ip - B)%*%muvec
Sigma <- (1/sigmasq)*Ip
## Process error
X <- randmvn(n=Nsims,
mu.vec=A + beta*W + B%*%xo.vec, cov.mat=Sigma)
#X <- cbind(xo.vec,X)
row.names(X) <- rnames
colnames(X) <- 1:Nsims
return(X)
}
And we can try different values, like above the Relative stage values in Fig. 4b of Fletcher et al. 2021.
probs.change.list <- list()
changes <- seq(5,-5, by=-2.5)
nchanges <- length(changes)
for(i in 1:nchanges){
probs.change.list[[i]] <- MVX.sim(
xo.vec = c(X1s[2,28],
X2s[2,28],X3s[2,28],X4s[2,28],X5s[2,28],X6s[2,28],X7s[2,28],
X8s[2,28],X9s[2,28],X10s[2,28],X11s[2,28],X12s[2,28],
X13s[2,28],
X14s[2,28],
X15s[2,28]),
W = W[,28],
theta.list = list(p=p,
Ip = diag(p),
muvec = muvec4kalman,
sigmasq = sigsq4kalman,
B = B,
beta = beta.4kalman),
rnames = row.names(S4mat),
Nsims = 100000,
pt.change = changes[i])
}
names(probs.change.list) <- as.character(sprintf("%.1f", changes))
probs.change.df <- bind_rows(
lapply(names(probs.change.list), function(name) {
df <- as.data.frame(probs.change.list[[name]])
# Save row names as a column
df$wetland <- rownames(probs.change.list[[name]])
# Add the original name as a column
df$change <- paste0("∆ ",name)
return(df)
}),
.id = "id" # Optional: adds an index column
)
table(probs.change.df$change)
##
## ∆ -2.5 ∆ -5.0 ∆ 0.0 ∆ 2.5 ∆ 5.0
## 15 15 15 15 15
probs.change.df.long <- probs.change.df |>
dplyr::select(!c(id)) |>
pivot_longer(-c(wetland,change),
names_to = "P.change_Sim",
values_to = "mu")
probs.change.df.long$change <- factor(probs.change.df.long$change,
levels = c("∆ 5.0",
"∆ 2.5",
"∆ 0.0",
"∆ -2.5",
"∆ -5.0"))
And make the density plots
change.water.log <- probs.change.df.long |>
ggplot(aes(x = mu,
color = factor(change),
fill = factor(change)))+
facet_wrap(~factor(wetland,
levels = c(
"KRV_Lake.Hatchineha","EVER_Everglades.NP","EVER_WCA.3B",
"KRV_Lake.Istokpoga","EVER_Loxahatchee.NWR", "KRV_East.Lake.Tohopekaliga",
"EVER_STA1E","KRV_Lake.Tohopekaliga","EAST_Grassy.Waters.Preserve",
"KRV_Lake.Kissimmee","OKEE_Lake.Okeechobee","SJM_SJM",
"EVER_WCA.2B","PP_Paynes.Prairie","EVER_WCA.3A"
)),
ncol = 3)+
geom_density(alpha = 0.05, position = "dodge")+
# geom_density_ridges(rel_min_height = 0.01,
# alpha = 0.05, scale = 0.9, jittered_points = FALSE,
# position = position_points_jitter(width = 0.05, height = 0))+
scale_color_manual(values=rev(met.brewer("OKeeffe1",n = nchanges))) +
scale_fill_manual(values=rev(met.brewer("OKeeffe1",n = nchanges))) +
labs(x = expression(italic(x)[italic(t)+1]))+
theme_classic()+
theme(legend.position = "bottom",
legend.title = element_blank(),
legend.background = element_rect(fill="white",
linewidth=0.5, linetype="solid",
colour ="black"),
plot.margin = margin(0.5, 1, 0.5, 0.5, "cm"))+
guides(color=guide_legend(nrow =1),
fill=guide_legend(nrow = 1)) +
coord_cartesian(xlim = c(-10,10))
ggsave(change.water.log, filename = "Changes in log-ab one step.jpg",dpi = 300,
width = 210, height = 180, units = "mm")
## Warning: Width not defined
## ℹ Set with `position_dodge(width = ...)`
But the previous figure is only one step, from the current year. If we want to compare across populations this effect, we can use as start point the mean expected \(\hat{\mu}\) from the overall trajectory
probs.change.list.mu <- list()
changes <- seq(5,-5, by=-2.5)
nchanges <- length(changes)
for(i in 1:nchanges){
probs.change.list.mu[[i]] <- MVX.sim(
xo.vec = c(muvec4kalman),
W = W[,28],
theta.list = list(p=p,
Ip = diag(p),
muvec = muvec4kalman,
sigmasq = sigsq4kalman,
B = B,
beta = beta.4kalman),
rnames = row.names(S4mat),
Nsims = 100000,
pt.change = changes[i])
}
names(probs.change.list.mu) <- as.character(sprintf("%.1f", changes))
probs.change.df.mu <- bind_rows(
lapply(names(probs.change.list.mu), function(name) {
df <- as.data.frame(probs.change.list.mu[[name]])
# Save row names as a column
df$wetland <- rownames(probs.change.list.mu[[name]])
# Add the original name as a column
df$change <- paste0("∆ ",name)
return(df)
}),
.id = "id" # Optional: adds an index column
)
table(probs.change.df.mu$change)
##
## ∆ -2.5 ∆ -5.0 ∆ 0.0 ∆ 2.5 ∆ 5.0
## 15 15 15 15 15
probs.change.df.long.mu <- probs.change.df.mu |>
dplyr::select(!c(id)) |>
pivot_longer(-c(wetland,change),
names_to = "P.change_Sim",
values_to = "mu")
probs.change.df.long.mu$change <- factor(probs.change.df.long.mu$change,
levels = c("∆ 5.0",
"∆ 2.5",
"∆ 0.0",
"∆ -2.5",
"∆ -5.0"))
change.water.log.mu <- probs.change.df.long.mu |>
ggplot(aes(x = mu,
color = factor(change),
fill = factor(change)))+
facet_wrap(~factor(wetland,
levels = c(
"KRV_Lake.Hatchineha","EVER_Everglades.NP","EVER_WCA.3B",
"KRV_Lake.Istokpoga","EVER_Loxahatchee.NWR", "KRV_East.Lake.Tohopekaliga",
"EVER_STA1E","KRV_Lake.Tohopekaliga","EAST_Grassy.Waters.Preserve",
"KRV_Lake.Kissimmee","OKEE_Lake.Okeechobee","SJM_SJM",
"EVER_WCA.2B","PP_Paynes.Prairie","EVER_WCA.3A"
)), ncol = 3)+
geom_density(alpha = 0.05, position = "dodge")+
# geom_density_ridges(rel_min_height = 0.01,
# alpha = 0.05, scale = 0.9, jittered_points = FALSE,
# position = position_points_jitter(width = 0.05, height = 0))+
scale_color_manual(values=rev(met.brewer("OKeeffe1",n = nchanges))) +
scale_fill_manual(values=rev(met.brewer("OKeeffe1",n = nchanges))) +
labs(x = expression(mu[italic(t)+1]))+
theme_classic()+
theme(legend.position = "bottom",
legend.direction = "vertical",
legend.title = element_blank(),
legend.background = element_rect(fill="white",
linewidth=0.5, linetype="solid",
colour ="black"),
plot.margin = margin(0.5, 1, 0.5, 0.5, "cm"))+
guides(color=guide_legend(nrow =1),
fill=guide_legend(nrow=1)) +
coord_cartesian(xlim = c(-5,8))
ggsave(change.water.log.mu, filename = "Changes in mu one step.jpg",dpi = 600,
width = 210, height = 180, units = "mm")
## Warning: Width not defined
## ℹ Set with `position_dodge(width = ...)`
ggsave(change.water.log.mu, filename = "Changes in mu one step.png",dpi = 600,
width = 210, height = 180, units = "mm")
## Warning: Width not defined
## ℹ Set with `position_dodge(width = ...)`
Change in expected abundance (log-scale) with change in relative water (stage) values, starting from the mean abundance at each wetland