Quando a pandemia diminuirá? Avaliando em Python com Pandas

imagem
Olá a todos.

Vi vários painéis no COVID-19, mas ainda não encontrei o principal - prever o tempo da recessão da epidemia. Portanto, escrevi um pequeno script Python. Ele pega dados das tabelas da OMS no Github, estabelece por país e desenha linhas de tendência. E ele faz previsões com base nelas - quando em cada país do TOP 20 pelo número de casos de COVID-19, pode-se esperar um declínio nas infecções. Admito que não sou especialista no campo da epidemiologia. Os cálculos abaixo são baseados em dados publicamente disponíveis e bom senso. Todos os detalhes estão em corte.

Atualização de 10/04/2020 - a tabela principal e os gráficos foram atualizados.

Atualização 16/04/2020 - Usuárioh3ckslfez uma atualização dos gráficos e os publicou em seu site .

Atualização de 29/04/2020 - Agora ficou aparente que as curvas do número diário de infecções são muito diferentes em diferentes países. E parece depender muito da extensão da infecção. Uma curva ideal em forma de sino modelada na Coréia do Sul provavelmente é uma exceção à regra. Uma forma mais comum é o aumento acentuado do pico e a diminuição subsequente que é mais prolongada ao longo do tempo. Portanto, na realidade, o declínio na incidência durará mais do que o previsto por esse modelo no início de abril.

Minuto de Cuidados com OVNI


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



, .

, , .

: |


Inicialmente, para avaliar o momento da epidemia, usei painéis publicamente disponíveis. Um dos primeiros foi o site do Centro de Ciência e Engenharia de Sistemas da Universidade Johns Hopkins.

imagem

Ele é muito bonito com sua simplicidade ameaçadora. Até recentemente, não permitia construir a dinâmica do incremento de infecções por dias, mas era corrigida. E é curioso, pois permite que você veja a propagação de uma pandemia em um mapa do mundo. Dada a gama de preto e vermelho do painel, não é recomendável assistir por um longo tempo. Eu acho que isso pode estar cheio de ocorrência de ataques de ansiedade, se transformando em várias formas de pânico.

Para manter meu dedo no pulso, gostei mais da página sobre o COVID-19no recurso Worldometer. Ele contém informações sobre os países na forma de uma tabela:

imagem

Quando você clica no país desejado, caímos em informações mais detalhadas, incluindo curvas diárias e cumulativas para infectados / recuperados / mortos.

Existem outros painéis. Por exemplo, para informações detalhadas sobre nosso país, basta digitar “COVID-19” na linha de pesquisa Yandex e você encontrará o seguinte:

imagem

Bem, talvez paremos por aí. Não nesses três painéis, nem em vários outros, não consegui encontrar informações com previsões quando, finalmente, a pandemia diminuirá.

Um pouco sobre gráficos


Atualmente, a infecção está apenas ganhando força e os mais interessantes são os gráficos com um aumento diário no número de casos. Aqui está um exemplo para os EUA:

imagem

pode-se ver que o número de pessoas infectadas está crescendo todos os dias. Nesses gráficos, é claro, não estamos falando de todas as pessoas infectadas (não é possível determinar o número exato delas), mas apenas de casos confirmados. Mas, por uma questão de brevidade, a seguir chamaremos esses infectados confirmados de simplesmente "infectados".

Como é uma pandemia domesticada ? Um exemplo aqui é um gráfico da Coréia do Sul:

imagem

Pode-se observar que o incremento diário dos infectados cresceu primeiro, depois atingiu um pico, diminuiu e se fixou em um determinado nível constante (cerca de 100 pessoas por dia). Esta não é uma vitória completa sobre o vírus, mas um sucesso significativo. Isso permitiu que o país iniciasse a economia (embora, segundo eles, os coreanos conseguissem não desacelerá-la) e retornasse a uma vida menos normal.

Pode-se supor que a situação em outros países se desenvolva de maneira semelhante. Ou seja, o crescimento no primeiro estágio será substituído pela estabilização e, em seguida, o número de pessoas doentes diminuirá todos os dias. O resultado é uma curva em forma de sino. Vamos olhar para os picos das curvas dos países mais "infectados".

Baixar e processar dados


