6  Introducción a JAGS y STAN

6.1 Sesgo de una moneda con JAGS y R

En este ejemplo estimamos la probabilidad de “cara” de una moneda, \(\theta\), a partir de una muestra de lanzamientos, usando JAGS (vía rjags) para automatizar el muestreo MCMC.

6.1.1 Carga de librerías y datos

Cargamos el paquete rjags y las utilidades de Kruschke, luego leemos un archivo CSV con los resultados (0=escudo, 1=corona):

library(rjags)
Loading required package: coda
Linked to JAGS 4.3.2
Loaded modules: basemod,bugs
source("DBDA2Eprograms/DBDA2E-utilities.R")

*********************************************************************
Kruschke, J. K. (2015). Doing Bayesian Data Analysis, Second Edition:
A Tutorial with R, JAGS, and Stan. Academic Press / Elsevier.
*********************************************************************
myData  <- read.csv("DBDA2Eprograms/z15N50.csv")
y       <- myData$y
Ntotal  <- length(y)
dataList <- list(
  y      = y,
  Ntotal = Ntotal
)

6.1.2 Modelo en JAGS

Definimos el modelo Bernoulli–Beta:

modelString <- "
model {
  for (i in 1:Ntotal) {
    y[i] ~ dbern(theta)      # verosimilitud: y_i ∼ Bernoulli(theta)
  }
  theta ~ dbeta(1, 1)        # prior no informativo Beta(1,1)
}
"
writeLines(modelString, con="TEMPmodel.txt")

Matemáticamente:

\[ y_i \sim \mathrm{Bernoulli}(\theta),\quad \theta \sim \mathrm{Beta}(1,1). \]

6.1.3 Inicialización de cadenas

Para mejorar la adaptación, inicializamos cada cadena cerca del MLE recalculado por bootstrap:

initsList <- function() {
  resampledY <- sample(y, replace=TRUE)
  thetaInit  <- sum(resampledY)/length(resampledY)
  thetaInit  <- 0.001 + 0.998*thetaInit  # evitar extremos 0,1
  list(theta=thetaInit)
}

6.1.4 Compilar y muestrear con rjags

jagsModel  <- jags.model(
  file    = "TEMPmodel.txt",
  data    = dataList,
  inits   = initsList,
  n.chains= 3,
  n.adapt = 500
)
Compiling model graph
   Resolving undeclared variables
   Allocating nodes
Graph information:
   Observed stochastic nodes: 50
   Unobserved stochastic nodes: 1
   Total graph size: 53

Initializing model
update(jagsModel, n.iter=500)  # burn-in
codaSamples <- coda.samples(
  jagsModel,
  variable.names = "theta",
  n.iter          = 3334      # 3×3334 ≃ 10 002 iteraciones
)

6.1.5 Diagnósticos de convergencia

Usamos la función diagMCMC (de DBDA2E–utilities) que emplea coda:

diagMCMC(codaObject=codaSamples, parName="theta")

Aquí se trazan las trace plots, los densidades superpuestas, el Gelman–Rubin y la ACF, mostrando representatividad y un ESS cercano al total.

6.1.6 Visualización de la posterior

Con plotPost (DBDA2E):

plotPost(codaSamples[,"theta"],
         main="Distribución posterior de θ",
         xlab=bquote(theta))

        ESS      mean    median      mode hdiMass    hdiLow   hdiHigh compVal
theta 10002 0.3083362 0.3058889 0.3017988    0.95 0.1910977 0.4354811      NA
      pGtCompVal ROPElow ROPEhigh pLtROPE pInROPE pGtROPE
theta         NA      NA       NA      NA      NA      NA
plotPost(codaSamples[,"theta"],
         cenTend="median",
         compVal=0.5,
         ROPE=c(0.45,0.55),
         credMass=0.90)

              ESS      mean    median      mode hdiMass    hdiLow   hdiHigh
Param. Val. 10002 0.3083362 0.3058889 0.3017988     0.9 0.2072132 0.4139092
            compVal pGtCompVal ROPElow ROPEhigh   pLtROPE    pInROPE pGtROPE
Param. Val.     0.5 0.00289942    0.45     0.55 0.9841032 0.01589682       0

Estadísticos básicos de la posterior de \(\theta\):

summary(codaSamples)

Iterations = 501:3834
Thinning interval = 1 
Number of chains = 3 
Sample size per chain = 3334 

1. Empirical mean and standard deviation for each variable,
   plus standard error of the mean:

          Mean             SD       Naive SE Time-series SE 
     0.3083362      0.0633048      0.0006330      0.0006422 

2. Quantiles for each variable:

  2.5%    25%    50%    75%  97.5% 
0.1922 0.2639 0.3059 0.3500 0.4378 

6.1.7 Alternativa con R2jags y bayesplot

Otra forma de ajustar con R2jags:

library(R2jags)

Attaching package: 'R2jags'
The following object is masked from 'package:coda':

    traceplot
bern_model <- function() {
  for (i in 1:N) {
    y[i] ~ dbern(theta) 
  }
  theta ~ dbeta(1, 1)   
}

