In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.dates import WeekdayLocator, DateFormatter
from scipy.optimize import curve_fit
In [2]:
today = pd.Timestamp.today(tz='UTC')
'Вычисления выполнены {:%Y-%m-%d %H:%M:%S %Z}'.format(today)
Out[2]:
'Вычисления выполнены 2020-04-22 10:30:38 UTC'

Исходные данные

Мы воспользуемся открытыми глобальными данными Университета Джонса Хопкинса.

Исходные данные получим из репозитория Johns Hopkins CSSE: CSSEGISandData/COVID-19

Мы загружаем данные о заражённых, умерших и выздоровевших по странам. Они обновляются каждые сутки, поэтому можно будет следить за динамикой, просто запуская этот Jupyter Notebook раз в день.

In [3]:
confirmed_url = 'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv'
deaths_url = 'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_global.csv'
recovered_url = 'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_recovered_global.csv'

confirmed_data = pd.read_csv(confirmed_url)
deaths_data = pd.read_csv(deaths_url)
recovered_data = pd.read_csv(recovered_url)

confirmed_data.head()
Out[3]:
Province/State Country/Region Lat Long 1/22/20 1/23/20 1/24/20 1/25/20 1/26/20 1/27/20 ... 4/12/20 4/13/20 4/14/20 4/15/20 4/16/20 4/17/20 4/18/20 4/19/20 4/20/20 4/21/20
0 NaN Afghanistan 33.0000 65.0000 0 0 0 0 0 0 ... 607 665 714 784 840 906 933 996 1026 1092
1 NaN Albania 41.1533 20.1683 0 0 0 0 0 0 ... 446 467 475 494 518 539 548 562 584 609
2 NaN Algeria 28.0339 1.6596 0 0 0 0 0 0 ... 1914 1983 2070 2160 2268 2418 2534 2629 2718 2811
3 NaN Andorra 42.5063 1.5218 0 0 0 0 0 0 ... 638 646 659 673 673 696 704 713 717 717
4 NaN Angola -11.2027 17.8739 0 0 0 0 0 0 ... 19 19 19 19 19 19 24 24 24 24

5 rows × 95 columns

Напишем функцию для преобразования данных к удобному для анализа виду.

  1. Избавимся от ненужных столбцов: drop(['Province/State', 'Lat', 'Long'], axis=1).
  2. Найдём сумму всех случаев заражений по каждой стране, то есть исключим деление на штаты/районы/провинции: groupby(by='Country/Region').sum().
  3. Транспонируем таблицу transpose(), чтобы даты стали столбцом 'date', а названия стран стали столбцами.
  4. Столбец с датами делаем индексом, таким образом получаем полноценный временной ряд.

Поскольку эту операцию нужно проделать со всеми таблицами, то напишем сначала функцию, а затем применим её к исходным данным.

In [4]:
def transform_data(data):
    data = (data
             .drop(['Province/State', 'Lat', 'Long'], axis=1)
             .groupby(by='Country/Region').sum()
             .transpose()
             .rename({'Country/Region' : 'date'})
            )

    data.index = pd.to_datetime(data.index)
    data.index.name = 'Дата'
    return data

confirmed = transform_data(confirmed_data.copy())
deaths = transform_data(deaths_data.copy())
recovered = transform_data(recovered_data.copy())

confirmed.tail()
Out[4]:
Country/Region Afghanistan Albania Algeria Andorra Angola Antigua and Barbuda Argentina Armenia Australia Austria ... United Kingdom Uruguay Uzbekistan Venezuela Vietnam West Bank and Gaza Western Sahara Yemen Zambia Zimbabwe
Дата
2020-04-17 906 539 2418 696 19 23 2669 1201 6522 14595 ... 109769 502 1405 204 268 402 6 1 52 24
2020-04-18 933 548 2534 704 24 23 2758 1248 6547 14671 ... 115314 508 1490 227 268 418 6 1 57 25
2020-04-19 996 562 2629 713 24 23 2839 1291 6547 14749 ... 121172 517 1565 256 268 437 6 1 61 25
2020-04-20 1026 584 2718 717 24 23 2941 1339 6547 14795 ... 125856 535 1627 256 268 449 6 1 65 25
2020-04-21 1092 609 2811 717 24 23 3031 1401 6547 14873 ... 130172 535 1678 285 268 466 6 1 70 28

