新型肺炎COVID-19の神戸における真のIgG抗体陽性患者数をrstanで推定する

読んだ。
Estimation of seroprevalence of novel coronavirus disease (COVID-19) using preserved serum at an outpatient setting in Kobe, Japan: A cross-sectional study. | medRxiv
神戸市の入院中の患者で、入院中の検査を適当(ランダムサンプリングのつもりで)にIgG抗体検査をすると、1000人中33人が抗体陽性だったということで、seropositive の頻度は3.3%(95%CI 2.3%〜4.6%)、と言っている。これは二項検定で計算できて

round(binom.test(33, 1000)$conf.int, 3)
[1] 0.023 0.046
attr(,"conf.level")
[1] 0.95

となるが、これは1000人から陽性患者を検出する確率pがたしかにpであるときに、最尤推定値が\frac{33}{1000}で、二項分布から信頼区間を計算していいのである。すなわち、抗体検査の感度と特異度がともに100(のとき、accuracy が1)のときに、「1000人中、抗体検査陽性になる患者がpだけ存在する」=「1000人中、抗体検査陽性になった患者がpだけ存在する」と言っていいのであって、(抗体検査陽性)=(本当に陽性で、抗体検査も陽性)+(本当は陰性で、抗体検査は陽性)である状況が考慮されていない。
最近やった。
mikuhatsune.hatenadiary.com
mikuhatsune.hatenadiary.com

KURABOの抗体検査は、論文によると(SARS-CoV-2 Antibody Detection Kit (IgM/IgG))、IgG検査は521人に行われ、感度 0.7638、特異度 1、精度 0.9424だった、という。これを一生懸命分割表で逆算すると

真に陽性 真に陰性 合計
検査陽性 感度 97 0 97
検査陰性性 30 特異度 394 424
127 394 N=521

となり、この検査精度を推定したときの対象集団の事前確率rは、精度(A_c:accuracy)とすれば
A_c=\frac{rNS_n+(1-r)NS_p}{N}=A_c
r=\frac{A_c-S_p}{S_n-S_p}
で、24.4%のようである。

Sn <- 0.7638
Sp <- 1
Ac <- 0.9424
N_KURABO <- 521

r <- (Ac - Sp)/(Sn - Sp)

M0 <- matrix(c(r*Sn, (1-r)*(1-Sp), r*Sn+(1-r)*(1-Sp), r*(1-Sn),
               (1-r)*Sp, r*(1-Sn)+(1-r)*Sp,
               r, (1-r), 1), 3, byrow=TRUE)
M <- round(M0*N_KURABO)

M[1,1]/M[3,1]
M[2,2]/M[3,2]
(M[1,1]+M[2,2])/M[3,3]

年齢構成を考えずに、とりあえずバルクの集団として、「真の抗体陽性の人の割合」を推定する。抗体検査の感度はほぼ100%なので、陽性になってしまえばその人はほぼ確実に真に陽性の患者(SPNINという)であるが、感度は75%程度なので、真に陽性である人が、検査陰性になってしまい、取りこぼしている可能性がある。というわけで、ただ単純に\frac{33}{1000}で二項分布を考えると、過小評価するおそれがある。
というわけでやった。
4.1%(2.6%〜5.9%)が真に抗体陽性の確率、となった。3.3%(2.3%〜4.6%)よりか多くなっており、論文では感度が75%であることを考慮していない分、過小評価となっている。
f:id:MikuHatsune:20200502225559p:plain
f:id:MikuHatsune:20200502230829p:plain

library(rstan)
rstan_options(auto_write=TRUE)
options(mc.cores=parallel::detectCores())
 
code <- "
data{
  int N_IgG;           // 神戸集団でのIgG検査数 1000
  int N_IgG_positive;  // 神戸集団でのIgG検査陽性数 33
  int N_KURABO_P;      // KURABO検査での陽性結果
  int N_KURABO_TP;     // KURABO検査での真の陽性結果
  int N_KURABO_N;      // KURABO検査での陰性結果
  int N_KURABO_TN;     // KURABO検査での真の陰性結果
}
parameters{
  real<lower=0, upper=1> p;  // IgG陽性の有病率
  real<lower=0, upper=1> Sn; // KURABOの検査の感度
  real<lower=0, upper=1> Sp; // KURABOの検査の特異度
}
transformed parameters{
  real<lower=0, upper=1> theta = p*Sn + (1-p)*(1-Sp);  // KURABO検査で陽性になる確率
}
model{
  N_KURABO_P ~ binomial(N_KURABO_TP, Sn);  // KURABO検査の感度
  N_KURABO_N ~ binomial(N_KURABO_TN, Sp);  // KURABO検査の特異度
  N_IgG_positive ~ binomial(N_IgG, theta); // 神戸集団でのIgG陽性
}
"

m0 <- stan_model(model_code=code)
standata <- list(N_IgG=1000, N_IgG_positive=33,
                 N_KURABO_P=M[1,1], N_KURABO_TP=M[3,1], N_KURABO_N=M[2,2], N_KURABO_TN=M[3,2])

fit <- sampling(m0, standata, iter=30000, warmup=10000, chain=4)
ex <- extract(fit, pars=head(fit@model_pars, -1))

alpha <- 0.05
cia <- c(alpha/2, 0.5, 1-alpha/2)

labs <- c("IgG positive prevalenve in Kobe", "KURABO Sensitivity", "KURABO Specificity", "KURABO Positive result probability")
cols <- c("lightgreen", "violet", "violet", "violet")
xl <- c(0, 1)
par(mfcol=c(4, 1), mar=c(4.5, 4.5, 3.5, 2), cex.axis=1.5)
for(i in seq(labs)){
  CrI <- quantile(ex[[i]], cia)
  # sprintf("%.2f [%.2f, %.2f]", CrI[2], CrI[1], CrI[3])
  hist(ex[[i]], main=sprintf("%s\n%.3f [%.3f, %.3f]", labs[i], CrI[2], CrI[1], CrI[3]), freq=FALSE, xlab="Probability", nclass=50, border=NA, col=cols[i], cex.main=1.5)
}

cols <- c(rgb(144,238,144,200,maxColorValue=256), rgb(238,130,238,120,maxColorValue=256))
hist(ex$p, xlab="Prevalence", nclass=100, border=NA, col=cols[1], cex.main=1.5, main="")
hist(ex$theta, xlab="Probability", nclass=100, border=NA, col=cols[2], cex.main=1.5, add=TRUE)
legend("topright", legend=c("真の推定", "素の推定"), col=cols, pch=15, cex=2, bty="n")


次に、年齢構成がデータとしてあるので、グループ別にやった。ただ分けてfor loopを回すだけである。
10歳未満や90歳以上は標本が少なく、信用区間の幅が非常に広くなってしまったが、それ以外の階級では概ね5%〜10%くらいが真に抗体陽性となった。男女別でみても6.5%〜6.7%とほとんど差はなく、男女合わせて年齢構成を考慮した場合、6.7%の抗体陽性となった。
f:id:MikuHatsune:20200502225419p:plain

Age_M <- c(7, 17, 17, 41, 64, 84, 87, 85, 81, 6)
Age_F <- c(1, 10, 19, 49, 91, 80, 84, 81, 83, 13)
Positive_M <- c(0, 0, 0, 1, 2, 4, 2, 6, 1, 0)
Positive_F <- c(0, 0, 0, 2, 3, 2, 3, 3, 4, 0)
names(Age_M) <- names(Age_F) <- c("10<", apply(matrix(c(1, head(rep(2:9, each=2), -1)), nr=2)*10, 2, paste, collapse="-"), ">90")

code <- "
data{
  int Cat;
  int Age_M[Cat];           // 年齢区分の数
  int Age_F[Cat];           // 年齢区分の数
  int Positive_M[Cat];
  int Positive_F[Cat];
  int N_KURABO_P;      // KURABO検査での陽性結果
  int N_KURABO_TP;     // KURABO検査での真の陽性結果
  int N_KURABO_N;      // KURABO検査での陰性結果
  int N_KURABO_TN;     // KURABO検査での真の陰性結果
}
parameters{
  vector<lower=0, upper=1>[Cat] pM;  // IgG陽性の有病率
  vector<lower=0, upper=1>[Cat] pF;  // IgG陽性の有病率
  real<lower=0, upper=1> Sn; // KURABOの検査の感度
  real<lower=0, upper=1> Sp; // KURABOの検査の特異度
}
transformed parameters{
  vector<lower=0, upper=1>[Cat] thetaM = pM*Sn + (1-pM)*(1-Sp);  // KURABO検査で陽性になる確率
  vector<lower=0, upper=1>[Cat] thetaF = pF*Sn + (1-pF)*(1-Sp);  // KURABO検査で陽性になる確率
}
model{
  N_KURABO_P ~ binomial(N_KURABO_TP, Sn);  // KURABO検査の感度
  N_KURABO_N ~ binomial(N_KURABO_TN, Sp);  // KURABO検査の特異度
  Positive_M ~ binomial(Age_M, thetaM); // 神戸集団でのIgG陽性
  Positive_F ~ binomial(Age_F, thetaF); // 神戸集団でのIgG陽性
}
generated quantities{
  int RM[sum(Age_M)];
  int RF[sum(Age_F)];
  for(i in 1:Cat){
    for(j in ((i==1)?1:(sum(Age_M[1:(i-1)])+1)):sum(Age_M[1:i])){
      RM[j] = bernoulli_rng(pM[i]);
    }
    for(j in ((i==1)?1:(sum(Age_F[1:(i-1)])+1)):sum(Age_F[1:i])){
      RF[j] = bernoulli_rng(pF[i]);
    }
  }
}
"


m1 <- stan_model(model_code=code)
standata <- list(Cat=length(Age_M), Age_M=Age_M, Age_F=Age_F,
                 Positive_M=Positive_M, Positive_F=Positive_F,
                 N_KURABO_P=M[1,1], N_KURABO_TP=M[3,1], N_KURABO_N=M[2,2], N_KURABO_TN=M[3,2])