bern_jags <- 
  jags(
    data = list(y = myData$y, N = nrow(myData)),
    model.file = bern_model,
    parameters.to.save = c("theta")
  )
module glm loaded
Compiling model graph
   Resolving undeclared variables
   Allocating nodes
Graph information:
   Observed stochastic nodes: 50
   Unobserved stochastic nodes: 1
   Total graph size: 53

Initializing model

Y para diagnóstico con bayesplot:

library(bayesplot)
This is bayesplot version 1.11.1
- Online documentation and vignettes at mc-stan.org/bayesplot
- bayesplot theme set to bayesplot::theme_default()
   * Does _not_ affect other ggplot2 plots
   * See ?bayesplot_theme_set for details on theme setting
bern_mcmc <- as.mcmc(bern_jags)
mcmc_trace(bern_mcmc, pars="theta")

mcmc_areas(bern_mcmc, pars="theta", prob=0.90)

6.1.8 Variantes de configuración

Se pueden ajustar el número de cadenas, iteraciones, burn‐in y thinnings:

set.seed(76543)
bern_jags2 <- jags(
  data            = list(y = myData$y, N = nrow(myData)),
  model.file      = bern_model,
  parameters.to.save = "theta",
  n.chains        = 4,
  n.iter          = 5000,
  n.burnin        = 1000
)
Compiling model graph
   Resolving undeclared variables
   Allocating nodes
Graph information:
   Observed stochastic nodes: 50
   Unobserved stochastic nodes: 1
   Total graph size: 53

Initializing model

Y variar inicializaciones:

# Todas las cadenas empiezan en theta~Beta(3,3)
bern_jags3 <- jags(
  data = list(y = myData$y, N = nrow(myData)),
  model.file = bern_model,
  inits = function() list(theta=rbeta(1,3,3)),
  parameters.to.save="theta"
)
Compiling model graph
   Resolving undeclared variables
   Allocating nodes
Graph information:
   Observed stochastic nodes: 50
   Unobserved stochastic nodes: 1
   Total graph size: 53

Initializing model
# Inicio manual de cada cadena
bern_jags4 <- jags(
  data = list(y = myData$y, N = nrow(myData)),
  model.file = bern_model,
  inits = list(
    list(theta=0.5), list(theta=0.7), list(theta=0.9)
  ),
  parameters.to.save="theta"
)
Compiling model graph
   Resolving undeclared variables
   Allocating nodes
Graph information:
   Observed stochastic nodes: 50
   Unobserved stochastic nodes: 1
   Total graph size: 53

Initializing model
mcmc_trace(as.mcmc(bern_jags4), pars="theta")

6.2 Sesgos de dos monedas: Reginald vs. Tony

En este ejemplo estimamos los sesgos \(\theta_{1}\) y \(\theta_{2}\) de las monedas de Reginald y Tony, y analizamos su diferencia \(\theta_{1}-\theta_{2}\) para evaluar, por ejemplo, la hipótesis \(H_{0}:\theta_{1}>\theta_{2}\).

6.2.1 Carga y resumen de datos

Partimos del conjunto z6N8z2N7 que contiene en cada fila un lanzamiento:

library(mosaic)
Registered S3 method overwritten by 'mosaic':
  method                           from   
  fortify.SpatialPolygonsDataFrame ggplot2

The 'mosaic' package masks several functions from core packages in order to add 
additional features.  The original behavior of these functions should not be affected by this.

Attaching package: 'mosaic'
The following objects are masked from 'package:dplyr':

    count, do, tally
The following object is masked from 'package:Matrix':

    mean
The following object is masked from 'package:ggplot2':

    stat
The following objects are masked from 'package:stats':

    binom.test, cor, cor.test, cov, fivenum, IQR, median, prop.test,
    quantile, sd, t.test, var
The following objects are masked from 'package:base':

    max, mean, min, prod, range, sample, sum
z6N8z2N7  <- read.csv("DBDA2Eprograms/z6N8z2N7.csv")
head(z6N8z2N7)   # primeras observaciones
  y        s
1 1 Reginald
2 0 Reginald
3 1 Reginald
4 1 Reginald
5 1 Reginald
6 1 Reginald

Renombramos columnas y comprobamos las proporciones de “éxitos” (1s) por sujeto:

library(dplyr)
Target <- z6N8z2N7 %>%
  rename(hit = y, subject = s)

df_stats(hit ~ subject, data = Target, props, attempts = length)
  response  subject    prop_0    prop_1 attempts
1      hit Reginald 0.2500000 0.7500000        8
2      hit     Tony 0.7142857 0.2857143        7

Los resultados muestran sesgos muy distintos entre Reginald y Tony, lo que justifica un \(\theta\) distinto para cada uno.

6.2.2 Modelo jerárquico en JAGS

Asumimos para cada observación \(i\) del sujeto \(s\):

\[ y_{i\mid s}\sim \mathrm{Bernoulli}(\theta_{s}), \quad \theta_{s}\sim \mathrm{Beta}(2,2). \]

En JAGS:

bern2_model <- function() {
  for (i in 1:Nobs) {
    hit[i] ~ dbern(theta[subject[i]])
  }
  for (s in 1:Nsub) {
    theta[s] ~ dbeta(2, 2)
  }
}