5 rows × 185 columns

In [5]:
cases_global = pd.DataFrame({
    'Заболевшие': confirmed.sum(axis='columns'),
    'Умершие': deaths.sum(axis='columns'),
    'Выздоровевшие': recovered.sum(axis='columns')})

Изучаем данные

Глобальные показатели

Важной описательной статистикой для эпидемий является летальность и смертность.

  • Летальность заболевания это отношение смертей к общему числу заболевших.
  • Смертность заболевания это отношение умерших к средней численности популяции.

Окончательные показатели летальности и смертности можно будет вычислить только после того как пандемия закончится и будут опубликованы официальные окончательные цифры. Однако ничто не мешает следить за текущей статистикой.

Вычисляем летальность (англ. case fatality rate):

In [6]:
cfr = (cases_global['Умершие'] / cases_global['Заболевшие'])
'Летальность: {:.2f}%'.format(np.around(float(cfr.tail(1)) * 100, 2))
Out[6]:
'Летальность: 6.91%'

Эти цифры можно сравнить с другими заболеваниями: List of human disease case fatality rates

На данный момент коронавирус более летален, чем любой из известных гриппов (у сезонного < 0.1%) и, например, малярия (~0.3%). Однако можно ожидать, что летальность в итоге уменьшится, особенно когда будут проведены оценки невыявленных случаев.

Тем не менее, сейчас цифры очень серьезные.

Смертность обычно оценивают в промилле ‰, то есть в тысячной доле (процент это сотая доля). Мы возьмём приблизительную численность населения планеты в 7,7 млрд. человек.

In [7]:
world_population = 7.7e9
mortality_rate = cases_global['Умершие'] / world_population
'Смертность: {:.4f}‰'.format(float(mortality_rate.tail(1)) * 1000)
Out[7]:
'Смертность: 0.0230‰'

А сколько всего человек заболело от общей численности населения?

In [8]:
confirmed_perc = cases_global['Заболевшие'] / world_population
('Заразились {} чел., это {:.4f}% от ' 
 'населения планеты.'.format(int(cases_global['Заболевшие'].tail(1)),
                                                    float(confirmed_perc.tail(1)) * 100))
Out[8]:
'Заразились 2561043 чел., это 0.0333% от населения планеты.'

По сравнению с летальностью, смертность реже используется для характеристики эпидемии и пандемии. Оно и понятно, когда станет ясно, что смертность высокая, будет уже поздно принимать меры. Это же касатеся и прочих оценок, полученных как отношение к общей численности населения.

Наконец, можно посмотреть на выздоравливаемость:

In [9]:
recovery_rate = (cases_global['Выздоровевшие'] / cases_global['Заболевшие'])
'Выздоравливаемость: {:.2f}%'.format(float(recovery_rate.tail(1)) * 100)
Out[9]:
'Выздоравливаемость: 26.54%'

Теперь пора построить графики. Сначала напишем общую для всех них функцию.

In [10]:
def plot_cases(df, **kwargs):
    ax = df.plot(figsize=(10, 4), grid=True)
    ax.set(**kwargs)
    
    ax.set_ylabel('Число человек')
    ax.xaxis.set_minor_formatter(plt.NullFormatter())
    ax.xaxis.set_major_locator(WeekdayLocator(interval=1))
    ax.xaxis.set_major_formatter(DateFormatter('%b %d'))
    return ax
