Bayesian methods using JAGS

Currently, we use Stan and Julia's Turing and also exploring the utility of Google's TensorFlow (via Greta) and R.

NOTE: The following is legacy code. Bugs is still very useful and interesting. But do consider Greta and Turing.


The use of WinBUGS is a powerful means of leveraging Bayesian methods. Its use is however a bit complicated in a non-MSWindows environment. I use the excellent and opensource: JAGS (Just Another Gibbs Sampler) created by Martyn Plummer, which is almost completely compatible with the BUGS syntax used in WinBUGS and ported to Linux, MAC and MSWindows. A direct connection to R is provided by the R-library, rjags. This means that a lot of things are more simply and flexibly done with JAGS. Install JAGS and the R-library rjags ( install.packages( "rjags" ) ).

The following is an example of how to use it to do some parameter fitting to a simple population dynamic model. For more details about the model, see the current version of the snow crab assessment. You will need to have some familiarity with Bayesian methods to understand what is being done below. Sorry, I will work on an introductory level tutorial when I get the chance.

  source( "http://bio-r.googlecode.com/files/functions.surplus.production.r" )

  source( "http://bio-r.googlecode.com/files/functions.jags.r" )

  require(rjags)

 

  # load a few useful modules (JAGS is modular in design)

  rjags::load.module("dic")

  # rjags::load.module("glm")

  # load some example data

  load( url( "http://bio-r.googlecode.com/files/L.bugs.test.rdata" ) )  # load L

  load( url( "http://bio-r.googlecode.com/files/B.bugs.test.rdata" ) )  # load B

  BI = B  # rename to avoid confusion as "B" is used below

  abundance.index = B

  # makes sure data are from overlapping years

  oo =  which(rownames(L) %in% rownames(BI) )

  landings = L[ oo,  ]

  # parameter list   

  sb = list(

    b0x = c(0.8, 0.6, 0.1),  # biomass scaled by carrying capacity

    q0x = c(1, 1, 1),  # catchability

    r0x = c(1, 1, 1),  # intrinsic rate of increase

    K0x = c(4, 50, 1 ),  #  carrying capacity

    O = abundance.index , # observed index of abundance

    C = landings , # catches

    sigma = 1/4,

    er = 0.2,  # target exploitation rate .. for projections

    nProj = 5 # TACS for prediction scenarios

  )

 

  # here is the core of the model (a observation and process error model)

  # It is stored for convenience in "functions.surplus.production.r"

  cat ( biomass.logistic.hierarchical.jags )

   # set up the model for a run and start a burn-in

   m = jags.model( file=jags.setup( biomass.logistic.hierarchical.jags ), data=sb, n.chains=3, n.adapt=20000 ) 

  

  # choose objects to monitor

  tomonitor = c("B", "r", "K", "q", "MSY", "BMSY", "FMSY", "XMSY",

      "sd.o", "sd.p", "sd.r", "sd.K", "sd.q", "TAC" )

 

  # check coefficients of the model to make sure starting parameters are reasonable 

  coef(m)

  # pDIC :

  m.dic = dic.samples(m, n.iter=10000) 

    # Mean deviance:  9.24  penalty 189  Penalized deviance: 198

 

  # check for convergence of MCMC

   y = jags.samples(m, variable.names=tomonitor, n.iter=20000, thin=10)

    gelman.plot(y[["r"]])

    gelman.plot(y[["K"]])

 

   # 10000 samples seems enough but a few more to be sure

   update(m, n.iter=50000 )

  # autocorrelation thinning

   y = coda.samples(m, variable.names=c("K", "r", "q"), n.iter=20000, thin=10) # sample from posterior

   autocorr.plot(y)

  # thin the data

    thin=400

    niter=1000*thin

 

  # sample from posterior

  y = jags.samples(m, variable.names=tomonitor, n.iter=niter, thin=thin)

  # extract data and do some summary statistics

    B = jags.extract( y, "B", mean )

    K = jags.extract( y, "K", mean )

  

  # instantaneous fishing mortality estimates

     summary(y$F, median)

  # a few plots

  ntacs = sb$nProj

  yrs0 = as.numeric( as.character( rownames(BI) ) )

  yrs = c( yrs0, (max(yrs0)+c(1:ntacs) ) )

  yrs.last = max(yrs0) + 0.5

  ndata = length(yrs0)

  hdat = 1:ndata

 

  graphics.off()

  layout( matrix(c(1,2,3), 3, 1 ))

  par(mar = c(5, 4, 0, 2))

  # timeseries of biomass

      for (i in 1:3) {

        yran = range(c(0, B[,i], BI[,i]/ q[i], BI[,i] ))*1.05

        plot( yrs, B[,i] , type="l", lwd=5, col="darkgray", ylim=yran, xlab="", ylab=""  )

        points( yrs0, BI[,i]/ q[i],  pch=20, col="black", cex=2 )

        abline (v=yrs.last , lwd=2, lty="dashed" )

        if (i==2) title( ylab="Fishable biomass (kt)" )

        if (i==3) title( xlab="Year" )

        points( yrs0, BI[,i], pch=20, col="darkgreen" )

        lines ( yrs0, BI[,i], lwd=2, col="darkgreen", lty="dashed" )

      }

  # Harvest Control plots

  graphics.off()

  layout( matrix(c(1,2,3), 3, 1 ))

  par(mar = c(5, 4, 0, 2))

  require(car)

  for (i in 1:3 ) {

    # x11()

    plot( B[hdat,i], F[hdat,i],  type="b", xlim=c(0, K[i] * 1.25 ), 

      ylim=c(0, 0.7), col="darkorange", cex=0.8, lwd=2, xlab="", ylab="", pch=20 )

    text( B[hdat,i], F[hdat,i],  labels=yrs0, pos=3, cex= 0.8 )

    nn = as.matrix( cbind( Bx=as.vector( y$B[ndata,i,,] ), Fx = as.vector( y$F[ndata,i,,] ) ))

    ellipse.2d(nn[,1], nn[,2], pv=0.1, sc=150)

   

      if (i==3) title( xlab="Fishable biomass (kt)" )

      if (i==2) title( ylab="Fishing mortality" )

    Fmsy = FMSY[,i]

    Fref = FMSY[,i] * 0.2

    Bmsy = BMSY[,i]

    Bref = K[,i] * 0.2

    BK = K[,i]

    Fhistorical = mean( F[hdat,i], na.rm=T )

    Bhistorical = mean( B[hdat,i], na.rm=T )

    yl = 0.6

   

    # abline (h=Fref, lty="dotted")

    # text( 0.25, Fref, "Target\n (0.2 * FMSY) ", pos=1 )

    abline (h=Fmsy, lty="dotted")

    text( 0.05*K[i], Fmsy, "FMSY", pos=3 )

    abline (h=Fhistorical, lty="dashed")

    text( 0.05*K[i], Fhistorical, "Historical\n mean", pos=1, lwd=2 )

   

    # abline (v=Bref, lty="dotted")

    # text( Bref-0.2, 0.25, "Lower biomass reference point\n (LBRP = 0.2 * BMSY)" , srt=90, pos=3)

    abline (v=Bmsy, lty="dotted")

    text( Bmsy-0.01*K[i], yl, "BMSY" , srt=90, pos=3)

 

    abline (v=BK, lty="dotted")

    text( BK-0.01*K[i], yl, "K" , srt=90, pos=3)

    abline (v=Bhistorical, lty="dashed")

    text( Bhistorical-0.01*K[i], yl, "Historical\n mean" , srt=90, pos=3,  lwd=2)

  }

   

    # densities of K

     

      graphics.off()

      layout( matrix(c(1,2,3), 3, 1 ))

      par(mar = c(5, 4, 0, 2))

      for (i in 1:3) plot(density( y$K[i,,] ), main="")

      qs = apply( y$K[,,], 1, quantile, probs=c(0.025, 0.5, 0.975) )

      qs