6.2.3 Preparación de datos y MCMC

Creamos la lista con datos y llamamos a jags() de R2jags:

library(R2jags)

TargetList <- list(
  Nobs    = nrow(Target),
  Nsub    = length(unique(Target$subject)),
  hit     = Target$hit,
  subject = as.numeric(as.factor(Target$subject))
)

set.seed(123)
bern2_jags <- jags(
  data              = TargetList,
  model.file        = bern2_model,
  parameters.to.save= "theta",
  n.chains          = 3, 
  n.iter            = 5000, 
  n.burnin          = 1000
)
Compiling model graph
   Resolving undeclared variables
   Allocating nodes
Graph information:
   Observed stochastic nodes: 15
   Unobserved stochastic nodes: 2
   Total graph size: 35

Initializing model

Convertimos a objeto mcmc para diagnóstico:

bern2_jags
Inference for Bugs model at "/tmp/Rtmpw6F0PX/model2a0a7407b239.txt", fit using jags,
 3 chains, each with 5000 iterations (first 1000 discarded), n.thin = 4
 n.sims = 3000 iterations saved. Running time = 0.013 secs
         mu.vect sd.vect   2.5%    25%    50%    75%  97.5%  Rhat n.eff
theta[1]   0.665   0.131  0.385  0.582  0.674  0.761  0.888 1.002  1700
theta[2]   0.364   0.141  0.120  0.260  0.354  0.459  0.657 1.001  3000
deviance  19.071   1.693 17.418 17.862 18.562 19.733 23.566 1.001  3000

For each parameter, n.eff is a crude measure of effective sample size,
and Rhat is the potential scale reduction factor (at convergence, Rhat=1).

DIC info (using the rule: pV = var(deviance)/2)
pV = 1.4 and DIC = 20.5
DIC is an estimate of expected predictive error (lower deviance is better).
bern2_mcmc <- as.mcmc(bern2_jags)

6.2.4 Diagnósticos de convergencia

Verificamos trazas, autocorrelación y pares:

library(bayesplot)

# Traceplot para θ₁
mcmc_trace(bern2_mcmc, pars="theta[1]")

# Autocorrelación
mcmc_acf(bern2_mcmc)

# Pares de parámetros
mcmc_pairs(bern2_mcmc, pars=c("theta[1]","theta[2]"))

# Combinado
mcmc_combo(bern2_mcmc)

# Cálculo del ESS
ess_vals <- effectiveSize(bern2_mcmc)
print(ess_vals)
deviance theta[1] theta[2] 
3000.000 3135.518 3000.000 
# Estadístico de Gelman–Rubin
gelman_res <- gelman.diag(bern2_mcmc, autoburnin=FALSE)
print(gelman_res)
Potential scale reduction factors:

         Point est. Upper C.I.
deviance          1          1
theta[1]          1          1
theta[2]          1          1

Multivariate psrf

1

Los diagnósticos indican buena mezcla y ESS adecuado.

6.2.5 Inferencia sobre la diferencia

Extraemos la muestra posterior y calculamos la diferencia:

# 1. Convierte el objeto jags a un objeto mcmc de coda
bern2_mcmc <- as.mcmc(bern2_jags)

# 2. Junta las  cadenas en una sola matriz de draws
post_mat <- as.matrix(bern2_mcmc)
# post_mat tendrá dos columnas: "theta[1]" y "theta[2]"

# 3. Calcula la diferencia
dif <- post_mat[,"theta[1]"] - post_mat[,"theta[2]"]

# 4. Estadísticos de interés
mean(dif)
[1] 0.3011272
quantile(dif, c(0.025, 0.975))
       2.5%       97.5% 
-0.09405656  0.65172258 
# 5. Densidad de la diferencia
library(ggformula)
gf_density(~ dif, 
           xlab="θ₁ - θ₂",
           main="Distribución posterior de la diferencia")

Para estimar \(P(H_{0}:\theta_{1}>\theta_{2})\):

mean(dif > 0)  
[1] 0.9346667

Así, la probabilidad de \(H_0\) es 0.93 y la de \(H_1:\theta_{1}<\theta_{2}\) es 0.067, lo que sugiere que es muy plausible que Reginald tenga mayor sesgo que Tony.

6.3 Distribuciones disponibles en JAGS

JAGS ofrece de forma nativa una amplia colección de distribuciones frecuentes, como dbeta, dgamma, dnorm, dbern, dbinomial, entre otras (ver manual de usuario de JAGS para la lista completa). Estas facilitan la especificación de modelos sin tener que definir cada función de densidad desde cero.

6.3.1 Extensión mediante el “ones trick” y el “zeros trick”

Cuando se requiere una distribución no incluida en JAGS, puede emplearse el Bernoulli ones trick. La idea es usar que

\[ \mathrm{dbern}(1\mid \theta)=\theta, \]

es decir, si queremos evaluar una función de densidad arbitraria \(pdf\bigl(y[i],|,\text{parámetros}\bigr)\), la definimos como

spy[i] <- pdf(y[i], parámetros) / C