In [11]:
%matplotlib inline
plot_cases(cases_global, title='COVID-19')
Out[11]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f08af1fc9a0>
In [12]:
ax = plot_cases(recovery_rate*100, title='Выздоравливаемость COVID-19')
ax.set_ylabel('Выздоравливаемость, %')
Out[12]:
Text(0, 0.5, 'Выздоравливаемость, %')
In [13]:
ax = plot_cases(cfr*100, title='Летальность COVID-19')
ax.set_ylabel('Летальность, %')
Out[13]:
Text(0, 0.5, 'Летальность, %')
In [14]:
plot_cases(cases_global.diff(), title='Данные по COVID-19 за сутки')
Out[14]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f08ace56d00>
In [15]:
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(13, 3))
ax1.plot(cases_global['Заболевшие'], cases_global['Заболевшие'].diff())
ax2.plot(cases_global['Заболевшие'], cases_global['Умершие'].diff())
ax3.plot(cases_global['Заболевшие'], cases_global['Выздоровевшие'].diff())

for ax in fig.get_axes():
    ax.set_xlabel('Заболевшие')

ax1.set_ylabel('Число заболевших за сутки')
ax2.set_ylabel('Число умерших за сутки')
ax3.set_ylabel('Число выздоровевших за сутки')

fig.tight_layout()

Данные по лидирующим странам

In [16]:
def get_top_countries_names(top):
    ranked = confirmed.tail(1).rank(axis='columns', ascending=False)
    top_countries = ranked.columns[(ranked <= top).values[0]]
    top_countries = top_countries[np.argsort(ranked[top_countries].values)[0]]
    return top_countries
In [17]:
top = 10
top_countries = get_top_countries_names(top)
confirmed[top_countries].tail()
Out[17]:
Country/Region US Spain Italy France Germany United Kingdom Turkey Iran China Russia
Дата
2020-04-17 699706 190839 172434 149130 141397 109769 78546 79494 83760 32008
2020-04-18 732197 191726 175925 149149 143342 115314 82329 80868 83787 36793
2020-04-19 759086 198674 178972 154097 145184 121172 86306 82211 83805 42853
2020-04-20 784326 200210 181228 156480 147065 125856 90980 83505 83817 47121
2020-04-21 823786 204178 183957 159297 148291 130172 95591 84802 83853 52763
In [18]:
plot_cases(confirmed[top_countries], title='TOP-{} стран по числу заражённых'.format(top))
Out[18]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f08acbf8c40>

Графики распространения эпидемии во всех странах очень похожи, значит можно попробовать подобрать однотипную модель, описывающую процесс. Посмотрим на то, какая ситуация с числом заражений в день.

In [19]:
plot_cases(confirmed[top_countries].diff(),
           title='Число новых случаев заражения в день в TOP-{} странах'.format(top))
Out[19]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f08acc3a5e0>

Видно, что скорость заражения в некоторых лидирующих странах превзошла аналогичные показатели у Китая. Таким образом, китайская модель довольно благоприятна и оптимистична, но стала она такой благодаря жёстким карантинным мерам.

Теперь посмотрим на летальность по лидирующим странам.

In [20]:
((deaths[top_countries] / confirmed[top_countries]).tail(1) * 100).round(1)
Out[20]:
Country/Region US Spain Italy France Germany United Kingdom Turkey Iran China Russia
Дата
2020-04-21 5.4 10.4 13.4 13.1 3.4 13.4 2.4 6.2 5.5 0.9

Виден очень сильный разброс и довольно большое отличие от среднего по всему миру.

Модель роста числа заболевших

Как на самом деле моделируют эпидемии можно посмотреть в Википедии.

Модифицированные модели под COVID-19 можно пощупать здесь и здесь.

Эти модели хорошо работают, в том числе и для России (с жёстким карантином прогноз очень благоприятный ~10 тыс. заболевших, без него - около миллиона заболевших к середине июня (источник)), но там можно утонуть в параметрах, мы же хотим что-нибудь попроще, но что более или менее будет работать, конечно. По ссылкам загляуть всё же стоит, дабы оценить насколько примитивным будет наш дальнейший подход.

