Up

PCR検査結果の統計学的解釈

母集団罹患率の推定

岡本安晴

 

注意: Mcrosoft Edgeによる表示が乱れた場合は、履歴のキャッシュをクリアしてから、再表示して下さい。

新型コロナウィルスワクチンの効果のベイズ分析を試みたウェブサイトも用意しました。

 

新型コロナウィルスのPCR検査で「陰性です(検出されませんでした)」という結果が出たときに、実は感染しているという確率はどれくらいであるのか、鼻咽頭ぬぐい液と唾液の場合について、感染者の比率の関数として計算してみた。「検査結果が陰性である」ときに「実は感染している」という確率は、検査対象母集団における感染者の比率によって大きく変わる。検査対象母集団における感染者の比率(罹患率)については、本ウェブサイトの後半で扱っている。

検査結果が「陰性」であるときの感染確率は条件付確率として次式(岡本安晴「心理学データ分析と測定」勁草書房、第12.3節「診断検査」参照)で計算できる。

確率は感度(sensitivity)と呼ばれ、確率は特異度(specificity)と呼ばれている。PCR検査におけるこれらの値を、Yokotaら(2020に報告されているものとした。

感染率の関数として確率のグラフを鼻咽頭ぬぐい液の場合について描くと図1のようになる。プログラムは、リスト1に示したものである。

図1 鼻咽頭ぬぐい液

 

感染率が上がると、確率も上がることが分かる。すなわち、「陰性」の結果が出ても、感染している確率が高くなる。感染率が100%で全員が感染しているときは、検査結果が陰性の人(確率は、1―感度)も全員感染している。感染率が0%のときは、検査結果が陰性の人(確率は、特異度)も全員感染していない。

感染率の具体例として以下の場合を考えてみた。ただし、感染率は大まかな推定である。

ダイアモンドプリンセス号の場合

国立感染症研究所の「現場からの概況:ダイアモンドプリンセス号におけるCOVID-19症例」(2020219日掲載)によれば、乗船者3711人に対して感染確定者(検査陽性者)531人となっている。感染率を

とおいて、確率を求めると

となる。検査で「陰性」と出ても、50人に1人は感染者であるということになる。

東京都の場合

感染者数は、

https://news.yahoo.co.jp/pages/article/covid19tokyo

に掲載されている202134日のJX通信社/FASTALERT提供のデータでは、2939人である。東京都の人口は、東京都のHPによれば令和3年現在13960236人である。感染率を

とおいて、確率を求めると

となる。検査で「陰性」と出た場合、33333人に1人は感染者であるということになる。

 

感染率の関数として確率のグラフを唾液(saliva)の場合について描くと図2のようになる。プログラムは、リスト2に示したものである。

図2 唾液

 

感染率が上がると、確率も上がることが分かる。すなわち、「陰性」の結果が出ても、感染している確率が高くなる。

感染率の具体例として以下の場合を考えてみた。ただし、感染率は大まかな推定である。

ダイアモンドプリンセス号の場合

国立感染症研究所の「現場からの概況:ダイアモンドプリンセス号におけるCOVID-19症例」(2020219日掲載)によれば、乗船者3711人に対して感染確定者(検査陽性者)531人となっている。感染率を

とおいて、確率を求めると

となる。検査で「陰性」と出ても、77人に1人は感染者であるということになる。

東京都の場合

感染者数は、

https://news.yahoo.co.jp/pages/article/covid19tokyo

に掲載されている202134日のJX通信社/FASTALERT提供のデータでは、2939人である。東京都の人口は、東京都のHPによれば令和3年現在13960236人である。

を感染率を

とおいて、確率を求めると

となる。検査で「陰性」と出た場合、62500人に1人は感染者であるということになる。

 

 

鼻咽頭ぬぐい液(NPS)と唾液(saliva)の比較を容易にするため、図1と図2のグラフを同じグラフ面上に描いてみた。リスト1とリスト2のプログラムを繋いだものをまとめてリスト3として実行すると図3のグラフが得られる。

図3 鼻咽頭ぬぐい液と唾液の比較

 

唾液の方が感染率P(infected)全体に亘って確率P(infected|neg.)の値が小さいことが分かる。

感染率の低い範囲0〜0.1についてグラフをリスト4のプログラムで描くと図4を得る。

図4 感染率0〜1%における比較

 

唾液の方が確率P(infected|Neg.)の値が小さい。「陰性」の結果が得られたときは、「唾液」による方法の方が安心である。

 

 

引用参考文献

Yokota, I., Shane, P. Y., Okada, K, Uroki, Y., Yang, Y., Inao, T., Sakamaki, K., Iwasaki, S., Hayasaka, K., Sugita, J., Nishida, M., Fujisawa, S., & Teshima, T. Mass screening of asymptomatic persons for SARS-Cov-2 using saliva. Clinical Infectious Diseases, 2020.9.25, doi: 10.1093/cid/claa1388.

岡本安晴(2014) 心理学データ分析と測定.勁草書房

岡本安晴(2019) いまさら聞けないPythonでデータ分析.丸善出版

 

 

リスト1 鼻咽頭ぬぐい液(nasopharyngeal swabs: NPSPCR検査のP(infected|negative)の計算

 

import numpy as np

import matplotlib.pyplot as plt

 

class Infected_Neg:

    """

           For calculation of P(infected|negative)

    """

    def __init__(self, sensitivity, specificity):

        self.Pr_pos_infctd = sensitivity                    #   P(positive|infected)

        self.Pr_neg_noninfctd = specificity                 #   P(negative|non-infected)

        self.Pr_neg_infctd = 1.0 - self.Pr_pos_infctd       #   P(negative|infected)

        self.Pr_pos_noninfctd = 1.0 - self.Pr_neg_noninfctd #   P(positive|non-infected)

    def value(self, Pr_infctd):

        """

                P(infected|negative) for P(infected)

        """

        return self.Pr_neg_infctd * Pr_infctd / \

            (self.Pr_neg_infctd * Pr_infctd + self.Pr_neg_noninfctd * (1.0 - Pr_infctd))

 

Pr_infctd = np.linspace(0, 1, 1000)

#

#           P(infected|negative) as the function of P(infected)

#               for nasopharyngeal swabs: NPSPCR test

#

Pr_infctd_neg = Infected_Neg(sensitivity = 0.86, specificity = 0.9993).value

Pr = [Pr_infctd_neg(v) for v in Pr_infctd]

plt.plot(Pr_infctd, Pr)

plt.xlabel('P(infected)', fontsize = 14)

plt.ylabel('P(infected|neg.)', fontsize = 14)

plt.title('Nasopharyngeal swabs (NPS) PCR test', fontsize = 16)

plt.savefig('naso.png')

plt.show()

 

Pr_infctd = 0.0002

print(f'{Pr_infctd_neg(Pr_infctd):.7f} for Pr(infected) = {Pr_infctd}')

 

Pr_infctd = 0.143

print(f'{Pr_infctd_neg(Pr_infctd):.7f} for Pr(infected) = {Pr_infctd}')

 

 

リスト2 唾液(salivaPCR検査のP(infected|negative)の計算

 

import numpy as np

import matplotlib.pyplot as plt

 

class Infected_Neg:

    """

           For calculation of P(infected|negative)

    """

    def __init__(self, sensitivity, specificity):

        self.Pr_pos_infctd = sensitivity                    #   P(positive|infected)

        self.Pr_neg_noninfctd = specificity                 #   P(negative|non-infected)

        self.Pr_neg_infctd = 1.0 - self.Pr_pos_infctd       #   P(negative|infected)

        self.Pr_pos_noninfctd = 1.0 - self.Pr_neg_noninfctd #   P(positive|non-infected)

    def value(self, Pr_infctd):

        """

                P(infected|negative) for P(infected)

        """

        return self.Pr_neg_infctd * Pr_infctd / \

            (self.Pr_neg_infctd * Pr_infctd + self.Pr_neg_noninfctd * (1.0 - Pr_infctd))

 

Pr_infctd = np.linspace(0, 1, 1000)

#

#           P(infected|negative) as the function of P(infected)

#                       for saliva PCR test

#

Pr_infctd_neg = Infected_Neg(sensitivity = 0.92, specificity = 0.9996).value

Pr_infctd = np.linspace(0, 1, 1000)

Pr = [Pr_infctd_neg(v) for v in Pr_infctd]

plt.plot(Pr_infctd, Pr)

plt.title('Saliva PCR test', fontsize = 16)

plt.xlabel('P(infected)', fontsize = 14)

plt.ylabel('P(infected|neg.)', fontsize = 14)

plt.savefig('saliva.png')

plt.show()

 

Pr_infctd = 0.0002

print(f'{Pr_infctd_neg(Pr_infctd):.7f} for Pr(infected) = {Pr_infctd}')

Pr_infctd = 0.143

print(f'{Pr_infctd_neg(Pr_infctd):.7f} for Pr(infected) = {Pr_infctd}')

 

 

リスト3 NPSsalivaPCR検査のP(infected|negative)のグラフ

 

import numpy as np

import matplotlib.pyplot as plt

   

class Infected_Neg:

    """

           For calculation of P(infected|negative)

    """

    def __init__(self, sensitivity, specificity):

        self.Pr_pos_infctd = sensitivity                    #   P(positive|infected)

        self.Pr_neg_noninfctd = specificity                 #   P(negative|non-infected)

        self.Pr_neg_infctd = 1.0 - self.Pr_pos_infctd       #   P(negative|infected)

        self.Pr_pos_noninfctd = 1.0 - self.Pr_neg_noninfctd #   P(positive|non-infected)

    def value(self, Pr_infctd):

        """

                P(infected|negative) for P(infected)

        """

        return self.Pr_neg_infctd * Pr_infctd / \

            (self.Pr_neg_infctd * Pr_infctd + self.Pr_neg_noninfctd * (1.0 - Pr_infctd))

 

Pr_infctd = np.linspace(0, 1, 1000)

 

#

#           P(infected|negative) as the function of P(infected)

#               for nasopharyngeal swabs: NPSPCR test

#

Pr_infctd_neg_nps = Infected_Neg(sensitivity = 0.86, specificity = 0.9993).value

Pr = [Pr_infctd_neg_nps(v) for v in Pr_infctd]

plt.plot(Pr_infctd, Pr, label = 'NPS')

 

#

#           P(infected|negative) as the function of P(infected)

#                       for saliva PCR test

#

Pr_infctd_neg_saliva = Infected_Neg(sensitivity = 0.92, specificity = 0.9996).value

Pr = [Pr_infctd_neg_saliva(v) for v in Pr_infctd]

plt.plot(Pr_infctd, Pr, label = 'Saliva')

plt.xlabel('P(infected)', fontsize = 14)

plt.ylabel('P(infected|neg.)', fontsize = 14)

plt.legend(fontsize = 14)

plt.savefig('Both.png')

plt.show()

 

 

リスト4 感染率が0〜1%のときのグラフ

 

import numpy as np

import matplotlib.pyplot as plt

   

class Infected_Neg:

    """

           For calculation of P(infected|negative)

    """

    def __init__(self, sensitivity, specificity):

        self.Pr_pos_infctd = sensitivity                    #   P(positive|infected)

        self.Pr_neg_noninfctd = specificity                 #   P(negative|non-infected)

        self.Pr_neg_infctd = 1.0 - self.Pr_pos_infctd       #   P(negative|infected)

        self.Pr_pos_noninfctd = 1.0 - self.Pr_neg_noninfctd #   P(positive|non-infected)

    def value(self, Pr_infctd):

        """

                P(infected|negative) for P(infected)

        """

        return self.Pr_neg_infctd * Pr_infctd / \

            (self.Pr_neg_infctd * Pr_infctd + self.Pr_neg_noninfctd * (1.0 - Pr_infctd))

 

Pr_infctd = np.linspace(0, 0.01, 1000)

 

#

#           P(infected|negative) as the function of P(infected)

#               for nasopharyngeal swabs: NPSPCR test

#

Pr_infctd_neg_nps = Infected_Neg(sensitivity = 0.86, specificity = 0.9993).value

Pr = [Pr_infctd_neg_nps(v) for v in Pr_infctd]

plt.plot(Pr_infctd, Pr, label = 'NPS')

 

#

#           P(infected|negative) as the function of P(infected)

#                       for saliva PCR test

#

Pr_infctd_neg_saliva = Infected_Neg(sensitivity = 0.92, specificity = 0.9996).value

Pr = [Pr_infctd_neg_saliva(v) for v in Pr_infctd]

plt.plot(Pr_infctd, Pr, label = 'Saliva')

plt.xlabel('P(infected)', fontsize = 14)

plt.ylabel('P(infected|neg.)', fontsize = 14)

plt.legend(fontsize = 14)

plt.tight_layout()

plt.savefig('Both_small.png')

plt.show()

 

 

検査母集団の罹患率の推定

 

まず、代数的計算による方法を説明する。その後、それに基づくベイズ分析例を示す。

検査母集団における罹患率と検査結果の陽性率の関係は次式で表される。

いま、

とおけば、

すなわち、sensitivityspecificityが判れば、陽性率から罹患率を求めることができる。

次の関数は、陽性率から罹患率を求めるものである。

 

def EstPopInfctn( sensitivity, specificity, prop_positive):

    """

            検査母数団の罹患率の計算

    """

    A = np.array([[sensitivity, 1 - specificity], [1 - sensitivity, specificity]])

    inv_A = np.linalg.inv(A)

 

    Results = np.array([prop_positive, 1 - prop_positive]).T

    P_inf_non_inf = inv_A @ Results

    return P_inf_non_inf

 

上の関数を用いて罹患率を求めるスクリプト例を、リストA1に示す。

リストA1では、sennsitivityspecificityを以下のようにおいている。

 

sensitivity = 0.90

specificity = 0.999

 

この設定の下で、ダイアモンドプリンセス号の陽性率0.143に対して罹患率を求めると以下のようになる。

 

ダイヤモンド号

陽性率   = 0.143

罹患率   = 0.15795328142380427

非罹患率 = 0.8420467185761956

 

東京都の場合、https://stopcovid19.metro.tokyo.lg.jp/monitoringの記載によると、検査人数5441.4人に対する陽性率は3.4%となっていた(2022.01.07)。

この場合の、罹患率は以下のようになる。

 

Tokyo

陽性率   = 0.034

罹患率   = 0.036707452725250285

非罹患率 = 0.9632925472747496

 

罹患率は、検査対象母集団に対するものであることに注意。

 

 

リストA1 罹患率を求めるスクリプト

import numpy as np

 

def EstPopInfctn( sensitivity, specificity, prop_positive):

    """

            検査母数団の罹患率の計算

    """

    A = np.array([[sensitivity, 1 - specificity], [1 - sensitivity, specificity]])

    inv_A = np.linalg.inv(A)

 

    Results = np.array([prop_positive, 1 - prop_positive]).T

    P_inf_non_inf = inv_A @ Results

    return P_inf_non_inf

 

sensitivity = 0.90

specificity = 0.999

print('sensi =', sensitivity)

print('specificity =', specificity)

 

print('\nダイヤモンド号')

p_positive = 0.143        #  陽性率

est_p_inf = EstPopInfctn(sensitivity, specificity, p_positive)

print('陽性率   =', p_positive)

print('罹患率   =', est_p_inf[0])

print('非罹患率 =', est_p_inf[1])

 

print('\nTokyo')

p_positive = 0.034   #   東京都20220107

est_p_inf = EstPopInfctn(sensitivity, specificity, p_positive)

print('陽性率   =', p_positive)

print('罹患率   =', est_p_inf[0])

print('非罹患率 =', est_p_inf[1])

 

 

罹患率のベイズ分析

 

罹患率を求めるベイズ分析は、次の式に基づいて行うことができる。

すなわち、sensitivityspecificityが与えられているとき、陽性率は罹患率の関数である。陽性率が与えられると、陽性者数は検査者総数における2項分布で表される。このモデルのStanスクリプトは以下のようになる。

 

data {

    int N_Pos;

    int N_All;

    real sens;   //   sensitivity

    real spec;   //   specificity

}

parameters {

    real<lower = 0.0, upper = 1.0> prob_infect;

}

transformed parameters {

    real<lower = 0.0, upper = 1.0> prob_pos;   //   probability of positive

    prob_pos = sens * prob_infect + (1.0 - spec) * (1.0 - prob_infect);

}

model {

    prob_infect ~ uniform(0.0, 1.0);

    N_Pos ~ binomial(N_All, prob_pos);

}

 

上のStanスクリプトを用いるPythonスクリプトを、リストB1に示す。

ダイアモンドプリンセス号の場合、陽性者531人、検査総数3711人として分析すると、図B1の罹患率の事後分布を得る。

B1

 

東京都の場合、https://stopcovid19.metro.tokyo.lg.jp/monitoringの記載によると、検査人数5441.4人に対する陽性率は3.4%となっていた(2022.01.07)ので、陽性者185人、検査者数5441人として分析すると、図B2の事後分布を得る。

B2

 

罹患率は、検査対象母集団におけるものであることに注意。

 

 

リストB1 罹患率のベイズ分析

import numpy as np

import matplotlib.pyplot as plt

import seaborn as sb

import pystan

import pickle

 

#       グラフの日本語表記の設定

plt.rcParams['font.family'] = ['Yu Gothic', 'Noto Sans CJK JP', 'Hiragino Maru Gothic Pro']

 

sm = pystan.StanModel(file = 'bysestpop.stan')

with open('sm.pkl', 'wb') as f:

    pickle.dump(sm, f)

 

 

Data = {'N_Pos': 531, 'N_All': 3711, 'sens': 0.90, 'spec': 0.999}

fit = sm.sampling(data = Data, n_jobs = 1)

print(fit)

 

sb.kdeplot(fit['prob_infect'])

plt.yticks([])

plt.xlabel('Probability', fontsize = 14)

plt.ylabel('Density', fontsize = 14)

plt.title('罹患率:ダイアモンドプリンセス号', fontsize = 18)

plt.savefig('FigDiamond.png')

plt.show()

 

Data = {'N_Pos': 185, 'N_All': 5441, 'sens': 0.90, 'spec': 0.999}

with open('sm.pkl', 'rb') as f:

    sm = pickle.load(f)

fit = sm.sampling(data = Data, n_jobs = 1)

print(fit)

 

sb.kdeplot(fit['prob_infect'])

plt.yticks([])

plt.xlabel('Probability', fontsize = 14)

plt.ylabel('Density', fontsize = 14)

plt.title('罹患率:東京都', fontsize = 18)

plt.savefig('FigTokyo.png')

plt.show()

 

 

ベイズ分析とPythonStanであるPyStanの解説を<岡本安晴いまさら聞けないPythonデータ分析丸善出版>で行っている。

 

 

Up