library(nimble)
<- nimbleFunction(
sir_incidence run = function(beta = double(0)) {
<- 100 # 100 days of simulation
tend <- 0.1 # time step of 0.1 day
dt
# initial condition
<- 999
St <- 1
It <- 0
Rt <- 0
CIt
# create vectors for the state variables
<- rep(0, tend)
S <- rep(0, tend)
I <- rep(0, tend)
R <- rep(0, tend) # cumulative incidence
CI
# first elements of the vectors are initial conditions
1] <- St
S[1] <- It
I[1] <- Rt
R[1] <- CIt
CI[
<- 0.2 # 1/gamma = duration of infectiousness
gamma
for (i in 2:tend) { # each day
for (j in 1:ceiling(1/dt)) { # time steps per day
<- St + It + Rt # total population size
Nt <- St * beta * It / Nt * dt # transition rate from S to I
rate_StoI <- gamma * It * dt # transition rate from I to R
rate_ItoR
<- - rate_StoI # rate of change for S
dS <- rate_StoI - rate_ItoR # rate of change for I
dI <- rate_ItoR # rate of change for R
dR <- rate_StoI # rate of change for cumulative incidence
dCI
<- St + dS # update the St
St <- It + dI # update the It
It <- Rt + dR # update the Rt
Rt <- CIt + dCI # update the CIt
CIt
}<- St # put St in the vector
S[i] <- It # put It in the vector
I[i] <- Rt # put Rt in the vector
R[i] <- CIt # put CIt in the vector
CI[i]
}# daily incidence from cumulative incidence
<- CI[2:tend] - CI[1:(tend-1)]
inc return(inc)
returnType(double(1)) # return type
} )
SEIR model using the Nimble pacakge
감염병 수리 모형을 개발하는 데 있어 가장 근본적인 질문 중 하나는 주어진 관찰값 (시계열)하에서 어떤 모형을 선택하고 그 모수의 값을 어떻게 결정하는가이다. 모형을 선택하는 과정은 따로 다루기로 하고 여기서는 일반적으로 사용되는 감염병 수리 모형 (i.e., SIR)을 사용할 때 모수를 추정하는 과정에 대해서 이야기해보자. 최대 가능도 (maximum likelihood) 방법에 대해서는 전에 언급하였다. 모수를 추정하는 여러 방법 중에 마르코프 연쇄 몬테카를로 (Markov Chain Monte Carlo; MCMC) 방법이 적절한 모수의 값을 찾아내고 그 값의 불확실성 (uncertainty)를 나타내는 데 가장 널리 쓰이는 방법 중의 하나이다. MCMC 알고리듬을 직접 작성해서 사용한는 것도 원리를 이해하는 데에는 도움이 되지만 이미 다양한 통계 패키지에서 MCMC가 사용되고 있으므로 기존 패키지를 사용하는 것도 합리적인 방법이 될 수 있다. 회귀 분석 등 통계모형의 경우BUGS (Bayesian Inference Using Gibbs Sampling) 혹은 JAGS (Just Another Gibbs Sampler), Stan, 그리고 NIMBLE 등에 구현된 MCMC를 사용하는 것이 많이 보편화 되어 있다.
이 중 NIMBLE 은 R 패키지 nimble을 이용해서 사용할 수 있고 패키지에서 제공하는 함수 기능을 이용해서 감염병 수리 모형을 구현하고 MCMC 까지 할 수 있다. syntax 또한 R과 유사해서 R를 사용하는 사람에게는 Stan 보다 더 접근이 용이한 것 같다. 아래에는 nimble 함수 기능을 이용하여 Euler 방법에 기반한 SIR 모형을 구현한 예이다.
모수 추정을 위해서 푸아송 분포를 이용하여 거짓 관찰값 (Y) 을 만들어보자.
# create observation
<- 0.4 # true beta
beta <- sir_incidence(beta) # true daily incidence
X <- rpois(length(X), lambda=X) # Poisson-distributed observation Y
아래와 같이 prior distribution, likelihood, 그리고 posterior predictive check위해서 ypred 도 함께 구현한다.
# BUGS style code
<- nimbleCode({
code ~ T(dnorm(0, sd = 2), 0, 2) # prior for beta truncated at 0 and 2
beta 1:N] <- sir_incidence(beta) # daily incidence from the model
mu[for (i in 1:N) {
~ dpois(mu[i]) # likelihood
y[i] ~ dpois(mu[i]) # posterior predictive value
ypred[i]
} })
아래와 같이 초기 조건을 설정하고 모형을 구성한다. 빠른 실행을 위해서 컴파일 한다.
# constants, data, and initial values
<- list(N = length(Y)) # number of observation
constants <- list(y = Y) # observation
data <- list(beta = 0.1) # starting point for beta
inits
# create the model object
<- nimbleModel(code = code,
sir_model constants = constants,
data = data,
inits = inits,
check = FALSE)
<- buildMCMC(sir_model, monitors=c('beta','ypred'))
sirMCMC <- compileNimble(sir_model)
Csir <- compileNimble(sirMCMC, project=Csir)
CsirMCMC
# thining interval was chosen based on previous analyses of ACF
<- runMCMC(CsirMCMC, niter=5000, thin=10, nburnin=1000)
samples # saveRDS(samples, "samples_nimble_20231125e.rds")
<- readRDS("samples_nimble_20231125e.rds")
samples plot(samples[,1], type="l", ylab=expression(beta), xlab="Iterations")
\(\beta\) 의 posterior distribution과 거짓 자료를 만들기 위해 사용했던 \(\beta\)값 (빨간색)을 비교해보자.
library(ggplot2)
::loadfonts("win", quiet=TRUE)
extrafonttheme_set(hrbrthemes::theme_ipsum_rc(base_size=14, subtitle_size=16, axis_title_size=12))
|>
samples as.data.frame() |>
ggplot()+
geom_histogram(aes(x=beta, fill="posterior"))+
geom_vline(aes(xintercept=0.4, color="true"), linewidth=1.2)+
labs(x=expression(beta), y="frequency")+
scale_fill_manual("", values=c("posterior"="grey"))+
scale_color_manual("", values=c("true"="firebrick"))+
theme(legend.position = "bottom")
Posterior predictive check
# posterior predictive check
<- nrow(samples)
nsamp <- data.frame(time=rep(1:99, nsamp+1),
df name=c(rep(1:nsamp, each=99),rep("data",99)))
$value <- c(c(t(samples[,2:100])), Y)
df
library(dplyr)
ggplot(df)+
geom_line(data=filter(df, name!="data"), aes(x=time, y=value, group=name,
color="posterior predictive values"))+
geom_point(data=filter(df, name=="data"), aes(x=time, y=value, color="data"),
size=1.2) +
labs(x='Time (day)', y='Daily infected')+
scale_color_manual("",values=c("posterior predictive values"="grey",
"data"="firebrick"))+
theme(legend.position = "bottom")
# ggsave("nimble_ppc_incidence.png", gg, units="in", width=3.4*2, height=2.7*2)