Gyps Vulture Survival Analysis and Postprocessing

Author

Brian W. Rolek

Published

May 14, 2026

1. Vultures photographed by Corinne Kendall

1. Background

This document provides R and Nimble code to implement multi-event capture-recapture analysis to estimate survival of Gyps vultures (African white-backed and Ruppells) in eastern Africa. Code is linked to the following manuscript:

B. W. Rolek, L. Dunn, C. J. Kendall, C. J. W. McClure, S. Thomsett, D. Muteti, M. Z. Virani, E. R. Buechley, R. Buij. 2026. Low survival of Gyps vultures in eastern Africa coinciding with interventions to address poisoning.

2. Run survival models

The following code implements three versions of the survival model from telemetry data while estimating probabilities of survival, tag failure, detection of fatality, and detection of tag loss. We do not run it here because the analysis is computationally intensive and may take ~3 hours to complete. Then we calculated WAIC values for model comparisons.

We loaded the necessary packages and data.

Code
library ('nimble')
library('parallel')
library('nimbleEcology')
load("data\\data.RData")
set.seed(5757575)

# Parameters:
# s: monthly survival probability
# tagfail: monthly probability that tag will fail
# p.dead: monthly probability of detecting a fatality
# p.tagfail: monthly probability of detecting a tag failure
# 
# States (S):
# 1 alive with functioning transmitter
# 2 alive, transmitter failed or lost
# 3 dead recovery with functioning transmitter
# 4 dead, transmitter failed or dropped

# Observations (O):
# 1 presumed alive, tag works
# 2 presumed alive, tag failed
# 3 presumed dead, tag works
# 4 presumed dead, tag failed
# 5 not observed, uncertain tag status and survival

2.1 The null model

We implemented the null model in Nimble without covariates.

Code
#**************************
#* Null Model
#*************************
code <- nimbleCode({ 
  # Priors and constraints
  mean.p.dead ~ dbeta(1, 1)   # uninformative prior for monthly probabilities
  l.p.dead <- logit(mean.p.dead)    # logit transformed intercept
  
  mean.tagfail ~ dbeta(1, 1)   # uninformative prior for monthly probabilities
  l.tagfail <- logit(mean.tagfail) # logit transformed intercept
  
  mean.p.tagfail ~ dbeta(1, 1)   # uninformative prior for monthly survival probabilities
  l.p.tagfail <- logit(mean.p.tagfail)    # logit transformed intercept
  
  for (x in 1:3){
    mean.s[x] ~ dbeta(1, 1)   # uninformative prior for monthly probabilities
    l.s[x] <- logit(mean.s[x])
  }# xx logit transformed intercept
  # ages 0,1,2,3,4,5,6
  alpha[1:7] <- c(1,1,1,1,1,1,1)
  pi[1,1:7] ~ ddirch(alpha[1:7])
  pi[2,1] <- 0
  pi[2,2:6] ~ ddirch(alpha[2:6])
  pi[2,7] <- 0
  
  #### MONTHLY SURVIVAL PROBABILITY
  for (i in 1:nind){
    first_age[i] ~ dcat(pi[known[i],1:7])
    for (t in f[i]:(ntime-1)){
      # translate to age classes: 
      # 0 year old = 1, 
      # 1-5 year old subadult = 2-6
      # and >=6 year old adult = 7
      age[i,t] <- ( (first_age[i]-1) + t/12 - f[i]/12 )
      age.class[i,t] <- ifgreaterFun( age[i,t], 1, 6 ) 
      
      logit(s[i,t]) <- l.s[ age.class[i,t] ] 
      
      logit(tagfailed[i,t]) <- l.tagfail  

      logit(p.tagfailed[i,t]) <- l.p.tagfail  # prob of detecting tag failure
      logit(p.dead[i,t]) <- l.p.dead  # probability of dead recovery
    } #t
  } #i
  
  # Define state-transition and observation matrices 
  for (i in 1:nind){
    for (t in f[i]:(ntime-1)){
      # Define probabilities of state S(t+1) [last dim] given S(t) [first dim]
      ps[i,1,1,t]<-(1-tagfailed[i,t])*s[i,t]
      ps[i,1,2,t]<-tagfailed[i,t]*s[i,t]
      ps[i,1,3,t]<-(1-tagfailed[i,t])*(1-s[i,t])
      ps[i,1,4,t]<-tagfailed[i,t]*(1-s[i,t])
      
      ps[i,2,1,t]<-0
      ps[i,2,2,t]<-1
      ps[i,2,3,t]<-0
      ps[i,2,4,t]<-0
      
      ps[i,3,1,t]<-0
      ps[i,3,2,t]<-0
      ps[i,3,3,t]<-1
      ps[i,3,4,t]<-0
      
      ps[i,4,1,t]<-0
      ps[i,4,2,t]<-0
      ps[i,4,3,t]<-0
      ps[i,4,4,t]<-1
      
      # Define probabilities of O(t) [last dim] given S(t)  [first dim]
      po[i,1,1,t]<-1
      po[i,1,2,t]<-0
      po[i,1,3,t]<-0
      po[i,1,4,t]<-0
      po[i,1,5,t]<-0
      
      po[i,2,1,t]<-0
      po[i,2,2,t]<-p.tagfailed[i,t]
      po[i,2,3,t]<-0
      po[i,2,4,t]<-0
      po[i,2,5,t]<-(1-p.tagfailed[i,t])
      
      po[i,3,1,t]<-0
      po[i,3,2,t]<-0
      po[i,3,3,t]<-p.dead[i,t]
      po[i,3,4,t]<-0
      po[i,3,5,t]<-(1-p.dead[i,t])
      
      po[i,4,1,t]<-0
      po[i,4,2,t]<-p.tagfailed[i,t]*(1-p.dead[i,t])
      po[i,4,3,t]<-(1-p.tagfailed[i,t])*p.dead[i,t]
      po[i,4,4,t]<-p.tagfailed[i,t]*p.dead[i,t]
      po[i,4,5,t]<-(1-p.tagfailed[i,t])*(1-p.dead[i,t])
      
    } #t
  } #i
  
  # Likelihood 
  for (i in 1:nind){
    y[i, (f[i] + 1):last[i]] ~ 
      dDHMMo(init = ps[i, y.first[i], 1:4, f[i]], 
             probObs = po[i, 1:4, 1:5, f[i]:(last[i] - 1)], 
             probTrans = ps[i, 1:4, 1:4, (f[i] + 1):(last[i] - 1)], 
             len = last[i] - f[i], 
             checkRowSums = 0)
  } #i
  
} ) # nimbleCode

inits <- function(){ list(mean.s = runif(3,0,1), 
                          mean.tagfail = runif(1), 
                          mean.p.tagfail = runif(1), 
                          mean.p.dead = runif(1),
                          alpha = rep(1/7, 7),
                          first_age = ifelse(is.na(datl$first_age), 3, NA)
)}

run <- function(seed, datl, constl, code, inits){
  library('nimble')
  library('nimbleEcology')
  source("R/functions.R")
  
  pars <- c(  "mean.s", "mean.tagfail", "mean.p.tagfail", "mean.p.dead",
              "l.s", "l.tagfail", "l.p.tagfail", "l.p.dead", "first_age", "pi",
              "logProb_y", "first_age", "pi") 
  
  nimbleOptions(showCompilerOutput= TRUE)
  mod <- nimbleModel(code, calculate=T, 
                     constants = constl,
                     data = datl, 
                     inits = inits,
                     buildDerivs = FALSE) 
  
  cmod <- compileNimble( mod )
  conf <- configureMCMC(cmod, monitors=pars, print = TRUE, enableWAIC = TRUE)
  mc <- buildMCMC(conf, project=cmod)
  cmc <- compileNimble(mc, project=cmod, showCompilerOutput = TRUE)
  printErrors()
  
  nc <- 1; nt <- 20; ni <- 100000; nb <- 80000
  
  post <- runMCMC(cmc,
                  niter = ni, 
                  nburnin = nb,
                  nchains = 1,
                  thin = nt,
                  samplesAsCodaMCMC = T,
                  setSeed = seed,
                  WAIC=TRUE)
  
  return(post)
} # run model function end