y luego forzamos a JAGS a “evaluar” ese valor con

ones[i] ~ dbern(spy[i])

Aquí, ones[i] es un vector de unos definido en el bloque data y \(C\) es una constante grande que asegura que \(0\le \text{spy}[i]\le1\). Dado que sólo importan las razones de densidades en MCMC, el factor de escala \(1/C\) no altera los resultados.

Análogamente, el Poisson zeros trick utiliza el hecho de que

\[ \mathrm{dpois}(0\mid \lambda)=e^{-\lambda}, \]

de modo que, definiendo

theta[i] <- pdf(y[i], parámetros) + K
0       ~ dpois(-log(theta[i]))

con \(K\) suficientemente grande para mantener positivo el argumento de la Poisson, obtenemos la misma contribución al logaritmo de la densidad.

6.3.2 Extensión a nuevas distribuciones

Para evitar estos trucos, es posible extender JAGS con distribuciones propias mediante la creación de módulos en C++ (vía runjags o la guía de Wabersich & Vandekerckhove), lo que permite declarar directamente en el modelo:

y[i] ~ dMiDistrib( parámetros )

sin recurrir a aproximaciones via dbern o dpois. Esta vía requiere programar la función de densidad y su derivada en un módulo compilado, pero ofrece máxima flexibilidad para modelos a medida.

6.4 Introducción a la modelación jerárquica

Para recordar el escenario básico, consideremos primero el modelo no jerárquico para el sesgo de una moneda. Sea \(y_{i}\) el resultado de cada lanzamiento (0/1) que sigue una distribución de Bernoulli con parámetro \(\theta\):

\[ y_{i} \sim \operatorname{dbern}(\theta). \]

El parámetro \(\theta\) tiene como priori una distribución beta con parámetros \((a,b)\):

\[ \theta \sim \operatorname{dbeta}(a,b). \]

Estos parámetros \((a,b)\) pueden reexpresarse en términos de la moda \(\omega\) y la concentración \(\kappa\) de la beta. En concreto,

  • \(\omega = \displaystyle\frac{a - 1}{a + b - 2}\) es la moda,
  • \(\kappa = a + b\) es la concentración total,

y, recíprocamente,

\[ a \;=\;\omega\,(\kappa - 2) + 1,\qquad b \;=\;(1-\omega)\,(\kappa - 2) + 1. \]

Con esa reparametrización, la priori en \(\theta\) queda como

\[ \theta \sim \operatorname{dbeta}\bigl(\,\omega(\kappa-2)+1\,,\;(1-\omega)(\kappa-2)+1\bigr). \]

Aquí, \(\omega\) indica la ubicación típica de \(\theta\) (p. ej., si \(\omega=0.25\), entonces \(\theta\) tenderá a situarse alrededor de 0.25), mientras que \(\kappa\) gobierna cuán concentrados están los valores de \(\theta\) alrededor de \(\omega\). Cuanto mayor es \(\kappa\), más cerca de \(\omega\) quedarán las muestras de \(\theta\). Por simplicidad, en este ejemplo tomamos \(\kappa\) como un valor fijo y lo llamamos \(K\).


6.4.1 Extensión a modelo jerárquico: estimar la moda \(\omega\)

Para introducir la jerarquía, ya no consideramos \(\omega\) como conocimiento previo fijo, sino como un parámetro a inferir. La idea es suponer que una “casa” (mint) fabrica monedas cuyo sesgo \(\theta\) se distribuye alrededor de un valor típico \(\omega\), con dispersión controlada por \(K\). Si \(K\) es grande, la casa produce monedas muy homogéneas (todas con \(\theta\approx\omega\)); si \(K\) es pequeño, las monedas saldrán más dispersas. Así, cuando extraemos una moneda al azar, los datos de sus lanzamientos nos informan ambas cosas:

  1. El sesgo particular de esa moneda, \(\theta\) (cercano a la proporción de caras observadas).
  2. La moda subyacente de la casa, \(\omega\), que refleja el sesgo más probable de las monedas en general.

Para ese segundo nivel, asignamos a \(\omega\) una priori que también sea beta, con parámetros \((A_\omega,B_\omega)\):

\[ p(\omega)\;=\;\operatorname{beta}(\omega\mid A_\omega,B_\omega). \]

De ese modo, creemos a priori que \(\omega\) queda cerca de \(\displaystyle\frac{A_\omega - 1}{A_\omega + B_\omega - 2}\), con la concentración (certeza) dada por \(A_\omega + B_\omega\).


6.4.2 Estructura jerárquica y regla de Bayes conjunta

La jerarquía de dependencias queda así resumida:

  1. Primero, \(\omega\) se elige según \(\operatorname{dbeta}(A_\omega,B_\omega)\).
  2. Luego, dado \(\omega\), el sesgo de la moneda \(\theta\) se extrae de \(\theta \sim \operatorname{dbeta}\bigl(\omega(\!K-2\!) +1,\;(1-\omega)(\!K-2\!) +1\bigr).\)
  3. Finalmente, cada observación \(y_i\) (0 = cruz, 1 = cara) viene de \(y_i \sim \operatorname{dbern}(\theta).\)

