以下では、コロナウィルスのニュースやツイートに出てくる数値を元に、感染予防対策のモデルを作成し、どのような施策がどういう結果をもたらすのかを見ていくことにする。
最初はウィルスに感染しているかどうかを判定する PCR 検査についてモデルを作っていく。
検査を実施した場合、被験者の実際の感染有無と検査の判定の組み合わせで以下の 4 つのグループに分かれる。なお、「陽性」とは感染しているもの、「陰性」とは感染していないもののことを意味する。
- | 検査の結果が陽性 | 検査の結果が陰性 |
---|---|---|
被験者は感染 | a | b |
被験者は未感染 | c | d |
このとき、
- 「感度」とは 感染している人が陽性として検出される割合 (
a / (a + b)
) - 「特異度」とは 感染していない人が陰性として検出される割合 (
d / (c + d)
) - 「偽陰性」とは 感染している人が陰性として検出される割合 (
b / (a + b)
) - 「偽陽性」とは 感染していない人が陽性として検出される割合 (
c / (c + d)
)
となる。
- PCR 検査の精度は感度 0.4 、特異度 0.9 と言われている(twitter より)
- また、 こちら の記事では感度が 0.3 〜 0.5 あるいは 0.7 となっている
- 鼻から採取した検体で検査すると感度が向上するらしいが、医療者が自身のリスクをとって鼻よりも感度の下がる喉から採取した検体を使う傾向があり、検査の感度が下がる傾向にあるとのこと
- また、感度を上げると特異度が下がるという性質があるらしい(参考)
- PCR検査が容易になると医療者が接する人の数が増えて、感染を避けるために鼻でなく喉でやるようなケースが増えるかもしれない
- あるいは医療関係者が感染して、検査を継続できなくなるケースも発生しうる
ここでは上記の条件に基づき、検査のシミュレーションをおこなう。
まずは確率を表すクラスを作る。
data class RationalProbability(
val numerator: Int,
val denominator: Int
) {
fun inverse(): RationalProbability =
RationalProbability(denominator - numerator, denominator)
fun runTest(): Boolean =
ThreadLocalRandom.current().nextInt(denominator) < numerator
companion object {
fun create(num: Int, denom: Int): RationalProbability? =
if (denom < num) null
else if (num < 0 || denom < 0) null
else RationalProbability(num, denom)
}
}
Double
などの値を用いても良さそうではあるが、感度 <--> 偽陽性の変換を安全に行える(inverse
)ように有理数で表現する。
実際にテストで検出されるかどうかをシミュレートするための関数である runTest
関数を設けている。
なお、このクラスはコンパニオンオブジェクトにファクトリーメソッドを用意して、不適切な値(numerator
が denomenator
より大きい等)になるのを防ぐ。
PCR 検査を表すクラスを作っていく。
先程記したとおり、検査には感度と特異度という 2 つのパラメーターがあるので、それをそのまま先程の確率クラスで表現する。
なお、感度を上げると特異度が下がるという性質があるらしいが、その関係性についてはここでは所与のものとして扱う(関心はない)。
data class PcrTest (
val sensitivity: RationalProbability,
val specificity: RationalProbability
) {
companion object {
fun create(sensitivity: RationalProbability?, specificity: RationalProbability?): PcrTest? =
if (sensitivity != null && specificity != null) PcrTest(sensitivity, specificity)
else null
}
}
こちらにもファクトリーメソッドを用意した。これは Probability
の計算失敗を吸収するようになっている。
詳しいレポート - https://note.com/chugaiigaku/n/n8583a93b5a80