this_cluster <- makeCluster(4)
post <- parLapply(cl = this_cluster,
                  X = 1:4,
                  fun = run,
                  datl = datl,
                  constl = constl,
                  code = code, 
                  inits = inits())
stopCluster(this_cluster)
save(post, datl, code, 
     file="outputs/gyps-28Apr2026-marginalized-null.RData")

2.2 The global model

We implemented the global model that included all covariates and assessed probability of direction (PD) values to retain significant covariates in the reduced model.

Code
#*******************
#* Global model
#*******************
code <- nimbleCode({ 
  # Priors and constraints
  mean.p.dead ~ dbeta(1, 1)   # uninformative prior for monthly probabilities
  l.p.dead <- logit(mean.p.dead)    # logit transformed intercept
  
  mean.tagfail ~ dbeta(1, 1)   # uninformative prior for monthly probabilities
  l.tagfail <- logit(mean.tagfail) # logit transformed intercept
  
  mean.p.tagfail ~ dbeta(1, 1)   # uninformative prior for monthly probabilities
  l.p.tagfail <- logit(mean.p.tagfail)    # logit transformed intercept
  
  for (x in 1:3){
    mean.s[x] ~ dbeta(1, 1)   # uninformative prior for monthly probabilities
    l.s[x] <- logit(mean.s[x])
  }# xx logit transformed survival intercept
  for (xx in 1:3){
    delta[xx] ~ dnorm(0, sd=10) # covariates for survival        
  } # x
  for (xxx in 1:2){
    beta[xxx] ~ dnorm(0, sd=10) # covariates for tagloss
  } # xxxx
  # ages 0,1,2,3,4,5,6
  alpha[1:7] <- c(1,1,1,1,1,1,1)
  pi[1,1:7] ~ ddirch(alpha[1:7])
  pi[2,1] <- 0
  pi[2,2:6] ~ ddirch(alpha[2:6])
  pi[2,7] <- 0
  
  #### MONTHLY SURVIVAL PROBABILITY
  for (i in 1:nind){
    first_age[i] ~ dcat(pi[known[i],1:7])
    for (t in f[i]:(ntime-1)){
      # translate to age classes: 
      # 0 year old = 1, 
      # 1-5 year old subadult = 2-6
      # and >=6 year old adult = 7
      age[i,t] <- ( (first_age[i]-1) + t/12 - f[i]/12 )
      age.class[i,t] <- ifgreaterFun( age[i,t], 1, 6 ) 
      
      logit(s[i,t]) <- l.s[ age.class[i,t] ] + 
        delta[1]*c(-1, 1)[ period.cat[t] + 1 ] + 
        delta[2]*c(-1, 1)[ rehabbed[i] + 1 ] + 
        delta[3]*c(-1, 1)[ sp[i] + 1 ] 
      
      logit(tagfailed[i,t]) <- l.tagfail + 
        beta[1]*tag.age.sc[i,t] +
        beta[2]*year.cont[t] 
      logit(p.tagfailed[i,t]) <- l.p.tagfail  # prob of detecting tag failure
      logit(p.dead[i,t]) <- l.p.dead  # probability of dead recovery
    } #t
  } #i
  
  # Define state-transition and observation matrices 
  for (i in 1:nind){
    for (t in f[i]:(ntime-1)){
      # Define probabilities of state S(t+1) [last dim] given S(t) [first dim]
      ps[i,1,1,t]<-(1-tagfailed[i,t])*s[i,t]    
      ps[i,1,2,t]<-tagfailed[i,t]*s[i,t]
      ps[i,1,3,t]<-(1-tagfailed[i,t])*(1-s[i,t])
      ps[i,1,4,t]<-tagfailed[i,t]*(1-s[i,t])
      
      ps[i,2,1,t]<-0
      ps[i,2,2,t]<-1
      ps[i,2,3,t]<-0
      ps[i,2,4,t]<-0
      
      ps[i,3,1,t]<-0
      ps[i,3,2,t]<-0
      ps[i,3,3,t]<-1
      ps[i,3,4,t]<-0
      
      ps[i,4,1,t]<-0
      ps[i,4,2,t]<-0
      ps[i,4,3,t]<-0
      ps[i,4,4,t]<-1
      
      # Define probabilities of O(t) [last dim] given S(t)  [first dim]
      po[i,1,1,t]<-1
      po[i,1,2,t]<-0
      po[i,1,3,t]<-0
      po[i,1,4,t]<-0
      po[i,1,5,t]<-0
      
      po[i,2,1,t]<-0
      po[i,2,2,t]<-p.tagfailed[i,t]
      po[i,2,3,t]<-0
      po[i,2,4,t]<-0
      po[i,2,5,t]<-(1-p.tagfailed[i,t])
      
      po[i,3,1,t]<-0
      po[i,3,2,t]<-0
      po[i,3,3,t]<-p.dead[i,t]
      po[i,3,4,t]<-0
      po[i,3,5,t]<-(1-p.dead[i,t])
      
      po[i,4,1,t]<-0
      po[i,4,2,t]<-p.tagfailed[i,t]*(1-p.dead[i,t])
      po[i,4,3,t]<-(1-p.tagfailed[i,t])*p.dead[i,t]
      po[i,4,4,t]<-p.tagfailed[i,t]*p.dead[i,t]
      po[i,4,5,t]<-(1-p.tagfailed[i,t])*(1-p.dead[i,t])
      
    } #t
  } #i
  
  # Likelihood 
  for (i in 1:nind){
    y[i, (f[i] + 1):last[i]] ~ 
      dDHMMo(init = ps[i, y.first[i], 1:4, f[i]], 
             probObs = po[i, 1:4, 1:5, f[i]:(last[i] - 1)], 
             probTrans = ps[i, 1:4, 1:4, (f[i] + 1):(last[i] - 1)], 
             len = last[i] - f[i], 
             checkRowSums = 0)
  } #i
  
} ) # nimbleCode

inits <- function(){ list(beta = rnorm(2,0,0.5),
                          delta = rnorm(3,0,0.5),
                          mean.s = runif(3,0,1), 
                          mean.tagfail = runif(1), 
                          mean.p.tagfail = runif(1), 
                          mean.p.dead = runif(1),
                          alpha = rep(1/7, 7),
                          first_age = ifelse(is.na(datl$first_age), 3, NA)
)}

run <- function(seed, datl, constl, code, inits){
  library('nimble')
  library('nimbleEcology')
  source("R/functions.R")
  
  pars <- c(  "beta", 
              "delta", 
              "mean.s", "mean.tagfail", "mean.p.tagfail", "mean.p.dead",
              "l.s", "l.tagfail", "l.p.tagfail", "l.p.dead",
              "logProb_y", "first_age", "pi") 
  
  nimbleOptions(showCompilerOutput= TRUE)
  mod <- nimbleModel(code, calculate=T, 
                     constants = constl,
                     data = datl, 
                     inits = inits,
                     buildDerivs = FALSE) 
  
  cmod <- compileNimble( mod )
  conf <- configureMCMC(cmod, monitors=pars, print = TRUE, enableWAIC = TRUE)
  mc <- buildMCMC(conf, project=cmod)
  cmc <- compileNimble(mc, project=cmod, showCompilerOutput = TRUE)
  printErrors()
  
  nc <- 1; nt <- 20; ni <- 100000; nb <- 80000
  
  post <- runMCMC(cmc,
                  niter = ni, 
                  nburnin = nb,
                  nchains = 1,
                  thin = nt,
                  samplesAsCodaMCMC = T,
                  setSeed = seed,
                  WAIC = TRUE)
  
  return(post)
} # run model function end