fit <- sampling(m1, standata, iter=10000, warmup=5000, chain=4)
ex <- extract(fit, pars=head(fit@model_pars, -1))

mapply(function(j) (ifelse(j==1, 1, sum(Age_M[1:(j-1)])+1)):sum(Age_M[1:j]), 1:10)

library(vioplot)

v <- lapply(list(ex$pM, ex$pF), as.data.frame)
v <- mapply(function(z1, z2) cbind.data.frame(z1, rowMeans(z2)), list(ex$pM, ex$pF), list(ex$RM, ex$RF), SIMPLIFY=FALSE)
cols <- c("skyblue", "pink")
Mcols <- c("blue", "red")
xl <- c(0, 1.1)
xd <- 0.23
P <- lapply(v, apply, 2, quantile, cia)

labs <- c(names(Age_M), "All")
par(mar=c(4.5, 4.5, 3.5, 9), cex.axis=1.5, cex=1.25)
plot(rev(seq(labs)), type="n", xlim=xl, ylim=c(0.5, length(labs)+0.5), yaxt="n", xlab="Prevalence", ylab="")
axis(2, at=rev(seq(labs)), labels=labs, las=1)
title("Age adjusted seroprevalence in Kobe")
pa <- par()$usr
for(i in seq(v)){
  vioplot(v[[i]], at=rev(seq(labs)), horizontal=TRUE, side=c("right", "left")[i], add=TRUE, rectCol=Mcols[i], col=cols[i], colMed=Mcols[i], lineCol=NA)
  for(j in seq(labs)){
    txt <- sprintf("%.3f [%.3f, %.3f]", P[[i]][2, j], P[[i]][1, j], P[[i]][3, j])
    text(pa[2], length(labs)+1-j+xd*c(1, -1)[i], txt, xpd=TRUE, pos=4)
    text(pa[2], length(labs)+1-j+xd*c(1, -1)[i], c("M", "F")[i], xpd=TRUE, pos=2, font=2)
  }
}
PAll <- quantile(rowMeans(cbind(ex$RM, ex$RF)), cia)
txt <- sprintf("%.3f [%.3f, %.3f]", PAll[2], PAll[1], PAll[3])
text(pa[2], pa[3]+xd, txt, xpd=TRUE, pos=4)
text(pa[2], pa[3]+xd, "All", xpd=TRUE, pos=2, font=2)
abline(h=rev(seq(labs)), lty=3)


草生える()

新型肺炎COVID-19の抗体検査から集団の有病率をrstanで推定する

結論から言うと、抗体検査を受けた202人の集団において、9.4%(95% 信用区間で5.4%〜13.9%)が感染者である。ただし、この抗体検査を受けた202人は、東京都(とか他の都道府県一般)のランダムサンプリングとは言えないので、単純に東京都人口の9.4%が感染している、とは言えないことに注意。

こんな記事を観測した。
https://www.tokyo-np.co.jp/s/article/2020043090070748.htmlwww.tokyo-np.co.jp
解析に必要な数値の事実ベースで抽出すると

一般市民の4.8%、医療従事者の9.1%が陽性(抗体あり)で、過去に感染していたことが分かった。
一般市民の147人の4.8%%にあたる7人が陽性、医療従事者55人のうち9.1%の5人が陽性だった。
ホームページで希望者を募り、20~80歳の男性123人、女性79人を検査した。
★このうち一カ月以内に発熱のあった人は52人
★同居者でコロナウイルス感染者がいる人は2人
PCR検査を受診したことがある人は9人
PCR検査で陽性反応だった1人も含む
★市民・医療従事者を合計した202人全体では5.9%の12人(男女とも6人)が陽性だった。
以前のPCR検査で陰性とされたが、抗体検査で陽性だった人もいた。

このうち、★は今回の解析で使用したデータである。

興味があるのは、「202人中、抗体検査で陽性だったのは12人(5.9%)」という結果から、「202人中、感染しているのはX人」を推定することである。検査しているのだから感染者(抗体検査なので既感染を含むが、面倒なので「今、感染している」こととする)検査陽性の人のことだろう、と思うかもしれないが、それは臨床検査学的にはよくある間違い(というかベイズ統計屋的に間違い)で、検査陽性というのは、
(本当に感染していて、検査で正しく陽性)+(本当は感染していないのに、検査で間違って陽性)
というパターンの足し算の結果、「検査陽性」となっているので、(本当は感染していない人)を加算していることに注意しなければならない。
が、検査の特性として、(本当は感染していないのに、検査で間違って陽性)としてしまう確率などがわかれば、ある程度正しく推定できる。これらの性能は感度と特異度と言われ、検査として売り出すなら絶対に評価している。

ここでは、PCR検査は感度 0.1-0.5、特異度0.99程度、とした。一方で、抗体検査は感度0.3-0.7、特異度0.8-0.99程度とした。というのも、インフルエンザの抗体検査がこれくらいだからである。気になる人は適当にパラメータをいじって追試してほしい。

さて、推定したいのは「感染している人の割合」であるが、202人は属性がバラバラである。一ヶ月以内に発熱のあった人、同居者でコロナウイルス感染者がいる人、PCR検査を受信したことがある人(は、つまり陰性だったのだろうと推測)、PCR検査で陽性だった人、を除外すると、202人中138人は、一体どういう人なのかよくわからないが、ホームページで募った希望者、ということから推測するに、無症状の人とする。つまり、
p_1:無症状の138人のうち本当に感染している人の割合
p_2:このうち一カ月以内に発熱のあった52人のうち本当に感染している人の割合
p_3:同居者でコロナウイルス感染者がいる2人のうち本当に感染している人の割合
p_4PCR検査を受診したことがある9人(で、PCR検査陰性だった)のうち本当に感染している人の割合
p_5PCR検査で陽性反応だった1人のうち本当に感染している人の割合
をそれぞれ推定することになる。ただし、202人が5つのカテゴリに属し、総計で12人検査陽性、という制限がある。
ここで、変数は5つあるが、PCR検査は「怪しそうな人にやる」のが原則である。「一カ月以内に発熱があった」というのは、いま感染しているかどうかにはあまり寄与しない(※抗体検査で既感染を疑う設定なら意味があるが、この解析では現時点の感染を意味しているので注意)。しかし、無症状よりかは感染していそうな感じはするので、p_1< p_2 くらいの緩い制限を持ちつつ、PCR検査は、事前確率p_3 の集団に行われる、とする。
すると、PCR検査陰性(p_4)とPCR検査陽性(p_5)の運命がふたつともある。ここで、事前確率p_3のときの陰性事後確率と陽性事後確率は、実はそれぞれ計算できて
p_4=\frac{p_3 (1-S_n)}{p_3 (1-S_n)+(1-p_3)(1-S_p)}PCR検査の陰性事後確率)
p_4=\frac{p_3 (1-S_n)}{p_3 (1-S_n)+(1-p_3)S_p} こっちです)
p_5=\frac{p_3S_n}{p_3S_n + (1-p_3)(1-S_p)}PCR検査の陽性事後確率)
となる。ただしS_nは感度、S_pは特異度である。
pは各カテゴリの感染者割合(確率)なので、実際に(抗体検査で)陽性になる確率q
q=pS_n + (1-p)(1-S_p)
である。ここでS_nS_pは抗体検査の感度と特異度である。
こちらでも似たようなことをやった。
mikuhatsune.hatenadiary.com


5つのカテゴリにq_{*}=(q_1, q_2,q_3, q_4, q_5) と抗体検査で陽性となる確率がついていて、それぞれ138, 52, 2, 9, 1 人がいて、検査陽性となる人が合計12人なので、ひたすらfor loop で回してbinomial_lpmf関数でtarget += する、としてみる。多項分布とかもっといい方法があれば教えてほしい。
最終的にはgenerated quantities ブロックで、202人5カテゴリの人たちが感染者かそうでないかをpでランダムサンプリング(bernoulli_rng でできる)することでシミュレーションし、最終的に「202人のうち感染者の割合」を推定する。

9.4%(95% CI 5.4%〜13.9%)が感染者と推定された。
f:id:MikuHatsune:20200501163624p:plain

p_1:無症状の138人のうち本当に感染している人の割合は1.67%
p_2:このうち一カ月以内に発熱のあった52人のうち本当に感染している人の割合は10.7%
p_3:同居者でコロナウイルス感染者がいる2人のうち本当に感染している人の割合は85.1%
p_4PCR検査を受診したことがある9人(で、PCR検査陰性だった)のうち本当に感染している人の割合は80.1%
p_5PCR検査で陽性反応だった1人のうち本当に感染している人の割合は99.8%
だった。
f:id:MikuHatsune:20200501163649p:plain
本当は「医療従事者であるか」とか「男性か女性か」の属性も加味したかったが断念した。

library(rstan)
rstan_options(auto_write=TRUE)
options(mc.cores=parallel::detectCores())