Посмотрим внимательно на Китай и Южную Корею.

In [21]:
ax = plot_cases(confirmed[['China', 'Korea, South']],
                title='COVID-19 в Китае и Южной Корее'.format(top))

В этих странах распространение замедлилось и довольно давно. Обращают на себя две особенности.

  • Во-первых, график является не экспонентой, а похож на логистическу S-кривую, то есть с медленным ростом в начале и в конце, но с быстрым в середине.

  • Во-вторых, в отличие от логистической кривой, наши графики не являются симметричными: правая часть более пологая и протяжённая.

Мы для простоты опустим вторую особенность и перейдём к описанию возможной модели.

Логистическое уравнение и функция

Нас интересует обыкновенное дифференциальное уравнение вида $$\dfrac{d}{dt} y(t) = cy (t) \left( 1 - \dfrac{y(t)}{a} \right),$$ которое называется логистическим или уравнением Ферхюльста.

  • Уравнение связывает скорость $\frac{d}{dt}y(t)$ заражения с максимально возможным числом заражённых $a$. Параметр $c$ пропорционален скорости.

  • Поскольку в правой части уравнения находится и число заражённых $y$ и и максимально возможное число заражённых $a$, то и скорость заражения зависит от них.

  • Когда все будут заражены, то есть $y = a$, выражение в скобках, а значит и вся правая часть, обратится в нуль и рост прекратится.

  • Когда число зараженных очень мало, то есть $y << a$, выражение в скобках $\approx 1$, в этом случае $$\dfrac{d}{dt} y(t) = cy(t) \left( 1 - \dfrac{y(t)}{a} \right) \approx cy(t),$$ что является дифференциальным уравнением для экспоненциального роста. То есть действительно в начале эпидемии рост похож на экспоненциальный.

Решением логистического уравнения служит логистическая функция $$ \boxed{y (t) = \dfrac{a}{1 + b\exp\left(-c t\right)}}$$ где

  • $y (t)$ является общим числом заражённых в момент времени $t$,

  • $\exp(x)$ это экспоненциальная функция $e^x$,

  • $b$ это параметр, связанный с исходным числом заражённых а момент $t=0$ $$y_0 (t = 0) = \dfrac{a}{1 + b},$$

  • $c > 0$ является параметром роста,

  • $e$ это число Эйлера.

Параметр роста $c$ можно попробовать соотнести (не знаю, обоснованно ли) с базовым репродуктивным числом $R_0 = 1 / c$, то есть со средним количеством лиц, напрямую инфицированных больным в течение всего заразного периода при условии попадания его в полностью уязвимую популяцию. Уязвимую здесь означает без иммунитета, как раз наша ситуация. Коротко $R_0$ можно назвать заразностью.

Легко также найти производную логистической функции $$\dfrac{d}{dt}y (t) = \dfrac{abc \exp{(-ct)}}{(1 + b\exp{(-ct)})^2}$$.

Логистическая функция является симметричной, поэтому максимальная скорость заражения будет тогда, когда будет заражена половина от всех тех, кто может заразиться, то есть $$ y(t) = \dfrac{c}{2},\quad t = \dfrac{\ln{b}}{c}.$$

In [22]:
def logistic(t, a, b, c):
    """Logistic growth.
    """
    return a / (1 + b * np.exp(-c * t))

def logistic_deriv(t, a, b, c):
    """Logistic growth derivation.
    """
    return a * b * c * np.exp(-c * t) / (1 + b * np.exp(-c * t))**2

def maximum_rate(b, c):
    return np.log(b) / c

Определение параметров модели

Посмотрим теперь, как наша модель работает на реальных данных.

Нам необходимо определить параметры модели, то есть коэффициенты $a, b ,c$. Сначала приводим необходимые функции.

