# Modelo matemático y computacional para predecir el comportamiento de la Covid-19

***Elaborado por:*** ***Carlos Alberto Álvarez Henao, I.C. DS.C*** ([carlosalvarezh@gmail.com](mailto:carlosalvarezh@gmail.com))

Abril 25, 2020 - Medellín, Colombia

***Nota:*** basado principalmente en los artículos publicados en:

- [Social Distancing to Slow the Coronavirus -  Modeling the flattening of the COVID-19 peaks](https://towardsdatascience.com/social-distancing-to-slow-the-coronavirus-768292f04296 "Towardsdatascience"): By Christian Hubbs, in Towardsdatascience.com, Mar 12, 2020

- [Why outbreaks like coronavirus spread exponentially, and how to “flatten the curve”](https://www.washingtonpost.com/graphics/2020/world/corona-simulator/): By Harry Stevens, in The Washington Post, Mar 14, 2020

- [Prevendo o crescimento de casos de COVID-19 (coronavírus) no Brasil com análise de dados, gráficos e equações! — Partes 1 e 2](https://medium.com/@caiquecoelho/prevendo-o-crescimento-de-casos-de-covid-19-coronavirus-no-brasil-com-an%C3%A1lise-de-dados-gr%C3%A1ficos-33ee525b62f8): By Caíque Coelho, in Towardsdatascience.com. Mar 19

Y tres artículos realizados por [Henri Froese](https://towardsdatascience.com/@hf2000510), en [Towardsdatascience.com](https://towardsdatascience.com/)

- [Infectious Disease Modelling: Understanding the models that are used to model Coronavirus](https://towardsdatascience.com/infectious-disease-modelling-part-i-understanding-sir-28d60e29fdfc): By Henri Froese, in Towardsdatascience.com, Apr 06,2020

- [Infectious Disease Modelling: Beyond the Basic SIR Model](https://towardsdatascience.com/infectious-disease-modelling-part-i-understanding-sir-28d60e29fdfc): By Henri Froese, in Towardsdatascience.com, Apr 11,2020

- [Infectious Disease Modelling: Fit Your Model to Coronavirus Data](https://towardsdatascience.com/infectious-disease-modelling-fit-your-model-to-coronavirus-data-2568e672dbc7): By Henri Froese, in Towardsdatascience.com, Apr 22,2020

y muchas otras lecturas realizadas a lo largo de su elaboración, que sin merecerlo, se me fueron pasando para sus respectivos créditos. Disculpas a todos ellos ahora "anónimos".

# Distanciamiento social como estrategia para ralentizar la propagación

## Contextualización

Como es de ámplio conocimiento de todos hoy en día, la Covid-19 ha crecido rápidamente en todo el mundo. La gran mayoría de paises han tomado medidas drásticas frente a la movilidad de sus ciudadanos. Estas medidas se toman con la intención de retrasar la propagación (no detenerla) de la enfermedad. Este tipo de estrategia se denomina *Distanciamiento Social*.

La idea detrás de esta política de salud pública es reducir el contacto *persona-a-persona* para que sea menos probable la propagación de la enfermedad y la capacidad de los sistemas de salud local no se saturen, ayudando a garantizar la adecuada atención a la población enferma y disminyendo la cantidad de decesos. Los efectos de una política en este sentido se pueden observar en la siguiente gráfica.

## Distanciamiento social - Definición

“Conjunto de medidas no farmacéuticas de control de infecciones, con el objetivo de detener o desacelerar la propagación de una enfermedad contagiosa.”

***Levítico, 13:46:***

***“Todo el tiempo que la llaga estuviere en él, será inmundo; estará impuro, y habitará solo; fuera del campamento será su morada.”***

Se han establecido colonias de leprosos y lazaretos como medios para impedir la transmisión de la lepra y otras enfermedades infecciosas.

***Lazareto - Ancona, Italia, 1700's***

***Agua de Dios, Colombia, 1870's***

# Modelos matemáticos

***“Essentially, all models are wrong, but some are useful”*** — George Box, British mathematician

Los modelos matemáticos son aproximaciones, bien sustentadas y justificadas de la realidad, y en este contexto intentan brindar elementos a la toma de decisiones de políticas públicas para intentan aplanar la curva de propagación de la enfermedad.

## Simulación física de dispersión de gotículas

Veamos primer una serie de simulaciones computacionales de la dispersión de partículas producto de la tos humana empleando técnicas de la [CFD](https://en.wikipedia.org/wiki/Computational_fluid_dynamics "CFD") (Dinámica de Fluidos Computaconal) y empleando un software comercial

```python
from IPython.display import IFrame

URL = 'https://www.ansys.com/about-ansys/covid-19-simulation-insights'
IFrame(src = URL, width = 980, height = 500)
```

## Propagación del contagio por contacto

Recientemente el Washington post publicó un artículo muy didadctico explicando como podria presentar el contagio en una población bajo diferentes escenarios. Vale la pena ver.

```python
URL = 'https://www.washingtonpost.com/graphics/2020/world/corona-simulator-spanish/'
IFrame(src = URL, width = 980, height = 500)
```

Información fiable, de calidad, y en abundante cantidad es el "santo grial" de la Ciencia de Datos. Por esto, es necesario contar con el conocimiento de los lugares a donde se debe buscar y recolectar.

## Modelos matemáticos de propagación

En las siguientes secciones se abordará el modelo *S.I.R.* y sus variaciones teniendo en consideración diferentes parámetos de análisis.

# S.I.R.

una estrategia empleada para modelar enfermedades contagiosas, que pueden propagarse entre los miembros de una población, es el modelo comportamental. Este tipo de modelo separa una población en diferentes comportamientos. La idea es obtener informacion acerca de qué tan rápido sucede dicha propagación, qué proporción de la población se infecta, qué proporción moriría, etc. Diferentes comportamientos se pueden indicar como (modelo básico):

- **S**usceptible (aún puede estar infectado, "sano")

- **I**nfectado

- **R**ecuperado (ha sido infectado y no puede volver a infectarse)

Para definir las variables involucradas en este simple modelo veamos el siguiente ejemplo.

## Ejemplo

Supóngase que se tiene una población de $1000$ habitantes ($N=1000$) y se sabe que $400$ de ellas se han infectado en un tiempo, $t$, de $7$ días después del brote de la epidemia. Lo anterior se puede denotar como $S(7)=400$. En esta nueva enfermedad, la probabilidad de que una persona infectada infecte a una persona sana es del $20\%$, y supongamos que el número promedio de personas con las que una persona está en contacto por día es de $5$. Entonces, por día, un individuo infectado se encuentra con $5$ personas resultando infectadas el $20\%$ de ellas, es decir, la persona enferma infectará a $1$ persona por día. Se denota a esto con el parámetro $\beta$, que es la cantidad esperada de personas que una persona infectada infecta por día.

Sea $D$ la cantidad de días que una persona infectada tiene y puede transmitir la enfermedad, este dato es extremadamente importante. Si $D = 7$, una persona infectada camina durante siete días propagando la enfermedad e infecta a $1$ persona por día ($\beta = 1$). Entonces, esperamos que una persona infectada infecte a $7$ personas más. Este es el número de reproducción básico, $R_0$, que representa el número total de personas que infecta una persona infectada, es decir, $R_0=\beta \times D$.

Si se piensa en $D$ como el número de días que una persona infectada tiene la enfermedad, entonces se puede pensar en otro parámetro $\gamma$, como la tasa de recuperación, o la proporción de infectados que se recuperan por día. Por ejemplo, si se tienen actualmente $30$ personas infectadas y $D=3$ (por lo que están infectadas durante tres días), entonces, por día $1/3$ de ellas se recuperarán ($10$ personas), es decir, $\gamma=1/3$. Siendo $\gamma=1/D$, entonces $D=1/\gamma$ y $R_0=\beta \times D$, se deduce que $R_0=\beta / \gamma$.

Resumiendo, las variables involucradas en el ejemplo anterior son:

- $N$: Total de la población

- $S(t)$: Número de personas susceptibles de ser infectadas en un día $t$

- $I(t)$: Número de personas infectadas en un día $t$

- $R(t)$: Número de personas recuperadas en un día $t$

- $\beta$: Cantidad esperada de personas que una persona infectada infecta por día

- $D$: Número de días que una persona infectada tiene y puede transmitir la enfermedad

- $\gamma$: Proporción de infectados recuperados por día ($\gamma = 1/D$)

- $R_0$: Número total de personas infectadas por una persona infectada ($R_0=\beta / \gamma$)

## Derivación de las ecuaciones

Deseamos determinar el número de *infectados*, *susceptibles* y *recuperados* para todos los días, dados únicamente los parámetros $\gamma$, $\beta$ y $N$. Es difícil obtener una fórmula directa para $S(t)$, $I(t)$ y $R(t)$, sin embargo, es muy simple describir el cambio por día de $S$, $I$ y $R$, es decir, como cambia el número de *susceptibles* / *infectados* / *recuperados* dependiendo de los actuales valores.

Ahora estamos en el día $t$ después del brote de la enfermedad. Aún así, la cantidad esperada de personas que una persona infectada infecta por día es $1$ ($\beta = 1$) y el número de días que una persona infectada tiene y puede transmitir la enfermedad es $7$ ($\gamma =1/7$ y $D=7$).

Digamos que en el día $t$, $60$ personas están infectadas, $I (t) = 60$, la población total es $100$, $N = 100$, y $30$ personas aún son susceptibles, $S(t) = 30$ y $R( t) = 100–60–30 = 10$. Ahora, ¿cómo cambian $S(t)$, $I(t)$ y $R(t)$ al día siguiente?

Se tienen $60$ personas infectadas. Cada uno de ellos infecta a $\beta = 1$ persona por día. Sin embargo, solo $30/100 = 30\%$ de las personas conocidas todavía son susceptibles y pueden infectarse (eso es $S(t) / N$). Entonces, infectan a $60 \times 1 \times 30/100 = 18$ personas (de nuevo, piensen en ello hasta que tenga sentido: $60$ infectados que infectan en promedio $1$ persona por día, pero solo $30$ de $100$ personas aún pueden estar infectadas, por lo que no infecte a $60 \times 1$ personas, sino que solo a $60 \times 1 \times 30 / 100 = 18$ personas). Entonces, $18$ personas de los susceptibles se infectan, por lo que $S(t)$ cambia en menos $18$. Al conectar las variables, acabamos de derivar la primera fórmula:

$$\text{cambio de } S(t) \text{ al día siguiente } = -\beta \cdot I(t) \cdot S(t) / N$$

Lo anterior es la expresión de la deriva, $S'(t) = dS/dt$, y reescribiendo

$$S'(t) = dS(t)/dt = -\beta \cdot I(t) \cdot S(t) / N$$

Ahora, ¿cómo cambia la cantidad de infectados? hay algunas personas nuevas infectadas. Exactamente la cantidad de personas que "*salen*" de $S(t)$ "*llegan*" a $I(t)$. Entonces, tenemos $18$ nuevos infectados y ya sabemos que la fórmula será similar a esta: $I'(t)= + β \times I(t) \times S(t)/N$ (se gana la cantidad exacta que $S(t)$ pierde, por lo que simplementese cambia el signo). Solo falta una cosa: algunas personas se recuperan. Recuerde, tenemos $\gamma$ para eso, es la proporción de infectados que se recuperan por día, ¡eso es justo lo que necesitamos!

Tenemos $60$ infectados y $\gamma = 1/3$, por lo que un tercio de los $60$ se recupera. Eso es $1/3 \times 60 = 20$. Finalmente, obtenemos la fórmula:

$$I'(t) = dI(t)/dt = \beta \cdot I(t) \cdot S(t) / N - \gamma \cdot I(t)$$

El primer término del lado derecho de la ecuación es el recién infectado de los susceptibles. La segunda parte son las recuperaciones.

Finalmente, llegamos a la última fórmula, el cambio en las recuperaciones. Los recién recuperados son exactamente los $20$ que acabamos de calcular; no hay personas saliendo del compartimento "*recuperado*". Se asume que una vez recuperados, permanecen inmunes:

$$R'(t) = dR(t)/dt = \gamma \cdot I(t)$$

Ya contamos con las fórmulas que se buscaban:

$$
\begin{align}
\frac{dS}{dt} & = -\beta \cdot I \cdot \frac{S}{N} \\
\frac{dI}{dt} & = \beta \cdot I \cdot \frac{S}{N} - \gamma \cdot I \\
\frac{dR}{dt} & = \gamma \cdot I
\end{align}
$$

Este es un conjunto de Ecuaciones Diferenciales Ordinarias (ODEs).

Ahora podemos describir el cambio en el número de personas *susceptibles*, *infectadas* y *recuperadas*. A partir de estas fórmulas, afortunadamente, podemos calcular los números que realmente nos interesan: $S(t)$, $I(t)$ y $R(t)$, el número de personas *susceptibles*, *infectadas* y *recuperadas* por cada día $t$.

## Codificando el modelo

Ahora vamos a codificar las ecuaciones. Se emplearán los códigos implementados en [Infectious Disease Modelling: Understanding the models that are used to model Coronavirus](https://towardsdatascience.com/infectious-disease-modelling-part-i-understanding-sir-28d60e29fdfc): By Henri Froese, in Towardsdatascience.com, Apr 06, 2020. El código completo lo pueden descargar de [su sitio en GitHub](https://github.com/hf2000510/infectious_disease_modelling).

Inicialmente se definiran los parámetros $N$, $\beta$, $D$, $\gamma$, y las condiciones iniciales, en $t=0$, para $S(t)$, $I(t)$ y  $R_0(t)$.

```python
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
```

```python
N = 1000
beta = 1.0  # infected person infects 1 other person per day
D = 4.0 # infections lasts four days
gamma = 1.0 / D

S0, I0, R0 = 999, 1, 0  # initial conditions: one infected, rest susceptible
```

Ahora se implementarán las fórmulas determinadas

```python
def deriv(y, t, N, beta, gamma):
    S, I, R = y
    dSdt = -beta * S * I / N
    dIdt = beta * S * I / N - gamma * I
    dRdt = gamma * I
    return dSdt, dIdt, dRdt
```

Ahora aquí es donde ocurre la magia: obtenemos nuestros valores $S(t)$, $I(t)$ y $R(t)$ de la función `odeint` de `Python` que toma las fórmulas que definimos anteriormente, las condiciones iniciales y nuestras variables $N$, $\beta$ y $\gamma$, y calcula $S$, $I$ y $R$ para cada uno de los siguientes $50$ días.

```python
t = np.linspace(0, 50, 50) # Grid of time points (in days)
y0 = S0, I0, R0 # Initial conditions vector

# Integrate the SIR equations over the time grid, t.
ret = odeint(deriv, y0, t, args=(N, beta, gamma))
S, I, R = ret.T
```

```python
def plotsir(t, S, I, R):
    f, ax = plt.subplots(1,1,figsize=(10,4))
    ax.plot(t, S, 'b', alpha=0.7, linewidth=2, label='Susceptible')
    ax.plot(t, I, 'y', alpha=0.7, linewidth=2, label='Infected')
    ax.plot(t, R, 'g', alpha=0.7, linewidth=2, label='Recovered')

    ax.set_xlabel('Time (days)')

    ax.yaxis.set_tick_params(length=0)
    ax.xaxis.set_tick_params(length=0)
    ax.grid(b=True, which='major', c='w', lw=2, ls='-')
    legend = ax.legend()
    legend.get_frame().set_alpha(0.5)
    for spine in ('top', 'right', 'bottom', 'left'):
        ax.spines[spine].set_visible(False)
    plt.show();

```

```python
plotsir(t, S, I, R)
```

Como puede ver, solo toma alrededor de $30$ días para que casi una población completa de $1000$ personas se infecte. Por supuesto, la enfermedad modelada aquí tiene un valor $R_0$ muy alto de $4.0$. Simplemente cambiando el número de personas que una persona infectada infecta por día $\beta$ a $0.5$ resulta en un escenario completamente diferente:

```python
N = 1000
beta = 0.50  # infected person infects 1 other person per day
D = 4.0 # infections lasts four days
gamma = 1.0 / D

S0, I0, R0 = 999, 1, 0  # initial conditions: one infected, rest susceptible
```

```python
t = np.linspace(0, 50, 50) # Grid of time points (in days)
y0 = S0, I0, R0 # Initial conditions vector

# Integrate the SIR equations over the time grid, t.
ret = odeint(deriv, y0, t, args=(N, beta, gamma))
S, I, R = ret.T
```

```python
plotsir(t, S, I, R)
```

Como puede ver, estos sistemas de EDO son extremadamente sensibles a los parámetros iniciales. Esa es también la razón por la cual es tan difícil modelar correctamente un brote emergente de una nueva enfermedad: simplemente no sabemos cuáles son los parámetros, e incluso los cambios leves producen resultados muy diferentes.

## Conclusiones SIR

El modelo S.I.R. es un modelo simple de la manera como se puede transmitir una enfermedad contagiosa en un grupo de personas, sin embargo, como todo modelo, aunque se trata de describir la realidad, son más las inquietudes que resultan que las respuestas que ofrece. Todo dependerá de los parámetros que se tienen en cuenta y la calidad de la información, datos, con que se cuente.

# *S.E.I.R.D.*

En la literatura existen otros modelos más completos, y complejos, como el ***S.E.I.R.D.*** que es una extensión del *S.I.R.* y que considera, además de los parámetros vistos, otros como:

- Un estado "***Expuesto***" para individuos que han contraído la enfermedad pero que aún no son infecciosos (este es conocido como el modelo ***S.E.I.R.***)

- Un estado "***Deceso***" que contempla las personas que fallecen debido a la enfermedad (este es conocido como el modelo ***S.E.I.R.D.***).

- Valores de $R_0$ dependientes del tiempo que nos permitirán modelar cuarentenas, bloqueos, ...

- Tasas de mortalidad dependientes de los recursos y la edad que nos permitirán modelar hospitales superpoblados, poblaciones con muchos jóvenes, ...

entre muchos otros posibles parámetros adicionales, que podrían complementar el modelo, pero lo harían mucho más complejo.

Al derivar las ecuaciones, ya las consideramos intuitivamente como "*instrucciones*" que nos dicen lo que le sucede a la población al día siguiente (por ejemplo, cuando $10$ personas están infectadas y la recuperación se produce a un ritmo de $\gamma = 1/5$, entonces el número de individuos recuperados al día siguiente debería aumentar en $1/5 \times 10 = 2$). Ahora solidificamos esta comprensión de las ecuaciones como "*direcciones*" o "*transiciones*" de un compartimento $S$, $I$ o $R$ a otro; esto simplificará enormemente las cosas cuando introduzcamos más compartimentos más adelante y las ecuaciones se vuelvan confusas.

## Definiciones

Los *compartimientos* son cajas, que denotan "*estados*", como este:

Las *transiciones* de un compartimento a otro se representan mediante flechas, con el siguiente etiquetado:

La *tasa* describe cuánto tiempo dura la transición, la *población* es el grupo de individuos a los que se aplica esta transición, y la *probabilidad* es la probabilidad de que la transición tenga lugar para un individuo.

### Ejemplo

Supongamos la transición de *Susceptibles* a *Infectados* en las ecuaciones $S.I.R.$, con $\beta=2$, una población total de $100$, $10$ infectados y $90$ susceptibles. La tasa es $1$, ya que las infecciones ocurren de inmediato; la población a la que se aplica la transición es $2 \times 10 = 20$ individuos, ya que los $10$ infectados infectan a $2$ personas; la probabilidad es del $90\%$, ya que $90/100$ personas aún pueden estar infectadas. Corresponde a esta notación intuitiva:

De forma más general, para todo el modelo (para $I \rightarrow R$, la *tasa* es $\gamma$ y la probabilidad es $1$ a medida que todos se recuperarán)

Como puede verse, las flechas que apuntan hacia un compartimento se agregan en la ecuación; las flechas que apuntan lejos de un compartimento se restan. Es una forma gráfica de presentar las ecuaciones determinadas arriba.

$$
\begin{align}
\frac{dS}{dt} & = -\beta \cdot I \cdot \frac{S}{N} \\
\frac{dI}{dt} & = \beta \cdot I \cdot \frac{S}{N} - \gamma \cdot I \\
\frac{dR}{dt} & = \gamma \cdot I
\end{align}
$$

Aunque ya tengamos un mejor entendimiento del modelo $S.I.R.$, e incluso se haya codificado en un lenguaje de programación como `python`, los resultados obtenidos no representan muy bien la realidad, y por ahora no deja de ser un simple "juguete" interesante.

Vamos a tratar de "mejorar" este modelo básico incluyendo otros "compartimientos"

## Introduciendo nuevos Compartimientos - *Expuestos* (*S.E.I.R.*)

Muchas enfermedades infecciosas tienen un período de incubación antes de ser infecciosas durante el cual el huésped aún no puede transmitir la enfermedad. Llamaremos a tales individuos, y a todo el compartimento, *expuestos*.

Intuitivamente, tendremos transiciones de la forma $S \rightarrow E \rightarrow I \rightarrow R$: las personas susceptibles pueden contraer el virus y así quedar expuestos, luego infectados y luego recuperados. La nueva transición $S \rightarrow E$ tendrá la misma flecha que la transición $S \rightarrow I$ actual, ya que la probabilidad es la misma (todos los *susceptibles* pueden estar *expuestos*), la tasa es la misma ("*exposición*" ocurre inmediatamente) y la población es igual (los individuos infecciosos pueden propagar la enfermedad y cada uno expone a $\beta$ nuevos individuos por día). Tampoco hay razón para que la transición de $I$ a $R$ cambie. La única nueva transición es la de $E$ a $I$: la probabilidad es $1$ (todos los que están expuestos se infectan), la población es $E$ (todos los expuestos se infectarán) y la tasa obtiene una nueva variable, $\delta$(delta). Llegamos a estas transiciones:

De estas transiciones, podemos derivar inmediatamente las nuevas ecuaciones:

$$
\begin{align}
\frac{dS}{dt} & = -\beta \cdot I \cdot \frac{S}{N} \\
\frac{dE}{dt} & = \beta \cdot I \cdot \frac{S}{N} - \delta \cdot E\\
\frac{dI}{dt} & = \delta \cdot E - \gamma \cdot I \\
\frac{dR}{dt} & = \gamma \cdot I
\end{align}
$$

## Programando el compartimiento de *Expuestos*

Retomando el código realizado antes y cambiando algunas líneas para adicionarle los nuevos compartimientos. Como ejemplo se modelará una enfermedad altamente infecciosa, $R_0=5.0$ en una población de $1$ millón, con un periodo de incubación de $5$ días y una recuperación de $7$ días.

```python
def plotsir(t, S, E, I, R):
    f, ax = plt.subplots(1,1,figsize=(10,4))
    ax.plot(t, S, 'b', alpha=0.7, linewidth=2, label='Susceptible')
    ax.plot(t, E, 'y', alpha=0.7, linewidth=2, label='Expuesto')
    ax.plot(t, I, 'r', alpha=0.7, linewidth=2, label='Infectado')
    ax.plot(t, R, 'g', alpha=0.7, linewidth=2, label='Recuperado')

    ax.set_xlabel('Tiempo (dias)')
    ax.set_ylabel('Población (habitantes)')

    ax.yaxis.set_tick_params(length=0)
    ax.xaxis.set_tick_params(length=0)
    ax.grid(b=True, which='major', c='w', lw=2, ls='-')
    legend = ax.legend()
    legend.get_frame().set_alpha(0.5)
    for spine in ('top', 'right', 'bottom', 'left'):
        ax.spines[spine].set_visible(False)
    plt.show();
```

Función para determinar las derivadas de las funciones

```python
def deriv(y, t, N, beta, gamma, delta):
    S, E, I, R = y
    dSdt = -beta * S * I / N
    dEdt = beta * S * I / N - delta * E
    dIdt = delta * E - gamma * I
    dRdt = gamma * I
    return dSdt, dEdt, dIdt, dRdt
```

Determinación de los valores iniciales

```python
N = 1000000
D = 4.0 # infections lasts four days
gamma = 1.0 / D
delta = 1.0 / 5.0  # incubation period of three days
R_0 = 5.0
beta = R_0 * gamma # infected person infects 1 other person per day

S0, E0, I0, R0 = N-1, 1, 0, 0  # initial conditions: one exposed
```

Cálculo de $S,E,I$ y $R$ para todo el tiempo de simulación (100 días)

```python
t = np.linspace(0, 100, 100) # Grid of time points (in days)
y0 = S0, E0, I0, R0 # Initial conditions vector

# Integrate the SIR equations over the time grid, t.
ret = odeint(deriv, y0, t, args=(N, beta, gamma, delta))
S, E, I, R = ret.T
```

```python
plotsir(t, S, E, I, R)
```

Cada vez tiene mejor cara... pero aún falta lo más cruel de todo, representar los posibles decesos.

## Obteniendo el compartimiento de "*Decesos*"

Recordemos el modelo $S.E.I.R.$

¿Cuándo puede morir una persona por la enfermedad? ¡Sólo mientras esté infectado!, esto significa que hay qué agregar una transición $I \rightarrow D$.

Por supuesto, las personas no mueren de inmediato; Definimos una nueva variable $\rho$(rho) para la tasa a la que las personas mueren (por ejemplo, cuando tarda $6$ días en morir, $\rho=1/6$). No hay razón para que la tasa de recuperación, $\gamma$, cambie. Entonces, nuestro nuevo modelo se verá:

Lo único que falta son las probabilidades de pasar de infectado a recuperado y de infectado a muerto. Esa será una variable más (¡la última por ahora!), La tasa de mortalidad $\alpha$. Por ejemplo, si $\alpha= 5\%$, $\rho = 1$ y $\gamma=1$ (por lo que las personas mueren o se recuperan en $1$ día, eso es un ejemplo más fácil) y $100$ personas están infectadas, entonces $5\% \times 100 = 5$ personas morirán. Eso deja $95\% \times 100 = 95$ personas en recuperación. En resumen, la probabilidad de $I \rightarrow D$ es $\alpha$ y, por lo tanto, la probabilidad de $I \rightarrow R$ es $1-\alpha$. Finalmente llegamos a este modelo:

Adicionando esta nueva ecuación al conjunto que teníamos de antes:

$$
\begin{align}
\frac{dS}{dt} & = -\beta \cdot I \cdot \frac{S}{N} \\
\frac{dE}{dt} & = \beta \cdot I \cdot \frac{S}{N} - \delta \cdot E\\
\frac{dI}{dt} & = \delta \cdot E - (1-\alpha) \cdot \gamma \cdot I - \alpha \cdot \rho \cdot I\\
\frac{dR}{dt} & = (1-\alpha) \cdot \gamma \cdot I \\
\frac{dD}{dt} & = \alpha \cdot \rho \cdot I
\end{align}
$$

## Programando el compartimiento de *Decesos*

Modificando el código que venimos trayendo para incluir los decesos. Solo es necesario realizar algunos cambios menores, como es incluir $\alpha=20\%$ y $\rho=1/9$:

```python
def deriv(y, t, N, beta, gamma, delta, alpha, rho):
    S, E, I, R, D = y
    dSdt = -beta * S * I / N
    dEdt = beta * S * I / N - delta * E
    dIdt = delta * E - (1 - alpha) * gamma * I - alpha * rho * I
    dRdt = (1 - alpha) * gamma * I
    dDdt = alpha * rho * I
    return dSdt, dEdt, dIdt, dRdt, dDdt
```

```python
D = 4.0 # infections lasts four days
gamma = 1.0 / D
delta = 1.0 / 5.0  # incubation period of five days
R_0 = 5.0
beta = R_0 * gamma  # R_0 = beta / gamma, so beta = R_0 * gamma
alpha = 0.2  # 10% death rate
rho = 1/9  # 9 days from infection until death
S0, E0, I0, R0, D0 = N-1, 1, 0, 0, 0  # initial conditions: one exposed
```

```python
t = np.linspace(0, 100, 100) # Grid of time points (in days)
y0 = S0, E0, I0, R0, D0 # Initial conditions vector

# Integrate the SIR equations over the time grid, t.
ret = odeint(deriv, y0, t, args=(N, beta, gamma, delta, alpha, rho))
S, E, I, R, D = ret.T
```

```python
def plotseird(t, S, E, I, R, D):
    f, ax = plt.subplots(1,1,figsize=(10,4))
    ax.plot(t, S, 'b', alpha=0.7, linewidth=2, label='Susceptible')
    ax.plot(t, E, 'y', alpha=0.7, linewidth=2, label='Exposed')
    ax.plot(t, I, 'r', alpha=0.7, linewidth=2, label='Infected')
    ax.plot(t, R, 'g', alpha=0.7, linewidth=2, label='Recovered')
    ax.plot(t, D, 'k', alpha=0.7, linewidth=2, label='Dead')
    ax.plot(t, S+E+I+R+D, 'c--', alpha=0.7, linewidth=2, label='Total')

    ax.set_xlabel('Time (days)')

    ax.yaxis.set_tick_params(length=0)
    ax.xaxis.set_tick_params(length=0)
    ax.grid(b=True, which='major', c='w', lw=2, ls='-')
    legend = ax.legend()
    legend.get_frame().set_alpha(0.5)
    for spine in ('top', 'right', 'bottom', 'left'):
        ax.spines[spine].set_visible(False)
    plt.show();
```

```python
plotseird(t, S, E, I, R, D)
```

Se adicionó una variable "*total*" que suma $S, E, I, R$ y $D$ para cada paso de tiempo como un "*control de sanidad*": los compartimentos siempre tienen que sumar $N$; Esto puede dar una pista sobre si las ecuaciones son correctas.

Ya sabemos cómo ir agregando nuevos compartimentos al modelo: piense qué transiciones deben agregarse y cambiarse; pensar en las probabilidades, poblaciones y tasas de estas nuevas transiciones; dibuja el diagrama; y finalmente escribe las ecuaciones. ¡La codificación definitivamente no es la parte difícil para estos modelos!

Por ejemplo, es posible que desee agregar un compartimento "*UCI*" para las personas infectadas que necesitan ir a una *UCI* (lo haremos en el próximo artículo). Piense desde qué compartimiento las personas pueden ir a la *UCI*, a dónde pueden ir después de la *UCI*, etc.

## Variables dependientes del tiempo

Resumamos el listado de las variables empleadas hasta ahora:

- $N$: población total

- $S(t)$: número de personas susceptibles en el día $t$

- $E(t)$: número de personas expuestas el día $t$

- $I(t)$: número de personas infectadas el día $t$

- $R(t)$: número de personas recuperadas el día $t$

- $D(t)$: número de personas fallecidas el día $t$

- $\beta$: cantidad esperada de personas que una persona infectada infecta por día

- $D$: número de días que una persona infectada tiene y puede transmitir la enfermedad

- $\gamma$: la proporción de infectados que se recuperan por día ($\gamma = 1/D$)

- $R_0$: el número total de personas que infecta una persona infectada ($R_0= \beta / \gamma$)

- $\delta$: duración del período de incubación

- $\alpha$: tasa de mortalidad

- $\rho$: tasa a la que muere la gente (= 1 / días desde la infección hasta la muerte)

Como puede ver, solo los compartimentos cambian con el tiempo (no son constantes). ¡Por supuesto, esto es muy poco realista! Como ejemplo, ¿por qué el valor $R_0$ debería ser constante? Seguramente, los bloqueos a nivel nacional reducen la cantidad de personas que infecta una persona infectada, ¡de eso se trata! Naturalmente, para acercarnos a modelar desarrollos del mundo real, tenemos que hacer que nuestras variables cambien con el tiempo.

### $R_0$ dependiente del tiempo

Primero, implementamos un cambio simple: en un día $L$, se aplica un estricto "*bloqueo*", empujando $R_0$ a $0.9$. En las ecuaciones, usamos $\beta$ β y no $R_0$, pero sabemos que $R_0=\beta / \gamma$, entonces $\beta = R_0 \cdot \gamma$. Eso significa que definimos una función
def R_0(t):
    return 5.0 if t < L else 0.9
y otra función para $\beta$ que llama a dicha función
def beta(t, gamma):
    return R_0(t) * gamma
Ahora solo resta cambiar el código

```python
def deriv(y, t, N, beta, gamma, delta, alpha, rho):
    S, E, I, R, D = y
    dSdt = -beta(t) * S * I / N
    dEdt = beta(t) * S * I / N - delta * E
    dIdt = delta * E - (1 - alpha) * gamma * I - alpha * rho * I
    dRdt = (1 - alpha) * gamma * I
    dDdt = alpha * rho * I
    return dSdt, dEdt, dIdt, dRdt, dDdt
```

```python
N = 1000000
D = 4.0 # infections lasts four days
gamma = 1.0 / D
delta = 1.0 / 5.0  # incubation period of three days
L = 100

#R_0 = 5.0
def R_0(t):
    return 5.0 if t < L else 0.9

#beta = R_0 * gamma # infected person infects 1 other person per day
def beta(t):
    return R_0(t) * gamma

alpha = 0.2 #20% death rate
rho = 1/9 # 9 days from infection until death

S0, E0, I0, R0, D0 = N-1, 1, 0, 0, 0  # initial conditions: one exposed
```

```python
t = np.linspace(0, 100, 100) # Grid of time points (in days)
n = len(t)
y0 = S0, E0, I0, R0, D0 # Initial conditions vector

# Integrate the SIR equations over the time grid, t.
ret = odeint(deriv, y0, t, args=(N, beta, gamma, delta, alpha, rho))
S, E, I, R, D = ret.T
```

```python
plotseird(t, S, E, I, R, D)
```

```python
D[n-1]
```

    76201.95041510701

En realidad, $R_0$ probablemente nunca "salta" de un valor a otro. Más bien, (más o menos rápidamente) cambia continuamente (y puede subir y bajar varias veces, por ejemplo, si se aflojan las medidas de distanciamiento social y luego se vuelven a ajustar). Puede elegir cualquier función que desee para $R_0$, pero una opción común para modelar el impacto inicial del distanciamiento social es una función [logística](https://en.wikipedia.org/wiki/Logistic_function).

La función (adoptada para estos propósitos) sería algo así como:

$$R_0(t)=\frac{R_{0_{start}}-R_{0_{end}}}{1+e^{-k(-x+x_0)}}+R_{0_{end}}$$

Y esto es lo que realmente hacen los parámetros:

- $R_{0_{start}}$ y $R_{0_{end}}$ son los valores de $R_0$ en el primer y el último día

- $x_0$ es el valor $x$ del punto de inflexión (es decir, la fecha de la disminución más pronunciada en $R_0$, esto podría considerarse como la fecha principal de "bloqueo")

- $k$ nos permite variar la rapidez con la que $R_0$ disminuye

Veamos antes como es el comportamiento de la curva logística para diferentes valores de $k=1.0, 0.5$ y $0.1$ y variando $R_0$ también entre un valor máximo inicial de $5.0$ y otro final de $0.5$

```python
def logistic(x, R0start, R0end, x0, k):
    return (R0start - R0end)/(1 + np.exp(-k*(-x+x0))) + R0end
```

```python
R0start, k, x0, R0end = 1.9, 0.5, 50, 0.5
k1 = 1.0
k2 = 0.5
k3 = 0.1

x = np.linspace(0, 100, 100)

logistica1 = [logistic(i, R0start, R0end, x0, k1) for i in range(len(x))]
logistica2 = [logistic(i, R0start, R0end, x0, k2) for i in range(len(x))]
logistica3 = [logistic(i, R0start, R0end, x0, k3) for i in range(len(x))]

f, ax = plt.subplots(1,1,figsize=(10,4))

ax.plot(x, logistica1, 'b', alpha=0.7, linewidth=2, label='k = 1.0')
ax.plot(x, logistica2, 'y', alpha=0.7, linewidth=2, label='k = 0.5')
ax.plot(x, logistica3, 'r', alpha=0.7, linewidth=2, label='k = 0.1')

ax.grid(b=True, which='major', c='w', lw=2, ls='-')
legend = ax.legend()
legend.get_frame().set_alpha(0.5)
plt.show()
```

Ahora cambiando el cógido que veníamos trabajando para implementar la variación de $R_0$

```python
N = 1000000
D = 4.0 # infections lasts four days
gamma = 1.0 / D
delta = 1.0 / 5.0  # incubation period of three days
R0start, k, x0, R0end = 5.0, 0.5, 50, 0.5

def beta(t):
    return logistic(t, R0start, R0end, x0, k) * gamma

alpha = 0.2 #20% death rate
rho = 1/9 # 9 days from infection until death

S0, E0, I0, R0, D0 = N-1, 1, 0, 0, 0  # initial conditions: one exposed
```

```python
y0 = S0, E0, I0, R0, D0 # Initial conditions vector

# Integrate the SIR equations over the time grid, t.
ret = odeint(deriv, y0, t, args=(N, beta, gamma, delta, alpha, rho))
S, E, I, R, D = ret.T
R0_over_time = [logistic(i, R0start, R0end, x0, k) for i in range(len(t))]  # to plot R_0 over time: get function values
```

```python
plotseird(t, S, E, I, R, D)
```

Dejamos que $R_0$ disminuya rápidamente de $5.0$ a $0.5$ alrededor del día $50$ y ahora realmente podemos ver las curvas aplanándose después del día $50$:

## Tasa de fatalidad relacionada con los recursos disponibles y la edad

Al igual que $R_0$, la tasa de mortalidad $\alpha$ probablemente no sea constante para la mayoría de las enfermedades reales. Puede depender de una variedad de cosas; Nos centraremos en la dependencia de los recursos y la edad.

***Dependencia de los recursos:*** La tasa de mortalidad será mayor cuando hay más personas infectadas. Para pensar en una función que tenga ésto en consideración, se necesitaría una tasa de mortalidad "*base*" u "*óptima*" para el caso de que solo unas pocas personas estén infectadas (y por lo tanto reciban un tratamiento óptimo y puedan recuperarse) y algún factor que tenga en cuenta qué proporción de la población está actualmente infectada. Este es un ejemplo de una función que implementa estas ideas:

$$\alpha(t)=s \cdot \frac{I(t)}{N}+\alpha_{opt}$$

Los modelos más elaborados pueden hacer que la tasa de mortalidad dependa de la cantidad de camas de UCI o ventiladores disponibles, etc. Esto se tendrá en cuenta más adelante cuando modelemos Coronavirus.

***Dependencia de la edad:*** La dependencia de la edad es un poco más difícil. Para implementarlo por completo, tendríamos que incluir compartimentos separados para cada grupo de edad (por ejemplo, un compartimento infectado para personas de 0 a 9 años, otro para personas de 10 a 19 años, ...). Eso es factible con un bucle for simple en `Python`, pero las ecuaciones se vuelven un poco confusas. Un enfoque más simple que aún puede producir buenos resultados es el siguiente:

Necesitamos 2 cosas para un enfoque más simple: *tasas de mortalidad por grupo de edad* y *proporción de la población total que se encuentra en ese grupo de edad*. Por ejemplo, podríamos tener las siguientes tasas de mortalidad y número de individuos por grupo de edad (empleando diccionarios de *Python*):

alpha_by_agegroup = {
    "0-29": 0.01, "30-59": 0.05, "60-89": 0.20, "89+": 0.30
}

proportion_of_agegroup = {
    "0-29": 0.1, "30-59": 0.3, "60-89": 0.4, "89+": 0.2
}
Esta sería una población extremadamente antigua con un $40\%$ en el rango $60-89$ y un $20\%$ en el rango $89+$. Ahora calculamos la tasa de mortalidad promedio general sumando la tasa de mortalidad del grupo de edad multiplicada por la proporción de la población en ese grupo de edad:

$$\alpha = 0.01 \cdot 0.1 + 0.05 \cdot 0.3 + 0.2 \cdot 0.4 + 0.3 \cdot 0.2 = 15.6\%$$

codificándolo
alpha_opt = sum(alpha_by_agegroup[i] * proportion_of_agegroup[i]
            for i in list(alpha_by_agegroup.keys()))
Una población bastante joven con las siguientes proporciones ...
proportion_of_agegroup = {
    "0-29": 0.4, "30-59": 0.4, "60-89": 0.1, "89+": 0.1
}
… Solo tendría una tasa de mortalidad promedio de $7.4\%$!

Si queremos usar nuestras fórmulas para la dependencia de los recursos y la edad, podríamos usar la fórmula de los recursos que acabamos de usar para calcular $\alpha_{opt}$ y usar eso en nuestra fórmula dependiente de los recursos de arriba.

con seguridad, hay formas más elaboradas de implementar tasas de mortalidad con el tiempo. Por ejemplo, no estamos teniendo en cuenta que solo los casos críticos que requieren cuidados intensivos llenan los hospitales y pueden aumentar las tasas de mortalidad; o que las muertes cambian la estructura de la población que usamos para calcular la tasa de mortalidad en primer lugar; o que el impacto de los infectados en las tasas de mortalidad debería tener lugar varios días después, ya que las personas no suelen morir de inmediato, lo que resultaría en una [ecuación diferencial con retardo](https://en.wikipedia.org/wiki/Delay_differential_equation).

## Implementación computacional de la tasa de fatalidad relacionada con los recursos disponibles y la edad

La implementación computacional es sencilla, no es necesario siquiera cambiar las ecuaciones principales (definimos *alfa* dentro de las ecuaciones ya que necesitamos acceso al valor actual $I(t)$).

```python
def deriv(y, t, N, beta, gamma, delta, alpha_opt, rho):
    S, E, I, R, D = y

    def alpha(t):
        return s * I/N + alpha_opt

    dSdt = -beta(t) * S * I / N
    dEdt = beta(t) * S * I / N - delta * E
    dIdt = delta * E - (1 - alpha(t)) * gamma * I - alpha(t) * rho * I
    dRdt = (1 - alpha(t)) * gamma * I
    dDdt = alpha(t) * rho * I
    return dSdt, dEdt, dIdt, dRdt, dDdt
```

```python
def plotseird(t, S, E, I, R, D=None, L=None, R0=None, Alpha=None):
    f, ax = plt.subplots(1,1,figsize=(10,4))
    ax.plot(t, S, 'b', alpha=0.7, linewidth=2, label='Susceptible')
    ax.plot(t, E, 'y', alpha=0.7, linewidth=2, label='Exposed')
    ax.plot(t, I, 'r', alpha=0.7, linewidth=2, label='Infected')
    ax.plot(t, R, 'g', alpha=0.7, linewidth=2, label='Recovered')
    if D is not None:
        ax.plot(t, D, 'k', alpha=0.7, linewidth=2, label='Dead')
        ax.plot(t, S+E+I+R+D, 'c--', alpha=0.7, linewidth=2, label='Total')
    else:
        ax.plot(t, S+E+I+R, 'c--', alpha=0.7, linewidth=2, label='Total')

    ax.set_xlabel('Time (days)')

    ax.yaxis.set_tick_params(length=0)
    ax.xaxis.set_tick_params(length=0)
    ax.grid(b=True, which='major', c='w', lw=2, ls='-')
    legend = ax.legend(borderpad=2.0)
    legend.get_frame().set_alpha(0.5)

    for spine in ('top', 'right', 'bottom', 'left'):
        ax.spines[spine].set_visible(False)

    if L is not None:
        plt.title("Lockdown after {} days".format(L))
    plt.show();

    if R0 is not None or CFR is not None:
        f = plt.figure(figsize=(12,4))

    if R0 is not None:
        # sp1
        ax1 = f.add_subplot(121)
        ax1.plot(t, R0, 'b--', alpha=0.7, linewidth=2, label='R_0')

        ax1.set_xlabel('Time (days)')
        ax1.title.set_text('R_0 over time')
        # ax.set_ylabel('Number (1000s)')
        # ax.set_ylim(0,1.2)
        ax1.yaxis.set_tick_params(length=0)
        ax1.xaxis.set_tick_params(length=0)
        ax1.grid(b=True, which='major', c='w', lw=2, ls='-')
        legend = ax1.legend()
        legend.get_frame().set_alpha(0.5)

        for spine in ('top', 'right', 'bottom', 'left'):
            ax.spines[spine].set_visible(False)

    if Alpha is not None:
        # sp2
        ax2 = f.add_subplot(122)
        ax2.plot(t, Alpha, 'r--', alpha=0.7, linewidth=2, label='alpha')

        ax2.set_xlabel('Time (days)')
        ax2.title.set_text('fatality rate over time')
        # ax.set_ylabel('Number (1000s)')
        # ax.set_ylim(0,1.2)
        ax2.yaxis.set_tick_params(length=0)
        ax2.xaxis.set_tick_params(length=0)
        ax2.grid(b=True, which='major', c='w', lw=2, ls='-')
        legend = ax2.legend()
        legend.get_frame().set_alpha(0.5)

        for spine in ('top', 'right', 'bottom', 'left'):
            ax.spines[spine].set_visible(False)

        plt.show();
```

```python
N = 1000000
D = 4.0 # infections lasts four days
gamma = 1.0 / D
delta = 1.0 / 5.0  # incubation period of three days

R0start, k, x0, R0end = 5.0, 0.5, 50, 0.5

def beta(t):
    return logistic(t, R0start, R0end, x0, k) * gamma

#alpha = 0.2 #20% death rate

alpha_by_agegroup = {"0-29": 0.01, "30-59": 0.05, "60-89": 0.20,
                     "89+": 0.30
                    }

proportion_of_agegroup = {"0-29": 0.1, "30-59": 0.3, "60-89": 0.4,
                          "89+": 0.2
                         }

s = 0.01

alpha_opt = sum(alpha_by_agegroup[i] * proportion_of_agegroup[i]
            for i in list(alpha_by_agegroup.keys()))

rho = 1/9 # 9 days from infection until death

S0, E0, I0, R0, D0 = N-1, 1, 0, 0, 0  # initial conditions: one exposed
```

```python
t = np.linspace(0, 100, 100) # Grid of time points (in days)
y0 = S0, E0, I0, R0, D0 # Initial conditions vector

# Integrate the SIR equations over the time grid, t.
ret = odeint(deriv, y0, t, args=(N, beta, gamma, delta, alpha_opt, rho))

S, E, I, R, D = ret.T

R0_over_time = [logistic(i, R0start, R0end, x0, k) for i in range(len(t))]  # to plot R_0 over time: get function values

Alpha_over_time = [s * I[i]/N + alpha_opt for i in range(len(t))]  # to plot alpha over time
```

```python
plotseird(t, S, E, I, R, D, R0=R0_over_time, Alpha=Alpha_over_time)
```

# Aplicación

## Donde obtener información

### Graficos

```python
URL = 'https://coronavirus.jhu.edu/map.html'
IFrame(src = URL, width = 960, height = 500)
```

### Globales

- [Johns Hopkins University of Medicine - Coronavirus Resource Center](https://github.com/CSSEGISandData/COVID-19)

- [Worldometers](https://www.worldometers.info/coronavirus/)

- [ONU](https://data.un.org/)

### Locales (Colombia)

- [Instituto Nacional de Salud](https://www.ins.gov.co/Noticias/Paginas/Coronavirus.aspx)

- [Alcaldía de Bogotá](https://bogota.gov.co/coronavirus-en-bogota/)

- [GPStrategy](https://covid19.gpstrategy.com.co/extensions/COVID19/COVID19.html)

## Caso colombiano

***Nota:*** En este apartado se seguirá el trabajo realizado por

- [Prevendo o crescimento de casos de COVID-19 (coronavírus) no Brasil com análise de dados, gráficos e equações! — Partes 1 e 2](https://medium.com/@caiquecoelho/prevendo-o-crescimento-de-casos-de-covid-19-coronavirus-no-brasil-com-an%C3%A1lise-de-dados-gr%C3%A1ficos-33ee525b62f8): By Caíque Coelho, in Towardsdatascience.com. Mar 19

Una vez obtenido un modelo relativamente mas completo. vamos a revisar los datos del comportamiento de la pandemia en Colombia a la fecha del día, es solo ejecutar las celdas para el día y el código captura la información al día.

Empezamos por importar las bibliotecas a emplear

```python
## Import Libraries
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels
import sklearn
import io
import requests
from sklearn.linear_model import LinearRegression
```

Vamos a emplear los datos levantados por:

- 2019 Novel Coronavirus COVID-19 (2019-nCoV) [Data Repository](https://github.com/CSSEGISandData/COVID-19) by Johns Hopkins CSSE

- [World O Meter](https://www.worldometers.info/coronavirus/)

Se trabajará con los datos de casos confirmados y decesos.

Primero se carga la información a nivel global

```python
# Casos confirmados a nivel mundial
url1 = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv"
confirmedCasesGlobal = pd.read_csv(url1)

# Decesos confirmados a nivel mundial
url2 = "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_global.csv"
deathsCasesGlobal = pd.read_csv(url2)
```

Se extraen los datos de los casos confirmados (positivos) y los decesos en ***Colombia*** al 18/04/2020

```python
confirmedcasesColombia = confirmedCasesGlobal.loc[confirmedCasesGlobal['Country/Region'] == 'Colombia']
confirmedcasesColombia.head()
```

      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/8/20
      4/9/20
      4/10/20
      4/11/20
      4/12/20
      4/13/20
      4/14/20
      4/15/20
      4/16/20
      4/17/20

      82
      NaN
      Colombia
      4.5709
      -74.2973
      0
      0
      0
      0
      0
      0
      ...
      2054
      2223
      2473
      2709
      2776
      2852
      2979
      3105
      3233
      3439

1 rows × 91 columns

```python
deathcasesColombia = deathsCasesGlobal.loc[deathsCasesGlobal['Country/Region'] == 'Colombia']
deathcasesColombia.head()
```

      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/9/20
      4/10/20
      4/11/20
      4/12/20
      4/13/20
      4/14/20
      4/15/20
      4/16/20
      4/17/20
      4/18/20

      82
      NaN
      Colombia
      4.5709
      -74.2973
      0
      0
      0
      0
      0
      0
      ...
      69
      80
      100
      109
      112
      127
      131
      144
      153
      153

1 rows × 92 columns

Las primeras cuatro columnas es información irrelevante para el estudio en cuestión, por lo que se retiran.

```python
confirmedcasesColombia.drop(['Province/State', 'Country/Region', 'Lat', 'Long'], axis=1, inplace=True)
confirmedcasesColombia.head()
```

    C:\Users\Lenovo\Anaconda3\lib\site-packages\pandas\core\frame.py:3997: SettingWithCopyWarning:
    A value is trying to be set on a copy of a slice from a DataFrame

    See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
      errors=errors,

      1/22/20
      1/23/20
      1/24/20
      1/25/20
      1/26/20
      1/27/20
      1/28/20
      1/29/20
      1/30/20
      1/31/20
      ...
      4/8/20
      4/9/20
      4/10/20
      4/11/20
      4/12/20
      4/13/20
      4/14/20
      4/15/20
      4/16/20
      4/17/20

      82
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      ...
      2054
      2223
      2473
      2709
      2776
      2852
      2979
      3105
      3233
      3439

1 rows × 87 columns

```python
deathcasesColombia.drop(['Province/State', 'Country/Region', 'Lat', 'Long'], axis=1, inplace=True)
deathcasesColombia.head()
```

    C:\Users\Lenovo\Anaconda3\lib\site-packages\pandas\core\frame.py:3997: SettingWithCopyWarning:
    A value is trying to be set on a copy of a slice from a DataFrame

    See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
      errors=errors,

      1/22/20
      1/23/20
      1/24/20
      1/25/20
      1/26/20
      1/27/20
      1/28/20
      1/29/20
      1/30/20
      1/31/20
      ...
      4/9/20
      4/10/20
      4/11/20
      4/12/20
      4/13/20
      4/14/20
      4/15/20
      4/16/20
      4/17/20
      4/18/20

      82
      0
      0
      0
      0
      0
      0
      0
      0
      0
      0
      ...
      69
      80
      100
      109
      112
      127
      131
      144
      153
      153

1 rows × 88 columns

Para generar las gráficas se requiere que los datos estén en columna, por lo que se realiza la transposición de los datos que están dados en fila.

```python
confirmedcasesColombia_transposed = confirmedcasesColombia.T
confirmedcasesColombia_transposed.columns = ['cases']
confirmedcasesColombia_transposed.head()
```

      cases

      1/22/20
      0

      1/23/20
      0

      1/24/20
      0

      1/25/20
      0

      1/26/20
      0

```python
deathcasesColombia_transposed = deathcasesColombia.T
deathcasesColombia_transposed.columns = ['cases']
deathcasesColombia_transposed.head()
```

      cases

      1/22/20
      0

      1/23/20
      0

      1/24/20
      0

      1/25/20
      0

      1/26/20
      0

Graficando los datos desde el primer día que se cuenta con información

```python
days = confirmedcasesColombia_transposed.index.values
confirmedcasesColombia_transposed['cases'].plot(figsize=(12,7))
```

```python
days = deathcasesColombia_transposed.index.values
deathcasesColombia_transposed['cases'].plot(figsize=(12,7))
```

Nos interesa únicamente a partir de las fechas en donde empezaron a tenerse casos confirmados y decesos

```python
confirmedcasesColombia_transposed_with_cases = confirmedcasesColombia_transposed[confirmedcasesColombia_transposed['cases'] > 0]
confirmedcasesColombia_transposed_with_cases.head(100)
```

      cases

      3/6/20
      1

      3/7/20
      1

      3/8/20
      1

      3/9/20
      1

      3/10/20
      3

      3/11/20
      9

      3/12/20
      9

      3/13/20
      13

      3/14/20
      22

      3/15/20
      34

      3/16/20
      54

      3/17/20
      65

      3/18/20
      93

      3/19/20
      102

      3/20/20
      128

      3/21/20
      196

      3/22/20
      231

      3/23/20
      277

      3/24/20
      378

      3/25/20
      470

      3/26/20
      491

      3/27/20
      539

      3/28/20
      608

      3/29/20
      702

      3/30/20
      798

      3/31/20
      906

      4/1/20
      1065

      4/2/20
      1161

      4/3/20
      1267

      4/4/20
      1406

      4/5/20
      1485

      4/6/20
      1579

      4/7/20
      1780

      4/8/20
      2054

      4/9/20
      2223

      4/10/20
      2473

      4/11/20
      2709

      4/12/20
      2776

      4/13/20
      2852

      4/14/20
      2979

      4/15/20
      3105

      4/16/20
      3233

      4/17/20
      3439

```python
deathcasesColombia_transposed_with_cases = deathcasesColombia_transposed[deathcasesColombia_transposed['cases'] > 0]
deathcasesColombia_transposed_with_cases.head(100)
```

      cases

      3/22/20
      2

      3/23/20
      3

      3/24/20
      3

      3/25/20
      4

      3/26/20
      6

      3/27/20
      6

      3/28/20
      6

      3/29/20
      10

      3/30/20
      12

      3/31/20
      16

      4/1/20
      17

      4/2/20
      19

      4/3/20
      25

      4/4/20
      32

      4/5/20
      35

      4/6/20
      46

      4/7/20
      50

      4/8/20
      54

      4/9/20
      69

      4/10/20
      80

      4/11/20
      100

      4/12/20
      109

      4/13/20
      112

      4/14/20
      127

      4/15/20
      131

      4/16/20
      144

      4/17/20
      153

      4/18/20
      153

```python
confirmedcasesColombia_transposed_with_cases['cases'].plot(figsize=(12,7))
```

```python
deathcasesColombia_transposed_with_cases['cases'].plot(figsize=(12,7))
```

## Comparación con otros países

Ya obtumvimos la información de los casos confirmados y los decesos en los 43 días desde hace que se detectó el primer positivo. Ahora resulta interesante, o mejor dicho, importante, comparar el comportamiento de estos casos con otros países para entender el comportamiento que se ha tenido en Colombia y dar elementos para analizar y proponer posibles planes de contingencia sobre la pandemia.

Vamos a realizar la comparación con los siguientes países:

- ***Europa:*** Italia, España, Francia, Alemania, por ser los más representativos (con información "creíble")

- ***América General:*** USA, México, Chile y Argentina

- ***Paises fronterisos:*** Panamá, Ecuador, Perú, Venezuela y Brasil, por ser paísis fronterizos

```python
countries = ["Italy","Spain","Germany"]

for country in countries:
    print(country)
    confirmedcases = confirmedCasesGlobal.loc[confirmedCasesGlobal['Country/Region'] == country]
    deathcases = deathsCasesGlobal.loc[deathsCasesGlobal['Country/Region'] == country]
    confirmedcases.drop(['Province/State', 'Country/Region', 'Lat', 'Long'], axis=1, inplace=True)
    confirmedcases_transposed = confirmedcases.T
    confirmedcases_transposed.columns = ['cases']
    days = confirmedcases_transposed.index.values
    confirmedcases_transposed_with_cases = confirmedcases_transposed[confirmedcases_transposed['cases'] > 100]
    confirmedcases_transposed_with_cases['cases'].plot(figsize = (12,7), label = country, legend=False)
plt.legend()
plt.show()
```

A continuación se hará un análisis comparativo con la situación en países con los que tenemos frontera, ya que es con ellos con los que podría haber una posibilidad mayor de nuevos casos dada la permeabilidad en ellas, pero haremos las siguientes consideraciones:

- Se excluye Brasil pues consideraré que la amazonía es un "tapón natural" que impide que haya cierto flujo de posibles contagiados hacia nuestro territorio.

- Igualmente podría pensarse de Panamá (Tapón del Darien) pero por la proximidad de centros poblados, así como lo permeable de la frontera por la situación social local, se considerará.

- El análisis con Venezuela deberá ser visto con mucho recelo, dada la poca información disponible (además de lo poco creíble que se presenta)

- Solo restan Perú y Ecuador con los que sí se puede considerar que se cuenta con una mejor cantidad y calidad de información.

```python
# Casos confirmados en paises limítrofes con Colombia

countries = ["Panama", "Ecuador", "Peru", "Colombia"] # ,"Venezuela"

for country in countries:
    print(country)
    confirmedcases = confirmedCasesGlobal.loc[confirmedCasesGlobal['Country/Region'] == country]
    confirmedcases.drop(['Province/State', 'Country/Region', 'Lat', 'Long'], axis=1, inplace=True)
    confirmedcases_transposed = confirmedcases.T
    confirmedcases_transposed.columns = ['cases']
    days = confirmedcases_transposed.index.values
    confirmedcases_transposed_with_cases = confirmedcases_transposed[confirmedcases_transposed['cases'] > 100]
    confirmedcases_transposed_with_cases['cases'].plot(figsize = (12,7), label = country, legend=False)
plt.legend()
plt.show()
```

```python
# Casos confirmados en paises limítrofes con Colombia

countries = ["Panama", "Ecuador", "Peru", "Colombia"] #, "Venezuela"

for country in countries:
    deathcases = deathsCasesGlobal.loc[deathsCasesGlobal['Country/Region'] == country]
    deathcases.drop(['Province/State', 'Country/Region', 'Lat', 'Long'], axis=1, inplace=True)
    deathcases_transposed = deathcases.T
    deathcases_transposed.columns = ['cases']
    days = deathcases_transposed.index.values
    deathcases_transposed_with_cases = deathcases_transposed[deathcases_transposed['cases'] > 0]
    deathcases_transposed_with_cases['cases'].plot(figsize = (12,7), label = country, legend=False)
plt.legend()
plt.show()
```

```python
len(confirmedcasesColombia_transposed_with_cases)
```

    43

Con esta información, que es tomada en campo, es decir, es la que daremos como "*real*", ya que es la información que ofrecen las autoridades colombianas, se puede realizar un estudio de tendencia y compararlo con los modelos descritos anteriormente, para determinar qué tan fiables pueden llegar a ser, entendiendo que se deben ajustar los parámetros lo mejor posible para que también sirvan para proyecciones.

Vamos a convertir la información en un arreglo a través de la biblioteca `numpy`

```python
Yconfirmed = np.array(confirmedcasesColombia_transposed_with_cases['cases'].values)
Yconfirmed
```

    array([   1,    1,    1,    1,    3,    9,    9,   13,   22,   34,   54,
             65,   93,  102,  128,  196,  231,  277,  378,  470,  491,  539,
            608,  702,  798,  906, 1065, 1161, 1267, 1406, 1485, 1579, 1780,
           2054, 2223, 2473, 2709, 2776, 2852, 2979, 3105, 3233, 3439],
          dtype=int64)

```python
Ydeath = np.array(deathcasesColombia_transposed_with_cases['cases'].values)
Yd
```

    array([  2,   3,   3,   4,   6,   6,   6,  10,  12,  16,  17,  19,  25,
            32,  35,  46,  50,  54,  69,  80, 100, 109, 112, 127, 131, 144,
           153, 153], dtype=int64)

```python
X_b = []
X_b.append(0)
for index in range(1, len(Yconfirmed)):
    X_b.append(Yconfirmed[index])
X_b = np.array(X_b)
```

```python
X_a = []
X_a.append(0)
for index in range(1, len(Yconfirmed)):
    X_a.append(index)
X_a = np.array(X_a)
```

```python
X_2d = []
for index in range(0, len(X_a)):
    X_2d.append([X_a[index], X_b[index]])

X_2d
```

    [[0, 0],
     [1, 1],
     [2, 1],
     [3, 1],
     [4, 3],
     [5, 9],
     [6, 9],
     [7, 13],
     [8, 22],
     [9, 34],
     [10, 54],
     [11, 65],
     [12, 93],
     [13, 102],
     [14, 128],
     [15, 196],
     [16, 231],
     [17, 277],
     [18, 378],
     [19, 470],
     [20, 491],
     [21, 539],
     [22, 608],
     [23, 702],
     [24, 798],
     [25, 906],
     [26, 1065],
     [27, 1161],
     [28, 1267],
     [29, 1406],
     [30, 1485],
     [31, 1579],
     [32, 1780],
     [33, 2054],
     [34, 2223],
     [35, 2473],
     [36, 2709],
     [37, 2776],
     [38, 2852],
     [39, 2979],
     [40, 3105],
     [41, 3233],
     [42, 3439]]

```python
X_reshape = X_b.reshape(-1, 1)
reg = LinearRegression().fit(X_reshape, Yconfirmed)
```

```python
reg.score(X_reshape, Yconfirmed)
```

    0.9999999816544333

```python
reg.coef_
```

    array([0.99998051])

```python
reg.intercept_
```

    0.04307100613834791

```python
reg.predict(np.array([[509]]))
```

    array([509.03315049])

```python
import pandas as pd
paises=pd.read_html("https://coronavirus.jhu.edu/data/mortality")
print(paises)
```

    [            Country  Confirmed  Deaths Case-Fatality  Deaths/100k pop.
    0                US     839675   46583          5.5%             14.24
    1             Italy     187327   25085         13.4%             41.51
    2             Spain     208389   21717         10.4%             46.48
    3            France     157125   21373         13.6%             31.91
    4    United Kingdom     134638   18151         13.5%             27.30
    ..              ...        ...     ...           ...               ...
    131          Malawi         23       3         13.0%              0.02
    132           Malta        444       3          0.7%              0.62
    133          Monaco         94       3          3.2%              7.76
    134        Ethiopia        116       3          2.6%              0.00
    135          Zambia         74       3          4.1%              0.02

    [136 rows x 5 columns]]

# Ajustando el modelo *S.E.I.R.D.* al caso colombiano

Con los modelos descritos anteriormente, se procederá a realizar el ajuste al caso colombiano, con los datos levantados por el [Instituto Nacional de Salud](https://www.ins.gov.co/Noticias/Paginas/Coronavirus.aspx) (INS) y las secretarías de salud de los municipios del pais (principalmente capitales) quiénes son los entes gubernamentales oficiales para emitir los comunicados de la situación de la pandemia en nuestro país.

Se va a realizar el ajuste de los datos de campo y compararlos con los modelos parámetros obtenidos en la primera parte de este trabajo. Para ello, vamos a realizar una breve introducción al ajuste de curvas.

## Ajuste de curvas

Los datos que se tienen de la información suministrada por los entes oficiales dia a dia son, entre otros, casos positivos, muertes acumuladas, recuperados, edad, condición (estable en casa, hospital, UCI), etc. Con esta información se pretende ajustar los parámetros que se ingresarán al modelo *S.E.I.R.D.* descrito arriba y contrastar los resultados obtenidos en el modelo con los datos reales.

Primero, vamos a generar algunos datos de forma aleatoria siguiendo una distribución normal con algo de ruido.

```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import lmfit
from lmfit.lineshapes import gaussian, lorentzian
```

```python
np.random.seed(42)
x = np.linspace(0,20.0, 1001)
data = (gaussian(x, 21, 6.1, 1.2) + np.random.normal(scale=0.1, size = x.size))
plt.plot(x,data)
```

Luego, necesitamos una función que tome el valor de $x$ como primer argumento y los tres parámetros que queremos ajustar (los llamaremos $a$, $b$, $c$) como los siguientes argumentos. Esta es la función que usaremos para ajustar los datos; nuestra biblioteca de ajuste de curvas variará el parámetro hasta que encuentre un buen ajuste (y con suerte encuentre los que usamos para generar los datos, es decir, $a = 21$, $b = 6.1$, $c = 1.2$).

Con esta función, generamos un modelo de ajuste de curvas (usando la biblioteca `lmfit`) y pasamos algunas conjeturas iniciales para los parámetros. Luego ajustamos los datos. Es importante tener en cuenta que los métodos de ajuste de curvas en general no garantizan encontrar un mínimo global y las suposiciones iniciales para los parámetros son cruciales.

```python
def f(x,a,b,c):
    return gaussian(x,a,b,c)
```

```python
mod = lmfit.Model(f)

mod.set_param_hint("a", value = 10.0, vary = True)
mod.set_param_hint("b", value = 10.0, vary = True)
mod.set_param_hint("c", value = 10.0, vary = True)

params = mod.make_params()
```

```python
result = mod.fit(data, params, method = "leastsq", x = x)
result.plot_fit(datafmt = "-")
result.best_values
```

como podrá observarse, los parámetros obtenidos son muy similares a los dados inicialmente para le generación de los datos con ruido, por lo que se puede considerar que el ajuste es adecuado.

Por ahora esto será todo lo que necesitaremos acerca del ajuste de datos para avanzar.

## Extendiendo el modelo *S.E.I.R.D.* al ajuste de curva

En el modelo presentado ariba: los individuos son los primeros susceptibles: pueden contraer Coronavirus (con probabilidad $S/N$) y quedar expuestos. Todavía no pueden transmitir el virus. Después de algunos días ($1/\delta$, para ser exactos), se infectan y propagan el virus. En el transcurso de la infección, pueden morir (más de $1 / \rho$ días, con probabilidad $\alpha$) o sobrevivir y finalmente recuperarse (después de $1 / \gamma$ días):

Agregandor un nuevo compartimento: *Crítico*, para personas que necesitan cuidados intensivos. Esto nos permitirá modelar hospitales desbordados. Por supuesto, solo las personas infectadas pueden ingresar al estado crítico. Desde el estado crítico, pueden morir o recuperarse.

Llegamos a estas transiciones:

Necesitamos la probabilidad de que un individuo infectado se vuelva gravemente enfermo. Llamaremos $p(I \rightarrow C)$. Lógicamente, la probabilidad de pasar de infectado a recuperado es, por lo tanto, $1-p(I \rightarrow C)$.

Necesitamos otra probabilidad, la probabilidad de morir mientras se está en estado crítico: $p(C \rightarrow D)$. Análogamente, la probabilidad de pasar de infectado a recuperado es $1-p(C \rightarrow D)$.

Ahora solo nos falta la tasa de enfermarse críticamente después de ser infectado, la tasa de morir mientras está gravemente enfermo y la tasa de recuperación mientras está gravemente enfermo. Leyendo las estimaciones actuales dadas por las entidades locales, obtengo los siguientes números (a medida que pasa el tiempo debería haber números más precisos):

- Número de días de infectados a críticos: $12 (\rightarrow tasa: 1/12)$

- Número de días de crítico a muerto: $7.5 (\rightarrow tasa: 1/7.5)$

- Número de días de crítico a recuperado: $(6.5 \rightarrow tasa: 1/6.5)$

Completando todo esto en:

## Triaje y recursos limitados

La idea de un [triaje](https://es.wikipedia.org/wiki/Triaje) clínico es estalecer quién recibe tratamiento con los recursos limitados disponibles. Esto podría incorporarse a un modelo de la siguiente manera:

Supongamos que un país (ciudad, o región) cuenta con $B$ camas [UCI](https://es.wikipedia.org/wiki/Unidad_de_cuidados_intensivos) (Unidades de Cuidados Intensivos) adecuadas para tratar casos de coronavirus críticamente enfermos. Si hay más de $B$ pacientes en estado crítico (la cantidad es $C$, nuestro compartimento crítico), todos los que están por encima de $B$ no pueden ser tratados y, por lo tanto, mueren. Por ejemplo, si $B = 500$ y $C = 700$, mueren $200$ pacientes porque no hay recursos para tratarlos. (esto suponiendo que todos necesitan de dichas instalaciones y ninguno sobrevive sin ese tipo de cuidado)

Esto significa que las personas $C-B$ mueren debido a la escasez. Por supuesto, si tenemos más camas que pacientes críticamente enfermos (por ejemplo, $B = 500$ y $C = 100$), entonces no tenemos a $C-B = -400$ personas muriendo, eso no tendría sentido. Por el contrario, tenemos un $max(0, C-B)$ de personas muriendo debido a la escasez (piénselo: si $C<B$ (más camas que pacientes), entonces $C-B<0$, entonces $max(0, C-B) = 0$, y $0$ personas mueren debido a la escasez; si $C>B$ (no hay suficientes camas), entonces $C-B>0$, entonces $max(0,C-B)=C-B$, y $C-B$ personas mueren por escasez)

Por lo tanto, debemos expandir nuestras transiciones: desde $C$, hay dos poblaciones a las que tenemos que mirar: $max(0, C-B)$ personas mueren debido a la escasez, y el resto recibe el tratamiento como hemos derivado anteriormente. ¿Qué es el resto? Bueno, si $C  B$ (no hay suficientes camas), entonces $B$ personas reciben tratamiento. Eso significa que "*el resto*" - la cantidad de personas que reciben tratamiento - es $min(B, C)$ (nuevamente, si $C  B$, entonces $min(B, C) =B$ personas reciben tratamiento; las matemáticas se verifican).

Finalmente obtenemos este modelo revisado (con muertes que ocurren inmediatamente para todos los que superan el número de camas disponibles; uno podría cambiar esto a tomar varios días, morir con una probabilidad del $75\%$, etc.):

Y estas son las ecuaciones que lo acompañan (tenga en cuenta que las camas son una función del tiempo, llegaremos a eso en solo un minuto)

$$
\begin{align}
\frac{dS}{dt} & = -\beta(t) \cdot I \cdot \frac{S}{N} \\
\frac{dE}{dt} & = \beta(t) \cdot I \cdot \frac{S}{N} - \delta \cdot E\\
\frac{dI}{dt} & = \delta \cdot E - \frac{1}{12} \cdot p(I \rightarrow C) \cdot I - \gamma \cdot (1-p(I \rightarrow C)) \cdot I\\
\frac{dC}{dt} & = \frac{1}{12} \cdot p(I \rightarrow C) \cdot I - \frac{1}{7.5} \cdot p(C \rightarrow D)  \cdot min(Beds(t),C) - max(0,C-Beds(t)) - \frac{1}{6.5} \cdot (1-p(C \rightarrow D)) \cdot min(Beds(t),C) \\
\frac{dR}{dt} & = \gamma \cdot (1-p(I \rightarrow C)) \cdot I + \frac{1}{6.5} \cdot (1-p(C \rightarrow D)) \cdot min(Beds(t),C) \\
\frac{dD}{dt} & = \frac{1}{7.5} \cdot p(C \rightarrow D) \cdot min(Beds(t),C) + max(0,C-Beds(t))
\end{align}
$$

Nuevamente, tómese un tiempo para ver cómo estas ecuaciones se relacionan directamente con las transiciones de estado anteriores. Parecen difíciles, pero son solo otra forma de describir el diagrama.

## Variables dependientes del tiempo

Para este modelo, solo tenemos dos variables dependientes del tiempo: $R_0(t)$ (y por lo tanto $\beta (t)$, como $R_0 = \beta / \gamma$) y $\text{Beds}(t)$.

Para $R_0(t)$, utilizaremos nuevamente la siguiente función logística:

$$R_0(t)=\frac{R_{0_{start}}-R_{0_{end}}}{1+e^{-k(-x+x_0)}}+R_{0_{end}}$$

Para $\text{Beds}(t)$, la idea es que, a medida que el virus se propaga, los países reaccionan y comienzan a construir hospitales, liberar camas, etc. Por lo tanto, el número de camas disponibles aumenta con el tiempo. Una forma (muy) simple de hacer esto es modelar la cantidad de camas como

$$\text{Beds}(t) = \text{Beds}_0 + s \cdot t \cdot \text{Beds}_0$$

Donde $\text{Beds}_0$ es la cantidad total de camas *UCI* disponibles y $s$ es un factor de escala. En esta fórmula, el número de camas aumenta en $s$ veces el número inicial de camas por día (por ejemplo, si $s = 0.01$, entonces el día $t = 100$, $\text{Beds}(t) = 2 * \text{Beds}_0$)

## Ajustando el modelo

En primer lugar, pensemos en lo que sabemos y lo que queremos saber. Esto nos ayuda a encontrar qué parámetros podemos arreglar y cuáles queremos ajustar.

Aquí están todos los parámetros que necesita nuestro modelo (es decir, todas las variables en las ecuaciones más las variables en las funciones para $R_0(t)$ y $\text{Beds}(t)$:

- $N$: población total

- $D(t)$: número de personas fallecidas el día $t$

- $\beta(t)$: cantidad esperada de personas que una persona infectada infecta por día

- $\gamma$: la proporción de infectados que se recuperan por día ($\gamma = 1/D$)

- $R_{0_{start}}$: parámetro en $R_0(t)$

- $R_{0_{end}}$: parámetro en $R_0(t)$

- $x_0$: parámetro en $R_0(t)$

- $k$: parámetro en $R_0(t)$

- $s$: parámetro en $\text{Beds}(t)$

- $\text{Beds}_0$: parámetro en $R_0(t)$

- $\delta$: duración del período de incubación

- $p(I \rightarrow C)$: Probabilidad de pasar de infectado a crítico

- $p(C \rightarrow D)$: Probabilidad de pasar de crítico a deceso

Veamos estos: ciertamente no tenemos que ajustar $N$, solo podemos ver la población de la región en la que queremos modelar la enfermedad. Lo mismo ocurre con $\text{Beds}_0$, podemos buscar fácilmente la cantidad de camas de la *UCI* en un region. $\delta$ y $\gamma$ están fijados en $\delta = 1/9$ y $\gamma = 1/3$, estas son las mejores estimaciones encontradas en la literatura. Con respecto a $\beta(t)$, estamos calculando beta a través de $R_0(t)$ y $\gamma$, por lo que no es necesario encontrar ningún parámetro separado para $\beta$. Se pueden instalar los factores de escala de camas; Es cierto que no juega un papel importante en el resultado ya que, hasta ahora, la cantidad de personas que no reciben tratamiento debido a la escasez era pequeña en comparación con la cantidad total de muertes.

He reunido dos estimaciones para las probabilidades $p(I \rightarrow C)$ y $p(C \rightarrow D)$, divididas por grupo de edad. Esto nos permitiría calcular las probabilidades como derivamos en el último artículo: ponderado por la proporción de la población por grupo de edad. Aquí, de hecho, intentaremos ajustar estos parámetros; hasta ahora, tratar de ajustar estos resultados siempre arrojados extremadamente cerca de las estimaciones recopiladas.

Con todo, solo nos quedan estos parámetros para ajustar:

- $p(I \rightarrow C)$

- $p(C \rightarrow D)$

- $R_{0_{start}}$: parámetro en $R_0(t)$

- $R_{0_{end}}$: parámetro en $R_0(t)$

- $x_0$: parámetro en $R_0(t)$

- $k$: parámetro en $R_0(t)$

- $s$: parámetro en $\text{Beds}(t)$

## Datos suplementarios y del coronavirus

Se han recopilado y limpiado datos para grupos de edad, probabilidades y camas de *UCI* de los [datos de la ONU](https://data.un.org/). Obtendremos los números de casos actualizados desde [aquí](https://data.humdata.org/dataset/novel-coronavirus-2019-ncov-cases). Echemos un vistazo:

Primero, cargue los datos ...

```python
beds = pd.read_csv("https://raw.githubusercontent.com/hf2000510/infectious_disease_modelling/master/data/beds.csv", header=0)
agegroups = pd.read_csv("https://raw.githubusercontent.com/hf2000510/infectious_disease_modelling/master/data/agegroups.csv")
probabilities = pd.read_csv("https://raw.githubusercontent.com/hf2000510/infectious_disease_modelling/master/data/probabilities.csv")
covid_data = pd.read_csv("https://tinyurl.com/t59cgxn", parse_dates=["Date"], skiprows=[1])  # I've shortened the humdata.org URL
covid_data["Location"] = covid_data["Country/Region"]
```

... y cree algunos diccionarios de búsqueda para facilitar el acceso a grupos de edad, camas, etc.

```python
# create some dicts for fast lookup
# 1. beds
beds_lookup = dict(zip(beds["Country"], beds["ICU_Beds"]))
# 2. agegroups
agegroup_lookup = dict(zip(agegroups['Location'], agegroups[['0_9', '10_19', '20_29', '30_39', '40_49', '50_59', '60_69', '70_79', '80_89', '90_100']].values))

# store the probabilities collected
prob_I_to_C_1 = list(probabilities.prob_I_to_ICU_1.values)
prob_I_to_C_2 = list(probabilities.prob_I_to_ICU_2.values)
prob_C_to_Death_1 = list(probabilities.prob_ICU_to_Death_1.values)
prob_C_to_Death_2 = list(probabilities.prob_ICU_to_Death_2.values)
```

Aquí hay un extracto de todas las tablas para tener una idea de lo que estamos tratando:

```python
beds.head(3)
```

      Country
      Continent
      ICU_Beds

      0
      Japan
      Asia
      7.3

      1
      South Korea
      Asia
      10.6

      2
      Russia
      Europe
      8.3

```python
agegroups.head(5)
```

      Unnamed: 0
      Location
      0_9
      10_19
      20_29
      30_39
      40_49
      50_59
      60_69
      70_79
      80_89
      90_100
      Total

      0
      0
      World
      1.342381e+09
      1.253463e+09
      1.192080e+09
      1.150350e+09
      973155000.0
      833622000.0
      591786000.0
      312459000.0
      124116000.0
      20814000.0
      7.794226e+09

      1
      1
      Sustainable Development Goal (SDG) regions
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN
      NaN
      0.000000e+00

      2
      2
      Sub-Saharan Africa
      3.243990e+08
      2.534300e+08
      1.851890e+08
      1.334170e+08
      89292000.0
      56464000.0
      33385000.0
      15120000.0
      3477000.0
      191000.0
      1.094364e+09

      3
      3
      Eastern Africa
      1.300380e+08
      1.056750e+08
      7.815600e+07
      5.410800e+07
      35271000.0
      21714000.0
      12849000.0
      5978000.0
      1513000.0
      102000.0
      4.454040e+08

      4
      4
      Burundi
      3.873000e+06
      2.725000e+06
      2.030000e+06
      1.539000e+06
      748000.0
      492000.0
      339000.0
      112000.0
      30000.0
      2000.0
      1.189000e+07

```python
probabilities
```

      Age
      prob_I_to_ICU_1
      prob_ICU_to_Death_1
      prob_I_to_ICU_2
      prob_ICU_to_Death_2

      0
      0_9
      0.001
      0.00
      0.00100
      0.000

      1
      10_19
      0.003
      0.00
      0.00300
      0.000

      2
      20_29
      0.009
      0.05
      0.00600
      0.000

      3
      30_39
      0.025
      0.06
      0.00900
      0.000

      4
      40_49
      0.030
      0.10
      0.02500
      0.120

      5
      50_59
      0.050
      0.26
      0.03560
      0.365

      6
      60_69
      0.070
      0.57
      0.05926
      0.675

      7
      70_79
      0.100
      0.86
      0.10100
      0.850

      8
      80_89
      0.140
      0.95
      0.14100
      0.950

      9
      90_100
      0.160
      0.98
      0.16000
      0.990

```python
covid_data
```

      Province/State
      Country/Region
      Lat
      Long
      Date
      Value
      ISO 3166-1 Alpha 3-Codes
      Region Code
      Sub-region Code
      Intermediate Region Code
      Location

      0
      NaN
      Afghanistan
      33.0
      65.0
      2020-04-25
      47
      AFG
      142.0
      34.0
      NaN
      Afghanistan

      1
      NaN
      Afghanistan
      33.0
      65.0
      2020-04-24
      43
      AFG
      142.0
      34.0
      NaN
      Afghanistan

      2
      NaN
      Afghanistan
      33.0
      65.0
      2020-04-23
      42
      AFG
      142.0
      34.0
      NaN
      Afghanistan

      3
      NaN
      Afghanistan
      33.0
      65.0
      2020-04-22
      40
      AFG
      142.0
      34.0
      NaN
      Afghanistan

      4
      NaN
      Afghanistan
      33.0
      65.0
      2020-04-21
      36
      AFG
      142.0
      34.0
      NaN
      Afghanistan

      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...
      ...

      25075
      NaN
      Zimbabwe
      -20.0
      30.0
      2020-01-26
      0
      ZWE
      2.0
      202.0
      14.0
      Zimbabwe

      25076
      NaN
      Zimbabwe
      -20.0
      30.0
      2020-01-25
      0
      ZWE
      2.0
      202.0
      14.0
      Zimbabwe

      25077
      NaN
      Zimbabwe
      -20.0
      30.0
      2020-01-24
      0
      ZWE
      2.0
      202.0
      14.0
      Zimbabwe

      25078
      NaN
      Zimbabwe
      -20.0
      30.0
      2020-01-23
      0
      ZWE
      2.0
      202.0
      14.0
      Zimbabwe

      25079
      NaN
      Zimbabwe
      -20.0
      30.0
      2020-01-22
      0
      ZWE
      2.0
      202.0
      14.0
      Zimbabwe

25080 rows × 11 columns

- La tabla de camas tiene la cantidad de camas de *UCI* por cada $100k$ habitantes para muchos países.

- La tabla de grupos de edad tiene la cantidad de personas por grupo de edad para todos los países.

- La tabla de probabilidades tiene probabilidades que reuní para las transiciones $I \rightarrow C$ y $C \rightarrow D$ por grupo de edad (dos probabilidades separadas; solo use $_1$ o $_2$) (no las usaremos ya que intentaremos ajustar las probabilidades de transición)

- `covid_data` es una tabla enorme con el número de muertes por región por día, desde $2020-01-22$ en adelante.

Como ejemplo, aquí están las muertes generales para todo el mundo:

```python
covid_data.groupby("Date").sum()[["Value"]].plot(figsize=(12, 8), title="Covid-19 total fatalities (world)");
```

No todos los países y regiones están incluidos en la tabla. Sin embargo, si desea modelar el brote en una región que no está incluida, debería poder encontrar los datos que necesita a través de una búsqueda rápida en *Google*.

Es posible que haya notado que solo estamos usando datos para el número de muertes y no el número de casos reportados. La razón es simple: la notificación de casos confirmados es extremadamente ruidosa y depende en gran medida de la cantidad de pruebas (y aún con suficientes pruebas, no todas las personas infectadas serán examinadas). Por ejemplo, el número de casos podría aumentar de $10000$ a $15000$ de un día para otro, pero eso podría deberse a que el número de pruebas aumentó en $5000$. En general, el número de muertes reportadas es mucho más preciso: las muertes son bastante difíciles perder, por lo que los números reportados son probablemente bastante cercanos a los números reales.

## Codificando el modelo

Una vez configurados todos los datos, ahora podemos comenzar a codificar nuestro modelo. De las últimas ecuaciones determinadas arriba, vamos a traducirlos a código (una advertencia: estamos calculando $\beta$ un poco simplificado aquí ya que calcularlo rigurosamente sería mucho más complicado y tendría un impacto insignificante en el resultado; puede ver la implementación extendida [aquí](https://github.com/hf2000510/infectious_disease_modelling/blob/master/extended_beta_implementation.ipynb) si eres curioso):

```python
def deriv(y, t, beta, gamma, sigma, N, p_I_to_C, p_C_to_D, Beds):
    S, E, I, C, R, D = y

    dSdt = -beta(t) * I * S / N
    dEdt = beta(t) * I * S / N - sigma * E
    dIdt = sigma * E - 1/12.0 * p_I_to_C * I - gamma * (1 - p_I_to_C) * I
    dCdt = 1/12.0 * p_I_to_C * I - 1/7.5 * p_C_to_D * min(Beds(t), C) - max(0, C-Beds(t)) - (1 - p_C_to_D) * 1/6.5 * min(Beds(t), C)
    dRdt = gamma * (1 - p_I_to_C) * I + (1 - p_C_to_D) * 1/6.5 * min(Beds(t), C)
    dDdt = 1/7.5 * p_C_to_D * min(Beds(t), C) + max(0, C-Beds(t))
    return dSdt, dEdt, dIdt, dCdt, dRdt, dDdt
```

Esas son realmente las ecuaciones escritas en `Python`, ¡no pasa nada emocionante! Ahora, sobre la función $R-0$ y todo el modelo que toma los parámetros para ajustarse (y algunos ya los conocemos) para calcular las curvas de $S, E, I, C, R$ y $D$:

```python
gamma = 1.0/9.0
sigma = 1.0/3.0

def logistic_R_0(t, R_0_start, k, x0, R_0_end):
    return (R_0_start-R_0_end) / (1 + np.exp(-k*(-t+x0))) + R_0_end

def Model(days, agegroups, beds_per_100k, R_0_start, k, x0, R_0_end, prob_I_to_C, prob_C_to_D, s):

    def beta(t):
        return logistic_R_0(t, R_0_start, k, x0, R_0_end) * gamma

    # agegroups is list with number of people per age group -> sum to get population
    N = sum(agegroups)

    def Beds(t):
        # the table stores beds per 100 k -> get total number
        beds_0 = beds_per_100k / 100_000 * N
        return beds_0 + s*beds_0*t  # 0.003

    y0 = N-1.0, 1.0, 0.0, 0.0, 0.0, 0.0  # one exposed, everyone else susceptible
    t = np.linspace(0, days, days)
    ret = odeint(deriv, y0, t, args=(beta, gamma, sigma, N, prob_I_to_C, prob_C_to_D, Beds))
    S, E, I, C, R, D = ret.T

    R_0_over_time = [beta(i)/gamma for i in range(len(t))]  # get R0 over time for plotting

    return t, S, E, I, C, R, D, R_0_over_time, Beds, prob_I_to_C, prob_C_to_D
```

Esto es lo que obtenemos cuando simulamos una enfermedad en una población con camas insuficientes en la *UCI* (la función graficada está en el cuaderno):

Puede ver el aumento en las muertes atribuidas a la escasez de recursos (no hay suficientes camas) en el gráfico en la esquina inferior derecha.

Bien, ahora tenemos el modelo y los datos. ¡Aprovechemos nuestras habilidades de ajuste de curvas!

## Ajuste de curvas

Primero, hacemos que los datos se ajusten y los parámetros que ya conocemos, y definimos las suposiciones iniciales y los límites inferior y superior para aquellos que no conocemos (para ayudar al ajustador de curvas y obtener buenos resultados): siéntase libre de cambiar los supuestos iniciales y límites

```python
country = "Colombia"
data = covid_data[covid_data["Location"] == country]["Value"].values[::-1]
agegroups = agegroup_lookup[country]
beds_per_100k = beds_lookup[country]
outbreak_shift = 30
# parameters to fit; form: {parameter: (initial guess, minimum value, max value)}
params_init_min_max = {"R_0_start": (3.0, 2.0, 5.0), "k": (2.5, 0.01, 5.0),
                       "x0": (90, 0, 120), "R_0_end": (0.9, 0.3, 3.5),
                       "prob_I_to_C": (0.05, 0.01, 0.1), "prob_C_to_D": (0.5, 0.05, 0.8),
                       "s": (0.003, 0.001, 0.01)}
```

Un parámetro que es muy importante y del que aún no hemos hablado es *outbreak_shift*: los datos del caso comienzan el 21 de enero, por lo que nuestro modelo pensará que el virus comenzó a extenderse ese día. Para muchos países, esto podría haber sido días o semanas más tarde o más temprano, y tiene un gran impacto en la adaptación. Por supuesto, todavía no sabemos cuándo se infectó la primera persona en cada país; utilice su mejor criterio. Por ejemplo, si cree que el país en el que está tratando de encajar tuvo el primer caso el 30 de enero, debe establecer outbreak_shift en -9.

(Lamentablemente, no es fácil usar *outbreak_shift* como parámetro adicional ya que solo se permiten enteros (días completos), y la programación de enteros es bastante difícil (NP-hard, de hecho); tendríamos que recurrir a costos computacionalmente altos).

Ahora llenamos los datos que queremos ajustar (las muertes por día) con ceros al principio para dar cuenta del *outbreak_shift*. También definimos los valores de $x$ para el ajuste; eso es solo una lista $[0, 1, 2, \ldots, \text{número total de días}]$.

```python
days = outbreak_shift + len(data)
if outbreak_shift >= 0:
    y_data = np.concatenate((np.zeros(outbreak_shift), data))
else:
    y_data = y_data[-outbreak_shift:]

x_data = np.linspace(0, days - 1, days, dtype=int)  # x_data is just [0, 1, ..., max_days] array
```

Para el ajuste, necesitamos una función que tome exactamente un valor de $x$ como primer argumento (el día) y todos los parámetros que queremos ajustar, y que devuelva las muertes predichas por el modelo para ese valor de $x$ y los parámetros, de modo que El ajustador de curvas puede comparar la predicción del modelo con los datos reales. Aquí está:

```python
def fitter(x, R_0_start, k, x0, R_0_end, prob_I_to_C, prob_C_to_D, s):
    ret = Model(days, agegroups, beds_per_100k, R_0_start, k, x0, R_0_end, prob_I_to_C, prob_C_to_D, s)
    # Model returns bit tuple. 7-th value (index=6) is list with deaths per day.
    deaths_predicted = ret[6]
    return deaths_predicted[x]
```

¡No queda mucho por hacer! Simplemente inicialice un modelo de ajuste de curvas, establezca los parámetros de acuerdo con los *inits*, *mins* y *maxs* que definimos, establezca un método de ajuste (puede probar diferentes aquí, diferencial_evolución podría funcionar bien, por ejemplo) y ajuste:

```python
mod = lmfit.Model(fitter)

for kwarg, (init, mini, maxi) in params_init_min_max.items():
    mod.set_param_hint(str(kwarg), value=init, min=mini, max=maxi, vary=True)

params = mod.make_params()
fit_method = "leastsq"
```

```python
result = mod.fit(y_data, params, method=fit_method, x=x_data)
```

```python
result.plot_fit(datafmt="-");
```

¡No está mal! Veamos los parámetros que predice el instalador:

```python
result.best_values
```

    {'R_0_start': 3.0268276911770853,
     'k': 0.04306186939469392,
     'x0': 91.98152811646635,
     'R_0_end': 1.3860850878281252,
     'prob_I_to_C': 0.012382763784346582,
     'prob_C_to_D': 0.12960345609884644,
     's': 0.009574174213453447}

¡Genial, se ven bastante realistas y en línea con muchos puntos de datos reportados en la vida real! $x_0$ es $84$, por lo que con los datos que comienzan el *21 de enero* y el cambio de brote establecido a *30 días*, el día $84$ para nuestro modelo es el *15 de marzo*. $x_0$ es la fecha de la disminución más pronunciada en $R_0$, por lo que nuestro modelo cree que el principal "*bloqueo*" Tuvo lugar en *Italia* alrededor del *15 de marzo*, muy cerca de la fecha real.

Usemos los parámetros que mejor se ajustan para ver el futuro que predice nuestro modelo (puede hacer zoom cuando mira el código en el cuaderno):

Tenga en cuenta el aumento en las muertes debido a los hospitales llenos a finales de marzo de 2020 que aumenta la tasa de mortalidad, que finalmente llega a alrededor del $1,4\%$.

Aquí está la predicción ampliada de marzo a mayo: si el modelo es correcto, Italia ya ha pasado por lo peor y las muertes deberían disminuir considerablemente en los próximos meses. Por supuesto, nuestro modelo piensa que $R_0$ se mantendrá alrededor de $0.6$; Si vuelve a subir a medida que se revierten los bloqueos, ¡los números comenzarán a aumentar nuevamente!

```python

```