this_cluster <- makeCluster(4)
post <- parLapply(cl = this_cluster,
                  X = 1:4,
                  fun = run,
                  datl = datl,
                  constl = constl,
                  code = code, 
                  inits = inits())
stopCluster(this_cluster)

save(post, datl, code, 
     file="outputs/gyps-28Apr2026-marginalized-global.RData")

2.3 The reduced model

We implemented the reduced model that only included significant covariates based on PD from the global model.

Code
#*************************
#* Reduced model
#*************************
code <- nimbleCode({ 
  # Priors and constraints
  mean.p.dead ~ dbeta(1, 1)   # uninformative prior for monthly probabilities
  l.p.dead <- logit(mean.p.dead)    # logit transformed intercept
  
  mean.tagfail ~ dbeta(1, 1)   # uninformative prior for monthly survival probabilities
  l.tagfail <- logit(mean.tagfail) # logit transformed intercept
  
  mean.p.tagfail ~ dbeta(1, 1)   # uninformative prior for monthly probabilities
  l.p.tagfail <- logit(mean.p.tagfail)    # logit transformed intercept
  
  for (x in 1:3){
    mean.s[x] ~ dbeta(1, 1)   # uninformative prior for monthly probabilities
    l.s[x] <- logit(mean.s[x])
  }# xx logit transformed survival intercept
  for (xx in 1:3){
    delta[xx] ~ dnorm(0, sd=10) # covariates for survival        
  } # x
  for (xxx in 1:2){
    beta[xxx] ~ dnorm(0, sd=10) # covariates for tagloss
  } # xxxx
  # ages 0,1,2,3,4,5,6
  alpha[1:7] <- c(1,1,1,1,1,1,1)
  pi[1,1:7] ~ ddirch(alpha[1:7])
  pi[2,1] <- 0
  pi[2,2:6] ~ ddirch(alpha[2:6])
  pi[2,7] <- 0
  
  #### MONTHLY SURVIVAL PROBABILITY
  for (i in 1:nind){
    first_age[i] ~ dcat(pi[known[i],1:7])
    for (t in f[i]:(ntime-1)){
      # translate to age classes: 
      # 0 year old = 1, 
      # 1-5 year old subadult = 2-6
      # and >=6 year old adult = 7
      age[i,t] <- ( (first_age[i]-1) + t/12 - f[i]/12 )
      age.class[i,t] <- ifgreaterFun( age[i,t], 1, 6 ) 
      
      logit(s[i,t]) <- l.s[ age.class[i,t] ] 
      
      logit(tagfailed[i,t]) <- l.tagfail + 
        beta[1]*tag.age.sc[i,t] +
        beta[2]*year.cont[t] 
      logit(p.tagfailed[i,t]) <- l.p.tagfail  # prob of detecting tag failure
      logit(p.dead[i,t]) <- l.p.dead  # probability of dead recovery
    } #t
  } #i
  
  # Define state-transition and observation matrices 
  for (i in 1:nind){
    for (t in f[i]:(ntime-1)){
      # Define probabilities of state S(t+1) [last dim] given S(t) [first dim]
      ps[i,1,1,t]<-(1-tagfailed[i,t])*s[i,t]
      ps[i,1,2,t]<-tagfailed[i,t]*s[i,t]
      ps[i,1,3,t]<-(1-tagfailed[i,t])*(1-s[i,t])
      ps[i,1,4,t]<-tagfailed[i,t]*(1-s[i,t])
      
      ps[i,2,1,t]<-0
      ps[i,2,2,t]<-1
      ps[i,2,3,t]<-0
      ps[i,2,4,t]<-0
      
      ps[i,3,1,t]<-0
      ps[i,3,2,t]<-0
      ps[i,3,3,t]<-1
      ps[i,3,4,t]<-0
      
      ps[i,4,1,t]<-0
      ps[i,4,2,t]<-0
      ps[i,4,3,t]<-0
      ps[i,4,4,t]<-1
      
      # Define probabilities of O(t) [last dim] given S(t)  [first dim]
      po[i,1,1,t]<-1
      po[i,1,2,t]<-0
      po[i,1,3,t]<-0
      po[i,1,4,t]<-0
      po[i,1,5,t]<-0
      
      po[i,2,1,t]<-0
      po[i,2,2,t]<-p.tagfailed[i,t]
      po[i,2,3,t]<-0
      po[i,2,4,t]<-0
      po[i,2,5,t]<-(1-p.tagfailed[i,t])
      
      po[i,3,1,t]<-0
      po[i,3,2,t]<-0
      po[i,3,3,t]<-p.dead[i,t]
      po[i,3,4,t]<-0
      po[i,3,5,t]<-(1-p.dead[i,t])
      
      po[i,4,1,t]<-0
      po[i,4,2,t]<-p.tagfailed[i,t]*(1-p.dead[i,t])
      po[i,4,3,t]<-(1-p.tagfailed[i,t])*p.dead[i,t]
      po[i,4,4,t]<-p.tagfailed[i,t]*p.dead[i,t]
      po[i,4,5,t]<-(1-p.tagfailed[i,t])*(1-p.dead[i,t])
      
    } #t
  } #i
  
  # Likelihood 
  for (i in 1:nind){
    y[i, (f[i] + 1):last[i]] ~ 
      dDHMMo(init = ps[i, y.first[i], 1:4, f[i]], 
             probObs = po[i, 1:4, 1:5, f[i]:(last[i] - 1)], 
             probTrans = ps[i, 1:4, 1:4, (f[i] + 1):(last[i] - 1)], 
             len = last[i] - f[i], 
             checkRowSums = 0)
  } #i
  
} ) # nimbleCode

inits <- function(){ list(beta = rnorm(2,0,0.5),
                          mean.s = runif(3,0,1), 
                          mean.tagfail = runif(1), 
                          mean.p.tagfail = runif(1), 
                          mean.p.dead = runif(1),
                          alpha = rep(1/7, 7),
                          first_age = ifelse(is.na(datl$first_age), 3, NA)
)}

run <- function(seed, datl, constl, code, inits){
  library('nimble')
  library('nimbleEcology')
  source("R/functions.R")
  
  pars <- c(  "beta",
              "mean.s", "mean.tagfail", "mean.p.tagfail", "mean.p.dead",
              "l.s", "l.tagfail", "l.p.tagfail", "l.p.dead", "first_age", "pi",
              "logProb_y", "first_age", "pi") 
  
  nimbleOptions(showCompilerOutput= TRUE)
  mod <- nimbleModel(code, calculate=T, 
                     constants = constl,
                     data = datl, 
                     inits = inits,
                     buildDerivs = FALSE) 
  
  cmod <- compileNimble( mod )
  conf <- configureMCMC(cmod, monitors=pars, print = TRUE, enableWAIC = TRUE)
  mc <- buildMCMC(conf, project=cmod)
  cmc <- compileNimble(mc, project=cmod, showCompilerOutput = TRUE)
  printErrors()
  
  nc <- 1; nt <- 20; ni <- 100000; nb <- 80000
  
  post <- runMCMC(cmc,
                  niter = ni, 
                  nburnin = nb,
                  nchains = 1,
                  thin = nt,
                  samplesAsCodaMCMC = T,
                  setSeed = seed,
                  WAIC=TRUE)
  
  return(post)
} # run model function end

this_cluster <- makeCluster(4)
post <- parLapply(cl = this_cluster,
                  X = 1:4,
                  fun = run,
                  datl = datl,
                  constl = constl,
                  code = code, 
                  inits = inits())
stopCluster(this_cluster)
save(post, datl, code, 
     file="outputs/gyps-28Apr2026-marginalized-reduced.RData")

3. Model comparison

We compared the predictive accuracy of models using WAIC. We didn’t evaluate the code here because it has a long runttime. Instead, we load the results file to inspect the results. The waic.func() is a custom function that uses Nimble’s calculateWAIC() to estimate WAIC values. This function is located in the functions.R file.