code <- "
data{
  int Ncat;        //カテゴリの数
  int Ns[Ncat]; //カテゴリごとの人数
  int N_pos;     //陽性患者 12人
  real<lower=0, upper=1> Sn_PCR[2];  // PCR検査の感度の範囲
  real<lower=0, upper=1> Sp_PCR[2];  // PCR検査の特異度の範囲
  real<lower=0, upper=1> Sn_Anti[2];   // 抗体検査の感度の範囲
  real<lower=0, upper=1> Sp_Anti[2];   // 抗体検査の特異度の範囲
  real<lower=0, upper=1> b1[2];           // 無症状の人の感染者割合の範囲
  real<lower=0, upper=1> b2[2];           // 一ヶ月以内に発熱した人の感染者割合の範囲
  real<lower=0, upper=1> b3[2];           // 同居している人がコロナウイルス感染者の人の感染者割合の範囲
  real<lower=0, upper=1> b4[2];           // PCR陰性者の感染者割合の範囲
  real<lower=0, upper=1> b5[2];           // PCR陽性者の感染者割合の範囲
}
parameters{
  real<lower=Sn_PCR[1], upper=Sn_PCR[2]> Sensitivity; // PCR検査の感度
  real<lower=Sp_PCR[1], upper=Sp_PCR[2]> Specificity; // PCR検査の特異度
  real<lower=Sn_Anti[1], upper=Sn_Anti[2]> Sn;                // 抗体検査の感度
  real<lower=Sp_Anti[1], upper=Sp_Anti[2]> Sp;                // 抗体検査の特異度
  real<lower=b1[1], upper=b1[2]> p1;
  real<lower=b2[1], upper=b2[2]> p2;
  real<lower=b3[1], upper=b3[2]> p3;
}
transformed parameters{
  real<lower=b4[1], upper=b4[2]> p4 = p3*(1-Sensitivity)/(p3*(1-Sensitivity) + (1-p3)*Specificity);
  real<lower=b5[1], upper=b5[2]> p5 = p3*Sensitivity/(p3*Sensitivity + (1-p3)*(1-Specificity));
  vector<lower=0, upper=1>[Ncat] theta;
  theta[1] = p1*Sn + (1-p1)*(1-Sp);
  theta[2] = p2*Sn + (1-p2)*(1-Sp);
  theta[3] = p3*Sn + (1-p3)*(1-Sp);
  theta[4] = p4*Sn + (1-p4)*(1-Sp);
  theta[5] = p5*Sn + (1-p5)*(1-Sp);
}
model{ // 合計12人が陽性になるようにひたすら回す
  for(i in 0:Ns[5]){
    target += binomial_lpmf(i | Ns[5], theta[5]);
    for(j in 0:Ns[3]){
      target += binomial_lpmf(j | Ns[3], theta[3]);
      for(k in 0:(Ns[4]-i-j)){
        target += binomial_lpmf(k | Ns[4], theta[4]);
        for(l in 0:(N_pos-i-j-k)){
          target += binomial_lpmf(l | Ns[2], theta[2]);
          target += binomial_lpmf(N_pos-l-k-j-i | Ns[1], theta[1]);
        }
      }
    }
  }
}
generated quantities{
  int e[sum(Ns)];
  for(i in 1:Ns[1]) e[i] = bernoulli_rng(p1);
  for(i in sum(Ns[1:(2-1)]):sum(Ns[1:2])) e[i] = bernoulli_rng(p2);
  for(i in sum(Ns[1:(3-1)]):sum(Ns[1:3])) e[i] = bernoulli_rng(p3);
  for(i in sum(Ns[1:(4-1)]):sum(Ns[1:4])) e[i] = bernoulli_rng(p4);
  for(i in sum(Ns[1:(5-1)]):sum(Ns[1:5])) e[i] = bernoulli_rng(p5);
}
"
m0 <- stan_model(model_code=code)

Ns <- c(138, 52, 2, 9, 1)
standata <- list(Ncat=length(Ns), Ns=Ns, N_pos=12, 
                 Sn_PCR=c(0.1, 0.5), Sp_PCR=c(0.99, 1),
                 Sn_Anti=c(0.3, 0.7), Sp_Anti=c(0.8, 0.99),
                 b1=c(0.0001, 0.3), b2=c(0.05, 0.8), b3=c(0.5, 0.99), b4=c(0, 1), b5=c(0.3, 1)
                 )

fit <- sampling(m0, standata, iter=5000, warmup=1000, chain=4)
ex <- extract(fit, pars=head(fit@model_pars, -1))

labs <- c("Sensitivity of PCR", "Specificity of PCR", "Sensitivity of immunoassay", "Specificity of immunoassay", "Prevalence of asymptomatic", "Prevalence of fever within a month", "Prevalence of housemate positive", "Prevalence of PCR negative", "Prevalence of PCR positive")
alpha <- 0.05
cia <- c(alpha/2, 0.5, 1-alpha/2)

xl <- c(0, 1)
par(mfcol=c(2, 5), mar=c(4.5, 4.5, 2.5, 2))
for(i in seq(labs)){
  CrI <- quantile(ex[[i]], cia)
  # sprintf("%.2f [%.2f, %.2f]", CrI[2], CrI[1], CrI[3])
  hist(ex[[i]], main=sprintf("%s\n%.2f [%.2f, %.2f]", labs[i], CrI[2], CrI[1], CrI[3]), freq=FALSE, xlab="Probability", nclass=50, border=NA, col="violet", xlim=xl)
}

CrI <- quantile(rowMeans(ex$e), cia)
hist(rowMeans(ex$e), main=sprintf("%s\n%.2f [%.2f, %.2f]", "Prevalence", CrI[2], CrI[1], CrI[3]), freq=FALSE, xlab="Probability", nclass=50, border=NA, col="lightgreen", xlim=xl)

新型肺炎COVID-19が流行してから全身麻酔件数がどれほど減ったかをrstanで推定する

こんなツイーヨを観測した。

全身麻酔の導入において、気管挿管飛沫感染のリスクが高いので、不要不急の手術は控えるように麻酔科学会、各種外科系学会から提言が出ている。
とはいっても、不要不急の手術ってなんぞや、ということで、手術をしないと死ぬような緊急疾患は麻酔をするとして、癌手術なんかもいずれは手術できなくなりそうである。

上記では、ツイッターで適当に麻酔科から「手術室の運営方針として、手術をどれくらい減らしているか」を聞いていて、(1)制限なし(100%運用)、(2)やや制限(半分以上として70%運用)、(3)制限(半分以下として40%運用)、(4)予定手術はしない、緊急手術のみ対応(12%)、と勝手にした。というのも、制限具合をベータ分布からサンプリングする上でいい感じに設定する必要があるからである。
平均m、分散v=0.01 として、各設定についてベータ分布のパラメータ\alpha\beta は、平均\frac{\alpha}{\alpha + \beta}、分散\frac{\alpha\beta}{(\alpha+\beta)^2(\alpha+\beta+1)}となるから、
\alpha=\frac{m^2(1-m)}{v-m}\beta=(1-m)(\frac{m(1-m)}{v}-1)
ベータ分布を使って、平均的な手術室稼働率の低下具合はこんな感じとする。
f:id:MikuHatsune:20200426225806p:plain

上記のアンケート結果を使って、手術室稼働率の低下具合の週ごとの推移はこんな感じになる。通常通り運営していた施設はどんどん減って、少し制限(半分以上)しながら運営している施設が増えている。大きく制限(半分以下)している施設も遅れて増えてきているが、予定手術はしない、という施設はまだそこまで増加していない。しかし、手術しない、という施設が増えるのも時間の問題のようである。
f:id:MikuHatsune:20200426225909p:plain
推定の問題として、アンケート回答は4つなので、どの回答をするかというパラメータ\thetasimplex であるが、通常通り、という回答をする施設、というか割合は確実に減るはずなので、あるアンケート回答週tにおいて、通常通り、という回答が最初の項だとすると\theta_{t,1}<\theta_{t+1,1} というようにしてorderedでパラメータを作りたかったが、うまくいかなかった。
\theta_tについては時系列サンプリングにしている。あまりよくなかった。

さて、こういう事態になって、実際の全身麻酔件数はどれくらい減っているのかを推定する。全国での全身麻酔の件数は統計があるようなのでここからexcelデータを拾ってくる。
すべて | 統計データを探す | 政府統計の総合窓口

週ごとにアンケートを取ってくれているようなので、少なくとも週に1件以上は全身麻酔をしているデータを対象としようと思ったが、適当に年間100件以上している施設だけ解析に使った。1989件あった。
さて、各施設H_jが、ある週t においてアンケート回答結果がd=\{1,2,3,4\}だったとき、pを手術室運営%とすると
d=1 ならば通常通り100%の手術室運営なので、p \sim U(0.95, 1.05)
d=2 ならば、やや制限(半分以上として70%運用)として、p\sim \beta(0.7)
d=3 ならば、制限(半分以下として40%運用)として、p\sim \beta(0.4)
d=4 ならば、予定手術はしない、緊急手術のみ対応として、p\sim \beta(0.12)
とサンプリングして、前回実績数にp をかけて手術件数を減ずることにする。
結果、こんな感じになった。
最初の頃に比べて、手術件数は2/3になっているようである。
f:id:MikuHatsune:20200426231152p:plain
これの推定の問題として、最初に例えばやや制限(半分以上として70%運用)と答えたとして、次の週以降はやや制限、制限、予定はしない、のいずれかしか答えないだろうが、通常通り、も答えうる、というのがよろしくないので、もう少しサンプリングの制限を加えた推定というかコードにしたいが、とりあえずここまで。

qdate <- c("03-13", "03-20", "03-27", "04-06", "04-10", "04-17", "04-27")
dat <- rbind(
c(89.3, 5.4, 3.6, 1.8),
c(76.6, 17, 2.1, 4.3),
c(82.2, 6.8, 4.1, 6.8),
c(64.4, 23.1, 6.9, 5.6),
c(45.7, 38.5, 13.1, 2.7),
c(26.7, 37.5, 26.7, 9.1),
c(24.7, 37.8, 29.5, 8)
)/100
dat <- dat/rowSums(dat)
colnames(dat) <- c("通常通り", "少し制限(半分以上)", "大きく制限(半分以下)", "原則予定手術なし")

N <- c(113, 47, 73, 160, 221, 232, 288)

datN <- round(dat * N)

# GA のデータは、エクセルの全身麻酔、というところだけあればよい
# 列名をGAにしている
# 下から5行は DPC対象病院群ということになっているので削除
GA <- read.csv("generalanesth.csv")
GAN <- na.omit(head(GA$GA, -5))
GAN12 <- round(GAN[GAN > 100]/12)

