¿Cuándo disminuirá la pandemia? Evaluando en Python con Pandas

imagen
Hola a todos.

Vi varios paneles en COVID-19, pero aún no he encontrado lo principal: el pronóstico del momento de la recesión de la epidemia. Por lo tanto, escribí un pequeño script de Python. Toma datos de las tablas de la OMS sobre Github, los presenta por país y dibuja líneas de tendencia. Y hace predicciones basadas en ellos: cuando en cada país del TOP 20 por el número de casos de COVID-19, se puede esperar una disminución de las infecciones. Lo admito, no soy un experto en el campo de la epidemiología. Los cálculos a continuación se basan en datos disponibles públicamente y sentido común. Todos los detalles están bajo corte.

Actualización del 10/04/2020: la tabla principal y los gráficos se han actualizado.

Actualización 16/04/2020 - Usuarioh3ckslrealizó una actualización de los gráficos y los publicó en su sitio web .

Actualización del 29/04/2020: ahora se ha hecho evidente que las curvas de la cantidad diaria de infecciones son muy diferentes en diferentes países. Y parece depender en gran medida de la extensión de la infección. Una curva ideal en forma de campana modelada en Corea del Sur es más probable una excepción a la regla. Una forma más común es con un fuerte aumento a un pico y una disminución posterior que se extiende más con el tiempo. Por lo tanto, en realidad, la disminución de la incidencia durará más de lo previsto por este modelo a principios de abril.

Minuto de atención ovni


COVID-19 — , SARS-CoV-2 (2019-nCoV). — , /, .



, .

, , .

: |


Al principio, para evaluar el momento de la epidemia, utilicé paneles disponibles públicamente. Uno de los primeros fue el sitio web del Centro de Ciencia e Ingeniería de Sistemas de la Universidad Johns Hopkins.

imagen

Es bastante bonito con su siniestra simplicidad. Hasta hace poco, no permitía construir la dinámica del incremento de infecciones por días, pero se corrigió. Y es curioso que te permita ver la propagación de una pandemia en un mapa mundial. Dada la gama negra y roja del tablero, no se recomienda mirarlo por mucho tiempo. Creo que esto puede estar plagado de ataques de ansiedad y convertirse en varias formas de pánico.

Para mantener el dedo en el pulso me gustó la página sobre COVID-19 másen el recurso Worldometer. Contiene información sobre los países en forma de tabla:

imagen

cuando hace clic en el país deseado, caemos en información más detallada al respecto, incluidas las curvas diarias y acumulativas para infectados / recuperados / muertos.

Hay otros tableros de instrumentos. Por ejemplo, para obtener información detallada sobre nuestro país, simplemente escriba "COVID-19" en la línea de búsqueda de Yandex y encontrará esto:

imagen

Bueno, tal vez esto se detendrá. No en estos tres paneles, ni en muchos otros, no pude encontrar información con pronósticos cuando, finalmente, la pandemia disminuirá.

Un poco sobre gráficos


Actualmente, la infección solo está ganando impulso y lo más interesante son los gráficos con un aumento diario en el número de casos. Aquí hay un ejemplo para los Estados Unidos:

imagen

se puede ver que la cantidad de personas infectadas crece cada día. En tales gráficos, por supuesto, no estamos hablando de todos aquellos que se han infectado (no es posible determinar su número exacto), sino solo de casos confirmados. Pero por brevedad, en lo sucesivo llamaremos a estos infectados confirmados simplemente "infectados".

¿Cómo es una pandemia domesticada ? Un ejemplo aquí es un gráfico de Corea del Sur:

imagen

Se puede ver que el incremento diario de los infectados primero creció, luego alcanzó un pico, bajó y se fijó en un cierto nivel constante (aproximadamente 100 personas por día). Esta no es una victoria completa sobre el virus, sino un éxito significativo. Esto permitió al país lanzar la economía (aunque, dicen, los coreanos lograron no desacelerarla) y volver a una vida menos normal.

Se puede suponer que la situación en otros países se desarrollará de manera similar. Es decir, el crecimiento en la primera etapa será reemplazado por la estabilización, y luego el número de personas enfermas disminuirá todos los días. El resultado es una curva en forma de campana. Busquemos los picos de las curvas de los países más "infectados".

Descargar y procesar datos


Los datos de distribución de coronavirus se actualizan de forma continua aquí . Las tablas de origen contienen curvas acumulativas, se pueden recoger y preprocesar con un pequeño script.

Me gusta esto
import pandas as pd
import ipywidgets as widgets
from ipywidgets import interact
import matplotlib.pyplot as plt