Code
#***********************
#* Model comparison
#************************
source("R/functions.R") # fetch waic.func()
# This part takes some time because we 
# need to reconstruct the models in nimble
waic.vals <- list()
waic.vals[[1]] <- waic.func("outputs/gyps-28Apr2026-marginalized-null.RData")
waic.vals[[2]] <- waic.func("outputs/gyps-28Apr2026-marginalized-global.RData")
waic.vals[[3]] <- waic.func("outputs/gyps-28Apr2026-marginalized-rehabbed.RData")
names(waic.vals) <- c("global", "null", "reduced")

lapply(waic.vals, function(x) {x$WAIC }) |> unlist()
waic.tab <- data.frame(
  names= lapply(waic.vals, function(x) {x$WAIC }) |> unlist() |> names(), 
  waic= lapply(waic.vals, function(x) {x$WAIC }) |> unlist(),
  dwaic= NA
)
waic.tab <- waic.tab[order(waic.tab$waic, decreasing = F),]
for (i in 1:nrow(waic.tab)){
  waic.tab[i,"dwaic"] <- waic.tab[i,"waic"]-waic.tab[1,"waic"]
}
waic.tab$weights <- NA
waic.tab$weights <- exp(-0.5*waic.tab$dwaic)/ sum(exp(-0.5*waic.tab$dwaic))
  
waic.tab
save( waic.tab, waic.vals, file="outputs/waic.RData" )
write.csv(waic.tab, "docs/waic-table.csv")
Code
load("outputs/waic.RData")
waic.tab[,c(2:4)] <- (waic.tab[,c(2:4)] |> round(2))
waic.tab
Table S1. Model comparison using WAIC.
names waic dwaic weights
reduced reduced 656.51 0.00 0.84
null null 659.82 3.31 0.16
global global 695.07 38.57 0.00

4. Sensitivity analysis

Observation state 2 may have been challenging to correctly assign, so we reassigned data from state 2 to either state 3, 4, or 5 to test the sensitivity of results to state misclassification. Note that misclassifications could be formally accounted for within the dynamic hidden Markov model but larger sample sizes would be needed.

Code
library ('nimble')
library('parallel')
library('nimbleEcology')
library('MCMCvis')
load("data\\data.RData")
set.seed(5757575)

#************************
#* Create model and run function 
#* for sensitivity tests
#************************
# model code
code <- nimbleCode({ 
  # Priors and constraints
  mean.p.dead ~ dbeta(1, 1)   # uninformative prior for monthly probabilities
  l.p.dead <- logit(mean.p.dead)    # logit transformed intercept
  
  mean.tagfail ~ dbeta(1, 1)   # uninformative prior for monthly probabilities
  l.tagfail <- logit(mean.tagfail) # logit transformed intercept
  
  mean.p.tagfail ~ dbeta(1, 1)   # uninformative prior for monthly probabilities
  l.p.tagfail <- logit(mean.p.tagfail)    # logit transformed intercept
  
  for (x in 1:3){
    mean.s[x] ~ dbeta(1, 1)   # uninformative prior for monthly probabilities
    l.s[x] <- logit(mean.s[x])
  }# xx logit transformed intercept
  for (xxx in 1:2){
    beta[xxx] ~ dnorm(0, sd=10) # covariates for tagloss
  } # xxxx
  # ages 0,1,2,3,4,5,6
  alpha[1:7] <- c(1,1,1,1,1,1,1)
  pi[1,1:7] ~ ddirch(alpha[1:7])
  pi[2,1] <- 0
  pi[2,2:6] ~ ddirch(alpha[2:6])
  pi[2,7] <- 0
  
  #### MONTHLY SURVIVAL PROBABILITY
  for (i in 1:nind){
    first_age[i] ~ dcat(pi[known[i],1:7])
    for (t in f[i]:(ntime-1)){
      # translate to age classes: 
      # 0 year old = 1, 
      # 1-5 year old subadult = 2-6
      # and >=6 year old adult = 7
      age[i,t] <- ( (first_age[i]-1) + t/12 - f[i]/12 )
      age.class[i,t] <- ifgreaterFun( age[i,t], 1, 6 ) 
      
      logit(s[i,t]) <- l.s[ age.class[i,t] ] 
      
      logit(tagfailed[i,t]) <- l.tagfail + 
        beta[1]*tag.age.sc[i,t] +
        beta[2]*year.cont[t] 
      logit(p.tagfailed[i,t]) <- l.p.tagfail  # prob of detecting tag failure
      logit(p.dead[i,t]) <- l.p.dead  # probability of dead recovery
    } #t
  } #i
  
  # -------------------------------------------------
  # Define state-transition and observation matrices 
  # -------------------------------------------------
  for (i in 1:nind){
    for (t in f[i]:(ntime-1)){
      # Define probabilities of state S(t+1) [last dim] given S(t) [first dim]
      ps[i,1,1,t]<-(1-tagfailed[i,t])*s[i,t]  
      ps[i,1,2,t]<-tagfailed[i,t]*s[i,t]
      ps[i,1,3,t]<-(1-tagfailed[i,t])*(1-s[i,t])
      ps[i,1,4,t]<-tagfailed[i,t]*(1-s[i,t])
      
      ps[i,2,1,t]<-0
      ps[i,2,2,t]<-1
      ps[i,2,3,t]<-0
      ps[i,2,4,t]<-0
      
      ps[i,3,1,t]<-0
      ps[i,3,2,t]<-0
      ps[i,3,3,t]<-1
      ps[i,3,4,t]<-0
      
      ps[i,4,1,t]<-0
      ps[i,4,2,t]<-0
      ps[i,4,3,t]<-0
      ps[i,4,4,t]<-1
      
      # Define probabilities of O(t) [last dim] given S(t)  [first dim]
      po[i,1,1,t]<-1
      po[i,1,2,t]<-0
      po[i,1,3,t]<-0
      po[i,1,4,t]<-0
      po[i,1,5,t]<-0
      
      po[i,2,1,t]<-0
      po[i,2,2,t]<-p.tagfailed[i,t]
      po[i,2,3,t]<-0
      po[i,2,4,t]<-0
      po[i,2,5,t]<-(1-p.tagfailed[i,t])
      
      po[i,3,1,t]<-0
      po[i,3,2,t]<-0
      po[i,3,3,t]<-p.dead[i,t]
      po[i,3,4,t]<-0
      po[i,3,5,t]<-(1-p.dead[i,t])
      
      po[i,4,1,t]<-0
      po[i,4,2,t]<-p.tagfailed[i,t]*(1-p.dead[i,t])
      po[i,4,3,t]<-(1-p.tagfailed[i,t])*p.dead[i,t]
      po[i,4,4,t]<-p.tagfailed[i,t]*p.dead[i,t]
      po[i,4,5,t]<-(1-p.tagfailed[i,t])*(1-p.dead[i,t])
      
    } #t
  } #i
  
  # Likelihood 
  for (i in 1:nind){
    y[i, (f[i] + 1):last[i]] ~ 
      dDHMMo(init = ps[i, y.first[i], 1:4, f[i]], 
             probObs = po[i, 1:4, 1:5, f[i]:(last[i] - 1)], 
             probTrans = ps[i, 1:4, 1:4, (f[i] + 1):(last[i] - 1)], 
             len = last[i] - f[i], 
             checkRowSums = 0)
  } #i
  
} ) # nimbleCode

