Простая модель эпидемии базовыми инструментами Python
- понедельник, 13 апреля 2020 г. в 00:23:30
“Почему бы не разжечь эпидемию” — эта мысль пришла внезапно. Работа из дома при правильной организации может оказаться эффективнее офисной, в результате появляется честное дополнительное время на “подумать” над чем-нибудь еще.
Началось все, конечно, из построения ежедневной визуализации данных о COVID-19 Европейского центра контроля заболеваний. Простой алгоритм ежедневно в полдень рисует графики по обновляемым данным. В графиках привлекают внимание эффективные противоэпидемические действия Китая, когда эпидемия в начале марта пошла на спад. Но эстафету подхватывают страны Европы — сначала Италия (может помните как в конце февраля власти Милана просили вернуться туристов в город, т.к. страдает экономика?), дальше Испания.
Сейчас невидимый враг приблизился и к моему родному дому — Украине, и подсознательно хочется, хотя-бы, видимость понимания и контроля. Почему в Украине при наличии всего 30 больных ввели карантин по всей стране — отменили междугороднее транспортное соединение, закрыли для посещений торговые центры, кафе, любимые кофейни? Насколько соответствуют эти меры угрозе? Ведь потери для бизнеса колоссальные.
Особенность всех “живых” отчетов о ситуации по COVID-19 в мире — эти отчеты отражают ситуацию как-бы снаружи. В то же время мой опыт работы врачом подсказывает, что видимой может быть только (малая!) часть айсберга. Это предположение присутствует и в ряде работ специалистов по данным. Интересно ведь проверить. Как же увидеть ситуацию изнутри и полностью? — Строим модель!
Модель будем строить по-медицински — от больного. Ну или как это сейчас модно в программировании — строим алгоритм от объектов реального мира. Каждый случай — это отдельный заболевший человек. Любой врач отдела статистики больницы скажет, что вся отчетность происходит от первичной документации — истории болезни. В нашей модели создадим на каждый случай такую упрощенную электронную историю болезни. В ней для начала 3 атрибута:
Важно, что наша модель всевидяща, и как только человек был инфицирован — на него заводится соответствующая электронная запись, отслеживается статус. Что конечно же пока невозможно в реальном мире. Что еще может сделать больной? Он может обратиться в больницу, где будет заведена настоящая бумажная история болезни — добавляем поле “Дата регистрации случая” caseIssuedDate (помним об айсберге и его вершине — это как раз она). Заболевание может проходить легко, а может тяжело — добавляем поле “тяжесть заболевания” severity с двумя градациями. И напоследок вспоминаем популярное сейчас слово “Изоляция” и добавляем соответствующий признак пациенту.
Все данные о больных будем держать в двумерном массиве: один больной-случай = 1 строка.
Таким образом нулевой пациент эпидемии выглядит так:
#['infectDate', 'endOfCaseDate', 'result', 'severity', 'caseDocumentedDate', 'isolated' ]
epidemy = [[0, None, 'infected', 'mild', None, False]]
*впоследствии, когда пациент выздоровеет\умрет его статус ‘result’ изменится. Итоговая таблица наблюдения случая эпидемии будет иметь статусы для пациентов ‘recovered’ и ‘dead’.
Теперь все просто — ходим за больным, смотрим что он делает и записываем…
Основное действие больного в контексте нашей задачи — контактировать с другими людьми. Сложно оценить количество таких контактов ежедневно, тем более оно может на порядок отличаться у разных людей (и тут вспоминается слово “суперпереносчик”, но ведь модель простая, поэтому у нас таких нет). Очень субъективно, если вспомнить свои контакты в течение обычного дня — оценочно их может быть порядка нескольких десятков ежедневно.
Каждый контакт больного со здоровым — это необязательно передача заболевания, существует такой параметр “контагиозность”. Например, контагиозность 0.1 (10%) означает что только в 1 из 10 контактов больного со здоровым передается заболевание. Но если 100 больных осуществляют по 10 контактов каждый ежедневно, то они вполне смогут передать заболевание 100 10 0.1 = 100 здоровым людям. Собственно, это и есть основной механизм: ежедневно считаем сумму контактов всех больных, умножаем на контагиозность и получаем ежедневное количество вновь зараженных. Каждого нового зараженного (больного) добавляем в наш эпидемический список новой строкой:
['infectDate', 'endOfCaseDate', 'result', 'severity', 'caseDocumentedDate', 'isolated' ]
Нужно не забыть, что случай заболевания ограничен во времени, и по истечению времени пациент либо выздоравливает (при этому получает иммунитет от повторного заражения — это важно!) либо умирает.
Ежедневно проходим по нашему эпидемическому списку — у пациентов со сроком заболевания, подходящим к концу, определяем исход. От чего зависит исход? Для роста эпидемии выздоровевший и умерший одинаково не подходят, но даже виртуальный больной хочет остаться в живых. Из своего клинического опыта взял аксиому: пациенты с легкой формой заболевания не склонны умирать, поэтому в нашей модели могут умирать только пациенты с тяжелой формой заболевания, при этом не все, а часть из них — с учетом deathRate.
Пришло время показать все введенные допущения и бизнес-правила модели::
#Социальная активность больного
dailyTransmissionContacts = 15 #количество ежедневных контактов с людьит
#Особенности вируса
contagiousness = 0.01 #контагиозность
severityRate = 0.3 #доля тяжелых случаев
deathRate = 0.5 #показатель смертности (только в тяжелых случаях)
caseDuration = 25 #длительность заболевания
#Параметры популяции
closedPopulation = 10000
#поведение больных
periodToDocumented = caseDuration * 0.6 #количетсво дней от инфицирования до обращения к врачу
mildDocumentedRate = 0.3
severeDocumentedRate = 0.9
isolationQualityRate = 0.1
#период наблюдения модели
showDays = 150 #період розрахунку показників
Касательно параметров поведения больного (см. рис) введены следующие правила:
periodToIssued — количество дней, во время которых пациент инфицирован, но не обращается к врачу
mildDocmentedRate — только 30% пациентов с легкой формой заболевания обращаются к врачу
severeDocumentedRate = 90% пациентов с тяжелой формой заболевания обращаются к врачу
isolationQualityRate — качество (само)изоляции больного. При 100% больной полностью воздерживается от социальных контактов
Почему именно эти цифры? Сложно сказать, моя модель — мои параметры… Цифры взял интуитивно, хотя, конечно, врачи склонны к чрезмерной самоуверенности, что иногда вредит. Но все же — интуиция это не что иное как опыт. А опыт умеет сводить разные наблюдения в одну точку. Забегая наперед можно сказать что с такими параметрами модель заработала, достаточно релевантно по отношению к реальным отчетам о COVID-19. Но не только — модель позволяет с определенной долей правдивости имитировать и грипп и туберкулез.
Алгоритм работы модели
Запускаем цикл для каждого дня. В каждом дне проводим следующие действия:
Закрываем случаи с истекшим сроком — изменяем статус каждого такого пациента с “инфицирован” на “выздоровел” или “умер”
Считаем количество больных, которые в этот день все еще имеют статус “инфицирован” — они как раз и будут заражать здоровых
На основе п.2 рассчитываем количество вновь зараженных в этот день
susceptibleToday = (closedPopulation - infectedCountToday - recoveredCountToday)
newAffectedPeopleCount = int(((activeInfectedCountToday * dailyTransmissionContacts * contagiousness) // 1 * susceptibleToday/closedPopulation + 1))
*заразиться могут лишь здоровые люди — именно они являются “топливом” для развития эпидемии. Их количество уменьшается с развитием эпидемии. Исчерпание запасов топлива( количества здоровых) людей является триггером для угасания эпидемии.
Добавляем всех новых зараженных в список эпидемии.
Повторяем действия для следующего дня и тд.
В дневной цикл можно вставить счетчики для построения графиков, либо же графики строить из датасетов конечного сформированного массива больных методами pandas.
График на основе данных модели для города с населением 10 тыс. Зеленая линия показывает количество чувствительных к заражению людей: 0 день чувствительных (т.е. здоровых) 100%, со временем их количество уменьшается и они “тушат” эпидемию.
Одновременно больным были максимум 6 тыс людей. На 120й день зеленая линия (чувстительные к заражению люди) и черная (общее количество умерших) сходятся — это означает что больше не осталось чувствительных к заболеванию людей, и эпидемия прекращается.
totalAffected: 10000 deathsTotal: 1582 deathRate_real = 15%
Критичным для популяции является показатель контагиозности (или его сочетание с количеством социальных контактов). При увеличении показателя контагиозности в 10 раз, эпидемия приобретает тотальный характер, резко охватывая все население города
Ключевым же для развития эпидемии является показатель длительности заболевания. Чем он меньше, тем сложнее заболеванию стать эпидемией. Например грипп, в отличие от COVID-19, при высокой контагиозности имеет значительно более короткий период течения болезни — 5-7 дней. Модель подтверждает, что грипп не вызывает эпидемию, но в то же время сохраняется в популяции на относительно низком уровне но постоянно — гриппом можно заболеть и зимой и летом
Зависимость исключительно от caseDuration при прочих одинаковых параметрах:
Обратите внимание на зеленую линию: у эпидемии есть некоторая критическая точка, ниже которой эпидемия не развивается, и заболевание переходит в формат персистирующего.
Пришло время разобраться почему вводимые противоэпидемические меры в странах оказываются запоздалыми и как правильно оценивать эпидемию. У каждого больного мы ввели показатель caseIssuedDate — дата регистрации случая заболевания в системе здравоохранения. Ведь только по учету зарегистрированных случае медики могут “увидеть” и посчитать эпидемию. Каждый врач знает правило: “не каждый больной обращается, и тем более своевременно обращается за медицинской помощью”. Классический диалог из моего прошлого работы врачом, который часто происходил на дежурствах, где-то в полночь между пятницей и субботой.
Я: “Больная, у Вас тяжелое воспаление, которое с Ваших слов началось остро в среду. Почему > Вы не обратились за помощью раньше?”
Больная: “Думала переболит, хотела подождать до понедельника...
То же происходит и во время эпидемии: больные терпят, терпят, терпят и только если совсем плохо — обращаются к врачу. В модель введены 2 параметра: mildDocumentedRate severDocumentedRate — доля обращения в медицинское учреждение (для статистики это равно регистрации случая) при мягкой форме и тяжелой форме заболевания. Эти показатели отличаются в разы: при легкой форме ожидаемо 7-9 из 10 пациентов будут продолжать ходить на работу, погуглят гомеопатию, при этом оставаясь активными разносчиками инфекции. При тяжелой форме заболевания большинство пациентов таки обратятся в больницу, при этом в группу риска быть зараженными попадают уже врачи и другие больные в больнице. В сочетании с правилом, что больные с легкой формой болезни не склонны умирать, понимаем, что большинство смертей, к которым причастна эпидемия, таки будут зарегистрированы медиками. Уже чувствуете да? Реальные статистические данные будут учитывать далеко не всех больных, и при этом доля тяжелых случаев и смертности по медицинским данным будет выше чем на самом деле. Это лишь предположение, проверим на модели.
mildIDocumentedRate = 0.3
severeDocumentedRate = 0.9
Реальное число больных в несколько раз отличается от задокументированных случаев, которые как раз и попадают в документированную медицинскую статистику. В начале эпидемии соотношение сохраняется, но абсолютная разница невелика: когда из 150 больных зарегистрировано всего 50 — это не критическая проблема, но на пике ошибка в оценке на десятки тысяч является существенной, т.к. может существенно повлиять на правильность расчетов необходимой медицинской помощи заболевшим.
Задокументированные смерти гораздо более точно отображают реальную картину, так как большинство умерших попадают в поле зрения системы здравоохранения.
На графике пик смертности смещен вправо по оси времени, т.к. больные умирают не сразу в день заражения, а только по истечении периода заболевания. Чем дольше длится заболевания у больного, тем более смещен пик смертности вправо. Выход показателя смертности на плато (пик) может быть косвенным признаком того что эпидемия спадает.
При этих же параметрах модели отличаются и результаты статистики (реальная — первая строка и задокументированная — вторая строка)
totalAffected: 10000 deathsTotal: 1582 deathRate_real = 15%
DocumentedTotal: 4789 deathsDocumented: 1406 deathRate_docum = 29%
Обратите внимание на показатель смертности. Фактический показатель смертности значительно ниже задокументированного.
На рисунке видно смещение (асимметрия) пиков графиков реальных случаев и задокументированных: момент пика реальной эпидемии раньше момента пика “видимой” ее части. Такое смещение обусловлено временем от инфицирования больного до его обращения в больницу — чем оно больше, тем больше будут смещены пики во времени.
На смещение графиков во времени влияет в первую очередь показатель periodToDocumented — время от инфицирования до обращения пациента в больницу. Конечно, “здоровый” больной без симптомов заболевания, но уже являющийся распространителем инфекции, в больницу не пойдет. Тем не менее, на этот показатель можно влиять — информационная работа с населением, раннее тестирование — эти действия могут сократить период между инфицированием и обращением больного к медикам, а в сочетании с изоляцией больного после визита к врачу, такая последовательность может быть одним из механизмов подавления эпидемии.
Зависимость от параметра periodToDocumented. Показатель mildDocumentedRate = 0.3 (30% пациентов с легкой формой обращаются к врачу). Увеличение времени до обращения больного к врачу не только смещает “видимую” эпидемию на более позднее время от реальной, но и приводит к выявлению меньшего количества больных, и наоборот.
Наиболее важен начальный период эпидемии: разница между зарегистрированными и действительными случаями может отличаться здесь в десятки и возможно сотни раз — со временем эта разница уменьшится, но момент начала эпидемии может быть упущен.
тот же датасет, фрагмент [20:30] дней.
Таким образом видимая часть эпидемии может существенно отличаться от действительной ситуации, и очень важно, то медицинские данные в реальном мире поступают с существенным отставанием — это нужно учитывать при оценке состояния развивающейся эпидемии.
Среди известных методов противодействия рассмотрим поведение модели при следующих влияниях:
Достигается проведением информационной работы с населением с принуждением оставаться дома в случае болезни.
Следует понимать что больной считает себя больным и самоизолируется либо обращается к врачу только при наступлении выраженных проявлений болезни. До этого момента человек может быть инфицирован, но, сохраняя социальную активность, продолжать заражать других.
На графике показана зависимость количества инфицированных от качества самоизоляции (0 — больные не самоизолируются, 1 — полная самоизоляция каждого больного).
Модель показывает, что исключительно увеличение показателя самоизоляции больных не приносит существенной пользы — в первую очередь, это связано с наличием “безсимптомного периода” у заболевания. Чем он больше, тем менее эффективна самоизоляция больных.
(на этапе до проявления выраженных симптомов) с последующей изоляцией выявленных инфицированных.
Сама по себе диагностика, конечно же, не принесет изменений в течении болезни и лишь будет приближать количество задокументированных случаев к их реальному количеству. Мерой профилактики может быть сочетание раннего выявления с изоляцией больных. Следствием таких действий является сокращение периода времени, в течение которого внешне здоровый инфицированный человек активно заражает других. Примем показатель самоизоляции выше среднего (например 0.7) и посмотрим зависимость от показателя periodToDocumented.
По умолчанию, показатель periodToDocumented (период от инфицирования до выявления больного) выставлен 60% от длительности всего заболевания, т.е. 25 * 0.6 = 15 дней. Протестируем модель с показателем 10, 7, 4 дня
Как видно, раннее выявление может существенно сдвинуть пик эпидемии, а очень раннее выявление — существенно “растянуть” эпидемию, что несомненно оптимизирует нагрузку на систему здравоохранения, и, возможно, большее число пациентов будут спасены медиками. В тоже время модель не показала преимуществ в общем количестве смертей, и лишь оттянула пик.
Результаты соответсвенно:
totalAffected: 10000 deathsTotal: 1486 deathRate_real = 14%
totalAffected: 10000 deathsTotal: 1556 deathRate_real = 15%
totalAffected: 10000 deathsTotal: 1439 deathRate_real = 14%
totalAffected: 8466 deathsTotal: 1248 deathRate_real = 14%
Основной смысл карантина — уменьшение контактов между людьми в целом — это обеспечивает в частности уменьшение контактов между больными и здоровыми людьми. В модели за механизм передачи возбудителя отвечают 2 показателя:
dailyTransmissionContacts = 15 #ежедневные контакты человека
contagiousness = 0.01 #контагиозность возбудителя
Их произведение вместе с количеством инфицированных дает “инфицирующий потенциал” для роста инфекции. Именно на количество ежедневных контактов влияет карантин.
totalAffected: 10000 deathsTotal: 1557 deathRate_real = 15%
totalAffected: 7727 deathsTotal: 1173 deathRate_real = 15%
totalAffected: 4597 deathsTotal: 709 deathRate_real = 15%
totalAffected: 2008 deathsTotal: 299 deathRate_real = 14%
Влияние карантина колоссальное. Ранний жесткий карантин сократил количество заболевших и общее количество умерших в 5 раз. Конечно, карантин связан с существенными экономическими потерями и известна фраза “не так страшен вирус как паника”. Расчет экономической целесообразности — тема для отдельного обсуждения.
Важно не только своевременно ввести карантин, но и своевременно его отменить.
В качестве дополнительного повода для размышлений задал параметры с ранней отменой мягкого карантина. Условия такие: мягкий карантин был введен на 60й день и отменен на 80й, когда начался видимый спад.
В результате, карантин хотя и оказал положительный эффект, “растянув” эпидемию, но за преждевременной отменой наступил повторный пик, количество умерших выросло до уровня “без карантина”
totalAffected: 10000 deathsTotal: 1557 deathRate_real = 15%
totalAffected: 4597 deathsTotal: 709 deathRate_real = 15%
totalAffected: 10000 deathsTotal: 1434 deathRate_real = 14%
Как мы уже вспоминали ранее, “топливом” для роста эпидемии являются здоровые люди, восприимчивые к заболеванию. При определенных параметрах, заболевание с активными показателями распространения коснется каждого человека в закрытой группе, либо может расти относительно неограниченно, если не вводить дополнительные условия, отражающие зонирование/кластеризацию популяции (города, страны, континенты). Основной причиной окончания эпидемии является “выжигание топлива” — уменьшение количества восприимчивых к болезни людей. По правилам нашей модели, переболевший человек становится устойчивым к повторному заражению. Не будем сейчас говорить об исключениях, мутации вируса и пр, хотя такие кейсы вполне реальны — вспомним хотя бы грипп, ставший эндемической инфекцией (основными особенностями гриппа являются короткое течение, что не дает ему вырасти в эпидемию, а также высокая степень мутации вируса, что дает возможность обойти планету за год, измениться и инфицировать тех же людей заново, и так каждый год — этот случай также обрабатывает модель, но эндемические инфекции — также тема для отдельного обсуждения).
Но забрать топливо у эпидемии можно и другим путем. Вакцинация! Она увеличивает долю людей, устойчивых к инфицированию, что и нужно для подавления инфекции.
Смоделировать вакцинацию легко: во время работы модели добавим в список epidemy некоторое количество вакцинированных (‘recovered’).
totalAffected: 7890 deathsTotal: 1162 deathRate_real = 11%
totalAffected: 10000 deathsTotal: 1557 deathRate_real = 15%
Вакцинация даже 5й части населения на 20 й день (предположим руководство города анализировало ситуацию вокруг и своевременно приняло решение о вакцинации) на 30% сократила общее количество умерших и почти на четверть — количество больных.
Модель всегда имеет расхождения с событиями реального мира, а количество возможных уточнений и калибровок неограниченно. В случае уточнения модели в первую очередь приходят на ум следующие дополнения:
Подход “от больного” позволил простыми средствами создать довольно жизнеспособную модель эпидемии, которая позволяет визуализировать взаимосвязи между разными параметрами эпидемии.
Лишь искусственная модель позволяет соотнести “вершину айсберга” эпидемии с целым “айсбергом” и в дальнейшем интерполировать эти данные для оценки реальных событий. Особенно критична разница между тем, что регистрирует система здравоохранения, и тем, что происходит на самом деле — в начальных стадиях развития эпидемии. В этот период погрешность между “записано” и “на самом деле” может составлять десятки раз.
Разные способы подавления могут существенно отличаться по эффекту: протестированные способы “самоизоляции” и даже “раннего выявления” не показали ожидаемых положительных результатов при параметрах модели, близких к COVID19. В то же время, любая мера подавления в той или иной степени “сглаживает” пик эпидемии, что может положительно влиять на работу системы здравоохранения в условиях эпидемии.
Наиболее эффективной мерой оказался карантин. Своевременный и достаточной силы карантин может не только “растянуть” течение эпидемии, снизив нагрузку на систему здравоохранения, но и в разы уменьшить как количество заболевших так и смертность.
Ноутбук на google colab по ссылке