pd.set_option('display.max_columns', None)

#  
url = 'https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/'
confirmed = pd.read_csv(url + 'time_series_covid19_confirmed_global.csv', sep = ',')
deaths = pd.read_csv(url + 'time_series_covid19_deaths_global.csv', sep = ',')
recovered = pd.read_csv(url + 'time_series_covid19_recovered_global.csv', sep = ',')

#   -   'dd.mm.yy'
new_cols = list(confirmed.columns[:4]) + list(confirmed.columns[4:].map(lambda x: '{0:02d}.{1:02d}.{2:d}'.format(int(x.split(sep='/')[1]), int(x.split(sep='/')[0]), int(x.split(sep='/')[2]))))
confirmed.columns = new_cols
recovered.columns = new_cols
deaths.columns = new_cols

#     
confirmed_daily = confirmed.copy()
confirmed_daily.iloc[:,4:] = confirmed_daily.iloc[:,4:].diff(axis=1)
deaths_daily = deaths.copy()
deaths_daily.iloc[:,4:] = deaths_daily.iloc[:,4:].diff(axis=1)
recovered_daily = recovered.copy()
recovered_daily.iloc[:,4:] = recovered_daily.iloc[:,4:].diff(axis=1)

#       
smooth_conf_daily = confirmed_daily.copy()
smooth_conf_daily.iloc[:,4:] = smooth_conf_daily.iloc[:,4:].rolling(window=8, min_periods=2, center=True, axis=1).mean()
smooth_conf_daily.iloc[:,4:] = smooth_conf_daily.iloc[:,4:].round(1)

#   ,    
last_date = confirmed.columns[-1]

#  20       
confirmed_top = confirmed.iloc[:, [1, -1]].groupby('Country/Region').sum().sort_values(last_date, ascending = False).head(20)
countries = list(confirmed_top.index)

# ,      20 
conf_tot_ratio = confirmed_top.sum() / confirmed.iloc[:, 4:].sum()[-1]

#    
# countries.append('Russia')

#       ,   
l1 = 'Infected at ' + last_date + ' - ' + str(confirmed.iloc[:, 4:].sum()[-1])
l2 = 'Recovered at ' + last_date + ' - ' + str(recovered.iloc[:, 4:].sum()[-1])
l3 = 'Dead at ' + last_date + ' - ' + str(deaths.iloc[:, 4:].sum()[-1])

#      
fig, ax = plt.subplots(figsize = [16,6])
ax.plot(confirmed.iloc[:, 4:].sum(), '-', alpha = 0.6, color = 'orange', label = l1)
ax.plot(recovered.iloc[:, 4:].sum(), '-', alpha = 0.6, color = 'green', label = l2)
ax.plot(deaths.iloc[:, 4:].sum(), '-', alpha = 0.6, color = 'red', label = l3)

ax.legend(loc = 'upper left', prop=dict(size=12))
ax.xaxis.grid(which='minor')
ax.yaxis.grid()
ax.tick_params(axis = 'x', labelrotation = 90)
plt.title('COVID-19 in all countries. Top 20 countries consists {:.2%} of total confirmed infected cases.'.format(conf_tot_ratio[0]))
plt.show()


Por lo tanto, puede obtener las mismas curvas que en los paneles:

imagen

hay un poco más en el script que no es necesario para mostrar el gráfico, pero será necesario en los próximos pasos. Por ejemplo, inmediatamente formé una lista de 20 países con el número máximo de infectados hoy. Por cierto, este es el 90% de todos los infectados.

Siga adelante. Las cifras del aumento diario en los enfermos son bastante "ruidosas". Por lo tanto, sería bueno suavizar el gráfico. Agregue una línea de tendencia a los gráficos diarios (utilicé una media móvil centrada). Resulta de alguna manera así:

imagen

La tendencia se muestra en negro. Esta es una curva bastante suave con un solo pico. Queda por encontrar el comienzo del brote de la enfermedad (flecha roja) y la incidencia máxima (verde). Con un pico, todo es simple: este es el máximo en datos suavizados. Además, si el máximo cae en el extremo derecho del gráfico, entonces el pico aún no se ha superado. Si el máximo permanece a la izquierda, lo más probable es que el pico se haya mantenido en el pasado.

El inicio de la explosión se puede encontrar de diferentes maneras. Supongamos por simplicidad que este es el momento en el que el número de infectados por día comienza a exceder el 1% del número máximo de infectados por día.