Os dados de distribuição do coronavírus são atualizados continuamente aqui . As tabelas de origem contêm curvas cumulativas, elas podem ser selecionadas e pré-processadas com um pequeno script.

Como isso
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()


Portanto, você pode obter as mesmas curvas dos painéis:

imagem

há um pouco mais no script que não é necessário para exibir o gráfico, mas será necessário nas próximas etapas. Por exemplo, eu imediatamente formei uma lista de 20 países com o número máximo de infectados hoje. A propósito - 90% de todos os infectados.

Ir em frente. Os números do aumento diário de doentes são bastante "barulhentos". Portanto, seria bom suavizar o gráfico. Adicione uma linha de tendência aos gráficos diários (usei uma média móvel centralizada). Acontece de alguma forma assim:

imagem

A tendência é mostrada em preto. Esta é uma curva bastante suave com um único pico. Resta encontrar o início do surto da doença (seta vermelha) e o pico de incidência (verde). Com um pico, tudo é simples - é o máximo em dados suavizados. Além disso, se o máximo cair no ponto mais à direita do gráfico, o pico ainda não foi ultrapassado. Se o máximo permanecer à esquerda - provavelmente o pico permaneceu no passado.

O início da explosão pode ser encontrado de diferentes maneiras. Suponha, por simplicidade, que esse é o momento em que o número de infectados por dia começa a exceder 1% do número máximo de infectados por dia.

O gráfico é bastante simétrico. Ou seja, a duração desde o início da epidemia até o pico é aproximadamente igual à duração do pico até o declínio.Assim, se encontrarmos o número de dias desde o início até o pico, aproximadamente o mesmo número de dias terá que esperar até a epidemia desaparecer .

Vamos colocar toda essa lógica no script a seguir.

Em um tal
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


Na saída, temos exatamente essa tabela para os 20 principais países.

Atualização 10.04.2020: Até o momento, a Rússia não estava entre os vinte primeiros, mas em 7 de abril apareceu em 20º lugar. O dia 10 de abril saiu no dia 18. A tabela também inclui as datas das medidas de quarentena em diferentes países.

imagem

As seguintes colunas estão na tabela:
total_confirmed - o número total de infectados no país;
last_day_conf - o número de infectados no último dia;
mortalidade,% - taxa de mortalidade (número de mortos / número de infectados);
trend_max - tendência máxima;
start_date - data em que a epidemia começou no país;
data_pico - data do pico;
peak_passed - sinalizador de pico (se True - pico aprovado);
days_to_peak - quantos dias se passaram desde o início até o pico;
end_date - data final da epidemia.

Atualização 10.04.2020: o pico de incidência foi atingido em 14 países em 20. Além disso, em média, desde o início das infecções em massa até o pico, leva em média 25 dias: de

imagem

acordo com a lógica acima, até o final de abril, a situação terá que se normalizar em quase todos os países em consideração . Como será, só o tempo dirá.

Atualização 10.04.2020: Você pode ver que os gráficos dos países europeus, em contraste com o gráfico da Coréia do Sul, apresentam uma recessão mais suave após o pico.

Há outro script que permite exibir gráficos para cada um dos países.

este
@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()


Aqui está a situação atual na Rússia (atualização de 10/04/2020):

imagem

Infelizmente, o pico ainda não foi ultrapassado, por isso é difícil fazer previsões quanto ao momento da diminuição da incidência. Mas, como o surto dura 26 dias, podemos esperar que durante a semana tenhamos um pico e a incidência comece a diminuir. Atrevo-me a sugerir que, no início de maio, a situação está normalizando (sempre fui otimista, devo dizer).

Muito obrigado pela leitura até o fim. Seja saudável e a força estará conosco. Abaixo estão gráficos para o resto dos vinte países em ordem decrescente do número de infectados. Se você precisar de dados mais recentes - poderá executar os scripts fornecidos no texto, tudo será recalculado para a data atual.

Atualização 10.04.2020 - os gráficos são atualizados.

EUA
image

Espanha
image

Itália
image

Alemanha
image

França
image

China
image

Eu corri
image

Reino Unido
image

Peru
image

Suíça
image

Bélgica
image

Países Baixos
image

Canadá
image

Áustria
image

Portugal
image

Brasil
image

Coreia do Sul
image

Israel
image

Suécia
image

Noruega
image

All Articles