生物医学网络科学Biomedical Network Science
Example10 / 14

从发病曲线估计时变再生数 R_t(Cori 方法)

所属专题
网络上的传播与免疫
下载
Notebook · .py

先用已知的 R_t(第 40 天因干预从 2.0 降到 0.7)和代际间隔分布 w_s 生成一条发病曲线(更新方程 + 泊松噪声), 再用 Cori 等 2013 的贝叶斯估计:在长度为 τ 的滑动窗口内,R_t 的后验为 Gamma 分布, 形状 = a + Σ I_s, 速率 = 1/b + Σ Λ_s, Λ_s = Σ_{u≥1} w_u I_{s-u}(总感染力)。 解读:估计值在约一个窗口长度的滞后后跟上真实 R_t 的下降;早期病例少时可信区间很宽。

Output输出图

从发病曲线估计时变再生数 R_t(Cori 方法):输出图 1

Output文字输出

第 10 天  真实 R = 2.0  估计 R_t = 2.03  95%CrI [1.53, 2.60]  当日病例 10
第 18 天  真实 R = 2.0  估计 R_t = 1.97  95%CrI [1.68, 2.28]  当日病例 34
第 26 天  真实 R = 2.0  估计 R_t = 2.04  95%CrI [1.88, 2.21]  当日病例 131
第 34 天  真实 R = 2.0  估计 R_t = 1.98  95%CrI [1.90, 2.07]  当日病例 441
第 42 天  真实 R = 0.7  估计 R_t = 1.32  95%CrI [1.28, 1.35]  当日病例 439
第 50 天  真实 R = 0.7  估计 R_t = 0.67  95%CrI [0.65, 0.70]  当日病例 248
第 58 天  真实 R = 0.7  估计 R_t = 0.71  95%CrI [0.67, 0.75]  当日病例 153
第 66 天  真实 R = 0.7  估计 R_t = 0.71  95%CrI [0.66, 0.76]  当日病例 78
第 74 天  真实 R = 0.7  估计 R_t = 0.75  95%CrI [0.68, 0.81]  当日病例 53
第 82 天  真实 R = 0.7  估计 R_t = 0.73  95%CrI [0.65, 0.82]  当日病例 27

Code代码

# 先用已知的 R_t(第 40 天因干预从 2.0 降到 0.7)和代际间隔分布 w_s 生成一条发病曲线(更新方程 + 泊松噪声),
# 再用 Cori 等 2013 的贝叶斯估计:在长度为 τ 的滑动窗口内,R_t 的后验为 Gamma 分布,
#   形状 = a + Σ I_s,  速率 = 1/b + Σ Λ_s,  Λ_s = Σ_{u≥1} w_u I_{s-u}(总感染力)。
# 解读:估计值在约一个窗口长度的滞后后跟上真实 R_t 的下降;早期病例少时可信区间很宽。
import numpy as np
from scipy.stats import gamma as gamma_dist

rng = np.random.default_rng(3)
T = 90
s = np.arange(1, 21)
w = gamma_dist.pdf(s, a=2.5, scale=2.0); w /= w.sum()      # 代际间隔:均值约 5 天
R_true = np.where(np.arange(T) < 40, 2.0, 0.7)
I = np.zeros(T); I[0] = 10
for t in range(1, T):
    lam = sum(w[u - 1] * I[t - u] for u in range(1, min(t, 20) + 1))
    I[t] = rng.poisson(R_true[t] * lam)

tau, a_prior, b_prior = 7, 1.0, 5.0
Lam = np.array([sum(w[u - 1] * I[t - u] for u in range(1, min(t, 20) + 1)) for t in range(T)])
for t in range(10, T, 8):
    shape = a_prior + I[t - tau + 1:t + 1].sum()
    rate = 1 / b_prior + Lam[t - tau + 1:t + 1].sum()
    lo, hi = gamma_dist.ppf([0.025, 0.975], shape, scale=1 / rate)
    print("第 %2d 天  真实 R = %.1f  估计 R_t = %.2f  95%%CrI [%.2f, %.2f]  当日病例 %d"
          % (t, R_true[t], shape / rate, lo, hi, I[t]))

import matplotlib.pyplot as plt
ts = np.arange(tau, T)
sh = np.array([a_prior + I[t - tau + 1:t + 1].sum() for t in ts])
rt = np.array([1 / b_prior + Lam[t - tau + 1:t + 1].sum() for t in ts])
lo, hi = gamma_dist.ppf(0.025, sh, scale=1 / rt), gamma_dist.ppf(0.975, sh, scale=1 / rt)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
ax1.bar(np.arange(T), I, color="0.6", width=1.0); ax1.axvline(40, ls="--", c="tab:red")
ax1.set_xlabel("day"); ax1.set_ylabel("daily cases"); ax1.set_title("Simulated epidemic curve")
ax2.fill_between(ts, lo, hi, color="tab:blue", alpha=0.25, label="95% CrI")
ax2.plot(ts, sh / rt, c="tab:blue", label="Cori estimate"); ax2.plot(np.arange(T), R_true, "k--", label="true R_t")
ax2.axhline(1, c="0.7", lw=0.8); ax2.set_ylim(0, 4)
ax2.set_xlabel("day"); ax2.set_ylabel("R_t"); ax2.legend(); ax2.set_title("Estimate lags the drop by ~one window")
plt.tight_layout(); plt.show()

Run在浏览器中运行

首次加载 Python 环境约需 20–40 秒;可以修改参数后重新运行。

从发病曲线估计时变再生数 R_t(Cori 方法)