# function to run model
run <- function(seed, datl, constl, code){
  library('nimble')
  library('coda')
  library('nimbleEcology')
  source("R/functions.R")
  
  inits <- function(){ list(beta = rnorm(2,0,0.5),
                            mean.s = runif(3,0,1), 
                            mean.tagfail = runif(1), 
                            mean.p.tagfail = runif(1), 
                            mean.p.dead = runif(1),
                            alpha = rep(1/7, 7),
                            first_age = ifelse(is.na(datl$first_age), 3, NA)
  )}
  
  pars <- c(  "beta",  
              "mean.s", "mean.tagfail", "mean.p.tagfail", "mean.p.dead",
              "l.s", "l.tagfail", "l.p.tagfail", "l.p.dead") 
  
  nimbleOptions(showCompilerOutput= TRUE)        
  mod <- nimbleModel(code, calculate=T, 
                     constants = constl,
                     data = datl, 
                     inits = inits()) 
  
  cmod <- compileNimble( mod )
  conf <- configureMCMC(cmod, monitors=pars, print = TRUE)
  mc <- buildMCMC(conf, project=cmod)
  cmc <- compileNimble(mc, project=cmod, showCompilerOutput = TRUE)
  printErrors()
  
  nc <- 1; nt <- 20; ni <- 100000; nb <- 80000
  
  post <- runMCMC(cmc,
                  niter = ni, 
                  nburnin = nb,
                  nchains = 1,
                  thin = nt,
                  samplesAsCodaMCMC = T)
  
  return(post)
} # run model function end

#************************
#* Sensitivity test state 2 to state 3
#************************
load("data\\data.RData")
set.seed(5757575)
# which y's are 2s
# and which are in 2009-2011
which(datl$y==2, arr.ind=T)
# Sensitivity test
# reassign all 
# (2) alive without a functioning transmitter 
# to (3) recently dead with a functioning transmitter
datl$y[datl$y==2] <- 3

this_cluster <- makeCluster(4)
post1 <- parLapply(cl = this_cluster,
                   X = 1:4,
                   fun = run,
                   datl=datl,
                   constl=constl, 
                   code=code)

#************************
#* Sensitivity test state 2 to state 4
#************************
load("data\\data.RData")
#load("/bsuscratch/brianrolek/gyps/data.RData")
set.seed(5757575)
# which y's are 2s
# and which are in 2009-2011
which(datl$y==2, arr.ind=T)
# Sensitivity test
# reassign all 
# (2) alive without a functioning transmitter  
# to (4) recently dead without a functioning transmitter
datl$y[datl$y==2] <- 4
post2 <- parLapply(cl = this_cluster,
                  X = 1:4,
                  fun = run,
                  datl=datl, 
                  constl=constl, 
                  code=code)

#************************
#* Sensitivity test state 2 to state 5
#************************
load("data\\data.RData")
#load("/bsuscratch/brianrolek/gyps/data.RData")
set.seed(5757575)
# which y's are 2s
# and which are in 2009-2011
which(datl$y==2, arr.ind=T)
# Sensitivity test
# reassign all 
# (2) alive without a functioning transmitter  
# to (3) recently dead with a functioning transmitter 
datl$y[datl$y==2] <- 5

post3 <- parLapply(cl = this_cluster,
                  X = 1:4,
                  fun = run,
                  datl=datl, 
                  constl=constl,
                  code=code)
stopCluster(this_cluster)

save(post1, post2, post3, datl, file="outputs/gyps-28Apr2026-sensitivitytest.RData")
Code
#********************
#* Sensitivity 
#********************
#* Compare results models with 
#* those having changed data
#* state two to three
library ('MCMCvis')
load("outputs/gyps-28Apr2026-sensitivitytest.RData")
pars <- c(  "beta",  
            "mean.s", "mean.tagfail", "mean.p.tagfail", "mean.p.dead",
            "l.s", "l.tagfail", "l.p.tagfail", "l.p.dead")
post.sens23 <- post1 
p23 <- MCMCpstr(post1, pars[-1], type="chains")

iters <- ncol(p23$beta)
sum95.sens23 <- MCMCsummary(post.sens23, pars[-c(1,7:10)], HPD=TRUE, digits=2, 
                            hpd_prob=0.95, pg0=TRUE, func=median, func_name="md")
coef.est.sens23 <- data.frame(Model="State 2 to 3",
                              Parameter= rownames(sum95.sens23),
                              Median=sum95.sens23$md, 
                              Mean=sum95.sens23$mean,
                              LHDI95=sum95.sens23$`95%_HPDL`, 
                              UHDI95=sum95.sens23$`95%_HPDU`,
                              p= sum95.sens23$`p>0`, 
                              Rhat=sum95.sens23$Rhat)

post.sens24 <- post2
p24 <- MCMCpstr(post2, pars[-1], type="chains")
iters <- ncol(p24$beta)
sum95.sens24 <- MCMCsummary(post.sens24, pars[-c(1,7:10)], HPD=TRUE, digits=2, 
                            hpd_prob=0.95, pg0=TRUE, func=median, func_name="md")
coef.est.sens24 <- data.frame( Model= "State 2 to 4",
                               Parameter= rownames(sum95.sens24),
                               Median=sum95.sens24$md, 
                               Mean=sum95.sens24$mean,
                               LHDI95=sum95.sens24$`95%_HPDL`, 
                               UHDI95=sum95.sens24$`95%_HPDU`,
                               p= sum95.sens24$`p>0`, 
                               Rhat=sum95.sens24$Rhat)

post.sens25 <- post3
p25 <- MCMCpstr(post3, pars[-1], type="chains")
iters <- ncol(p25$beta)
sum95.sens25 <- MCMCsummary(post.sens25, pars[-c(1,7:10)], HPD=TRUE, digits=2, 
                            hpd_prob=0.95, pg0=TRUE, func=median, func_name="md")
coef.est.sens25 <- data.frame(Model= "State 2 to 5",
                              Parameter= rownames(sum95.sens25),
                              Median=sum95.sens25$md, 
                              Mean=sum95.sens25$mean,
                              LHDI95=sum95.sens25$`95%_HPDL`, 
                              UHDI95=sum95.sens25$`95%_HPDU`,
                              p= sum95.sens25$`p>0`, 
                              Rhat=sum95.sens25$Rhat
)

load("outputs\\gyps-28Apr2026-marginalized-reduced.RData")
post.sens.red <- lapply(post, function(x){ x$samples })
pr <- MCMCpstr(post.sens.red, pars, type="chains")
iters <- ncol(pr$beta)
sum95.sens.r <- MCMCsummary(post.sens.red, pars[-c(1,7:10)], HPD=TRUE, digits=2, 
                            hpd_prob=0.95, pg0=TRUE, func=median, func_name="md")
coef.est.reduced <- data.frame(Model= "Reduced",
                              Parameter= rownames(sum95.sens.r),
                              Median=sum95.sens.r$md, 
                              Mean=sum95.sens.r$mean,
                              LHDI95=sum95.sens.r$`95%_HPDL`, 
                              UHDI95=sum95.sens.r$`95%_HPDU`,
                              p= sum95.sens.r$`p>0`, 
                              Rhat=sum95.sens.r$Rhat
)

# Compare outputs
df.sens.tab <- rbind(coef.est.sens23[1:3,1:6],
                    coef.est.sens24[1:3,1:6],
                    coef.est.sens25[1:3,1:6],
                    coef.est.reduced[1:3,1:6])
df.sens.tab[,3:6] <- df.sens.tab[,3:6] |> round(3)
df.sens.tab
Table S2. Survival estimates when substituting observation state 2 with either 3, 4, or 5 to evaluate the sensitivity of models to misclassification. Survival here is presented as monthly survival.
Model Parameter Median Mean LHDI95 UHDI95
State 2 to 3 mean.s[1] 0.94 0.93 0.87 0.99
State 2 to 3 mean.s[2] 0.98 0.98 0.96 0.99
State 2 to 3 mean.s[3] 0.98 0.98 0.96 0.99
State 2 to 4 mean.s[1] 0.94 0.94 0.88 0.99
State 2 to 4 mean.s[2] 0.98 0.98 0.96 1.00
State 2 to 4 mean.s[3] 0.98 0.98 0.97 0.99
State 2 to 5 mean.s[1] 0.94 0.93 0.87 0.99
State 2 to 5 mean.s[2] 0.98 0.98 0.96 0.99
State 2 to 5 mean.s[3] 0.98 0.98 0.97 0.99
Reduced mean.s[1] 0.94 0.94 0.88 0.99
Reduced mean.s[2] 0.98 0.98 0.96 1.00
Reduced mean.s[3] 0.99 0.98 0.97 0.99
Code
# write.csv(file= "C:\\Users\\rolek.brian\\OneDrive - The Peregrine Fund\\Documents\\GitHub\\Gyps Vulture Survival in Africa\\docs\\sensitivity-table.csv", 
#           df.compare)