El gráfico es bastante simétrico. Es decir, la duración desde el inicio de la epidemia hasta un pico es aproximadamente igual a la duración desde el pico hasta la disminución.Por lo tanto, si encontramos el número de días desde el principio hasta el pico, aproximadamente el mismo número de días tendrá que esperar hasta que la epidemia disminuya .

Pondremos toda esta lógica en el siguiente script.

En semejante
from datetime import timedelta, datetime

#     20  + .
confirmed_top = confirmed_top.rename(columns={str(last_date): 'total_confirmed'})
dates = [i for i in confirmed.columns[4:]]

for country in countries:
    
    #       ,      ,  
    confirmed_top.loc[country, 'total_confirmed'] = confirmed.loc[confirmed['Country/Region'] == country,:].sum()[4:][-1]
    confirmed_top.loc[country, 'last_day_conf'] = confirmed.loc[confirmed['Country/Region'] == country,:].sum()[-1] - confirmed.loc[confirmed['Country/Region'] == country,:].sum()[-2]
    confirmed_top.loc[country, 'mortality, %'] = round(deaths.loc[deaths['Country/Region'] == country,:].sum()[4:][-1]/confirmed.loc[confirmed['Country/Region'] == country,:].sum()[4:][-1]*100, 1)
    
    #       .
    smoothed_conf_max = round(smooth_conf_daily[smooth_conf_daily['Country/Region'] == country].iloc[:,4:].sum().max(), 2)
    peak_date = smooth_conf_daily[smooth_conf_daily['Country/Region'] == country].iloc[:,4:].sum().idxmax()
    peak_day = dates.index(peak_date)
    
    #      ,      1%    
    start_day = (smooth_conf_daily[smooth_conf_daily['Country/Region'] == country].iloc[:,4:].sum() < smoothed_conf_max/100).sum()
    start_date = dates[start_day-1]
    
    #     
    confirmed_top.loc[country, 'trend_max'] = smoothed_conf_max
    confirmed_top.loc[country, 'start_date'] = start_date
    confirmed_top.loc[country, 'peak_date'] = peak_date
    confirmed_top.loc[country, 'peak_passed'] = round(smooth_conf_daily.loc[smooth_conf_daily['Country/Region'] == country, last_date].sum(), 2) != smoothed_conf_max
    confirmed_top.loc[country, 'days_to_peak'] = peak_day - start_day
    
    #   ,    
    if confirmed_top.loc[country, 'peak_passed']:
         confirmed_top.loc[country, 'end_date'] = (datetime.strptime(confirmed_top.loc[country, 'peak_date']+'20', "%d.%m.%Y").date() + timedelta(confirmed_top.loc[country, 'days_to_peak'])).strftime('%d.%m.%Y')

#    
confirmed_top.loc['China', 'start_date'] = '22.01.20'
confirmed_top


En la salida, obtenemos tal tabla para los 20 principales países.

Actualización 10.04.2020: al momento de escribir, Rusia no estaba entre los veinte primeros, pero el 7 de abril apareció en el lugar 20. 10 de abril salió el 18. La tabla también incluye las fechas de las medidas de cuarentena en diferentes países.

imagen

Las siguientes columnas están en la tabla:
total_confirmado - el número total de infectados en el país;
last_day_conf: el número de infectados en el último día;
mortalidad,% - tasa de mortalidad (número de muertos / número de infectados);
trend_max - tendencia máxima;
start_date: fecha en que comenzó la epidemia en el país;
peak_date - fecha pico;
peak_passed: indicador de pico (si es verdadero: se ha superado el pico);
days_to_peak: cuántos días han pasado desde el principio hasta el pico;
end_date: fecha de finalización de la epidemia.

Actualización 10.04.2020: el pico de incidencia se alcanzó en 14 países de 20. Además, en promedio, desde el comienzo de las infecciones masivas hasta el pico, pasa un promedio de 25 días:

imagen

según la lógica anterior, a fines de abril, la situación tendrá que normalizarse en casi todos los países considerados . Como será, solo el tiempo lo dirá.

Actualización 10.04.2020: puede ver que los gráficos de los países europeos, en contraste con el gráfico de Corea del Sur, tienen una recesión más suave después del pico.

Hay otro script que le permite mostrar gráficos para cada uno de los países.