m <- c(0.7, 0.4, 0.12)
v <- 0.01
b <- cbind(m^2*(1-m)/v-m, (1-m)*(m*(1-m)/v-1))
x <- seq(0, 1, length=300)
d <- mapply(function(z) dbeta(x, b[z, 1], b[z, 2]), seq(m))
cols <- c("green", "orange", "red")
par(mar=c(5, 5, 2, 2), las=1, cex.lab=1.5)
matplot(x*100, d, type="l", lwd=3, lty=1, xlab="平時と比べて手術件数の減少割合[%]", ylab="確率密度", col=cols)
legend("topright", legend=sprintf("平均%d%sの減少", m*100, "%"), col=cols, pch=15, cex=1.5)


library(rstan)
rstan_options(auto_write=TRUE)
options(mc.cores=parallel::detectCores())

code <- "
data {
  int<lower=1> T; // 時間の数
  matrix<lower=0, upper=1>[T, 4] M;
  int<lower=0> N_hospital; // 全身麻酔をしている病院の数
  int<lower=0> Ope[N_hospital]; // 各病院の全身麻酔件数
  int<lower=0> N[T, 4]; // アンケート回答数
  matrix<lower=0>[3, 2] b; // beta distribution
}
parameters {
  simplex[4] y[T];
  real<lower=0> s[4];
}
model {
  for(i in 3:T){
    for(j in 1:4){
      y[i][j] ~ normal(2*y[i-1][j] - y[i-2][j], s[j]);
    }
  }
  for(i in 1:T){
    N[i,] ~ multinomial(y[i]);
  }
}
generated quantities{
  matrix[T, N_hospital] R;
  int<lower=0> idx;
  for(i in 1:T){
    for(j in 1:N_hospital){
      idx = categorical_rng(y[i]);
      // multinomial_rng で整数をサンプリングしたいが、
      // matrix がrealしかもたないのでこうした
      if(idx == 1){
        R[i, j] = Ope[j] * uniform_rng(0.95, 1.05);
      } else if(idx == 2){
        R[i, j] = Ope[j] * beta_rng(b[1, 1], b[1, 2]);
      } else if(idx == 3){
        R[i, j] = Ope[j] * beta_rng(b[2, 1], b[2, 2]);
      } else {
        R[i, j] = Ope[j] * beta_rng(b[3, 1], b[3, 2]);
      }
    }
  }
}
"

m0 <- stan_model(model_code=code)
standata <- list(T=nrow(dat), M=dat, N_hospital=length(GAN12), Ope=GAN12, N=datN, b=b)
fit <- sampling(m0, standata, iter=2000, warmup=1000, chain=4)
ex <- extract(fit, pars=head(fit@model_pars, -1))


alpha <- 0.05
cia <- c(alpha/2, 0.5, 1-alpha/2)
m <- abind::abind(mapply(function(z) apply(ex$y[,,z], 2, quantile, cia), seq(4), SIMPLIFY=FALSE), along=3)

# 透明にしながら実線も描けるようにする
cols256 <- list("green", "yellow", "orange", "red")
cols256 <- lapply(lapply(cols256, col2rgb), c)
cols <- mapply(function(z) rgb(z[1], z[2], z[3], maxColorValue=256), cols256)
colsa <- mapply(function(z) rgb(z[1], z[2], z[3], 20, maxColorValue=256), cols256)

yl <- c(0, 1)
par(mar=c(5, 5, 2, 2), cex.lab=1.5, las=1)
matplot(m[2,,], type="n", col=colsa, lty=1, lwd=3, ylim=yl, xaxt="n", xlab="週", ylab="アンケート回答割合")
axis(1, at=seq(qdate), labels=gsub("-", "/", qdate))
legend("topright", legend=colnames(dat), ncol=1, col=cols, pch=15, yjust=-0.1, xpd=TRUE, bty="n", cex=1.4)
for(i in seq(dim(m)[3])){
  lines(c(seq(N)), m[2,,i], col=cols[i], lwd=4)
  polygon(c(c(seq(N)), rev(c(seq(N)))), c(m[1,,i], rev(m[3,,i])), col=colsa[i], border=NA)
}


library(vioplot)
Z <- as.data.frame(t(mapply(function(z) rowSums(ex$R[z,,]), seq(dim(ex$R)[1]))))
Z <- Z/4
par(mar=c(5, 5, 2, 2), cex.lab=1.5, las=0)
plot(c(0.5, length(qdate)+0.5), c(0, 1), type="n", xaxt="n", ylim=c(0, max(Z)), xlab="週", ylab="全施設での全身麻酔件数 [週あたり]")
axis(1, at=seq(qdate), labels=gsub("-", "/", qdate))
vioplot(Z, add=TRUE)
text(seq(qdate), apply(Z, 2, min), sprintf("%.1f%s", colMeans(Z)/colMeans(Z)[1]*100, "%"), pos=1)

新型肺炎COVID-19の無症状感染者の割合を慶応大学病院のPCR検査の結果からrstanで推定する

慶応大学病院で、入院中で(新型肺炎COVID-19について)無症状の患者67人をPCR検査すると、6%(=4人)の患者で陽性だった、という話。
感染症の先生は、めちゃめちゃ単純な推定を使って、一番楽観的な推定で47万人、一番悲観的な推定で430万人、東京に無症状の感染者がいるのではないか、と言っている。


慶応のPCR6%の意味 - 楽園はこちら側

この推定が雑なので、もう少し真面目に考えてみる。
PCR検査を受けたのが67人、PCR陽性だったのが4人、であることは確定したデータであるので、これを使って、無症状(67人は全員無症状である)なのに感染している人の割合をpPCR検査の感度をSn、特異度をSp とする。
N=67人の運命は

真に感染(p) 真に非感染(1-p) 合計
検査で陽性 NpS_n N(1-p)(1-S_p) 4=N\{pS_n+(1-p)(1-S_p)\}
検査で陰性 Np(1-S_n) N(1-p)S_p 63=N\{p(1-S_n)+(1-p)S_p\}
Np N(1-p) N=67

となる。
全体のN=67人から確率\theta=pS_n+(1-p)(1-S_p) で4人の陽性者が出る(真陽性と偽陽性も含む)とすればよい。ここで、PCRの特性として、感度は30-70%、特異度は99%以上、というのが有識者の認識のようなので、感度はS_n\sim U(0.3, 0.7)、特異度はS_p\sim U(0.99, 1)からサンプリングされるとする。これで区間付き推定がそれなりにできるはず。

結果はp=0.14 [0.04, 0.37] となった。およそ14%の人が無症状で感染しているようである。
f:id:MikuHatsune:20200424004911p:plain

 2.5%   50% 97.5%
 73 0.040 0.141 0.366

この結果を単純に東京都の人口1395万人に当てはめると、196万人が無症状で感染しているようである。ただし、慶応大学病院に入院している人が、東京都の人口のランダムサンプリングとはそう簡単には言えないので、もっと多いか、もっと少ないかすらもよくわからない。

   2.5%     50%   97.5% 
 560364 1965582 5102252
library(rstan)
rstan_options(auto_write=TRUE)
options(mc.cores=parallel::detectCores())
code <- "
data{
  int N_keio;
  int P_keio;
  int Pop; // 対象としている人口
  real<lower=0, upper=1> Sn[2];  // 感度の範囲
  real<lower=0, upper=1> Sp[2];  // 特異度の範囲
}
parameters{
  real<lower=0, upper=1> p; // prior probability
  real<lower=Sn[1], upper=Sn[2]> Sensitivity;
  real<lower=Sp[1], upper=Sp[2]> Specificity;
}
transformed parameters{
  real<lower=0, upper=1> theta;
  theta = p*Sensitivity + (1-p)*(1-Specificity);
}
model{
  P_keio ~ binomial(N_keio, theta);
}
generated quantities{
  int e;
  e = binomial_rng(Pop, p);
}
"
m0 <- stan_model(model_code=code)

N_keio <- 67
P_keio <- 4

standata <- list(N_keio=N_keio, P_keio=P_keio, Sn=c(0.3, 0.7), Sp=c(0.99, 1), Pop=13950000)
fit <- sampling(m0, standata, iter=1000, warmup=500, chain=4)
ex <- extract(fit, pars=head(fit@model_pars, -1))

alpha <- 0.05
cia <- c(alpha/2, 0.5, 1-alpha/2)
hist(ex$p, xlab="無症状性有病者の割合", ylab="頻度", freq=FALSE, las=1, main="", col="yellow")

round(quantile(ex$p, cia), 3) # 無症状感染者の割合
round(quantile(ex$e, cia))     # 東京都の人口で考えたときの無症状感染者の人数

新型肺炎COVID-19のSEIRS two stain model がrstanで出来たので満足した

諦めていた。
mikuhatsune.hatenadiary.com
しかし、なんか出来そうになった。
mikuhatsune.hatenadiary.com

\beta型の)コロナウイルスの流行具合から、相互に免疫があるものとして、新型肺炎の今後数年の流行がどうなっていくかをSEIRSモデルで、かつ2種類のウイルス(two strain)がいるモデルでシミュレーションした話。

最初の記事の16コンパートメントの推移は式が間違っていたようなのでがんばって修正した。
普通にRで\frac{dy}{dt}を足し合わせた場合の結果はこんな感じになる。
I_1 のコンパートメントはI_1=I_1S_2+I_1E_2+I_1I_2+I_1R_2
I_2 のコンパートメントはI_2=S_1I_2+E_1I_2+I_1I_2+R_1I_2
とすると、図になる。
f:id:MikuHatsune:20200423211552p:plain

さて、rstanを使ってパラメータの変動を考慮しつつSEIRS two strain モデルをやってみる。
一生懸命コードを書くだけであるが、16コンパートメントのsimplex を時系列T 分だけ定義する。時間依存パラメータ(ここでは\beta(t))も、時間依存でないパラメータたちも全部、ひとまず時間依存パラメータとして(つまりすべてのTで一定)として、パラメータの数と同じ長さをもつvectorをもつvector で定義する。
こんな感じでできた。
f:id:MikuHatsune:20200423212249p:plain