5. Postprocessing

We created tables and figures from the reduced and global model outputs.

Code
library (MCMCvis)
library (coda)
library (ggplot2)
library (reshape2)
library (tidybayes)
library (bayestestR)
library (ggpubr)
library (scales)
options(scipen=999)
load("data//data.RData")
source("R//functions.R")
pars <- c(  "delta", "beta",
            "mean.s", "mean.tagfail", "mean.p.tagfail", "mean.p.dead",
            "l.s", "l.tagfail", "l.p.tagfail", "l.p.dead")
# load output from global model
load("outputs\\gyps-28Apr2026-marginalized-global.RData")
postl.global <- lapply(post, function(x){ x$samples })
post.global <- do.call(rbind, postl.global)
p1 <- MCMCpstr(postl.global, pars, type="chains")
p2 <- mcmc.list(postl.global)
# load output from reduced model
load("outputs\\gyps-28Apr2026-marginalized-reduced.RData")
postl.reduced <- lapply(post, function(x){ x$samples })
post.reduced <- do.call(rbind, postl.reduced)
p3 <- MCMCpstr(postl.reduced, pars[-1], type="chains")
p4 <- mcmc.list(postl.reduced)


# Model diagnostics

5.1 Model diagnostics

We checked that rows of the observation and state transition matrices sum to one using randomly generated probabilities. The function sumtoone.func() can be found in the functions.R file. All rows sum to one.

Code
sumtoone.func()
$truestates.rowsums
[1] 1 1 1 1

$obsstates.rowsums
[1] 1 1 1 1

$truestates.probs
          [,1]      [,2]      [,3]       [,4]
[1,] 0.7533801 0.1221537 0.1071008 0.01736541
[2,] 0.0000000 1.0000000 0.0000000 0.00000000
[3,] 0.0000000 0.0000000 1.0000000 0.00000000
[4,] 0.0000000 0.0000000 0.0000000 1.00000000

$obsstates.probs
     [,1]      [,2]      [,3]      [,4]      [,5]
[1,]    1 0.0000000 0.0000000 0.0000000 0.0000000
[2,]    0 0.1973677 0.0000000 0.0000000 0.8026323
[3,]    0 0.0000000 0.3212755 0.0000000 0.6787245
[4,]    0 0.1339583 0.2578661 0.0634094 0.5447662
Code
# Check for convergence
# Priors are depicted in red

We examined traceplots and density plots of parameters from the global model. The global model appears to have converged well.

Code
iters <- ncol(p1$delta)
MCMCtrace(postl.global, "delta", pdf=F, Rhat=T, 
          priors=rnorm(iters, 0, 10), post_zm = FALSE)
MCMCtrace(postl.global, "beta", pdf=F, Rhat=T, 
          priors=rnorm(iters, 0, 10), post_zm = FALSE)       
MCMCtrace(postl.global, c("mean.s", "mean.tagfail", "mean.p.tagfail", "mean.p.dead"), pdf=F, Rhat=T, 
          priors=rbeta(iters, 1, 1), post_zm = FALSE)  

Figure S1: Traceplots (left) and density plots (right) of parameters from the global model. In density plots the black line depicts posterior density while the red line depicts prior density.

We examined traceplots and density plots of parameters from the reduced model. The reduced model appears to have converged well.

Code
iters <- ncol(p3$beta)
MCMCtrace(postl.reduced, "beta", pdf=F, Rhat=T, 
          priors=rnorm(iters, 0, 10), post_zm = FALSE)       
MCMCtrace(postl.reduced, c("mean.s", "mean.tagfail", "mean.p.tagfail", "mean.p.dead"), pdf=F, Rhat=T, 
          priors=rbeta(iters, 1, 1), post_zm = FALSE) 

Figure S2: Traceplots (left) and density plots (right) of parameters from the reduced model. In density plots the black line depicts posterior density while red line depicts prior density.

5.2 Model estimates

Next, we printed model estimates from the global and reduced models.

Code
# Estimates
# Survival by management period
ps <- c(  "delta", "beta",
          "mean.s", "mean.tagfail", "mean.p.tagfail", "mean.p.dead")
pars <- list( ps[-c(1,2)], ps, ps[-1])
flnms <- c(null="outputs/gyps-28Apr2026-marginalized-null.RData", 
           global="outputs/gyps-28Apr2026-marginalized-global.RData",
           reduced="outputs/gyps-28Apr2026-marginalized-reduced.RData")
coef.ests <- list()
for (i in 1:length(flnms)){
  load(flnms[i])
  postl <- lapply(post, function(x){ x$samples })
  post.samps <- do.call(rbind, postl)
  sum95 <- MCMCsummary(postl, pars[[i]], HPD=TRUE, digits=2, 
                       hpd_prob=0.95, pg0=TRUE, 
                       func=median, func_name="median",
                       exact=FALSE, ISB=TRUE)
  
  coef.ests[[i]] <- cbind(Model=names(flnms)[i], sum95)
} # end loop i
names(coef.ests) <- c("null", "global", "reduced")
coef.ests2 <- do.call(rbind, coef.ests)
coef.ests2
Table S3. Parameter estimates from the null, global, and reduced models.
Model mean sd 95%_HPDL 95%_HPDU Rhat n.eff p>0 median
null.mean.s[1] null 0.940 0.0310 0.880 0.990 1.00 3818 1.00 0.940
null.mean.s[2] null 0.970 0.0130 0.950 1.000 1.01 1452 1.00 0.970
null.mean.s[3] null 0.970 0.0120 0.950 0.990 1.02 1016 1.00 0.970
null.mean.tagfail null 0.019 0.0110 0.002 0.037 1.02 884 1.00 0.018
null.mean.p.tagfail null 0.280 0.2300 0.025 0.810 1.02 1199 1.00 0.200
null.mean.p.dead null 0.450 0.2100 0.160 0.910 1.02 1125 1.00 0.390
global.delta[1] global 0.180 0.4700 -0.680 1.100 1.01 1743 0.65 0.190
global.delta[2] global -0.380 0.2700 -0.930 0.120 1.00 3999 0.08 -0.390
global.delta[3] global -0.410 0.2700 -0.970 0.110 1.00 3491 0.07 -0.410
global.beta[1] global 4.100 0.9800 2.100 6.000 1.03 445 1.00 4.100
global.beta[2] global -2.500 0.5800 -3.700 -1.500 1.03 578 0.00 -2.500
global.mean.s[1] global 0.900 0.0570 0.790 0.990 1.00 3093 1.00 0.920
global.mean.s[2] global 0.970 0.0190 0.930 1.000 1.00 1980 1.00 0.970
global.mean.s[3] global 0.970 0.0150 0.940 0.990 1.01 1418 1.00 0.980
global.mean.tagfail global 0.630 0.1800 0.280 0.950 1.04 437 1.00 0.650
global.mean.p.tagfail global 0.150 0.0870 0.030 0.290 1.04 1674 1.00 0.130
global.mean.p.dead global 0.550 0.1500 0.250 0.840 1.00 1672 1.00 0.540
reduced.beta[1] reduced 3.900 1.0000 2.000 5.900 1.01 314 1.00 3.800
reduced.beta[2] reduced -2.500 0.5400 -3.600 -1.600 1.01 430 0.00 -2.500
reduced.mean.s[1] reduced 0.940 0.0320 0.880 0.990 1.00 4000 1.00 0.940
reduced.mean.s[2] reduced 0.980 0.0089 0.960 1.000 1.00 3392 1.00 0.980
reduced.mean.s[3] reduced 0.980 0.0053 0.970 0.990 1.00 2838 1.00 0.990
reduced.mean.tagfail reduced 0.610 0.1900 0.260 0.940 1.00 351 1.00 0.620
reduced.mean.p.tagfail reduced 0.130 0.0560 0.038 0.250 1.00 3877 1.00 0.120
reduced.mean.p.dead reduced 0.600 0.1400 0.330 0.890 1.00 2453 1.00 0.590
Code
# write.csv(file= "docs\\Coef_ests.csv",
#           coef.ests2)

