如何根据用户提供的变量有条件地运行 JAGS 脚本的元素?

机器算法验证 r 贝叶斯 锯齿
2022-04-10 11:39:29

背景:我经常发现在使用 JAGS 时,当我探索不同的模型时,我最终会得到很多 JAGS 脚本。一段时间后,我可能会确定一组我将要报告的模型,但随之而来的是保持这些模型之间的设置一致的问题,这些模型旨在保持一致。例如,公共参数的先验应该是一致的,或者参数的变量名应该是一致的。虽然一致性并不难实现,但它通常有点烦人。例如,我可能决定我想调用一些东西而不是 beta,现在所有的脚本都需要更新。因此,我正在尝试研究如何拥有一个可以服务于多种用途的脚本。

示例: 这是一个简单的示例。假设我有两个模型,一个有时间作为预测变量,一个没有。我向 jags 提供了一个逻辑变量timevariable来指示我是否要对时间建模。因此,我希望能够写出这样的东西。

for (i in 1:length(y)) {
    if (timevariable) {
        mu[i] <- beta1 + beta2 * time[i]
    } else {
        mu[i] <- beta1
    }
    y[i]  ~ dnorm(mu[i], tau)
}

但是,if-then 不是语言的一部分请注意,我并不是要根据参数的估计值来合并 if-then。相反,我只想拥有一个可以灵活输入的脚本。

问题

根据用户提供的变量有条件地运行部分 JAGS 脚本的好方法是什么?

最初的想法

  • 将宏名称放入文本中,并使用 R 中的文本替换功能gsub将宏名称替换为所需的文本。我想这会起作用,但它似乎有点像黑客。将所有内容都包含在脚本中会很好。
2个回答

这是为 JAGS 脚本实现基本宏替换系统的一个示例。

系统说明

  • 定义一个函数,该函数将脚本的任何可选元素作为参数。
  • 对于因参数值而异的脚本的任何方面,请记录宏标记。这应该是一些独特的文本。以一些符号开始和结束可能有助于明确这一点。
  • 对于每个宏标记,包括函数参数的所有可能组合下的替换字符串应该是什么的代码。
  • 根据参数将宏标记替换为适当的放置文本。
  • 下面的代码提供了一个如何指定宏以及如何将宏应用于原始脚本的示例(欢迎提出更优雅地执行此操作的建议)。
  • 该函数返回一个替换的模型字符串,如有必要,可以将其传递给textConnection函数以与 rjags 一起使用。

我喜欢这个系统有几个原因:

  • 原始脚本易于阅读
  • 生成的脚本有适当的缩进
  • 您不必担心与出现在同一行的命令相关的错误

例子

具体来说,此示例旨在允许用户拟合特定类型的多级非线性模型。它被设计为允许三种函数形式:二参数幂、三参数幂和三参数指数。

宏部分将宏构造为列表列表。顶级列表包含每个宏标记的一个元素。对于每个宏标记,都有宏标记文本和条件替换文本。

最后,for 循环将所有宏替换应用到原始脚本。

见下文(需要滚动):