In [23]:
def fit_model(
    data_model,
    bounds=(0, [10e6, 10e6, 1.0]),
    p0=[50000, 30, 0.2]):
    
    data_model = data_model.dropna()
    
    delta_t0 = (data_model.index.to_julian_date() -
                data_model.index[0].to_julian_date())

    params, cov = curve_fit(
        logistic, delta_t0,
        data_model.values,
        bounds=bounds, p0=p0,
        max_nfev=1e5
    )

    params_stdev = np.sqrt(np.diag(cov))
    
    mrt_days = int(maximum_rate(*params[1:]))
    mrt_date = pd.to_timedelta(
        mrt_days, unit='days') + data_model.index[0]
    mrt = int(logistic_deriv(mrt_days, *params))
    
    out = {'a': int(params[0]), 'a_std': int(params_stdev[0]),
          'b': params[1], 'b_std': params_stdev[1],
          'c': params[2], 'c_std': params_stdev[2],
           'mrt' : mrt, 'mrt_date':mrt_date, 'mrt_days': mrt_days,
           'start_date': data_model.index[0], 'R0' : np.around(1/params[2], 1)
          }
    return pd.Series(out)

def print_report(res):
    print('Начало: {:%Y-%m-%d}'.format(res.start_date))
    print("Максимальное число заражённых:" 
          "a = {:.0f} +\- {:.0f}".format(res.a, res.a_std))
    print('b = {:.2f} +\- {:.2f}'.format(res.b, res.b_std))
    print('c = {:.4f} +\- {:.4f}'.format(res.c, res.c_std))
    print('Заразность R0 = {:.1f}'.format(res.R0))
    print("Макс. скорость заражения {:.0f} чел./сутки " 
          "наступит {:%Y-%m-%d}, через {:.0f} дн. после начала.".format(
              res.mrt, res.mrt_date, res.mrt_days))
    
def plot_prediction(data_model, res, days=90):
    dates_extra = pd.to_timedelta(days, unit='days')
    date_predict = (pd.date_range(data_model.index[0],
                                  data_model.index[0] +
                                  dates_extra))
    date_predict_delta = (date_predict.to_julian_date() -
                          data_model.index[0].to_julian_date())
    predicted = pd.Series(
        logistic(date_predict_delta, res.a, res.b, res.c),
        index=date_predict, name='Логистическая модель')

    data_plot = pd.DataFrame({
        'Логистическая модель': predicted,
        'Реальные данные': data_model})
    
    ax = plot_cases(data_plot)
    ax.axvline(x=res.mrt_date, color='g',
               label='Макс. скорость')
    ax.legend()
    return ax

Теперь построим модель для Китая по полным доступным данным.

In [24]:
country = 'China'
data_model = confirmed[country].copy()

data_model[data_model == 0] = np.nan
data_model.dropna(inplace=True)

res = fit_model(data_model)
print_report(res)
plot_prediction(data_model, res)
Начало: 2020-01-22
Максимальное число заражённых:a = 81917 +\- 254
b = 48.09 +\- 4.64
c = 0.2161 +\- 0.0053
Заразность R0 = 4.6
Макс. скорость заражения 4381 чел./сутки наступит 2020-02-08, через 17 дн. после начала.
Out[24]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f08acd37b80>

Выглядит неплохо. Однако сила моделирования заключается в способности предсказывать. Если бы сейчас было начало февраля, смогли бы мы предсказать общее число заражённых в Китае на сегодняшний день? С какого момента модель начинает давать близкие к реальности результаты?

Для ответа на эти вопросы проанализируем сходимость педсказанных моделью значений к реальными данным за последние сутки.