5.3 Tag failure

We plotted predictions of tag failure in response to age of transmitter.

Code
#***************
#* Plot effect from tag age 
#* on tag failure
#* from reduced model
#***************
ta <- seq(0,5.3, by=0.1)
ta.sc <- (ta-5.436775)/3.997963
ni <- ncol(p3$beta)
yrs <- c(-0.73, 0.46) # set to 2011 for early period and 2020 for late
pred.ta <- array(NA, dim=c(length(ta.sc),2,ni), 
                 dimnames=list(tagage=ta, period=c("Early", "Late"), iter=1:ni) )
for (i in 1:length(ta.sc)){
  for (j in 1:2){
pred.ta[i,j,] <- p3$l.tagfail[1,] + p3$beta[1,]*ta.sc[i] + p3$beta[2,]*yrs[j]
}}
lp.ta <- as.data.frame.table(pred.ta, responseName = "value") 
lp.ta$tagage <- as.numeric(as.character(lp.ta$tagage))
lp.ta$pred <- plogis(lp.ta$value)
md <- plogis(apply(pred.ta, c(1,2), median, na.rm=T))
lhdi95 <- plogis(apply(pred.ta, c(1,2), HDInterval::hdi, na.rm=T)[1,,])
uhdi95 <- plogis(apply(pred.ta, c(1,2), HDInterval::hdi, na.rm=T)[2,,])
lhdi85 <- plogis(apply(pred.ta, c(1,2), HDInterval::hdi, na.rm=T, credMass=0.85)[1,,])
uhdi85 <- plogis(apply(pred.ta, c(1,2), HDInterval::hdi, na.rm=T, credMass=0.85)[2,,])
df <- data.frame(md=c(md[,1], md[,2]),
                 lhdi95=c(lhdi95[,1],lhdi95[,2]), 
                 uhdi95=c(uhdi95[,1],uhdi95[,2]),
                 lhdi85=c(lhdi85[,1],lhdi85[,2]), 
                 uhdi85=c(uhdi85[,1], uhdi85[,2]),
                 ta=c(ta, ta),
                 period=c(rep("Early", length(ta)), rep("Late", length(ta))) )

pyta <- ggplot() + theme_minimal() +
  geom_line(data=lp.ta, aes(x=tagage, y=1-((1-pred)^12), group=iter),
            color="gray40", linewidth=0.5, alpha=0.05) +
  scale_x_continuous(breaks=c(0,2,4,6)) +
  geom_line(data=df, aes(x=ta, y=1-((1-md)^12)), linewidth=1) +
  geom_line(data=df, aes(x=ta, y=1-((1-lhdi95)^12)), linewidth=0.5, linetype="dashed") +
  geom_line(data=df, aes(x=ta, y=1-((1-uhdi95)^12)), linewidth=0.5, linetype="dashed") +
  ylab("Transmitter failure (yearly probability)") + xlab("Transmitter age (years)") +
  facet_wrap("period")

pmta <- ggplot() + theme_minimal() +
  geom_line(data=lp.ta, aes(x=tagage, y=pred, group=iter),
            color="gray40", linewidth=0.5, alpha=0.05) +
  geom_line(data=df, aes(x=ta, y=md), linewidth=1) +
  geom_line(data=df, aes(x=ta, y=lhdi95), linewidth=0.5, linetype="dashed") +
  geom_line(data=df, aes(x=ta, y=uhdi95), linewidth=0.5, linetype="dashed") +
  ylab("Transmitter failure (monthly probability)") + xlab("Transmitter age (years)") +
  facet_wrap("period")


pyta
pmta
# ggsave("figs\\transmitter age and failure.tiff",
#        pyta, device="tiff",
#        width=6.5, height=4, units="in", dpi=300)
# 
# ggsave("figs\\transmitter age and failure-byMonth.tiff",
#        pmta, device="tiff",
#        width=6.5, height=4, units="in", dpi=300)

df2 <- data.frame(early.md = (1-((1-md)^12))[,1],
                  early.lhdi95= (1-((1-lhdi95)^12))[,1],
                  early.uhdi95= (1-((1-uhdi95)^12))[,1],
                  late.md = (1-((1-md)^12))[,2],
                  late.lhdi95= (1-((1-lhdi95)^12))[,2],
                  late.uhdi95= (1-((1-uhdi95)^12))[,2]
)
# df2 |> round(2)

(A) Yearly probabilities (above)

(B) Monthly probabilities (above)

Figure S3. Predicted tag failure in response to age of transmitter.

We plotted predictions of tag failure in response to year of study.

Code
#***************
#* Plot effect from year of study 
#* on tag failure
#***************
yr <- seq(2009,2025, by=0.1)
yr.sc <- (yr-2016)/7
ni <- ncol(p3$beta)
pred.tf.yr <- array(NA, dim=c(length(yr.sc), ni), 
                    dimnames=list(year=yr, iter=1:ni) )
for (i in 1:length(yr.sc)){
  pred.tf.yr[i,] <- p3$l.tagfail[1,] + p3$beta[1,]*-1.355 + p3$beta[2,]*yr.sc[i] 
}
lp.tf.yr <- as.data.frame.table(pred.tf.yr, responseName = "value") 
lp.tf.yr$year <- as.numeric(as.character(lp.tf.yr$year))
lp.tf.yr$pred <- plogis(lp.tf.yr$value)
lp.tf.yr <- lp.tf.yr[ (lp.tf.yr$year>=2009 & lp.tf.yr$year<=2011) |
                        (lp.tf.yr$year>=2017 & lp.tf.yr$year<=2024),  ]
lp.tf.yr$Period <- factor(ifelse(lp.tf.yr$year<=2011, "Early", "Late"), 
                          levels=c("Early", "Late"))
md <- plogis(apply(pred.tf.yr, 1, median, na.rm=T))
lhdi95 <- plogis(apply(pred.tf.yr, 1, HDInterval::hdi, na.rm=T)[1,])
uhdi95 <- plogis(apply(pred.tf.yr, 1, HDInterval::hdi, na.rm=T)[2,])
lhdi85 <- plogis(apply(pred.tf.yr, 1, HDInterval::hdi, na.rm=T, credMass=0.85)[1,])
uhdi85 <- plogis(apply(pred.tf.yr, 1, HDInterval::hdi, na.rm=T, credMass=0.85)[2,])
df <- data.frame(md=md, 
                 lhdi95=lhdi95, uhdi95=uhdi95, 
                 lhdi85=lhdi85, uhdi85=uhdi85,
                 yr=yr, 
                 md12= 1-((1-md)^12) )
sub.df <- (df$yr >=2009 & df$yr<=2011) |
  (df$yr>=2017 & df$yr<=2024)
df <- df[sub.df, ]  
df$Period <- factor(ifelse(df$yr<=2011, "Early", "Late"), 
                    levels=c("Early", "Late"))

pyty <- ggplot() + theme_minimal() +
  geom_line(data=lp.tf.yr, aes(x=year, y=1-((1-pred)^12), group=iter),
            color="gray40", linewidth=0.5, alpha=0.05) +
  geom_line(data=df, aes(x=yr, y=1-((1-md)^12)), linewidth=1) +
  geom_line(data=df, aes(x=yr, y=1-((1-lhdi95)^12)), linewidth=0.5, linetype="dashed") +
  geom_line(data=df, aes(x=yr, y=1-((1-uhdi95)^12)), linewidth=0.5, linetype="dashed") +
  ylab("Transmitter failure (yearly probability)") + xlab("Year of study") +
  scale_x_continuous(breaks=c(2009:2011, 2018, 2020, 2022, 2024)) +
  facet_wrap("Period", scales="free_x")