jags_model <- function (f=c('power2', 'power3', 'exp3')) {
    f <- match.arg(f)

    # raw script
    script <- 
"model {
    # Model
    for (i in 1:length(y)) {
        $FUNCTION
        y[i]  ~ dnorm(mu[i], tau[subject[i]])
    }

    # Random coefficients
    for (i in 1:N) {    
        theta1[i] ~ dnorm(theta1.mu, theta1.tau)T(0, 1000)
        theta2[i] ~ dnorm(theta2.mu, theta2.tau)T(-10,  0)
        $THETA3DISTRIBUTION
        sigma[i] ~ dnorm(sigma.mu, sigma.tau)T(0, 100);
        tau[i] <- 1/(sigma[i]^2)
    }


    theta1.mu  ~ dunif(0, 100)
    theta2.mu   ~ dunif(-2, 0)
    $THETA3PRIOR.MU
    sigma.mu ~ dunif(0, 20)

    theta1.sigma ~ dunif(0, 100)
    theta2.sigma ~ dunif(0, 2)
    $THETA3PRIOR.SIGMA
    sigma.sigma ~ dunif(0, 10)

    # Transformations
    theta1.tau <- 1/(theta1.sigma^2)
    theta2.tau <- 1/(theta2.sigma^2)
    $THETA3.TAU 
    sigma.tau <- 1/(sigma.sigma^2)
}"


    # define macros
    macros <- list(list("$FUNCTION",  
              switch(f,
                     power2="mu[i] <- theta1[subject[i]] * pow(trial[i], theta2[subject[i]])", 
                     power3="mu[i] <- theta1[subject[i]] * pow(trial[i], theta2[subject[i]]) + theta3[subject[i]];",
                     exp3="mu[i] <- theta1[subject[i]] * exp(theta2[subject[i]] * (trial[i] - 1)) + theta3[subject[i]];") ), 
            list("$THETA3DISTRIBUTION",  
          switch(f, 
                 power3=, exp3= "theta3[i] ~ dnorm(theta3.mu, theta3.tau)T(0, 1000)", 
                 power2="") ), 
        list("$THETA3PRIOR.MU",  
              switch(f, 
                     power3=, exp3= "theta3.mu  ~ dunif(0, 100)", 
                     power2="") ), 
            list("$THETA3PRIOR.SIGMA",  
          switch(f, 
                 power3=, exp3= "theta3.sigma ~ dunif(0, 100)", 
                 power2="") ), 
        list("$THETA3.TAU",  
          switch(f, 
                 power3=, exp3= "theta3.tau <- 1/(theta3.sigma^2)", 
                 power2="") )
        )

    # apply macros
    for (m in seq(macros)) {
        script <- gsub(macros[[m]][1], macros[[m]][2], script, fixed=TRUE)
    }
    script
}

示范

因此,我们可以生成处理后的 JAGS 模型

x <- jags_model(f='power3')

如果我们想查看生成的模型,我们可以这样做

cat(x)

这导致

model {
    # Model
    for (i in 1:length(y)) {
        mu[i] <- theta1[subject[i]] * pow(trial[i], theta2[subject[i]]) + theta3[subject[i]];
        y[i]  ~ dnorm(mu[i], tau[subject[i]])
    }

    # Random coefficients
    for (i in 1:N) {    
        theta1[i] ~ dnorm(theta1.mu, theta1.tau)T(0, 1000)
        theta2[i] ~ dnorm(theta2.mu, theta2.tau)T(-10,  0)
        theta3[i] ~ dnorm(theta3.mu, theta3.tau)T(0, 1000)
        sigma[i] ~ dnorm(sigma.mu, sigma.tau)T(0, 100);
        tau[i] <- 1/(sigma[i]^2)
    }


    theta1.mu  ~ dunif(0, 100)
    theta2.mu   ~ dunif(-2, 0)
    theta3.mu  ~ dunif(0, 100)
    sigma.mu ~ dunif(0, 20)

    theta1.sigma ~ dunif(0, 100)
    theta2.sigma ~ dunif(0, 2)
    theta3.sigma ~ dunif(0, 100)
    sigma.sigma ~ dunif(0, 10)

    # Transformations
    theta1.tau <- 1/(theta1.sigma^2)
    theta2.tau <- 1/(theta2.sigma^2)
    theta3.tau <- 1/(theta3.sigma^2) 
    sigma.tau <- 1/(sigma.sigma^2)
}

在这种特定情况下,您可以将beta2术语设置为零

for (i in 1:length(y)) {
    mu[i] <- beta1 + indicator[i] * beta2 * time[i]
    y[i]  ~ dnorm(mu[i], tau)
}

其中indicator[]是一个向量,对于您要建模的那些数据点是一个向量,beta2否则为 0。您还可以使用标量来整体更改模型。但是,这种方法可能效率较低,因为它仍然从不需要的参数分布中采样。我以前使用过这种gsub方法,但我同意它并不漂亮。

如果模型是在 R 脚本中编写和执行的,则可以将其作为任何其他字符串进行操作:

modelstring <- paste("
model {  
  for ( i in 1:N ) {
",ifelse(timevariable,"
   mu[i] <- beta1 + beta2 * time[i]
","mu[i] <- beta1"),"
  }
  y[i]  ~ dnorm(mu[i], tau) 
}
")
writeLines(modelstring,con=filename)