# SEIRS をシミュレーション
# mu beta nu x12 x21 gamma delta1 delta2
library(stringr)
labs <- c(outer(paste0(lab, 1), paste0(lab, 2), paste0))
times <- 0:200
y <- rep(0, 16)
y[c(1, 2, 5)] <- c(0.997, 0.002, 0.001)
Y <- y
p <- c(1/80, 2.2, 1/3, 0.78, 0.51, 1/5, 1/45, 1/45)
for(i in 2:length(times)){
  dydt <- rep(0, length(y))
  I1 <- (y[3]+y[7]+y[11]+y[15])
  I2 <- (y[9]+y[10]+y[11]+y[12])
  dydt[1 ] = -p[2]*(I1+I2)*y[1] + p[7]*y[4] + p[8]*y[13] + p[1] - y[1]*p[1];
  dydt[2 ] = -(p[1]+p[3]+(1-p[4])*p[2]*I2)*y[2]  + p[2]*I1*y[1] + p[8]*y[14];
  dydt[3 ] = -(p[1]+p[6]+(1-p[4])*p[2]*I2)*y[3]  + p[3]*y[2]    + p[8]*y[15];
  dydt[4 ] = -(p[1]+p[7]+(1-p[4])*p[2]*I2)*y[4]  + p[6]*y[3]    + p[8]*y[16];
  dydt[5 ] = -(p[1]+p[3]+(1-p[5])*p[2]*I1)*y[5]  + p[2]*I2*y[1] + p[7]*y[8];
  dydt[9 ] = -(p[1]+p[6]+(1-p[5])*p[2]*I1)*y[9]  + p[3]*y[5]    + p[7]*y[12];
  dydt[13] = -(p[1]+p[8]+(1-p[5])*p[2]*I1)*y[13] + p[6]*y[9]    + p[7]*y[16];

  dydt[6 ] = -(2*p[3]+p[1])*y[6] + p[2]*((1-p[4])*I2*y[2] + (1-p[5])*I1*y[5]);
  dydt[7 ] = -(p[1]+p[3]+p[6])*y[7] + (1-p[4])*p[2]*I2*y[3] + p[3]*y[6];
  dydt[8 ] = -(p[1]+p[3]+p[7])*y[8] + (1-p[4])*p[2]*I2*y[4] + p[6]*y[7];
  dydt[10] = -(p[1]+p[3]+p[6])*y[10] + (1-p[5])*p[2]*I1*y[9] + p[3]*y[6];
  dydt[14] = -(p[1]+p[3]+p[8])*y[14] + (1-p[5])*p[2]*I1*y[13] + p[6]*y[10];

  dydt[11] = -(2*p[6]+p[1])*y[11] + p[3]*(y[7]+y[10]);
  dydt[12] = -(p[1]+p[6]+p[7])*y[12] + p[3]*y[8] + p[6]*y[11];
  dydt[15] = -(p[1]+p[6]+p[8])*y[15] + p[3]*y[14] + p[6]*y[11];
  dydt[16] = -(p[1]+p[7]+p[8])*y[16] + p[6]*(y[12]+y[15]);
  Y <- rbind(Y, y+dydt)
  y <- y + dydt
}

i1 <- rep(1:4, 4)
i2 <- rep(1:4, each=4)
i12 <- list(i1, i2)

Y0 <- c(list(Y), mapply(function(z) t(apply(Y, 1, tapply, z, sum)), i12, SIMPLIFY=FALSE))

cols <- sample(na.omit(str_extract(colors(), "^(?!.*gr[ea]y).*$")), length(labs))
lwd <- 3
par(mfrow=c(1, 3), las=1, mar=c(4, 5, 2, 2), cex.lab=1.5)
matplot(Y0[[1]], type="l", lty=1, lwd=lwd, xlab="Time", ylab="Proportion", col=cols)
legend("topright", legend=labs, ncol=4, col=cols, pch=15, xpd=TRUE, bty="n", cex=1.5)
ci <- tapply(sample(seq(labs), 8), rep(1:2, each=4), c)
for(i in 2:3){
  matplot(Y0[[i]], type="l", lty=1, lwd=lwd, col=cols[ci[[i-1]]], xlab="Time", ylab="Proportion")
  legend("topright", legend=sprintf("%s%d", lab, i-1), ncol=4, col=cols[ci[[i-1]]], pch=15, xpd=TRUE, bty="n", cex=1.5)
}

# beta をb(t) にしたうえでSEIRS
library(rstan)
rstan_options(auto_write=TRUE)
options(mc.cores=parallel::detectCores())

code <- "
data {
   int<lower=1> T; // 時間の数
   int<lower=1> nC; // コンパートメントの数
   real TS[T]; // 各時刻
   real Y0[nC]; // Y の初期値 SEIR
   real m01[2]; // mu     の範囲
   real n01[2]; // nu     の範囲
   real x01[2]; // x12    の範囲
   real x02[2]; // x21    の範囲
   real g01[2]; // gamma  の範囲
   real d01[2]; // delta1 の範囲
   real d02[2]; // delta2 の範囲
   real p01[2]; // phi    の範囲
   real f01[2]; // f      の範囲
   real R01[2]; // R0     の範囲

}
parameters {
   real<lower=m01[1], upper=m01[2]> mu;
   real<lower=n01[1], upper=n01[2]> nu;
   real<lower=x01[1], upper=x01[2]> x12;
   real<lower=x02[1], upper=x02[2]> x21;
   real<lower=g01[1], upper=g01[2]> gamma;
   real<lower=d01[1], upper=d01[2]> delta1;
   real<lower=d02[1], upper=d02[2]> delta2;
   real<lower=p01[1], upper=p01[2]> phi;
   real<lower=f01[1], upper=f01[2]> f;
   real<lower=R01[1], upper=R01[2]> R0;
}
transformed parameters {
   simplex[16] y[T];
   vector<lower=0>[8] p[T];
   real<lower=0> I1[T];
   real<lower=0> I2[T];
   y[1] = to_vector(Y0); // Initialize
   for(i in 1:T){
     p[i][1] = mu;
     p[i][2] = gamma * R0 * (f/2*cos(2*pi()/52*(TS[i]-phi)) + (1-f/2)); // beta
     p[i][3] = nu;
     p[i][4] = x12;
     p[i][5] = x21;
     p[i][6] = gamma;
     p[i][7] = delta1;
     p[i][8] = delta2;
   }
   for(i in 1:(T-1)){
     // S1S2 E1S2 I1S2 R1S2 1-4
     // S1E2 E1E2 I1E2 R1E2 5-8
     // S1I2 E1I2 I1I2 R1I2 9-12
     // S1R2 E1R2 I1R2 R1R2 13-16
     I1[i] = (y[i][3]+y[i][7] +y[i][11]+y[i][15]);
     I2[i] = (y[i][9]+y[i][10]+y[i][11]+y[i][12]);
     y[i+1][1 ] = y[i][1 ] + -p[i][2]*(I1[i]+I2[i])*y[i][1] + p[i][7]*y[i][4] + p[i][8]*y[i][13] + p[i][1] - y[i][1]*p[i][1];
     y[i+1][2 ] = y[i][2 ] + -(p[i][1]+p[i][3]+(1-p[i][4])*p[i][2]*I2[i])*y[i][2]  + p[i][2]*I1[i]*y[i][1] + p[i][8]*y[i][14];
     y[i+1][3 ] = y[i][3 ] + -(p[i][1]+p[i][6]+(1-p[i][4])*p[i][2]*I2[i])*y[i][3]  + p[i][3]*y[i][2]    + p[i][8]*y[i][15];
     y[i+1][4 ] = y[i][4 ] + -(p[i][1]+p[i][7]+(1-p[i][4])*p[i][2]*I2[i])*y[i][4]  + p[i][6]*y[i][3]    + p[i][8]*y[i][16];
     y[i+1][5 ] = y[i][5 ] + -(p[i][1]+p[i][3]+(1-p[i][5])*p[i][2]*I1[i])*y[i][5]  + p[i][2]*I2[i]*y[i][1] + p[i][7]*y[i][8];
     y[i+1][9 ] = y[i][9 ] + -(p[i][1]+p[i][6]+(1-p[i][5])*p[i][2]*I1[i])*y[i][9]  + p[i][3]*y[i][5]    + p[i][7]*y[i][12];
     y[i+1][13] = y[i][13] + -(p[i][1]+p[i][8]+(1-p[i][5])*p[i][2]*I1[i])*y[i][13] + p[i][6]*y[i][9]    + p[i][7]*y[i][16];

     y[i+1][6 ] = y[i][6 ] + -(2*p[i][3]+p[i][1])*y[i][6] + p[i][2]*((1-p[i][4])*I2[i]*y[i][2] + (1-p[i][5])*I1[i]*y[i][5]);
     y[i+1][7 ] = y[i][7 ] + -(p[i][1]+p[i][3]+p[i][6])*y[i][7]  + (1-p[i][4])*p[i][2]*I2[i]*y[i][3]  + p[i][3]*y[i][6];
     y[i+1][8 ] = y[i][8 ] + -(p[i][1]+p[i][3]+p[i][7])*y[i][8]  + (1-p[i][4])*p[i][2]*I2[i]*y[i][4]  + p[i][6]*y[i][7];
     y[i+1][10] = y[i][10] + -(p[i][1]+p[i][3]+p[i][6])*y[i][10] + (1-p[i][5])*p[i][2]*I1[i]*y[i][9]  + p[i][3]*y[i][6];
     y[i+1][14] = y[i][14] + -(p[i][1]+p[i][3]+p[i][8])*y[i][14] + (1-p[i][5])*p[i][2]*I1[i]*y[i][13] + p[i][6]*y[i][10];

     y[i+1][11] = y[i][11] + -(2*p[i][6]+p[i][1])*y[i][11] + p[i][3]*(y[i][7]+y[i][10]);
     y[i+1][12] = y[i][12] + -(p[i][1]+p[i][6]+p[i][7])*y[i][12] + p[i][3]*y[i][8]  + p[i][6]*y[i][11];
     y[i+1][15] = y[i][15] + -(p[i][1]+p[i][6]+p[i][8])*y[i][15] + p[i][3]*y[i][14] + p[i][6]*y[i][11];
     y[i+1][16] = y[i][16] + -(p[i][1]+p[i][7]+p[i][8])*y[i][16] + p[i][6]*(y[i][12]+y[i][15]);
   }
   I1[T] = (y[T][3]+y[T][7] +y[T][11]+y[T][15]);
   I2[T] = (y[T][9]+y[T][10]+y[T][11]+y[T][12]);

}
model {
   mu     ~ uniform(m01[1], m01[2]);
   nu     ~ uniform(n01[1], n01[2]);
   x12    ~ uniform(x01[1], x01[2]);
   x21    ~ uniform(x02[1], x02[2]);
   gamma  ~ uniform(g01[1], g01[2]);
   delta1 ~ uniform(d01[1], d01[2]);
   delta2 ~ uniform(d02[1], d02[2]);
   phi    ~ uniform(p01[1], p01[2]);
   f      ~ uniform(f01[1], f01[2]);
   R0     ~ uniform(R01[1], R01[2]);
}