pmty <- ggplot() + theme_minimal() +
  geom_line(data=lp.tf.yr, aes(x=year, y=pred, group=iter),
            color="gray40", linewidth=0.5, alpha=0.05) +
  geom_line(data=df, aes(x=yr, y=md), linewidth=1) +
  geom_line(data=df, aes(x=yr, y=lhdi95), linewidth=0.5, linetype="dashed") +
  geom_line(data=df, aes(x=yr, y=uhdi95), linewidth=0.5, linetype="dashed") +
  ylab("Transmitter failure (monthly probability)") + xlab("Year of study") +
  scale_x_continuous(breaks=c(2009:2011, 2018, 2020, 2022, 2024)) +
  facet_wrap("Period", scales="free_x")

pyty
pmty
# ggsave("figs\\tagfailure-year.tiff",
#        pyty, device="tiff", 
#        width=6.5, height=4, units="in", dpi=300)
# 
# ggsave("figs\\tagfailure-month.tiff",
#        pmty, device="tiff", 
#        width=6.5, height=4, units="in", dpi=300)

df2 <- data.frame(md =1-((1-md)^12), 
                  lhdi95= 1-((1-lhdi95)^12),
                  uhdi95=1-((1-uhdi95)^12) )

(A) Yearly probabilities (above)

(B) Monthly probabilities (above)

Figure S4. Predicted tag failure in response to year.

5.4 Survival by age class and study period

We plotted predictions of survival by study period and compared to predictions of all study periods combined.

Code
#****************
#* plot survival in response to period
#****************
lss <- as.data.frame.table(p3$mean.s, responseName = "value")

ps2 <- lss |>
  ggplot(aes(x = value, y = Var1)) + theme_minimal() +
  scale_y_discrete(labels=c("First year", "Subadult", "Adult")) +
  stat_halfeye(.width=c(0.85, 0.95), point_interval="median_hdci") +
  ylab("Age class") + xlab("Survival (monthly probability)") +
  coord_flip() 

ps3 <- lss |>
  ggplot(aes(x = value^12, y = Var1)) + 
  theme_minimal() +
  scale_y_discrete(labels=c("", "", "")) +
  stat_halfeye(.width=c(0.85, 0.95), point_interval="median_hdci") +
  ylab("") + xlab("Survival (yearly probability)") +
  xlim(0, 1) +
  coord_flip() +
  ggtitle("(B) Combined") 

ni <- ncol(p1$delta)
pred.man <- array(NA, dim=c(3, 2, ni), 
                 dimnames=list(c("First year", "Subadult", "Adult"), c("Early", "Late"), 1:ni) )
for (a in 1:3){
for (m in 1:2){
  pred.man[a,m,] <- p1$l.s[a,] + 
                    p1$delta[1,]*c(-1,1)[m]
}}
lp.man <- as.data.frame.table(pred.man, responseName = "value")
colnames(lp.man)[1:3] <- c("Ageclass", "Period", "iter" )
lp.man$pred <- plogis(lp.man$value)

p4 <- ggplot(data=lp.man, aes(x=pred^12, y=Period)) + theme_minimal() +
  geom_line(data=lp.man, aes(x=pred^12, y=Period, group=iter),
            color="gray40", linewidth=0.5, alpha=0.025) +
  stat_pointinterval(.width=c(0.85, 0.95), 
                     point_interval ="median_hdci") +
  facet_wrap(facets=vars(Ageclass)) + 
  xlim(0,1) +
  coord_flip() +
  ylab("Period") + xlab("Survival (yearly probability)") +
  ggtitle("(A) Period")

all_p <- ggarrange(p4, ps3, nrow=2)

all_p

Figure S5. Predicted survival (yearly) by study period (A) and combined (B).
Code
# ggsave("figs\\survival-ageclass-period-combined.tiff",
#        all_p, device="jpeg", 
#        width=6, height=6, units="in", dpi=300)

We printed a table and compared survival estimates between periods and combined.

Code
# survival by age class
# and management period
md <- plogis(apply(pred.man, c(1,2), median, na.rm=T))^12
mn <- plogis(apply(pred.man, c(1,2), mean, na.rm=T))^12
lhdi95 <- plogis(apply(pred.man, c(1,2), HDInterval::hdi, na.rm=T)[1,,])^12
uhdi95 <- plogis(apply(pred.man, c(1,2), HDInterval::hdi, na.rm=T)[2,,])^12

df <- data.frame(as.data.frame.table(md, responseName = "value"), 
                 Mean= as.data.frame.table(mn, responseName = "value")[,3],
                 lhdi95=as.data.frame.table(lhdi95, responseName = "value")[,3] |> round(2), 
                 uhdi95=as.data.frame.table(uhdi95, responseName = "value")[,3] |> round(2))
df <- df[order(df$Var1, df$Var2),]
colnames(df)[1:3] <- c("Age.class", "Period", "Median")
df$Median <- round(df$Median, 2)
df$Mean <- round(df$Mean, 2)

# get combined estimates from reduced model
df2 <- data.frame("Age.class"= c("First year", "Subadult", "Adult"),
                  "Period"= rep("Combined", 3),
                  "Median"= apply(p3$mean.s^12, 1, median) |> round(2),
                  "Mean"= apply(p3$mean.s^12, 1, mean) |> round(2),
                  "lhdi95"=apply(p3$mean.s^12, 1, HDInterval::hdi, credMass=0.95)[1,] |> round(2), 
                  "uhdi95"=apply(p3$mean.s^12, 1, HDInterval::hdi, credMass=0.95)[2,] |> round(2)
)
df3 <- rbind(df, df2)
df3 <- df3[order(df3$Age.class, df3$Period), ]
df3
# write.csv(file= "docs\\Survival_age_period.csv",
#           df3  )

# Generate probability of direction
# for comparing survival of age classes
# First we calculate the posterior survival differences
# Then we calculate the median and HDIs of survival differences
# Then we calculate PDs for age classes
s.diffs <- list()
s.diffs[[1]] <- p3$mean.s[3,] - p3$mean.s[2,]
s.diffs[[2]] <- p3$mean.s[3,] - p3$mean.s[1,]
s.diffs[[3]] <- p3$mean.s[2,] - p3$mean.s[1,]
surv.diffs <- data.frame( comparison = c("adults and subadults", "adults and first years", "subadults and first years"),
                          pd = lapply(s.diffs, function(x) { pd(x)$pd} ) |> unlist() |> round(2), 
                          median.diff = lapply(s.diffs, median ) |> unlist() |> round(3),
                          lapply(s.diffs, HDInterval::hdi, credMass=0.95) |> do.call(what=rbind) |> round(3) ) 
surv.diffs
Survival during early, late, and combined periods. {#tbl-anonymous-7874780-1}
Age.class Period Median Mean lhdi95 uhdi95
1 First year Early 0.29 0.30 0.00 0.84
4 First year Late 0.41 0.42 0.07 0.77
mean.s[1] First year Combined 0.47 0.48 0.17 0.81
2 Subadult Early 0.67 0.67 0.08 0.94
5 Subadult Late 0.75 0.76 0.51 0.90
mean.s[2] Subadult Combined 0.79 0.79 0.62 0.94
3 Adult Early 0.69 0.70 0.12 0.95
6 Adult Late 0.77 0.78 0.61 0.89
mean.s[3] Adult Combined 0.83 0.83 0.73 0.93
Differences in survival by age class. {#tbl-anonymous-7874780-2}
comparison pd median.diff lower upper
adults and subadults 0.68 0.004 -0.014 0.024
adults and first years 0.98 0.045 -0.006 0.113
subadults and first years 0.95 0.040 -0.012 0.111

Table S4. Yearly survival estimates.