##' Creates and fits a selection curve to the supplied \code{bsmdata} object. The correct data list
##' is created, the JAGS model constructed, and the model is run with default settings. It is also
##' possible to suppress the running of the JAGS and simply output the model and data.
##'
##' There are going to be a lot of details ...
##'
##' @title Fit a Bayesian Selectivity Model
##'
##' @param x an object of class \code{bsmdata}, from the \code{bsmData} function
##'
##' @param family \code{"logistic"} (default) or \code{"poisson"}, the family for the likelihood
##'
##' @param curve \code{"logistic"} or \code{"richards"}, the type of selection curve to be fitted
##' (will likely allow other options, for example "Bspline", future)
##'
##' @param check.od logical, if \code{TRUE}, then overdispersion estimates will be produced
##'
##' @param od logical, if \code{TRUE}, the model will be fit allowing for overdispersion
##'
##' @param combine logical, if \code{TRUE}, then the 'combined hauls' approach is used, otherwise a
##' hierarchical approach is used.
##'
##' @param random vector of parameters to have hierarchical or random effects
##'
##' @param L50 the formula for L50, default is L50 = ~1
##'
##' @param SR the formula for SR, default is SR = ~1
##'
##' @param phi the formula for phi, default is phi = ~1
##'
##' @param delta the formula for delta, default is delta = ~1
##'
##' @param priors the prior distributions for specified parameters. See details.
##'
##' @param inits initial values for parameters
##'
##' @param length.dist 'iid' or "multinomial", the type of length distribution for the lambda
##' parameters. See details for more information.
##'
##' @param parameters the parameters to save in the JAGS output. NOTE: only use this if you really
##' really only want these parameters - may cause errors in summary output and plots depending on
##' parameters selected.
##'
##' @param extra.pars additional parameters to be tracked as well as \code{parameters}. Lets you
##' keep the default parameters.
##'
##' @param file the file name to save the JAGS model in. If \code{NULL}, then a temporary file is
##' created
##'
##' @param fit logical, if \code{TRUE} then the JAGS model is fit, otherwise only the model and data
##' are returned.
##'
##' @param include.lambda logical, if \code{TRUE}, the lambda parameter will be saved as well
##'
##' @param n.samples the total number of samples to obtain (excludes burnin and thin)
##'
##' @param n.thin the thinning interval
##'
##' @param n.burn the burn in period
##'
##' @param n.chains the number of chains
##'
##' @param max.attempts the maximum number of times bsm will attempt to fit the JAGS model (often
##' due to bad initial values, the model cannot be initiated and fails)
##'
##' @param quiet suppress all output messages from JAGS?
##'
##' @param progress.bar one of "none", "text" or "gui" for the JAGS progress bar
##'
##' @param show.info logical, if \code{FALSE}, the package information will not be printed
##'
##' @param ... additional arguments
##'
##' @return an object of class \code{bsmfit}
##'
##' @author Tom Elliott
##'
##' @export
bsm <- function(x, family = "binomial", curve = "logistic", check.od = TRUE, od = FALSE,
combine = FALSE, random = NULL, L50 = ~1, SR = ~1,
phi = if (attr(x, "paired")) ~1 else NULL,
delta = if (curve == "richards") ~1 else NULL,
priors = NULL, inits = NULL, length.dist = "iid",
parameters = par$summary.parameters, extra.pars = NULL, file = NULL, fit = TRUE,
include.lambda = FALSE,
n.samples = 1000, n.thin = 1, n.burn = n.samples * n.thin, n.chains = 3,
max.attempts = 10, quiet = FALSE, progress.bar = "text",
show.info = TRUE, ...) {
if (class(x) != "bsmdata")
stop("x must be a bsmdata object from the bsmData function")
default.priors <- list(L50 = "dnorm(0, 1E-6)T(0,)",
SR = "dlnorm(0, 1E-5)",
delta = "dlnorm(0, 1E-3)",
phi = "dnorm(0, 1E-3)")
# sig_od = "dgamma(1E-6, 1E-6)")
if (!is.null(priors)) {
priors <- c(priors, default.priors[!names(default.priors) %in% names(priors)])
} else priors <- default.priors
if (combine) {
## If we are to combine, we need to account for sampling fractions:
## We will recreate 'x' to remove haul etc etc etc
df <- as.data.frame(x)
y1 <- df$y1 / df$q1
y2 <- df$y2 / df$q2
lens <- sort(unique(df$length))
y1 <- round(tapply(y1, df$length, sum))
y2 <- round(tapply(y2, df$length, sum))
x <- bsmData(y1 = y1, y2 = y2, length = lens,
length.unit = attr(x, "length.unit"),
paired = attr(x, "paired"))
combine <- FALSE
## Can't check overdispersion ...?
## check.od <- FALSE # (for now ...)
}
## Check for "haul" in the design
## Create design matrices for parameters:
L50des <- SRdes <- phides <- deltades <- NULL
if (!is.null(L50)) {
if ("haul" %in% attr(terms(L50), "term.labels")) {
if (!"L50" %in% random)
random <- c(random, "L50")
L50 <- update.formula(L50, ~.-haul)
}
L50des <- makeDesign(L50, as.data.frame(x))
}
if (!is.null(SR)) {
if ("haul" %in% attr(terms(SR), "term.labels")) {
if (!"SR" %in% random)
random <- c(random, "SR")
SR <- update.formula(SR, ~.-haul)
}
SRdes <- makeDesign(SR, as.data.frame(x))
}
if (!is.null(phi) & attr(x, "paired")) {
if ("haul" %in% attr(terms(phi), "term.labels") & !"phi" %in% random)
random <- c(random, "phi")
phides <- makeDesign(phi, as.data.frame(x))
}
if (!is.null(delta)) {
if ("haul" %in% attr(terms(delta), "term.labels") & !"delta" %in% random)
random <- c(random, "delta")
deltades <- makeDesign(delta, as.data.frame(x))
}
mod <- makeModel(family, curve, check.od, od, combine, random,
L50des, SRdes, phides, deltades, priors, length.dist,
paired = attr(x, "paired"), file, show.info)
dat <- makeData(x, family, combine, random, L50des, SRdes, phides, deltades)
par <- getPars(family, curve, check.od, od, combine, random,
L50des, SRdes, phides, deltades, length.dist,
paired = attr(x, "paired"))
## Some more convenient ways of including additional parameters:
if (include.lambda & family == "poisson") {
parameters <- c(parameters, "lambda")
}
if (!is.null(extra.pars)) {
parameters <- c(parameters, extra.pars)
}
default.inits <- function()
list(mu_L50 = rnorm(1, median(dat$x), diff(range(dat$x))/2),
mu_SR = rlnorm(1, log(diff(range(dat$x))/3)))
if (is.null(inits)) {
final.inits <- default.inits
} else if (is.function(inits)) {
## Combine output of two functions ......??
final.inits <- function() {
ini <- inits()
def.ini <- default.inits()
c(ini, def.ini[!names(def.ini) %in% names(ini)])
}
} else {
if (!all(sapply(inits, class) == "list"))
stop("Inits needs to be a list of lists (one per chain), or a function returning a list. See ?jags")
if (length(inits) < n.chains)
stop(sprintf("Inits needs to be a list of %s lists. See ?jags", n.chains))
final.inits <- lapply(1:n.chains, function(i) {
ini <- inits[[1]]
def.ini <- default.inits()
c(ini, def.ini[!names(def.ini) %in% names(ini)])
})
}
## Fit the model!!
out <- list()
if (fit) {
for (i in 1:max.attempts) {
if (quiet)
capture.output(suppressMessages(
j <- try(R2jags::jags(dat, final.inits, parameters, mod,
n.chains = n.chains, n.iter = n.burn + n.samples * n.thin,
n.burnin = n.burn, n.thin = n.thin,
progress.bar = progress.bar),
TRUE)),
file = tempfile()
)
else
j <- try(R2jags::jags(dat, final.inits, parameters, mod,
n.chains = n.chains, n.iter = n.burn + n.samples * n.thin,
n.burnin = n.burn, n.thin = n.thin,
progress.bar = progress.bar),
TRUE)
if (!inherits(j, "try-error"))
break
}
if (inherits(j, "try-error")) {
cat("\n", paste0(rep("=", options()$width), collapse=""), "\n")
warning(paste0(
"The following error occured while attempting to fit the model:\n\n",
as.character(j),
" You may need to specify initial values. See details in ?bsm for help."))
} else {
out$fit <- j
}
}
bugs <- out$fit$BUGSoutput
ordL <- bugs$long.short
names(ordL) <- pp <- bugs$root.short
indL <- lapply(bugs$indexes.short, function(i) if (is.null(i)) 0 else unlist(i))
pp <- rep(pp, sapply(ordL, length))
ord <- unlist(sapply(parameters, function(x) ordL[[x]]))
ind <- unlist(indL, use.names = FALSE)[ord]
indo <- ifelse(ind == 0, "", paste0("[", ind, "]"))
all.parameters <- paste0(pp[ord], indo)
out <- c(out, list(data = dat, file = mod,
summary.parameters = parameters,
all.parameters = all.parameters,
object = x, combine = combine,
L50 = L50des, SR = SRdes, phi = phides, delta = deltades,
L50f = L50, SRf = SR, phif = phi, deltaf = delta,
family = family, curve = curve, check.od = check.od, od = od))
class(out) <- "bsmfit"
out
}
##' @param model logical, if \code{TRUE}, then the JAGS model is printed, otherwise summary
##' information is provided
##' @param coda logical, if \code{TRUE}, then the summary from the \code{coda} package is used
##'
##' @describeIn bsm Print the summary of the model, or the model code
##' @export
print.bsmfit <- function(x, model = FALSE, coda = FALSE, ...) {
if (model) {
cat(readLines(x$file), sep = "\n")
} else if ("fit" %in% names(x)) {
if (coda) {
## Maintain the ordering of the parameters
pp <- rownames(x$fit$BUGSoutput$summary)
print(summary(coda::as.mcmc(x$fit)[, pp]), ...)
} else {
print(x$fit, ...)
}
} else {
cat("\n")
cat(" JAGS model written to \"", x$file, "\"\n", sep = "")
cat(" Data can be accessed by `$data`\n\n")
}
return(invisible(NULL))
}
##' @param object a bsmfit object
##' @param p.values logical, include significance tests for parameters != 0?
##' @param formula.values logical, display values in formulae?
##' @param predict.values values to be included in the prediction
##'
##' @describeIn bsm Generate summary output for a bsmfit object
##' @export
summary.bsmfit <- function(object, p.values = FALSE, formula.values = FALSE,
predict.values = NULL, ...) {
x <- object
fit <- x$fit
## In several parts - first, create summary "list" with a class - then give this a print method
## 1: Summary information of the model.
out <- list()
out$type <- ifelse(attr(x$object, "paired"), "paired", "covered-codend")
out$family <- x$family
out$curve <- x$curve
bugs <- fit$BUGSoutput
ordL <- bugs$long.short
names(ordL) <- pp <- bugs$root.short
indL <- lapply(bugs$indexes.short, function(i) if (is.null(i)) 0 else unlist(i))
pp <- rep(pp, sapply(ordL, length))
ord <- c(ordL$mu_L50, ordL$mu_SR)
if ("mu_phi" %in% pp)
ord <- c(ord, ordL$mu_phi)
if ("mu_delta" %in% pp)
ord <- c(ord, ordL$mu_delta)
if ("sig2_L50" %in% pp)
ord <- c(ord, ordL$sig2_L50)
if ("sig2_SR" %in% pp)
ord <- c(ord, ordL$sig2_SR)
if ("sig2_phi" %in% pp)
ord <- c(ord, ordL$sig2_phi)
if ("sig2_delta" %in% pp)
ord <- c(ord, ordL$sig2_delta)
if ("beta" %in% pp)
ord <- c(ord, ordL$beta)
if ("gamma" %in% pp)
ord <- c(ord, ordL$gamma)
if ("omega" %in% pp)
ord <- c(ord, ordL$omega)
if ("zeta" %in% pp)
ord <- c(ord, ordL$zeta)
mat <- bugs$summary[ord, c("mean", "sd", "2.5%", "50%", "97.5%", "Rhat")]
ind <- unlist(indL, use.names = FALSE)[ord]
indo <- ifelse(ind == 0, "", paste0("[", ind, "]"))
ppo <- pp[ord]
rownames(mat) <- paste(ppo, indo, sep = "")
out$summary.matrix <- mat
if (!is.null(x$L50))
out$L50f <- generateFormula("L50", x$L50f, x$object,
mat[, "mean", drop = FALSE], getMean(x, "L50"))
if (!is.null(x$SR))
out$SRf <- generateFormula("SR", x$SRf, x$object,
mat[, "mean", drop = FALSE], getMean(x, "SR"))
if (!is.null(x$phi))
out$phif <- generateFormula("phi", x$phif, x$object,
mat[, "mean", drop = FALSE], getMean(x, "phi"))
if (!is.null(x$delta))
out$deltaf <- generateFormula("delta", x$deltaf, x$object,
mat[, "mean", drop = FALSE], getMean(x, "delta"))
## Generate predictions for each 'curve'
## Have to update variables to take away center
if (!is.null(predict.values)) {
predict.values <- list(original = predict.values,
centers =
lapply(pred.names <- names(predict.values), function(var) {
obj.df <- as.data.frame(x$object)
if (var %in% colnames(obj.df)) {
center <- mean(obj.df[, var])
} else {
warning(paste0(var, " is not a known variable."))
center <- 0
}
center
}))
names(predict.values$centers) <- pred.names
}
if (!is.null(x$L50))
L50mat <- predictPar(out$L50f, predict.values = predict.values)
if (!is.null(x$SR))
SRmat <- predictPar(out$SRf, predict.values = predict.values)
if (!is.null(x$phi))
phimat <- predictPar(out$phif, predict.values = predict.values)
if (!is.null(x$delta))
deltamat <- predictPar(out$deltaf, predict.values = predict.values)
## Prediction matrix:
if (!is.null(x$L50)) {
pred.mat <- L50mat
} else {
pred.mat <- matrix(mat["mu_L50", "mean"], dimnames = list(1, "L50"))
}
if (!is.null(x$SR)) {
pred.mat <-
if (is.null(pred.mat)) SRmat
else merge(pred.mat, SRmat)
} else {
pred.mat <-
if (is.null(pred.mat)) matrix(mat["mu_SR", "mean"], dimnames = list(1, "SR"))
else cbind(pred.mat, SR = mat["mu_SR", "mean"])
}
if (!is.null(x$phi)) {
pred.mat <-
if (is.null(pred.mat)) phimat
else merge(pred.mat, phimat)
} else if (attr(x$object, "paired")) {
pred.mat <-
if (is.null(pred.mat)) matrix(mat["mu_phi", "mean"], dimnames = list(1, "phi"))
else cbind(pred.mat, phi = mat["mu_phi", "mean"])
}
if (!is.null(x$delta)) {
pred.mat <-
if (is.null(pred.mat)) deltamat
else merge(pred.mat, deltamat)
} else if (x$curve == "richards") {
pred.mat <-
if (is.null(pred.mat)) matrix(mat["mu_delta", "mean"], dimnames = list(1, "delta"))
else cbind(pred.mat, delta = mat["mu_delta", "mean"])
}
which <- colnames(pred.mat) %in% c("L50", "SR", "phi", "delta")
pred.response <- pred.mat[, which, drop = FALSE]
pred.explanatory <- pred.mat[, !which, drop = FALSE]
predict <- cbind(pred.explanatory, pred.response)
predict.df <- as.data.frame(predict)
predict.df <- do.call(data.frame, lapply(predict.df, function(x) {
tmp <- try(as.numeric(x))
if (inherits(tmp, "try-error"))
x
else
tmp
}))
dropCols <- apply(predict.df, 2, function(x) if (is.numeric(x)) all(x == 0) else FALSE)
out$predict <- as.data.frame(predict[, !dropCols, drop = FALSE])
if (x$check.od) {
odmat <- bugs$summary[c("od_exp", "od_obs", "p_od"), "mean"]
out$overdispersion <- list(
check = list(
expected.chisquare = odmat[1],
observed.chisquare = odmat[2],
p.overdispersion = odmat[3]
)
)
}
if (x$od) {
if (!x$check.od)
out$overdispersion <- list()
out$overdispersion$variance = bugs$summary[c("sig2_od"), "mean"]
}
## "significance" tests
if (p.values)
out$p.values <- apply(bugs$sims.matrix[, rownames(mat)], 2,
function(x) {
m <- mean(x < 0)
min(m, 1 - m)
})
## MCMC info
out$mcmc <- with(x$fit$BUGSoutput,
list(
chains = n.chains,
samples = n.keep,
thin = n.thin,
burn = n.burnin,
iterations = n.iter,
total = n.sims,
DIC = DIC,
pD = pD
))
out$gelman <- coda::gelman.diag(as.mcmc(x))
out$geweke <- coda::geweke.diag(as.mcmc(x))
out$heidel <- coda::heidel.diag(as.mcmc(x))
out$formula.values <- formula.values
class(out) <- "summary.bsmfit"
out
}
##' @export
print.summary.bsmfit <- function(x, ...) {
cat("\n")
cat(sprintf("Summary of model fitted to %s experimental trawl using JAGS.\n\n", x$type))
cat(" Likelihood: ", x$family, "\n", sep = "")
cat(" Curve: ", x$curve, "\n\n", sep = "")
cat("Summary of posterior distribution for the model:\n\n")
cat(apply(cbind("",
format(c("", rownames(x$summary.matrix)), justify = "right"),
do.call(cbind, lapply(colnames(x$summary.matrix), function(v) {
format(c(v, format(x$summary.matrix[, v], digits = 4)), justify = "right")
})),
if (!is.null(x$p.values)) {
p <- x$p.values
format(c("Pr(x=0)",
ifelse(p < 2e-16, "<2e-16", signif(p, 2))),
justify = "right")
} else { "" },
"\n"),
1, paste, collapse = " "), sep = "")
if (any(NL <- sapply(x[c("L50f", "SRf", "phif", "deltaf")], function(x) !is.null(x))))
cat(sprintf("\nFormula%s for selection curve parameter%s:\n",
ifelse(sum(NL) > 1, "e", ""), ifelse(sum(NL) > 1, "s", "")))
if (!is.null(x$L50f))
print(x$L50f, use.values = x$formula.values)
if (!is.null(x$SRf))
print(x$SRf, use.values = x$formula.values)
if (!is.null(x$phif))
print(x$phif, use.values = x$formula.values)
if (!is.null(x$deltaf))
print(x$deltaf, use.values = x$formula.values)
cat("\n")
if (!is.null(x$overdispersion)) {
cat("Overdispersion information:\n")
od <- x$overdispersion
if (exists("variance", od)) {
cat("The estimated (mean) overdispersion parameter is:",
format(od$variance, digits = 3), "\n")
}
if (exists("check", od)) {
cat("\n")
pval <- unlist(od$check)[3]
cat(apply(cbind(" ",
format(c(paste(format(c("Expected", "Observed"), justify = "right"),
"Pearson Chi-square:"),
"Pr[overdispersion]:"), justify = "right"),
format(c(format(unlist(od$check)[1:2], digits = 4),
ifelse(pval < 1e-12, "< 1e-12", signif(pval, 2))),
justify = "right"),
"\n"),
1, paste, collapse = " "), sep = "")
}
cat("\n")
}
cat("MCMC Information:\n")
mcmc <- x$mcmc
cat(apply(cbind("",
format(c("Chains", mcmc$chains), justify = "right"),
format(c("Iterations/chain", mcmc$iterations), justify = "right"),
format(c("Burn-in", mcmc$burn), justify = "right"),
format(c("Thin", mcmc$thin), justify = "right"),
format(c("Total", mcmc$total), justify = "right"),
"\n"),
1, paste, collapse = " "), sep = "")
cat(sprintf("\n Model DIC = %s, pD = %s\n",
format(mcmc$DIC, digits = 5), format(mcmc$pD, digits = 5)))
cat(sprintf(" Gelman and Rubin Convergence Statistic (multivariate) = %s\n",
format(x$gelman$mpsrf, digits = 4)))
## check if Heidelberger says it's stationary:
heidel <- x$heidel
stest <- all(sapply(heidel, function(x) {
X <- x[, "stest"]
all(X[!is.na(X)] == 1)
}))
htest <- all(sapply(heidel, function(x) {
X <- x[, "htest"]
all(X[!is.na(X)] == 1)
}))
if (stest & htest) {
cat(" Heidelberger & Welch's Stationarity and Half-width Tests passed.\n")
} else {
if (!stest & !htest)
txt <- "Heidelberger & Welch's Stationarity and Half-width Tests failed."
else if (!stest)
txt <- "Heidelberger & Welch's Stationarity Test failed."
else if (!htest)
txt <- "Heidelberger & Welch's Half-width Test failed."
cat("\nWarning message:\n ", txt, "\n You should try increasing the chain length.", sep = "")
}
cat("\n")
}
##' .. content for description{} (no empty lines) ..
##'
##' .. content for details{} ..
##' @title Predict parameter values for selection curve
##' @param object bsmfit or summary.bsmfit object
##' @param sort character vector of variables to sort by
##' @param predict.values values to be included in the prediction
##' @param ... extra arguments
##' @return matrix
##' @author Tom Elliott
##' @export
predict.bsmfit <- function(object, sort = NULL, predict.values = NULL, ...) {
predict(summary(object, predict.values = predict.values), sort = sort, ...)
}
##' @method predict summary.bsmfit
##' @rdname predict.bsmfit
##' @export
predict.summary.bsmfit <- function(object, sort = NULL, ...) {
df <- object$predict
if (!is.null(sort))
df <- df[do.call(order, df[, sort, drop = FALSE]), ]
class(df) <- c("bsm.predict", "data.frame")
df
}
##' @export
print.bsm.predict <- function(x, ...) {
m <- do.call(cbind, lapply(colnames(x), function(i) {
if (i %in% c("L50", "SR", "phi", "delta"))
format(c(i, format(x[, i], digits = 4)), justify = "right")
else
format(c(i, as.character(x[, i])), justify = "right")
}))
apply(cbind("", m, "\n"), 1, cat, sep = " ")
}
##' @export
postsample <- function(x, n = 50, pars = colnames(p)) {
p <- x$fit$BUGSoutput$sims.matrix
p[sample(p, size = n), pars]
}
##' @export
as.mcmc.bsmfit <- function(x) {
drop <- c("p_od")
coda::as.mcmc(x$fit)[, c(x$all.parameters[!x$all.parameters %in% drop], "deviance")]
}
##' Extract the DIC value from an object
##'
##' Generic Method
##' @title Extract DIC Value
##' @param x an object, such as bsmfit
##' @param ... extra arguments
##' @return the DIC summary information
##' @author Tom Elliott
##' @export
dic <- function(x, ...)
UseMethod("dic")
##' @describeIn dic Extract the DIC information from the JAGS object
##' @export
dic.bsmfit <- function(x, ...) {
dd <- list(...)
if (length(dd) > 0)
xL <- c(list(x), dd)
else
xL <- list(x)
res <- lapply(xL, function(y)
with(summary(y)$mcmc, structure(c(DIC, pD), .Names = c("DIC", "pD"))))
names(res) <- as.character(match.call())[-1]
class(res) <- "dic.summary"
res
}
##' @export
print.dic.summary <- function(x, ...) {
m <- do.call(rbind, x)
apply(cbind("",
format(c("Model", rownames(m)), justify = "right"),
format(c("DIC", format(m[, "DIC"], digits = 4)), justify = "right"),
format(c("pD", format(m[, "pD"], digits = 2)), justify = "right"),
"\n"), 1, cat, sep = " ")
}
##' @export
`[.dic.summary` <- function(x, i, j) {
mat <- do.call(rbind, x)
mat[i, j]
}
##' @export
`$.dic.summary` <- function(x, name) {
x[, name]
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.