"

m0 <- stan_model(model_code=code)
times <- 0:200
y0 <- rep(0, 16)
y0[c(2, 5)] <- c(0.002, 0.001)
y0[1] <- 1-sum(y0)
standata <- list(T=length(times), TS=times, Y0=y0, nC=length(y0),
                 m01=1/c(81, 79), n01=1/c(14, 3), x01=c(0, 1), x02=c(0, 1),
                 g01=1/c(14, 3), d01=1/c(100, 25), d02=1/c(100, 25),
                 f01=c(0.18, 0.25), p01=c(1.5, 2.4), R01=c(2, 3))

standata <- list(T=length(times), TS=times, Y0=y0, nC=length(y0),
                 m01=1/c(81, 79), n01=1/c(14, 3), x01=c(0.5, 1), x02=c(0, 0.5),
                 g01=1/c(14, 3), d01=1/c(50, 20), d02=1/c(30, 25),
                 f01=c(0.18, 0.25), p01=c(1.5, 2.4), R01=c(3, 5))

fit <- sampling(m0, standata, iter=1000, warmup=0, chain=4)
ex <- extract(fit, pars=head(fit@model_pars, -1))

alpha <- 0.05
cia <- c(alpha/2, 0.5, 1-alpha/2)
m <- abind::abind(mapply(function(z) cbind(c(0, y0[z], 0), apply(ex$y[,,z], 2, quantile, cia)), seq(y0), SIMPLIFY=FALSE), along=3)

lab <- c("S", "E", "I", "R")
labs <- c(outer(paste0(lab, 1), paste0(lab, 2), paste0))
names(y0) <- labs

# 透明にしながら実線も描けるようにする
cols256 <- list(S1S2='lightgreen', E1S2='yellow', I1S2='orange', R1S2='blueviolet',
                S1E2='yellow2', E1E2='yellow3', I1E2='orange2', R1E2='cadetblue',
                S1I2='orangered', E1I2='orangered2', I1I2='orangered3', R1I2='deepskyblue',
                S1R2='powderblue', E1R2='royalblue', I1R2='skyblue', R1R2='pink')
cols256 <- lapply(lapply(cols256, col2rgb), c)

cols <- mapply(function(z) rgb(z[1], z[2], z[3], maxColorValue=256), cols256)
colsa <- mapply(function(z) rgb(z[1], z[2], z[3], 20, maxColorValue=256), cols256)

par(mar=c(5, 5, 2, 2), cex.lab=1.5, las=1)
matplot(m[2,,], type="n", col=colsa, lty=1, lwd=3, xlab="Time (days)", ylab="Proportion of S/E/I/R patients")
#legend(0, 1, names(y0), ncol=length(y0), col=cols, pch=15, yjust=-0.1, xpd=TRUE, bty="n", cex=1.6)
legend("topright", legend=names(y0), ncol=4, col=cols, pch=15, yjust=-0.1, xpd=TRUE, bty="n", cex=1.4)
#legend(par()$usr[2], 1, names(y0), col=cols, pch=15, xpd=TRUE, bty="n", cex=1.6)
for(i in seq(dim(m)[3])){
  lines(c(0, times), m[2,,i], col=cols[i], lwd=4)
  polygon(c(c(0, times), rev(c(0, times))), c(m[1,,i], rev(m[3,,i])), col=colsa[i], border=NA)
}

###
library(googleVis)
library(stringr)
gmat <- cbind.data.frame(Time=times, head(m[2,,], -1))
colnames(gmat) <- c("Times", labs)
url <- "https://science.sciencemag.org/content/early/2020/04/14/science.abb5793"
webpage <- "Science. 2020 Apr 14. pii: eabb5793. doi: 10.1126/science.abb5793."
headertxt <- sprintf("<span><a href='%s' target='_blank'>%s</a></span> on %s", url, webpage, Sys.Date())
# googlevis 用のプロットオプション
ops <- list(
  fontSize=12, height=600, width=600, lineWidth=1,
  vAxis="{title:'Proportion', fontSize: 20, titleTextStyle: {fontSize: 20, bold: 'true', italic: 'false'}, viewWindow: {min: 0, max: 1}, gridlines: {color: '#00F', minSpacing: 100}, logScale: 'false'}",
  sizeAxis="{minValue: -80, maxValue: 75, maxSize: 15}",
  hAxis="{title:'Time', fontSize: 20, titleTextStyle: {fontSize: 20, bold: 'true', italic: 'false'}, viewWindow: {min: 0, max: 205}}",
  explorer="{actions: ['dragToZoom', 'rightClickToReset']}",
  focusTarget="category",
  selectionMode="multiple",
  legend="{position: 'right', textStyle: {color: 'black', fontSize: 10}, pageIndex: 1}"
)
gvis <- gvisLineChart(gmat, options=ops)
gvis$html$caption <- NULL
gvis$html$footer <- str_replace(gvis$html$footer,
                                "<span> \n  .+\n  .+\n</span>",
                                headertxt)
plot(gvis)




LineChartID105e2da79c06






Science. 2020 Apr 14. pii: eabb5793. doi: 10.1126/science.abb5793. on 2020-04-23

新型肺炎COVID-19の感染推移を時間依存パラメータを含むSEIRSモデルで推定しようと思って断念していたが出来るかもしれない

諦めていた。
mikuhatsune.hatenadiary.com
というのも、SEIRSモデルでS→Eになるパラメータ\beta が、時間依存的なパラメータとして
\beta(t)=\gamma R_0 \left[\frac{f}{2}\cos(\frac{2\pi}{52}(t-\phi))-(1-\frac{f}{2})\right]
で定義されるが、これはrstanではできなさそうである。というのが、integrate_ode関数が時間依存のパラメータをどう頑張っても取れないみたいだからである。
ODEs with time dependent parameters - Modeling - The Stan Forums

というわけでstanの神が助言してくれた。


S/E/I/R の各コンパートメントは、ある時刻t においてすべて足せば和は1になるので、simplexを必要な時刻数分だけリストか何かで持っておいて、transformed parametersブロック内でぶん回せばよさそうである。
まずは普通に\frac{dy}{dt}を地味に計算してSEIRモデルをシミュレーションした。
f:id:MikuHatsune:20200421183612p:plain

さて、rstan でtransformed parametersブロックにsimplexを各時刻に定義してリストとして持ち、上記式がf\sim U(0.18, 0.25), \phi\sim U(1.5, 2), R_0\sim U(2, 3) でサンプリングされて\beta(t)が決まる、としてコードを実行した。
推定区間がついたSEIRの推移が推定できた。
f:id:MikuHatsune:20200421183631p:plain

times <- 0:200
f <- 0.21
phi <- 2
R0 <- 2.2
Rt <- R0*(f/2*cos(2*pi/52*(times-phi)) + (1-f/2))
plot(times, Rt)

g <- 1.2    # gamma
d <- 0.3    # delta
b <- g * Rt # beta
e <- 0.4   # eta

y0 <- c(0.999, 0.001, 0, 0)
names(y0) <- c("S", "E", "I", "R")
res <- rbind.data.frame(y0, matrix(0, nr=length(times)-1, nc=4))
colnames(res) <- c("S", "E", "I", "R")
for(i in 2:length(times)){
  dS <- -b[i-1]*res$S[i-1]*res$I[i-1] + e*res$R[i-1]
  dE <-  b[i-1]*res$S[i-1]*res$I[i-1] - d*res$E[i-1]
  dI <-  d*res$E[i-1] - g*res$I[i-1]
  dR <-  g*res$I[i-1] - e*res$R[i-1]
  res[i, ] <- res[i-1,] + c(dS, dE, dI, dR)
}
cols <- c("black", "orange", "red", "lightgreen")
matplot(res, type="l", lty=1, lwd=3, col=cols, xlab="Time", ylab="Proportion")
legend("topright", legend=colnames(res), pch=15, col=cols)

# simplex でやってみる
code <- "
data {
   int<lower=1> T; // 時間の数
   real TS[T]; // 各時刻
   real Y0[4]; // Y の初期値 SEIR
   real b[T];
   real d;
   real g;
   real e;
}
parameters {
  real a; // dummy
}
transformed parameters {
   simplex[4] y_[T];
   y_[1] = to_vector(Y0);
   for(i in 2:T){
     y_[i][1] = y_[i-1][1] + -b[i-1]*y_[i-1][1]*y_[i-1][3] + e*y_[i-1][4];
     y_[i][2] = y_[i-1][2] +  b[i-1]*y_[i-1][1]*y_[i-1][3] - d*y_[i-1][2];
     y_[i][3] = y_[i-1][3] +  d*y_[i-1][2] - g*y_[i-1][3];
     y_[i][4] = y_[i-1][4] +  g*y_[i-1][3] - e*y_[i-1][4];
   }
}
model {
  a ~ uniform(0, 1); // 何もしない
}
"
m0 <- stan_model(model_code=code)
standata <- list(T=length(times), TS=times, Y0=y0, b=b, d=d, g=g, e=e)
fit <- sampling(m0, standata, iter=200, warmup=100, chain=2)
ex <- extract(fit, pars=head(fit@model_pars, -1))