En notación de probabilidad conjunta, la distribución a posteriori de \((\theta,\omega)\), tras observar los datos \(\gamma={y_i}\), es

\[ \begin{aligned} p(\theta,\omega \mid \gamma) &=\frac{p(\,\gamma \mid \theta,\omega)\;p(\theta,\omega)}{p(\gamma)} \;=\;\frac{p(\,\gamma \mid \theta)\;p(\theta\mid\omega)\;p(\omega)}{p(\gamma)}. \end{aligned} \]

  • Aquí, \(p(\gamma\mid\theta)\) es la verosimilitud binomial/Bernoulli según los datos de la moneda,
  • \(p(\theta\mid\omega)\) es la beta definida mediante modo \(\omega\) y concentración \(K\),
  • \(p(\omega)\) es la beta \((A_\omega,B_\omega)\).

6.4.3 Ejemplo de Modelación Jerárquica (sección 9.2.4, Kruschke)

Contexto: El Toque Terapéutico (TT) es una práctica de enfermería que se dice trata condiciones médicas manipulando un “campo de energía humano” con las manos de los practicantes (ver explicación). Rosa et al. (1998) evaluaron la afirmación de que los practicantes de “toque terapéutico” pueden percibir el “campo energético” de un paciente, incluso sin contacto visual. Para ello, cada practicante extendía ambas manos tras una pantalla que le impedía ver al experimentador. En cada uno de 10 ensayos, un asistente (un niño de 9 años) lanzaba una moneda y colocaba su mano unos centímetros por encima de una de las manos del practicante, según el resultado. El practicante debía adivinar cuál de sus manos estaba siendo “energéticamente” detectada. Cada intento se calificaba como acierto o error. Participaron 21 practicantes; siete de ellos fueron evaluados de nuevo un año después, siendo considerados por los autores como sujetos independientes, para un total nominal de 28 sujetos. La capacidad de los practicantes para identificar correctamente la posición de la mano del investigador se comparó con una tasa de éxito del 50% esperada por casualidad.

Pregunta de Investigación: ¿Las tasas de acierto para los practicantes será mayor al 50%?

Carga de datos:

TherapeuticTouch <- read.csv('DBDA2Eprograms/TherapeuticTouchData.csv')
head(TherapeuticTouch, 3)
  y   s
1 1 S01
2 0 S01
3 0 S01

Las tasas de acierto empíricas para cada individuo se pueden visualizar de la siguiente forma:

gf_bar(s ~ ., data = TherapeuticTouch, fill = ~ factor(y))

de donde es interesante observar la diferencia entre tasas de acierto entre individuos (que en este caso está ordenado del que tiene menos al que tiene más acierto). Es por esto que un modelo con tasa de acierto variable por individuos tendría sentido.

En este caso usamos un modelo jerárquico que tendría la forma:

\[\begin{align*} y_{i|s} & \sim \text{Ber}(\theta_s)\\ \theta_s & \sim \text{Beta}(\omega(K-2)+1,(1-\omega)(K-2)+1)\\ \omega & \sim \text{Beta}(1,1)\\ K-2 & \sim \text{Gamma}(0.01,0.01) \end{align*}\]