Esta
@interact
def plot_by_country(country = countries):
    
    #   
    l1 = 'Infected at ' + last_date + ' - ' + str(confirmed.loc[confirmed['Country/Region'] == country,:].sum()[-1])
    l2 = 'Recovered at ' + last_date + ' - ' + str(recovered.loc[recovered['Country/Region'] == country,:].sum()[-1])
    l3 = 'Dead at ' + last_date + ' - ' + str(deaths.loc[deaths['Country/Region'] == country,:].sum()[-1])
    
    #        
    df = pd.DataFrame(confirmed_daily.loc[confirmed_daily['Country/Region'] == country,:].sum()[4:])
    df.columns = ['confirmed_daily']
    df['recovered_daily'] = recovered_daily.loc[recovered_daily['Country/Region'] == country,:].sum()[4:]
    df['deaths_daily'] = deaths_daily.loc[deaths_daily['Country/Region'] == country,:].sum()[4:]
    df['deaths_daily'] = deaths_daily.loc[deaths_daily['Country/Region'] == country,:].sum()[4:]
    df['smooth_conf_daily'] = smooth_conf_daily.loc[smooth_conf_daily['Country/Region'] == country,:].sum()[4:]
    
    #  
    fig, ax = plt.subplots(figsize = [16,6], nrows = 1)
    plt.title('COVID-19 dinamics daily in ' + country)
    
    #      ,   
    ax.bar(df.index, df.confirmed_daily, alpha = 0.5, color = 'orange', label = l1)
    ax.bar(df.index, df.recovered_daily, alpha = 0.6, color = 'green', label = l2)
    ax.bar(df.index, df.deaths_daily, alpha = 0.7, color = 'red', label = l3)
    ax.plot(df.index, df.smooth_conf_daily, alpha = 0.7, color = 'black')
    
    #     .
    start_date = confirmed_top[confirmed_top.index == country].start_date.iloc[0]
    start_point = smooth_conf_daily.loc[smooth_conf_daily['Country/Region'] == country, start_date].sum()
    ax.plot_date(start_date, start_point, 'o', alpha = 0.7, color = 'black')
    shift = confirmed_top.loc[confirmed_top.index == country, 'trend_max'].iloc[0]/40
    plt.text(start_date, start_point + shift, 'Start at ' + start_date, ha ='right', fontsize = 20)
    
    peak_date = confirmed_top[confirmed_top.index == country].peak_date.iloc[0]
    peak_point = smooth_conf_daily.loc[smooth_conf_daily['Country/Region'] == country, peak_date].sum()
    ax.plot_date(peak_date, peak_point, 'o', alpha = 0.7, color = 'black')
    plt.text(peak_date, peak_point + shift, 'Peak at ' + peak_date, ha ='right', fontsize = 20)
    
    ax.xaxis.grid(False)
    ax.yaxis.grid(False)
    ax.tick_params(axis = 'x', labelrotation = 90)
    ax.legend(loc = 'upper left', prop=dict(size=12))
    
    #         .
    max_pos = max(df['confirmed_daily'].max(), df['recovered_daily'].max())
    if confirmed_top[confirmed_top.index == country].peak_passed.iloc[0]:
        estimation = 'peak is passed - end of infection ' + confirmed_top[confirmed_top.index == country].end_date.iloc[0]
    else:
        estimation = 'peak is not passed - end of infection is not clear'
    plt.text(df.index[len(df.index)//2], 3*max_pos//4, country, ha ='center', fontsize = 50)
    plt.text(df.index[len(df.index)//2], 2*max_pos//3, estimation, ha ='center', fontsize = 20)
    
    #plt.savefig(country + '.png', bbox_inches='tight', dpi = 75)
    plt.show()


Aquí está la situación actual en Rusia (actualización del 10/04/2020):

imagen

Desafortunadamente, el pico aún no se ha superado, por lo que es difícil hacer predicciones sobre el momento de la disminución de la incidencia. Pero dado que el brote dura 26 días, podemos esperar que durante la semana veremos un pico y la incidencia comenzará a disminuir. Me aventuraría a sugerir que a principios de mayo la situación se está normalizando (siempre he sido optimista, debo decir).

Muchas gracias por leer hasta el final. Sé saludable y la fuerza estará con nosotros. A continuación se muestran gráficos para el resto de los veinte países en orden descendente del número de infectados. Si necesita datos más recientes, puede ejecutar los scripts que figuran en el texto, todo se volverá a calcular para la fecha actual.

Actualización 10.04.2020: los gráficos se actualizan.

Estados Unidos
image

España
image

Italia
image

Alemania
image

Francia
image

China
image

Corrí
image

Reino Unido
image

Turquía
image

Suiza
image

Bélgica
image

Países Bajos
image

Canadá
image

Austria
image

Portugal
image

Brasil
image

Corea del Sur
image

Israel
image

Suecia
image

Noruega
image

All Articles