# beta をb(t) にする
code <- "
data {
   int<lower=1> T; // 時間の数
   real TS[T]; // 各時刻
   real Y0[4]; // Y の初期値 SEIR
   // real b[T];
   real d;
   real g;
   real e;
   //real b01[2]; // beta の範囲
   real p01[2]; // phi  の範囲
   real f01[2]; // f の範囲
   real R01[2]; // R0 の範囲
}
parameters {
   real<lower=p01[1], upper=p01[2]> phi;
   real<lower=f01[1], upper=f01[2]> f;
   real<lower=R01[1], upper=R01[2]> R0;
}
transformed parameters {
   simplex[4] y_[T];
   real<lower=0> b[T];
   y_[1] = to_vector(Y0);
   for(i in 1:T){
     b[i] = g * R0 * (f/2*cos(2*pi()/52*(TS[i]-phi)) + (1-f/2));
   }
   // b = g * Rt;
   for(i in 2:T){
     y_[i][1] = y_[i-1][1] + -b[i-1]*y_[i-1][1]*y_[i-1][3] + e*y_[i-1][4];
     y_[i][2] = y_[i-1][2] +  b[i-1]*y_[i-1][1]*y_[i-1][3] - d*y_[i-1][2];
     y_[i][3] = y_[i-1][3] +  d*y_[i-1][2] - g*y_[i-1][3];
     y_[i][4] = y_[i-1][4] +  g*y_[i-1][3] - e*y_[i-1][4];
   }
}
model {
   f   ~ uniform(f01[1], f01[2]);
   phi ~ uniform(p01[1], p01[2]);
   R0  ~ uniform(R01[1], R01[2]);
}
"

m0 <- stan_model(model_code=code)
standata <- list(T=length(times), TS=times, Y0=c(0.999, 0.001, 0, 0), d=d, g=g, e=e,
                 f01=c(0.18, 0.25), p01=c(1.5, 2.4), R01=c(2, 3))
fit <- sampling(m0, standata, iter=1000, warmup=500, chain=4)
ex <- extract(fit, pars=head(fit@model_pars, -1))

alpha <- 0.05
cia <- c(alpha/2, 0.5, 1-alpha/2)
m <- abind::abind(mapply(function(z) cbind(c(0, y0[z], 0), apply(ex$y_[,,z], 2, quantile, cia)), seq(y0), SIMPLIFY=FALSE), along=3)

parms <- apply(ex$b, 2, quantile, cia)

lab <- c("S", "E", "I", "R")
# 透明にしながら実線も描けるようにする
cols256 <- list(S=c(0, 255, 0), E=c(255, 202, 5), I=c(255, 0, 0), R=c(0, 255, 255))
cols <- mapply(function(z) rgb(z[1], z[2], z[3], maxColorValue=256), cols256)
colsa <- mapply(function(z) rgb(z[1], z[2], z[3], 20, maxColorValue=256), cols256)

par(mar=c(5, 5, 3, 5), cex.lab=1.5, las=1)
matplot(m[2,,], type="n", col=colsa, lty=1, lwd=3, xlab="Time (days)", ylab="Proportion of S/E/I/R patients")
#legend(0, N, names(y0), ncol=length(y0), col=cols, pch=15, yjust=-0.1, xpd=TRUE, bty="n", cex=2)
legend(par()$usr[2], 1, names(y0), col=cols, pch=15, xpd=TRUE, bty="n", cex=2)
for(i in seq(dim(m)[3])){
  lines(c(0, times), m[2,,i], col=cols[i], lwd=4)
  polygon(c(c(0, times), rev(c(0, times))), c(m[1,,i], rev(m[3,,i])), col=colsa[i], border=NA)

}

新型肺炎COVID-19が今後2022年まで流行が続くというのをrstanで再現しようとしたが断念した

読んだ。
Projecting the transmission dynamics of SARS-CoV-2 through the postpandemic period. - PubMed - NCBI
COVID-19がこのままだと2022年も続いているのではないか、といろいろ話題になった論文。
githubがおいてあるが、ここにあるデータは
GitHub - c2-d2/CoV-seasonality: Code for regression model from Kissler, Tedijanto et al. 2020.

コロナウイルスの流行具合をCDCのNREVSSというデータベースから2015-2019年の間までデータを使った、というが、肝心のデータは2018年4月以降しかもう置いてないっぽい。
NREVSS | Coronavirus National Trends | CDC
https://www.cdc.gov/surveillance/nrevss/images/coronavirus/Corona4PP_Nat.htm
こちらの過去の報告の図からデータを引っこ抜ければ、2014-2017年はデータが取れるが、めんどうなのでやめた。そもそも再現できないじゃん…
Human coronavirus circulation in the United States 2014-2017. - PubMed - NCBI

コロナウイルスの検査数と、インフルエンザ様症状(ILI)の受診割合でかけることで、週ごとの(\beta型)コロナウイルスの頻度、としている。日ごとのR_u

R_u=\displaystyle\sum_{t=u}^{u+i_{max}}\frac{b(t)g(t-u)}{\displaystyle\sum_{a=0}^{i_{max}}b(t-a)g(a)}
b(t):時刻tの株(strain)ごとの発生率
g(a):時刻a のgeneration interval。論文では平均8.4日、標準偏差3.8日のweibull 分布を使っているが、githubでは他の報告の対数正規分布やガンマ分布も使っている。
i_{max}:generation interval が累積確率分布で99%になる日。論文では19日としている。
github ではfunc.WTという関数で定義されているが、各株のデータのはいっているdata.frameを入力するようなので、vectorを入力するような感じに書き換えた。

