[PYTHON] Compte tenu de la situation au Japon par le statisticien Nate Silver, "Le nombre de personnes infectées par le coronavirus n'a pas de sens"

introduction

Statisticien américain [Nate Silver](https://ja.wikipedia.org/wiki/%E3%83%8D%E3%82%A4%E3%83%88] célèbre pour la prédiction électorale de l'élection présidentielle américaine % E3% 83% BB% E3% 82% B7% E3% 83% AB% E3% 83% 90% E3% 83% BC) sur un site appelé FiveThirtyEight , "Le nombre de cas de coronavirus n'a pas de sens" Je vais.

L'allégation dans cet article est: ** «À moins que vous ne sachiez suffisamment comment les tests sont effectués, le nombre d'infections au COVID-19 signalé n'est pas un indicateur utile [^ 1]. On l'appelle *. Bien que je pense intuitivement «c'est vrai», l'infection virale est un phénomène exponentiel, donc même si le nombre de tests est réduit (peut-être ** arbitrairement **), le taux de reproduction Je vois et j'entends aussi des affirmations qui (expliquées plus tard) devraient être observables. D'un point de vue complètement différent, on prétend que les soins médicaux s'effondreront si les tests ne sont pas limités. L'article de Nate comprend une simulation simple que les lecteurs peuvent essayer sous forme de feuille de calcul Excel (https://fivethirtyeight.com/wp-content/uploads/2020/04/covidia_1.05_updated.xlsx) Pour que vous puissiez découvrir comment le nombre de personnes infectées est signalé et le nombre réel de personnes infectées est différent en fonction de la politique de test (comment sélectionner la cible de test et le nombre total de tests) et du contrôle de l'infection (distanciation sociale et verrouillage). Il est devenu. Vous pouvez également observer la différence entre le taux de reproduction réel et le taux de reproduction apparent observé.

Le modèle de feuille de calcul [^ 2] est pratique pour jouer facilement avec les paramètres et voir la différence dans les résultats, mais chaque cellule de la feuille de calcul indique $ \ text {BH32} $ "colonne" Il est représenté par une combinaison de "= alphabet" et "ligne = nombre", et leur relation est écrite dans une formule de type BASIC, donc elle n'est pas très appropriée pour comprendre ce qui est calculé et comment. ..

[^ 2]: Nate Silver n'a pas lu la feuille de calcul comme modèle. Il ne s'agit pas de prédire quoi que ce soit.

J'ai donc converti le modèle de feuille de calcul en un programme Python (manuellement) pour comprendre ce que je faisais. De plus, j'ai donné la situation actuelle au Japon (22 avril 2020) comme paramètre et l'ai essayé. Ensuite, bien que le nombre déclaré de personnes infectées vient de dépasser 10 000, le nombre réel de patients dépasse en fait ** 7,5 millions , ce qui signifie qu'environ 6% de la population totale est infectée. J? ai compris. Avec cela, vous devriez penser: " Si vous sortez, la personne suivante est une personne infectée **".

programme

Il y a un notebook jupyter sur github.

Si vous considérez le phénomène d'infection comme un phénomène qui se produit en continu, cela devient une équation différentielle et il est difficile pour un amateur de le gérer, il est donc considéré comme un phénomène discret très grossier. En d'autres termes, considérez la chaîne d'infection comme une chaîne de la génération précédente [^ 3] à la génération suivante, et simplifiez encore le calcul en fixant la différence de temps entre les générations à une certaine constante (par exemple, 5 jours).

Le paramètre le plus important est le taux de reproduction ($ R $), qui spécifie combien d'individus infectés $ n + 1 $ dans la génération $ n $ transmettront la maladie. .. Cela dépend de l'infectivité de la maladie, mais aussi du comportement des personnes dans la société. Le taux de reproduction du bateau de croisière aurait été très élevé et le taux de reproduction de Wuhan aurait été faible depuis qu'il a été verrouillé et toutes les sorties ont été interdites. Si $ R $ est supérieur à 1, l'infection se propage et s'il est inférieur à 1, la maladie finira par disparaître.

[^ 3]: Bien sûr, cette génération n'est pas la génération du baby-boom ou la génération du millénaire, mais les premières personnes infectées sont appelées la 0ème génération, les personnes infectées à partir de là sont appelées la 1ère génération, et ainsi de suite.

réglages des paramètres

Les paramètres liés à la propagation de la maladie sont exprimés par la classe «Disease_spread_params».

Les paramètres de population sont représentés par la classe «Population_params».

Les paramètres liés aux tests PCR sont exprimés par la classe Testing_params.

Les valeurs de ces paramètres sont données par défaut en sélectionnant des valeurs plausibles dans divers articles.

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import time, re
import datetime

iround = lambda x: int((x * 2 + 1) // 2)

class Disease_spread_params:
    def __init__(self,
                 R_uncontrolled = 2.7,
                 R_intermediate = 1.4,
                 R_lockdown = 0.7,
                 cluster = 0.5,
                 begin_intermediate = 11,
                 begin_lockdown = 15,
                 mild = 0.6,
                 asymptomatic = 0.3,
                 zero_date = '2020-01-01',
                 serial = 5):
        self.R_uncontrolled = R_uncontrolled
        self.R_intermediate = R_intermediate
        self.R_lockdown = R_lockdown
        self.cluster = cluster
        self.begin_intermediate = begin_intermediate
        self.begin_lockdown = begin_lockdown
        self.severe = 1 - mild - asymptomatic
        self.mild = mild
        self.asymptomatic = asymptomatic
        self.zero_date = zero_date
        self.serial = serial

class Population_params:
    def __init__(self,
                 population = 10_000_000,
                 initial_infection = 1,
                 faux_severe = 0.001,
                 faux_mild = 0.025,
                 desire_severe = 1.0,
                 desire_mild = 0.5,
                 desire_asymptomatic = 0.02):
        self.population = population
        self.initial_infection = initial_infection
        self.faux_severe = faux_severe
        self.faux_mild = faux_mild
        self.desire_severe = desire_severe
        self.desire_mild = desire_mild
        self.desire_asymptomatic = desire_asymptomatic

class Testing_params:
    def __init__(self,
                 initial_tests = 1_000,
                 ramp_period = 3,
                 test_growth_rate = 0.5,
                 test_max = 10_000_000,
                 rationed_test_ratio = 0.75,
                 false_negative = 0.20,
                 false_positive = 0.002,
                 delay = 2):
        self.initial_tests = initial_tests
        self.ramp_period = ramp_period
        self.test_growth_rate = test_growth_rate
        self.test_max = test_max
        self.rationed_test_ratio = rationed_test_ratio
        self.false_negative = false_negative
        self.false_positive = false_positive
        self.delay = delay

Ici, pour arrondir un nombre réel à un entier, j'utilise ma propre fonction appelée ʻiround () au lieu de la fonction intégrée de Python round () . Ceci est pour la compatibilité avec la fonction Excel ROUND` [^ 4].

[^ 4]: «round ()» de Python est arrondi à un nombre pair, tandis que «round ()» d'EXCEL est arrondi.

Simulateur

En examinant les dépendances cellule par cellule de la feuille de calcul dont je parlais, j'ai compris comment elle avait été calculée et réimplémentée. Pour être honnête, c'est sale (pas humble).

La classe Simulator prend un paramètre, l'instancie, l'exécute en appelant la méthode run () et renvoie le résultat sous la forme pandas.DataFrame.

class Simulator:
    def __init__(self,
                 disease_spread_params,
                 population_params,
                 testing_params):
        self.disease_spread_params = disease_spread_params
        self.population_params = population_params
        self.testing_params = testing_params
        
        self.columns = [ 'date', 'actual.R', 'doubling time in days' ]
        self.date_regex = re.compile('(\d+)-(\d+)-(\d+)')

    def decode_date(self, date):
            match = self.date_regex.fullmatch(date)
            if match:
                y, m, d = match.group(1, 2, 3)
                timestamp = time.mktime((int(y), int(m), int(d), 0, 0, 0, -1, -1, -1))
                return timestamp
            return None
    
    def encode_date(self, timestamp):
        t = time.localtime(timestamp)
        return '{0:04d}-{1:02d}-{2:02d}'.format(t[0], t[1], t[2])

    def run(self, zero_day = '2020-01-01', generations = 40):
        self.generations = generations
        self.df = pd.DataFrame(index = range(0, self.generations))
        t0 = self.decode_date(zero_day)
        self.df['date'] = [ self.encode_date(t0 + d * self.disease_spread_params.serial * 24 * 60 * 60) for d in range(0, self.generations) ]

        self.set_target_R()
        self.compute_actual_infection()
        self.compute_tests()
        
        return self.df

    def set_target_R(self):
        begin_lockdown = self.disease_spread_params.begin_lockdown
        begin_intermediate = self.disease_spread_params.begin_intermediate
        self.df['target_R'] = np.NaN
        
        for i in range(0, self.generations):
            if begin_lockdown != None and i >= begin_lockdown:
                self.df.at[i, 'target_R'] = self.disease_spread_params.R_lockdown
            elif  begin_intermediate != None and i >= begin_intermediate:
                self.df.at[i, 'target_R'] = self.disease_spread_params.R_intermediate
            else:
                self.df.at[i, 'target_R'] = self.disease_spread_params.R_uncontrolled

    def compute_actual_infection(self):
        population = self.population_params.population
        initial_infection = self.population_params.initial_infection
        cluster = self.disease_spread_params.cluster
        serial = self.disease_spread_params.serial

        df = self.df
        df['susceptible'] = np.NaN
        df.at[0, 'susceptible'] = population - initial_infection
        df['new_infection'] = np.NaN
        df.at[0, 'new_infection'] = initial_infection
        df['cumulative_infection'] = np.NaN
        df.at[0, 'cumulative_infection'] = initial_infection
        df['actual_R'] = np.NaN
        df['actual_doubling_time'] = np.NaN
        
        for i in range(1, self.generations):
            df.at[i, 'new_infection'] = iround(df.at[i-1, 'susceptible']*(1-(1-((df.at[i-1, 'target_R']*(df.at[i-1, 'susceptible']/population)**cluster)/population))**df.at[i-1, 'new_infection']))
            df.at[i, 'cumulative_infection'] = df.at[i-1, 'cumulative_infection'] + df.at[i, 'new_infection']
            df.at[i, 'susceptible'] = population - df.at[i, 'cumulative_infection']
            df.at[i-1, 'actual_R'] = df.at[i, 'new_infection'] / df.at[i-1, 'new_infection']
            if df.at[i-1, 'cumulative_infection'] != 0:
                df.at[i-1, 'actual_doubling_time'] = np.inf if df.at[i, 'new_infection'] == 0 else serial * np.log(2) / np.log(df.at[i, 'cumulative_infection']/df.at[i-1, 'cumulative_infection'])

    def compute_tests(self):
        population = self.population_params.population
        ramp_period = self.testing_params.ramp_period
        tests_max = self.testing_params.test_max
        test_growth_rate = self.testing_params.test_growth_rate
        rationed_test_ratio = self.testing_params.rationed_test_ratio
        mild = self.disease_spread_params.mild
        asymptomatic = self.disease_spread_params.asymptomatic
        faux_severe = self.population_params.faux_severe
        faux_mild = self.population_params.faux_mild
        desire_severe = self.population_params.desire_severe
        desire_mild = self.population_params.desire_mild
        desire_asymptomatic = self.population_params.desire_asymptomatic
        false_negative = self.testing_params.false_negative
        false_positive = self.testing_params.false_positive
        delay = self.testing_params.delay
        serial = self.disease_spread_params.serial        

        cumulative_tests_conducted = 0
        cumulative_detected_cases = 0
        
        df = self.df
        df['tests_available'] = 0
        df['new_detected_cases'] = 0
        df['cumulative_detected_cases'] = 0
        
        for i in range(0, self.generations):
            if i  == 0:
                df.at[i, 'tests_available'] = 0
            elif i == 1:
                df.at[i, 'tests_available'] = self.testing_params.initial_tests
            elif i < ramp_period:
                df.at[i, 'tests_available'] = df.at[i-1, 'tests_available']
            else:
                df.at[i, 'tests_available'] = iround(min(tests_max, df.at[i-1, 'tests_available'] * (1 + test_growth_rate)))
            tests_available = df.at[i, 'tests_available']
            rationed_tests =  iround(tests_available * rationed_test_ratio)
            on_demand_tests = tests_available - rationed_tests

            new_infection_severe = iround(df.at[i, 'new_infection'] * (1 - mild - asymptomatic))
            new_infection_mild = iround(df.at[i, 'new_infection'] * mild)
            new_infection_asymptomatic = df.at[i, 'new_infection'] - new_infection_severe - new_infection_mild
            
            population_severe = iround((population - df.at[i, 'new_infection']) * faux_severe) + new_infection_severe
            population_mild = iround((population - df.at[i, 'new_infection']) * faux_mild) + new_infection_mild
            population_asymptomatic = population - population_severe - population_mild
            
            desiring_tests_severe = iround(population_severe * desire_severe * (1 - cumulative_tests_conducted/population))
            desiring_tests_mild = iround(population_mild * desire_mild * (1 - cumulative_tests_conducted/population))
            desiring_tests_asymptomatic = iround(population_asymptomatic * desire_asymptomatic * (1 - cumulative_tests_conducted/population))
            
            alloc_rationed_tests_severe = min(rationed_tests, desiring_tests_severe)
            alloc_rationed_tests_mild = min(desiring_tests_mild, rationed_tests-alloc_rationed_tests_severe)
            alloc_rationed_tests_asymptomatic = min(desiring_tests_asymptomatic, rationed_tests-alloc_rationed_tests_severe-alloc_rationed_tests_mild)

            unfilled_test_demand_severe = desiring_tests_severe - alloc_rationed_tests_severe
            unfilled_test_demand_mild = desiring_tests_mild - alloc_rationed_tests_mild
            unfilled_test_demand_asymptomatic = desiring_tests_asymptomatic - alloc_rationed_tests_asymptomatic
            unfilled_test_demand = unfilled_test_demand_severe + unfilled_test_demand_mild + unfilled_test_demand_asymptomatic
            
            alloc_on_demand_tests_severe = 0 if unfilled_test_demand == 0 else iround(on_demand_tests * unfilled_test_demand_severe / unfilled_test_demand)
            alloc_on_demand_tests_mild = 0 if unfilled_test_demand == 0 else iround(on_demand_tests * unfilled_test_demand_mild / unfilled_test_demand)
            alloc_on_demand_tests_asymptomatic = 0 if unfilled_test_demand == 0 else iround(on_demand_tests * unfilled_test_demand_asymptomatic / unfilled_test_demand)
            
            tests_conducted_severe = alloc_rationed_tests_severe + alloc_on_demand_tests_severe
            tests_conducted_mild = alloc_rationed_tests_mild + alloc_on_demand_tests_mild
            tests_conducted_asymptomatic = alloc_rationed_tests_asymptomatic + alloc_on_demand_tests_asymptomatic
            df.at[i, 'tests_conducted_severe'] = tests_conducted_severe
            df.at[i, 'tests_conducted_mild'] = tests_conducted_mild
            df.at[i, 'tests_conducted_asymptomatic'] = tests_conducted_asymptomatic
            tests_conducted = tests_conducted_severe + tests_conducted_mild + tests_conducted_asymptomatic
            
            cumulative_tests_conducted += tests_conducted
            
            positive_tests_severe = iround(tests_conducted_severe * new_infection_severe / population_severe * (1 - false_negative)) + \
              iround(tests_conducted_severe * (1 - new_infection_severe / population_severe) * false_positive)
            positive_tests_mild = iround(tests_conducted_mild * new_infection_mild / population_mild * (1 - false_negative)) + \
              iround(tests_conducted_mild * (1 - new_infection_mild / population_mild) * false_positive)
            positive_tests_asymptomatic = iround(tests_conducted_asymptomatic * new_infection_asymptomatic / population_asymptomatic * (1 - false_negative)) + \
              iround(tests_conducted_asymptomatic * (1 - new_infection_asymptomatic / population_asymptomatic) * false_positive)
            if i+delay < self.generations:
                df.at[i+delay, 'new_detected_cases'] = positive_tests_severe + positive_tests_mild + positive_tests_asymptomatic

            cumulative_detected_cases += df.at[i, 'new_detected_cases']
            df.at[i, 'cumulative_detected_cases'] = cumulative_detected_cases

            if i > 0 and df.at[i-1, 'new_detected_cases'] > 0:
                df.at[i-1, 'observed_R'] = df.at[i, 'new_detected_cases'] / df.at[i-1, 'new_detected_cases']
                df.at[i-1, 'observed_doubling_time'] = np.inf if df.at[i, 'new_detected_cases'] == 0 else serial * np.log(2) / np.log(df.at[i, 'cumulative_detected_cases']/df.at[i-1, 'cumulative_detected_cases'])

simulation

Scénario 1 de Covidia

L'article de Nate Silver présente une simulation du pays virtuel Covidia. Le graphique ci-dessous est une visualisation des résultats. Covidia compte 10 millions d'habitants et commencera à se propager avec l'arrivée de la première personne infectée le 1er janvier 2020. Nous supposons un taux de reproduction initial de 2,7 $, une étape intermédiaire de 1,4 $ et un verrouillage de 0,7 $.

fig1.png

La ligne bleue représente le nombre réel de personnes nouvellement infectées et la ligne jaune correspond à la transition du nombre de personnes nouvellement infectées signalées par l'inspection. Dans ce scénario, la capacité de test initiale est aussi petite que 1000 par génération, mais en augmentant indéfiniment le nombre de tests, il semble que le nombre de rapports augmente en fonction du nombre réel de personnes infectées, bien qu'il y ait un décalage dans le temps. ..

Cependant, si vous regardez de plus près, vous pouvez voir que dans la phase où le nombre de personnes infectées augmente, il est sous-estimé de 20 fois ou plus, et lorsqu'il a tendance à diminuer, il est surestimé.

Scénario japonais?

Le Japon a été critiqué pour avoir maintenu le nombre de tests PCR à un faible niveau. Le ministère de la Santé, du Travail et du Bien-être rend compte quotidiennement [^ 5], mais on dit que le nombre d'inspections ne suit toujours pas la demande. Par conséquent, j'ai essayé ce qui se passerait si le nombre de tests PCR était bas pour la population japonaise de 126 millions. En passant, j'ai également tracé le nombre de personnes confirmées positives qui ont été effectivement signalées jusqu'à présent. Alors que le graphique ci-dessus montre le nombre de personnes nouvellement infectées, il s'agit du nombre cumulé de personnes infectées [^ 6].

[^ 5]: Par exemple, dans Dernier rapport, 7826 nouveaux tests PCR ont été effectués à partir du rapport de la veille. Tu peux voir ça. Contrairement à la figure précédente, il s'agit d'un graphique du nombre cumulé de personnes infectées. [^ 6]: Les données réelles sont données de manière cumulative, et je pensais que cela m'était plus familier.

![fig2.png] (https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223481/193b7bce-f37f-4b9e-727b-223c95b838f5.png)

Ici, la ligne verte représente le nombre réel de personnes infectées au Japon [^ 6].

[^ 6]: J'utilise les données publiées par l'Université John Hopkins sur github.

Je n'ai pas l'intention de prédire quoi que ce soit dans cette simulation, mais j'aimerais d'abord que vous voyiez l'ampleur de la différence entre la valeur réelle (ligne bleue) et le résultat de la simulation et la valeur rapportée (ligne verte). Ce qui suit peut être observé.

Mais que se passerait-il si nous avions augmenté notre capacité de test dès le début et avions des tests illimités? Le graphique suivant montre les résultats lorsque la valeur initiale de la capacité d'inspection est fixée à 10 000 et que la capacité d'inspection est augmentée progressivement mais de manière illimitée.

fig3.png

Même dans ce cas, il n'est toujours pas possible de capturer le nombre réel de personnes infectées, mais nous avons pu observer environ 10 fois le nombre actuel de journalistes. De plus, bien que l'augmentation du nombre de reporters (jaune) soit un peu lente, la pente est proche de la pente du nombre réel de personnes infectées, donc la différence entre les deux peut être considérée comme la différence dans le sens gauche-droite (délai), ce qui est correct. Il semble que le taux de production de $ R $ puisse être observé.

Résumé

Je pense qu'il est vrai qu'il n'y a pas de différence en termes de traitement parce qu'il n'y a pas d'hôpitaux ou d'établissements d'isolement où la personne infectée est connue. Cependant, je pense que le comportement quotidien des gens peut changer un peu s'ils savent que le nombre de personnes véritablement infectées est 10 fois plus élevé que celui rapporté, et dans certains cas 100 fois plus élevé.

Je me demande si les experts de la télévision en sciences infectieuses élaborent une version beaucoup plus détaillée de ce modèle pour prédire l'avenir.

Je ne sais pas.

Recommended Posts

Compte tenu de la situation au Japon par le statisticien Nate Silver, "Le nombre de personnes infectées par le coronavirus n'a pas de sens"
J'ai essayé de prédire le nombre de personnes infectées par le virus corona au Japon par la méthode du dernier article en Chine
Visualisons le nombre de personnes infectées par le virus corona avec matplotlib
Prédire le nombre de personnes infectées par COVID-19 avec Prophet
J'ai essayé de prédire le nombre de personnes infectées par le virus corona en tenant compte de l'effet de s'abstenir de sortir
Créez un BOT qui affiche le nombre de personnes infectées dans le nouveau Corona
Un serveur qui renvoie le nombre de personnes devant la caméra avec bottle.py et OpenCV
[Homologie] Comptez le nombre de trous dans les données avec Python
J'ai essayé de prédire le nombre de personnes infectées au niveau national de la nouvelle corona avec un modèle mathématique
Créez un bot qui publie sur Slack le nombre de personnes positives pour le nouveau virus corona à Tokyo
Générez une liste contenant le nombre de jours du mois en cours.
Afficher le statut de l'infection COVID 19 au Japon avec Splunk (version GitHub)
Calculons la transition du nombre de reproduction de base du nouveau virus corona par préfecture