In [25]:
def plot_convergence(data_model, freq='1D', print_iterations=False):
    min_d = pd.to_timedelta(10, unit='D')
    date_range = pd.date_range(data_model.index[0] +
                               min_d, data_model.index[-1],
                               freq=freq)[1:]

    results = []
    for date in date_range:
        data_i = data_model.loc[data_model.index[0]:date]
        res_i = fit_model(data_i)
        res_i['Дата'] = date
        predict_date = (confirmed.index[-1].to_julian_date() -
                        res_i.start_date.to_julian_date())
        res_i['predicted'] = logistic(
            predict_date, res_i.a, res_i.b, res_i.c)
        results.append(res_i)
        if print_iterations:
            print('{:%Y-%m-%d} {:10.0f} +\- {:5.0f}'.format(
                date, int(res_i.a), int(res_i.a_std)))

    results = pd.DataFrame({'Заболевшие': data_model,
                        'Предсказанное число': pd.DataFrame(results)
                            .set_index('Дата')['predicted']})
    ax = plot_cases(results)
    ax.axhline(int(data_model.tail(1).values), color='r',
               label='Действительное число')
    ax.legend()
    return ax
In [26]:
plot_convergence(data_model)
Out[26]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f08a6efd100>

Порядок предсказанных цифр заражения на сегодня стал близким к действительному в середине февраля, то есть уже ближе к концу роста. Однако в китайских данных был один артефакт в середине, когда скорость резко изменилась, что повлияло на поведение функции.

В целом, однако, предсказательная способность у модели слабая.

Пробуем делать прогноз

Прогноз по лидирующим странам

И всё же попробуем сделать прогноз. У каждой страны есть свои особенности, поэтому модель нужно подбирать для каждого случая в отдельности. Сделаем это для лидирующих стран по числу заражённых.

In [27]:
top = 20
top_countries_predict = confirmed[get_top_countries_names(top)].copy()
top_countries_predict[top_countries_predict < 10] = np.nan
top_countries_predict = top_countries_predict.rolling(4).mean()

top_countries_predict = top_countries_predict.apply(
    lambda x: fit_model(x)).transpose()
top_countries_predict['mrt_date'] = (top_countries_predict['mrt_date']
                                     .dt.strftime('%Y-%m-%d'))

print('Прогноз на {:%Y-%m-%d}:'.format(today))
top_countries_predict
Прогноз на 2020-04-22:
Out[27]:
a a_std b b_std c c_std mrt mrt_date mrt_days start_date R0
Country/Region
US 853215 12360 39024.7 6927.22 0.166743 0.00328277 35526 2020-04-09 63 2020-02-06 6
Spain 197286 2273 265.63 32.2933 0.167814 0.00430852 8272 2020-04-02 33 2020-02-29 6
Italy 181233 2347 100.902 9.91232 0.132031 0.00350346 5958 2020-03-29 34 2020-02-24 7.6
France 211094 8167 1596.43 259.097 0.120844 0.00389203 6377 2020-04-12 61 2020-02-11 8.3
Germany 147346 977 14083.7 1846.94 0.168014 0.00253985 6157 2020-04-01 56 2020-02-05 6
United Kingdom 147216 2077 1082.53 84.0787 0.157104 0.00238641 5774 2020-04-11 44 2020-02-27 6.4
Turkey 107316 2489 83.239 5.42232 0.178128 0.00439246 4753 2020-04-12 24 2020-03-19 5.6
Iran 91928 1164 77.7522 4.50622 0.114045 0.00215876 2620 2020-04-02 38 2020-02-24 8.8
China 81871 206 33.3347 2.34069 0.213686 0.00419122 4365 2020-02-10 16 2020-01-25 4.7
Russia 174180 9888 3346.15 85.5001 0.164108 0.00147101 7136 2020-04-27 49 2020-03-09 6.1
Brazil 63974 3238 260.969 17.9198 0.139848 0.00385383 2229 2020-04-17 39 2020-03-09 7.2
Belgium 43242 860 137.683 12.9794 0.149316 0.00404618 1605 2020-04-07 32 2020-03-06 6.7
Canada 43104 1220 786.735 114.452 0.151095 0.00458441 1628 2020-04-11 44 2020-02-27 6.6
Netherlands 36568 768 102.009 9.24193 0.13667 0.00388722 1245 2020-04-06 33 2020-03-04 7.3
Switzerland 27531 262 97.9644 10.0528 0.171052 0.00441641 1171 2020-03-29 26 2020-03-03 5.8
Portugal 21376 341 110.208 11.3677 0.168267 0.00479283 893 2020-04-05 27 2020-03-09 5.9
India 26886 767 1031.58 74.509 0.168287 0.00300773 1130 2020-04-17 41 2020-03-07 5.9
Peru 21422 579 1441.12 166.139 0.214262 0.00495189 1135 2020-04-15 33 2020-03-13 4.7
Ireland 26046 1598 162.305 11.1529 0.128506 0.00424453 835 2020-04-17 39 2020-03-09 7.8
Sweden 19303 361 96.2147 3.50638 0.114815 0.00176782 553 2020-04-11 39 2020-03-03 8.7