donde esta última escogencia se hiperparámetros se basa en la aplicación de la siguiente función, que está en el paquete de github CalvinBayes (https://github.com/CalvinData/CalvinBayes). Nota: este paquete permite hacer los mismos gráficos de diagnósticos con el objeto obtenido en R2jags:

library(CalvinBayes)

Attaching package: 'CalvinBayes'
The following objects are masked _by_ '.GlobalEnv':

    DbdaAcfPlot, DbdaDensPlot, diagMCMC, diagStanFit, HDIofGrid,
    HDIofICDF, HDIofMCMC, plotPost, summarizePost, TherapeuticTouch,
    z6N8z2N7
The following object is masked from 'package:bayesplot':

    rhat
The following object is masked from 'package:datasets':

    HairEyeColor
gamma_params(mean = 1, sd = 10)
# A tibble: 1 × 6
  shape  rate scale mode   mean    sd
  <dbl> <dbl> <dbl> <lgl> <dbl> <dbl>
1  0.01  0.01   100 NA        1    10

que garantiza una v.a. Gamma con media 1 y desviación estándar 10 (previas no-informativas).

La definición del modelo en lenguaje JAGS sería:

touch_model <- function() {
  for (i in 1:Ntotal) {
    y[i] ~ dbern(theta[s[i]])
  }
  for (s in 1:Nsubj) {
    theta[s] ~ dbeta(omega * (kappa - 2) + 1, (1 - omega) * (kappa - 2) + 1)
  }
  omega ~ dbeta(1, 1)
  kappa <- kappaMinusTwo + 2
  kappaMinusTwo ~ dgamma(0.01, 0.01)     # mean = 1, sd = 10
}

con el arreglo de datos:

TouchData <- list(
  Ntotal = nrow(TherapeuticTouch),
  Nsubj = length(unique(TherapeuticTouch$s)),
  y = TherapeuticTouch$y,
  # must convert subjects to sequence 1:Nsubj
  s = as.numeric(factor(TherapeuticTouch$s))
)

Se procede a un ajuste preliminar, con los valores por default de JAGS:

set.seed(1234)
touch_jags <-
  jags(
    data = TouchData,
    model = touch_model,
    parameters.to.save = c("theta", "kappa", "omega"),
  )
Compiling model graph
   Resolving undeclared variables
   Allocating nodes
Graph information:
   Observed stochastic nodes: 280
   Unobserved stochastic nodes: 30
   Total graph size: 602

Initializing model

Si analizamos los diagnósticos de convergencia, notamos que hay mucho autocorrelación en la series (usando el tamaño efectivo de muestra):

touch_jags
Inference for Bugs model at "/tmp/Rtmpw6F0PX/model2a0a685563ad.txt", fit using jags,
 3 chains, each with 2000 iterations (first 1000 discarded)
 n.sims = 3000 iterations saved. Running time = 0.491 secs
          mu.vect sd.vect    2.5%     25%     50%     75%   97.5%  Rhat n.eff
kappa      89.762  82.245   9.342  25.457  58.085 134.433 305.163 1.102    25
omega       0.435   0.035   0.372   0.411   0.435   0.457   0.506 1.062    39
theta[1]    0.376   0.084   0.182   0.328   0.388   0.436   0.518 1.034    74
theta[2]    0.393   0.079   0.211   0.348   0.400   0.445   0.531 1.024   110
theta[3]    0.412   0.073   0.246   0.370   0.416   0.459   0.547 1.030   130
theta[4]    0.413   0.073   0.256   0.372   0.417   0.460   0.550 1.027   110
theta[5]    0.413   0.073   0.254   0.368   0.418   0.461   0.547 1.018   160
theta[6]    0.413   0.073   0.254   0.371   0.417   0.459   0.551 1.025   110
theta[7]    0.411   0.073   0.248   0.370   0.417   0.460   0.543 1.015   190
theta[8]    0.412   0.074   0.256   0.370   0.416   0.460   0.553 1.021   140
theta[9]    0.413   0.074   0.252   0.371   0.416   0.460   0.551 1.021   120
theta[10]   0.412   0.073   0.251   0.369   0.417   0.459   0.547 1.024   150
theta[11]   0.431   0.071   0.289   0.388   0.431   0.474   0.574 1.010   270
theta[12]   0.432   0.072   0.290   0.389   0.431   0.476   0.580 1.023   110
theta[13]   0.430   0.074   0.275   0.387   0.432   0.474   0.584 1.016   190
theta[14]   0.429   0.074   0.274   0.383   0.431   0.477   0.572 1.016   170
theta[15]   0.432   0.072   0.284   0.388   0.432   0.477   0.576 1.014   250
theta[16]   0.451   0.074   0.303   0.405   0.448   0.495   0.607 1.015   230
theta[17]   0.451   0.073   0.309   0.405   0.449   0.492   0.610 1.015   250
theta[18]   0.452   0.072   0.314   0.405   0.451   0.495   0.604 1.012   330
theta[19]   0.451   0.072   0.309   0.407   0.449   0.494   0.604 1.017   220
theta[20]   0.449   0.072   0.310   0.404   0.447   0.492   0.602 1.009   340
theta[21]   0.452   0.072   0.317   0.407   0.449   0.494   0.604 1.021   150
theta[22]   0.452   0.073   0.316   0.405   0.447   0.495   0.613 1.017   250
theta[23]   0.469   0.077   0.336   0.419   0.463   0.510   0.648 1.016   220
theta[24]   0.471   0.076   0.339   0.421   0.465   0.513   0.643 1.021   180
theta[25]   0.491   0.081   0.356   0.436   0.480   0.535   0.685 1.021   130
theta[26]   0.487   0.081   0.350   0.433   0.476   0.530   0.670 1.016   210
theta[27]   0.487   0.080   0.356   0.433   0.476   0.533   0.673 1.020   130
theta[28]   0.508   0.089   0.372   0.447   0.493   0.557   0.719 1.026    97
deviance  380.061   5.075 369.092 376.823 380.731 383.616 388.871 1.033    70

For each parameter, n.eff is a crude measure of effective sample size,
and Rhat is the potential scale reduction factor (at convergence, Rhat=1).

DIC info (using the rule: pV = var(deviance)/2)
pV = 12.5 and DIC = 392.6
DIC is an estimate of expected predictive error (lower deviance is better).

Ajustamos el modelo con un número de mayor de cadenas, con mayor tamaño de muestra posterior y con thinning. Noten que ejecutamos el modelo paralelizando el proceso (1 core por cadena)

touch_jags <-
  jags.parallel(
    data = TouchData,
    model = touch_model,
    parameters.to.save = c("theta", "kappa", "omega"),
    n.burnin = 1000,
    n.iter = 41000,
    n.chains = 5,
    n.thin = 10,
    jags.seed = 54321
  )   

y la autorrelación se mejora considerablemente, al comparar el tamaño de muestra efectivo del primer modelo con respecto al segundo:

touch_jags$BUGSoutput$summary[,'n.eff']
 deviance     kappa     omega  theta[1]  theta[2]  theta[3]  theta[4]  theta[5] 
     8600      2000      5700      4300      6300     20000      7000     20000 
 theta[6]  theta[7]  theta[8]  theta[9] theta[10] theta[11] theta[12] theta[13] 
    14000     20000     17000     12000      6600     20000     20000     13000 
theta[14] theta[15] theta[16] theta[17] theta[18] theta[19] theta[20] theta[21] 
    20000     15000      6500     17000     20000      9000     14000     20000 
theta[22] theta[23] theta[24] theta[25] theta[26] theta[27] theta[28] 
    20000      7600      8200      8900      8300      7200      7600 

Ahora hacemos algunos gráficos para analizar las muestras posteriores:

touch_mcmc <- as.mcmc(touch_jags)
plot_post(touch_mcmc[, "omega"], comparison_value = 0.5)

$posterior
          ESS      mean   median      mode
var1 12352.91 0.4355177 0.436148 0.4362388

$hdi
  prob        lo        hi
1 0.95 0.3632788 0.5092646

$comparison
  value P(< comp. val.) P(> comp. val.)
1   0.5           0.962           0.038

De donde se concluye que el 96% de la población de practicantes no tienen una tasa de acierto general mayor al 50%. Este análisis se puede realizar para la tasa de acierto del sujeto #28:

plot_post(touch_mcmc[, "theta[28]"], comparison_value = 0.5)

$posterior
          ESS    mean    median      mode
var1 7157.362 0.52434 0.5128259 0.4924208

$hdi
  prob       lo        hi
1 0.95 0.363208 0.7222406

$comparison
  value P(< comp. val.) P(> comp. val.)
1   0.5         0.44185         0.55815

Diagnósticos del parámetro de precisión de la tasa de acierto individual:

diag_mcmc(touch_mcmc, par = "kappa")

Gráfico de dispersión entre las muestras posteriores de \(K\) y \(\omega\):

mcmc_pairs(touch_mcmc, pars = c("omega", "kappa"))

Muestreo a partir de la previa:

TouchData_pre <- list(
  Ntotal = 0,
  Nsubj = length(unique(TherapeuticTouch$s)),
  # must convert subjects to sequence 1:Nsubj
  s = as.numeric(factor(TherapeuticTouch$s))
)

touch_jags_pre <-
  jags.parallel(
    data = TouchData_pre,
    model = touch_model,
    parameters.to.save = c("theta", "kappa", "omega"),
    n.burnin = 1000,
    n.iter = 41000,
    n.chains = 5,
    n.thin = 10,
    jags.seed = 54321,
    DIC = F
  )

Diagnósticos:

diag_mcmc(touch_mcmc, par = "theta[1]")

mcmc_pairs(touch_mcmc, pars = c("omega", "kappa"))

6.5 Introducción a STAN

Carga de librería:

library(rstan)
rstan_options(auto_write = TRUE)

Para ilustrar el uso de STAN, se va a ajustar un modelo Bernoulli a datos sintéticos. El modelo está definido en el archivo moneda.stan en el repositorio de estas notas.

modelo_moneda <- stan_model(file = 'moneda.stan',verbose = T)

TRANSLATING MODEL '' FROM Stan CODE TO C++ CODE NOW.
modelo_moneda
S4 class stanmodel 'anon_model' coded as follows:
//
// This Stan program defines a simple model, with a
// vector of values 'y' modeled as normally distributed
// with mean 'mu' and standard deviation 'sigma'.
//
// Learn more about model development with Stan at:
//
//    http://mc-stan.org/users/interfaces/rstan.html
//    https://github.com/stan-dev/rstan/wiki/RStan-Getting-Started
//
// The input data is a vector 'y' of length 'N'.
data {
  int<lower=0> N;  // N is a non-negative integer
  int y[N];          // y is a length-N vector of integers
}
// The parameters accepted by the model. Our model
// accepts two parameters 'mu' and 'sigma'.
parameters {
  real<lower=0,upper=1> theta;  // theta is between 0 and 1
}
// The model to be estimated. We model the output
// 'y' to be normally distributed with mean 'mu'
// and standard deviation 'sigma'.
model {
  theta ~ beta (1,1);
  y ~ bernoulli(theta);
} 

Ajuste del modelo en condiciones similares a las que JAGS trabaja por default:

simple_stanfit <- 
  sampling(
    modelo_moneda, 
    data  = list(
      N = 50,
      y = c(rep(1, 15), rep(0, 35))
    ),
    chains = 3,     # default is 4
    iter = 1000,    # default is 2000
    warmup = 200    # default is half of iter
  )

SAMPLING FOR MODEL 'anon_model' NOW (CHAIN 1).
Chain 1: 
Chain 1: Gradient evaluation took 2e-06 seconds
Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 0.02 seconds.
Chain 1: Adjust your expectations accordingly!
Chain 1: 
Chain 1: 
Chain 1: Iteration:   1 / 1000 [  0%]  (Warmup)
Chain 1: Iteration: 100 / 1000 [ 10%]  (Warmup)
Chain 1: Iteration: 200 / 1000 [ 20%]  (Warmup)
Chain 1: Iteration: 201 / 1000 [ 20%]  (Sampling)
Chain 1: Iteration: 300 / 1000 [ 30%]  (Sampling)
Chain 1: Iteration: 400 / 1000 [ 40%]  (Sampling)
Chain 1: Iteration: 500 / 1000 [ 50%]  (Sampling)
Chain 1: Iteration: 600 / 1000 [ 60%]  (Sampling)
Chain 1: Iteration: 700 / 1000 [ 70%]  (Sampling)
Chain 1: Iteration: 800 / 1000 [ 80%]  (Sampling)
Chain 1: Iteration: 900 / 1000 [ 90%]  (Sampling)
Chain 1: Iteration: 1000 / 1000 [100%]  (Sampling)
Chain 1: 
Chain 1:  Elapsed Time: 0 seconds (Warm-up)
Chain 1:                0.003 seconds (Sampling)
Chain 1:                0.003 seconds (Total)
Chain 1: 

SAMPLING FOR MODEL 'anon_model' NOW (CHAIN 2).
Chain 2: 
Chain 2: Gradient evaluation took 1e-06 seconds
Chain 2: 1000 transitions using 10 leapfrog steps per transition would take 0.01 seconds.
Chain 2: Adjust your expectations accordingly!
Chain 2: 
Chain 2: 
Chain 2: Iteration:   1 / 1000 [  0%]  (Warmup)
Chain 2: Iteration: 100 / 1000 [ 10%]  (Warmup)
Chain 2: Iteration: 200 / 1000 [ 20%]  (Warmup)
Chain 2: Iteration: 201 / 1000 [ 20%]  (Sampling)
Chain 2: Iteration: 300 / 1000 [ 30%]  (Sampling)
Chain 2: Iteration: 400 / 1000 [ 40%]  (Sampling)
Chain 2: Iteration: 500 / 1000 [ 50%]  (Sampling)
Chain 2: Iteration: 600 / 1000 [ 60%]  (Sampling)
Chain 2: Iteration: 700 / 1000 [ 70%]  (Sampling)
Chain 2: Iteration: 800 / 1000 [ 80%]  (Sampling)
Chain 2: Iteration: 900 / 1000 [ 90%]  (Sampling)
Chain 2: Iteration: 1000 / 1000 [100%]  (Sampling)
Chain 2: 
Chain 2:  Elapsed Time: 0 seconds (Warm-up)
Chain 2:                0.003 seconds (Sampling)
Chain 2:                0.003 seconds (Total)
Chain 2: 

SAMPLING FOR MODEL 'anon_model' NOW (CHAIN 3).
Chain 3: 
Chain 3: Gradient evaluation took 0 seconds
Chain 3: 1000 transitions using 10 leapfrog steps per transition would take 0 seconds.
Chain 3: Adjust your expectations accordingly!
Chain 3: 
Chain 3: 
Chain 3: Iteration:   1 / 1000 [  0%]  (Warmup)
Chain 3: Iteration: 100 / 1000 [ 10%]  (Warmup)
Chain 3: Iteration: 200 / 1000 [ 20%]  (Warmup)
Chain 3: Iteration: 201 / 1000 [ 20%]  (Sampling)
Chain 3: Iteration: 300 / 1000 [ 30%]  (Sampling)
Chain 3: Iteration: 400 / 1000 [ 40%]  (Sampling)
Chain 3: Iteration: 500 / 1000 [ 50%]  (Sampling)
Chain 3: Iteration: 600 / 1000 [ 60%]  (Sampling)
Chain 3: Iteration: 700 / 1000 [ 70%]  (Sampling)
Chain 3: Iteration: 800 / 1000 [ 80%]  (Sampling)
Chain 3: Iteration: 900 / 1000 [ 90%]  (Sampling)
Chain 3: Iteration: 1000 / 1000 [100%]  (Sampling)
Chain 3: 
Chain 3:  Elapsed Time: 0 seconds (Warm-up)
Chain 3:                0.003 seconds (Sampling)
Chain 3:                0.003 seconds (Total)
Chain 3: 
simple_stanfit
Inference for Stan model: anon_model.
3 chains, each with iter=1000; warmup=200; thin=1; 
post-warmup draws per chain=800, total post-warmup draws=2400.

        mean se_mean   sd   2.5%    25%    50%    75%  97.5% n_eff Rhat
theta   0.31    0.00 0.07   0.19   0.26   0.31   0.35   0.44   742    1
lp__  -32.64    0.03 0.80 -34.90 -32.79 -32.34 -32.16 -32.10   949    1

Samples were drawn using NUTS(diag_e) at Tue Jun 24 22:33:10 2025.
For each parameter, n_eff is a crude measure of effective sample size,
and Rhat is the potential scale reduction factor on split chains (at 
convergence, Rhat=1).

Algunos diagnósticos:

gf_dens(~theta, data = posterior(simple_stanfit))

simple_mcmc <- as.matrix(simple_stanfit)
mcmc_areas(as.mcmc.list(simple_stanfit), prob = 0.9, pars = "theta")

diag_mcmc(as.mcmc.list(simple_stanfit))