func.SI_pull <- function(value, serial_int){
  if(serial_int == "SARS"){
    return(dweibull(value, shape=SARS_shape, scale=SARS_scale))
  } else if(serial_int == "nCoV_Li"){
    return(dgamma(value, shape=nCoV_Li_shape, scale=nCoV_Li_scale))
  } else if(serial_int == "nCoV_Nishiura"){
    return(dweibull(value, shape=nCoV_Nishiura_shape, scale=nCoV_Nishiura_scale))
  }
}
func.WT <- function(week_list, daily_inc, proxy_name, org_list, serial_int){
  df.Reff_results <- data.frame(Week_start=week_list) # Initialize dataframe to hold results
  stop <- get(paste0(serial_int, "_stop")) 
  # Estimate daily R effective
  for(v in org_list){ #For each strain
    RDaily <- numeric()
    for(u in 1:(length(week_list)*7)){ #Estimate for each day
      sumt <- 0
      for(t in u:(u+stop)){ #Look ahead starting at day u through (u+max SI)
        suma <- 0 
        for(a in 0:(stop)){ #Calc denominator, from day t back through (t-max SI)
          suma = daily_inc[t-a,v]*func.SI_pull(a, serial_int) + suma
        } 
        sumt = (daily_inc[t,v]*func.SI_pull(t-u, serial_int))/suma + sumt
      }
      RDaily[u] = sumt
    }   
    # Take geometric mean over daily values for current week, and one week before + after (for smoothing), to get weekly estimates
    RWeekly <- numeric()
    for(i in 1:length(week_list)) RWeekly[i] <- geoMean(as.numeric(RDaily[max(1,7*(i-2)+1):min(length(RDaily),7*(i+1))]), na.rm=TRUE)
    RWeekly[1:3] <- NA #Set results for first 3 weeks to NA (unreliable using WT)
    RWeekly[(length(RWeekly)-2):length(RWeekly)] <- NA #Set results for last 3 weeks to NA (unreliable using WT)
    df.Reff_results[,paste0(v,"_R_",proxy_name)] <- RWeekly
  } 
  return(df.Reff_results)
}
Ru <- function(vec, pdf="weibull", pars=c(2.35, 9.48), alpha=0.99){
  Imax <- eval(parse(text=sprintf("ceiling(q%s(%f, %f, %f))", pdf, alpha, pars[1], pars[2])))
  g <- function(x) eval(parse(text=sprintf("d%s(%f, %f, %f)", pdf, x, pars[1], pars[2])))
  res <- mapply(function(u)  sum(
         mapply(function(t) (vec[t]*g(t-u))/sum(mapply(function(a) vec[t-a]*g(a), 0:Imax)), u:(u+Imax))
         ),  (Imax+1):(length(RDaily)-Imax))
  NAs <- rep(NA, Imax)
  RDaily <- c(NAs, res, NAs)
  RWeekly <- mapply(function(i) geoMean(RDaily[max(1,7*(i-2)+1):min(length(RDaily),7*(i+1))], na.rm=TRUE), 1:(ceiling(length(RDaily)/7))
  RWeekly[1:3] <- RWeekly[(length(RWeekly)-2):length(RWeekly)] <- NA
  retuen(list(RDaily, RWeekly))
}

感染の流行はtwo-strain SEIRS モデルというものを使っている。要は図のSEIRS x two strain の16コンポーネント微分方程式になるのだが、解けるわけがないので前にやったとおりrstanで推定してみる。
mikuhatsune.hatenadiary.com
16コンポーネントはこんな感じになる。
f:id:MikuHatsune:20200419224151p:plain

さて、このSEIRSモデルに\mu, \beta(t), \nu, \chi_{12}, \chi_{21}, \gamma, \delta_1, \delta_2 の8つのパラメータが設定してある。ここで、\beta(t) だけが時間依存のパラメータで、再生産係数R_0 に対して
\beta(t)=\gamma R_0(t)
R_0=\max(R_0)\left[\frac{f}{2}\cos(\frac{2\pi}{52}(t-\phi))+(1-\frac{f}{2})\right]
と、R_0(t)に依存することになる。
これをrstanで頑張ってみようと思ったが、rstanで微分方程式を作ったときに、パラメータは各時刻tですべて一定でしか(自分の力量では)出来ないので、今回の再現では\betaは一様分布からサンプリングされる決め打ちのシミュレーションになってしまった。誰か時間依存\beta(t) のままやれる方法を教えてほしい。f:id:MikuHatsune:20200419224943p:plain
16コンパートメントもあるとゴチャゴチャするので、googlevisも作ってみた。

(htmlが破壊されてレイアウトが変になったので削除しました)


うまく再現できなかったがこれも引っ張り過ぎると他のことが進まなくなるのでここらへんで終了。

# SEIRS two-strain 16 compornents
lab <- c("S", "E", "I", "R")
labs <- c(outer(paste0(lab, 1), paste0(lab, 2), paste0))
library(igraph)
mat <- as.matrix(read.table(text="
0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0
0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0
0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0
0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0
0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0
0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1
1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0
0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0
0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1
0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0
", header=FALSE))
rownames(mat) <- colnames(mat) <- labs
g <- graph_from_adjacency_matrix(mat)

rot <- -pi/4
lay <- cbind(rep(c(-3,-1,1,3), each=4), rep(c(3,1,-1,-3), 4))
lay <- t(matrix(c(cos(rot), sin(rot), -sin(rot), cos(rot)), 2) %*% t(lay))


par(mar=rep(0.5, 4))
V(g)$label.cex <- 1.5
V(g)$label.font <- 2
V(g)$size <- 25
E(g)$curved <- 0 #0.05
E(g)$curved[c(7, 25, 31:32)] <- c(1, -1) #0.05
E(g)$curved[c(15, 27, 23, 29)] <- c(1, -1)*0.7 #0.05
plot(g, layout=lay)

# SEIRS two strain
library(rstan)
rstan_options(auto_write=TRUE)
options(mc.cores=parallel::detectCores())

code <- "
functions {
   real[] sir(
      real t,
      real[] y,
      real[] p, // mu beta nu x12 x21 gamma delta1 delta2
      real[] x_r,
      int[] x_i
   ) {
     // S1S2 E1S2 I1S2 R1S2 1-4
     // S1E2 E1E2 I1E2 R1E2 5-8
     // S1I2 E1I2 I1I2 R1I2 9-12
     // S1R2 E1R2 I1R2 R1R2 13-16
      real dydt[16];
      dydt[1] = -p[1]*y[1]*y[3]-p[2]*y[1]*(y[3]+y[7]+y[11]+y[15])-p[2]*y[1]*(y[9]+y[10]+y[11]+y[12])+p[7]*y[4]+p[8]*y[13];
      dydt[2] = p[1]*y[1]*y[3]-p[1]*y[2]-p[3]*y[2]-(1-p[4])*p[2]*y[2]*(y[9]+y[10]+y[11]+y[12])+p[8]*y[14];
      dydt[3] = p[3]*y[2]-p[6]*y[3]-p[1]*y[3]-(1-p[4])*y[3]*(y[9]+y[10]+y[11]+y[12])+p[8]*y[15];
      dydt[4] = -(p[1]+p[7]+(1-p[4])*(y[9]+y[10]+y[11]+y[12]))*y[4]+p[6]*y[3]+p[8]*y[16];
      dydt[5] = -(p[1]+p[3]+(1-p[5])*p[2]*(y[3]+y[7]+y[11]+y[15]))*y[5]+p[2]*(y[9]+y[10]+y[11]+y[12])*y[1]+p[7]*y[8];
      dydt[6] = -(p[1]+2*p[3])*y[6]+p[2]*((1-p[4])*(y[9]+y[10]+y[11]+y[12])*y[2]+(1-p[5])*(y[3]+y[7]+y[11]+y[15])*y[5]);
      dydt[7] = -(p[1]+p[3]+p[6])*y[7]+p[3]*y[6]+(1-p[4])*p[2]*(y[9]+y[10]+y[11]+y[12])*y[3];
      dydt[8] = -(p[1]+p[3]+p[7])*y[8]+p[6]*y[7]+(1-p[5])*p[2]*(y[9]+y[10]+y[11]+y[12])*y[4];
      dydt[9] = -(p[1]+p[6]+(1-p[6])*p[2]*(y[3]+y[7]+y[11]+y[15]))*y[9]+p[7]*y[12]+p[3]*y[5];
      dydt[10]= -(p[1]+p[3]+p[6])*y[7]+p[3]*y[6]+(1-p[5])*p[2]*(y[3]+y[7]+y[11]+y[15])*y[9];
      dydt[11]= -(p[1]+2*p[6])*y[11]+p[3]*(y[7]+y[10]);
      dydt[12]= -(p[1]+p[6]+p[7])*y[12]+p[3]*y[8]+p[6]*y[11];
      dydt[13]= -(p[1]+p[8]+(1-p[6])*p[2]*(y[3]+y[7]+y[11]+y[15]))*y[13]+p[6]*y[9]+p[7]*y[16];
      dydt[14]= -(p[1]+p[3]+p[8])*y[14]+p[6]*y[10]+(1-p[6])*p[2]*(y[3]+y[7]+y[11]+y[15])*y[13];
      dydt[15]= -(p[1]+p[6]+p[8])*y[15]+p[3]*y[14]+p[6]*y[11];
      dydt[16]= -(p[1]+p[7]+p[8])*y[16]+p[6]*(y[12]+y[15]);
      return dydt;
   }
}
data {
   int<lower=1> T; // 時間の数
   real T0; // time 0
   real TS[T]; // 各時刻
   real Y0[16]; // Y の初期値 SEIR
   real m01[2]; // mu   の範囲
   real b01[2]; // beta の範囲
   real n01[2]; // nu   の範囲
   real x12[2]; // x12  の範囲
   real x21[2]; // x21  の範囲
   real g01[2]; // gamma  の範囲
   real d1[2]; // delta1 の範囲
   real d2[2]; // delta2 の範囲
}
transformed data {
   real x_r[0];
   int x_i[0];
}
parameters {
   real<lower=m01[1], upper=m01[2]> mu;
   real<lower=b01[1], upper=b01[2]> beta;
   real<lower=n01[1], upper=n01[2]> nu;
   real<lower=x12[1], upper=x12[2]> X12;
   real<lower=x21[1], upper=x21[2]> X21;
   real<lower=g01[1], upper=g01[2]> gamma;
   real<lower=d1[1], upper=d1[2]> delta1;
   real<lower=d2[1], upper=d2[2]> delta2;
}
transformed parameters {
   real y_hat[T,16];
   real theta[8];
   theta[1] = mu;
   theta[2] = beta;
   theta[3] = nu;
   theta[4] = X12;
   theta[5] = X21;
   theta[6] = gamma;
   theta[7] = delta1;
   theta[8] = delta2;
   y_hat = integrate_ode(sir, Y0, T0, TS, theta, x_r, x_i);
}
model {
  // 何もしない
}
"

m0 <- stan_model(model_code=code)
y0 <-  replace(rep(0, 16), 1, 0) # 初期値
y0[c(3, 7, 11, 15, 9, 10, 12)] <- 0.0005
y0 <- replace(y0, 1, 1-sum(y0))
times <- seq(0, 100, by=1)[-1] # 時刻 0.2 刻みはどういうこと?

# 各パラメータを振る範囲
lim <- list(m01=1/c(81, 79), b01=1/c(14, 1), n01=1/c(14, 3), x12=c(0, 1), x21=c(0, 1), g01=1/c(14, 3), d1=1/c(100, 25), d2=1/c(100, 25))

standata <- c(list(T=length(times), T0=0, TS=times, Y0=y0), lim)
fit <- sampling(m0, standata, iter=200, warmup=100, chain=2)
ex <- extract(fit, pars=head(fit@model_pars, -1))

cols <- matlab::jet.colors(length(labs))
i <- 23 # たまたまこのシミュレーションが見栄えがよかったので
matplot(ex$y_hat[i,,], type="l", lty=1, col=cols, lwd=2, xlab="time", ylab="proportion")
legend("topright", legend=labs, ncol=4, col=cols, pch=15, bg="white")

# googlevis で可視化
library(googleVis)
library(stringr)
gmat <- cbind.data.frame(Time=times, ex$y_hat[i,,])
colnames(gmat) <- c("Times", labs)
url <- "https://science.sciencemag.org/content/early/2020/04/14/science.abb5793"
webpage <- "Science. 2020 Apr 14. pii: eabb5793. doi: 10.1126/science.abb5793."
headertxt <- sprintf("<span><a href='%s' target='_blank'>%s</a></span> on %s", url, webpage, Sys.Date())
# googlevis 用のプロットオプション
ops <- list(
  fontSize=12, height=600, width=600, lineWidth=1,
  vAxis="{title:'Proportion', fontSize: 20, titleTextStyle: {fontSize: 20, bold: 'true', italic: 'false'}, viewWindow: {min: 0, max: 1}, gridlines: {color: '#00F', minSpacing: 100}, logScale: 'false'}",
  sizeAxis="{minValue: -80, maxValue: 75, maxSize: 15}",
  hAxis="{title:'Time', fontSize: 20, titleTextStyle: {fontSize: 20, bold: 'true', italic: 'false'}, viewWindow: {min: 0, max: 105}}",
  explorer="{actions: ['dragToZoom', 'rightClickToReset']}",
  focusTarget="category",
  selectionMode="multiple",
  legend="{position: 'right', textStyle: {color: 'black', fontSize: 10}, pageIndex: 1}"
)
gvis <- gvisLineChart(gmat, options=ops)
gvis$html$caption <- NULL
gvis$html$footer <- str_replace(gvis$html$footer,
                                "<span> \n  .+\n  .+\n</span>",
                                headertxt)
plot(gvis)