Напоминаю, что a это максимальное число заражённых, mrt_date это дата (спустя mrt_days дней с начала моделирования start_date), когда скорость заражения будет максимальной и равна mrt заражённых в день.

Вообще говоря, некоторые решения могут получиться нестабильными, это видно по большому значению a_std или по завышенному значению a, как и других коэффициентов. Кроме того, решения очень чувствительны к ошибкам исходных данных. Это один из недостатков принятого подхода и модели.

Кроме того, если дата с максимальным ростом mrt_date очень близка к последней дате, на которую вообще имеются данные, то опять же вероятны неточности в решении.

Решения будут тем лучше, чем ближе рост к завершению, то есть при приближениии к верхней асимптоте. Понятно, что с точки зрения предсказания это слабое место. Более сложные и реалистичные модели позволяют моделировать процессы распространения эпидемий и на начальных этапах, принимая во внимание многие факторы. Логистическая функция такими свойствами не обладает ввиду своей простоты.

R0 это заразность, то есть сколько один человек заразит других. Для топовых стран:

In [28]:
'Базовое репродуктивное число COVID-19 R0 = {:.1f}'.format(top_countries_predict.R0.median())
Out[28]:
'Базовое репродуктивное число COVID-19 R0 = 6.2'

Для сравнения:

  • R0 сезонного гриппа — 0.9 – 2.1;
  • R0 гриппа «Испанки» — 1.4 – 2.8;
  • R0 кори — 12-18 .

Наш коронавирус на данный момент весьма заразен, куда больше любого из гриппов.

Высокая заразность и высокая летальность довольно очевидно должны приводить к большому числу жертв в абсолютной мере.

Прогноз на Россию

Теперь сделаем прогноз на Россию. Мы, как и прежде для других стран, отсекаем период, когда заболевших было меньше десяти. Сделано это для того, чтобы исключить случайные данные из анализа. Несколько выявленных случаев в самом начале не позволяют делать каких-то прогнозов, но после десяти случаев распространение начинает хорошо укладываться в логистическую модель.

In [29]:
country = 'Russia'
data_model = confirmed[country].copy()

data_model[data_model < 10] = np.nan
data_model.dropna(inplace=True)

res = fit_model(data_model)
print_report(res)
plot_prediction(data_model, res)
Начало: 2020-03-06
Максимальное число заражённых:a = 165222 +\- 9170
b = 4365.29 +\- 129.03
c = 0.1659 +\- 0.0019
Заразность R0 = 6.0
Макс. скорость заражения 6840 чел./сутки наступит 2020-04-25, через 50 дн. после начала.
Out[29]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f08a6d60070>

Заодно проверим сходимость, как довольно неплохой индикатор надёжности нашей модели.

In [30]:
plot_convergence(data_model)
Out[30]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f08a6bfeac0>