Content from Introducción a la analítica de brotes


Última actualización: 2024-11-19 | Mejora esta página

Tiempo estimado: 94 minutos

Hoja de ruta

Preguntas

  • ¿Cómo modelar y analizar un brote?

Objetivos

Al final de este taller usted podrá:

  • Identificar los parámetros necesarios en casos de transmisión de enfermedades infecciosas de persona a persona.

  • Estimar la probabilidad de muerte (CFR).

  • Calcular y graficar la incidencia.

  • Estimar e interpretar la tasa de crecimiento y el tiempo en que se duplica la epidemia.

  • Estimar e interpretar el número de reproducción instantáneo de la epidemia.

Tiempos de ejecución


Explicación del taller (10 minutos)

Realización del taller (100 minutos taller)

  • Parte 1: Estructura de datos y CFR (15 min)

  • Parte 2: Incidencia y tasa de crecimiento (45 min)

  • Parte 3: Rt (40 min)

Discusión 30 minutos

Introducción


Un nuevo brote de virus del Ébola (EVE) en un país ficticio de África occidental

Conceptos básicos a desarrollar

En esta práctica se desarrollarán los siguientes conceptos:

  • Transmisión de enfermedades infecciosas de persona a persona

  • Número de reproducción básico

  • Número de reproducción instantáneo

  • Probabilidad de muerte (IFR, CFR)

  • Intervalo serial

  • Tasa de crecimiento

  • Incidencia

Preparación previa

Antes de comenzar,

  1. descargue la carpeta con los datos y el proyecto desde Carpetas de datos .

  2. Ahí mismo encontrará un archivo .R para instalar las dependencias necesarias para este taller.

  3. Recuerde abrir el archivo RProject denominado Taller-Brotes-Ebola.Rproj antes de empezar a trabajar.

Este paso no solo le ayudará a cumplir con las buenas prácticas de programación en R, sino también a mantener un directorio organizado, permitiendo un desarrollo exitoso del taller.

1. Estructura de datos


Cargue de librerías:

Cargue las librerías necesarias para el análisis epidemiológico. Los datos serán manipulados con tidyverse que es una colección de paquetes para la ciencia de datos.

R

library(tidyverse) # contiene ggplot2, dplyr, tidyr, readr, purrr, tibble
library(readxl) # para leer archivos Excel
library(binom) # para intervalos de confianza binomiales
library(knitr) # para crear tablas bonitas con kable()
library(incidence) # para calcular incidencia y ajustar modelos
library(EpiEstim) # para estimar R(t)

Cargue de bases de datos

Se le ha proporcionado la siguiente base de datos:

  • casos: una base de datos de casos que contiene información de casos hasta el 1 de julio de 2014.

Para leer en R este archivo, utilice la función read_rds de tidyverse. Se creará una tabla de datos almacenada como objeto de clase tibble.

R

casos <- read_rds("data/casos.rds")

Estructura de los datos

Explore la estructura de los datos. Para esto puede utilizar la función glimpse de tidyverse, la cual nos proporciona una visión rápida y legible de la estructura interna de nuestro conjunto de datos.

R

glimpse(casos)

SALIDA

Rows: 166
Columns: 11
$ id_caso                  <chr> "d1fafd", "53371b", "f5c3d8", "6c286a", "0f58…
$ generacion               <dbl> 0, 1, 1, 2, 2, 0, 3, 3, 2, 3, 4, 3, 4, 2, 4, …
$ fecha_de_infeccion       <date> NA, 2014-04-09, 2014-04-18, NA, 2014-04-22, …
$ fecha_inicio_sintomas    <date> 2014-04-07, 2014-04-15, 2014-04-21, 2014-04-…
$ fecha_de_hospitalizacion <date> 2014-04-17, 2014-04-20, 2014-04-25, 2014-04-…
$ fecha_desenlace          <date> 2014-04-19, NA, 2014-04-30, 2014-05-07, 2014…
$ desenlace                <chr> NA, NA, "Recuperacion", "Muerte", "Recuperaci…
$ genero                   <fct> f, m, f, f, f, f, f, f, m, m, f, f, f, f, f, …
$ hospital                 <fct> Military Hospital, Connaught Hospital, other,…
$ longitud                 <dbl> -13.21799, -13.21491, -13.22804, -13.23112, -…
$ latitud                  <dbl> 8.473514, 8.464927, 8.483356, 8.464776, 8.452…

Como puede observar contactos tiene 11 columnas (variables) y 166 filas de datos. En un rápido vistazo puede observar el tipo de las variables por ejemplo, la columna desenlace tiene formato carácter (chr) y contiene entre sus valores "Recuperación" o "Muerte".

Además, puede encontrar estas variables:

  • El identificador id_caso

  • La generación de infectados (cuantas infecciones secundarias desde la fuente hasta el sujeto han ocurrido)

  • La fecha de infección

  • La fecha de inicio de síntomas

  • La fecha de hospitalización

  • La fecha del desenlace que, como se puede observar, en la siguiente variable puede tener entre sus opciones NA (no hay información hasta ese momento o no hay registro), recuperación y muerte

  • La variable género que puede ser f de femenino o m de masculino

  • El lugar de hospitalización, en la variable hospital

  • Y las variables longitud y latitud

Note que las fechas ya están en formato fecha (date).

2. CFR


Probabilidad de muerte en los casos reportados (CFR, por Case Fatality Risk)

R

table(casos$desenlace, useNA = "ifany")

SALIDA


      Muerte Recuperacion         <NA>
          60           43           63 

Desafío 1

Calcule la probabilidad de muerte en los casos reportados (CFR) tomando el número de muertes y el número de casos con desenlace final conocido del objeto casos. Esta vez se calculará el CFR con el método Naive. Los cálculos Naive (inocentes) tienen el problema de que pueden presentar sesgos, por lo que no deberían ser utilizados para informar decisiones de salud pública. Hablaremos de estos sesgos en profundidad en el día 4.

Durante este taller se le presentarán algunos retos, para los cuales obtendrá algunas pistas, por ejemplo en el presente reto se le presenta una pista, la cual es un fragmento del código que usted debe completar para alcanzar la solución. En los espacios donde dice COMPLETE por favor diligencie el código faltante.

R

muertes <-  COMPLETE

casos_desenlace_final_conocido <- sum(casos$desenlace %in% c("Muerte", "Recuperacion")) 

CFR <- COMPLETE / COMPLETE

Ejemplo,

R

# Reto
muertes <-  COMPLETE
#Solución
muertes <- sum(casos$desenlace %in% "Muerte") 

SALIDA

[1] 0.5825243

R

muertes <- sum(casos$desenlace %in% "Muerte") 

casos_desenlace_final_conocido <- sum(casos$desenlace %in% c("Muerte", "Recuperacion")) 

CFR <- muertes / casos_desenlace_final_conocido

print(CFR)

SALIDA

[1] 0.5825243

Para complementar el calculo del CFR se pueden calcular sus intervalos de confianza por medio de la función binom.confint. La función binom.confint se utiliza para calcular intervalos de confianza para una proporción en una distribución binomial, que corresponde, por ejemplo, a cuando tenemos el total de infecciones con desenlace final conocido (recuperado o muerte). Esta función pide tres argumentos: 1) el número de muertes y 2) el número total de casos con desenlace final conocido, es decir sin importar que hayan muerto o se hayan recuperado, pero sin cuenta los datos con NA; 3) el método que se utilizará para calcular los intervalos de confianza, en este caso “exact” (método Clopper-Pearson).

Desafío 2

Determine el CFR con sus intervalos de confianza utilizando la función binom.confint. Y obtenga este resultado:

CFR con intervalos de confianza
method x n mean lower upper
exact 60 103 0.5825243 0.4812264 0.6789504

Recuerde diligenciar los espacios donde dice COMPLETE. Y obtenga este resultado

R

CFR_con_CI <- binom.confint(COMPLETE, COMPLETE, method = "COMPLETE") %>%
  kable(caption = "**COMPLETE ¿QUE TITULO LE PONDRÍA?**")

CFR_con_CI

R

CFR_con_CI <- binom.confint(muertes, 
                                       casos_desenlace_final_conocido, method = "exact") %>%
  kable(caption = "**CFR con intervalos de confianza**")

CFR_con_CI
CFR con intervalos de confianza
method x n mean lower upper
exact 60 103 0.5825243 0.4812264 0.6789504

3. Incidencia


3.1. Curva de incidencia diaria

El paquete incidence es de gran utilidad para el análisis epidemiológico de datos de incidencia de enfermedades infecciosas, dado que permite calcular la incidencia a partir del intervalo temporal suministrado (e.g. diario o semanal). Dentro de este paquete esta la función incidence la cual cuenta con los siguientes argumentos:

  1. dates contiene una variable con fechas que representan cuándo ocurrieron eventos individuales, como por ejemplo la fecha de inicio de los síntomas de una enfermedad en un conjunto de pacientes.

  2. interval es un intervalo de tiempo fijo por el que se quiere calcular la incidencia. Por ejemplo, interval = 365 para un año. Si no se especifica, el valor por defecto es diario.

  3. last_date fecha donde se establecerá un limite temporal para los datos. Por ejemplo, la última fecha de hospitalización. Para este tercer argumento, podemos incluir la opción max y la opción na.rm. La primera para obtener la última fecha de una variable y la segunda para ignorar los NA en caso de que existan.

Por ejemplo, se podría escribir last_date = max(base_de_datos$vector_ultima_fecha, na.rm = TRUE)

Con esta información la función agrupa los casos según el intervalo de tiempo especificado y cuenta el número de eventos (como casos de enfermedad) que ocurrieron dentro de cada intervalo.

Desafío 3

Calcule la incidencia diaria usando únicamente el primer argumento de la función incidence ¿Qué fecha sería la más adecuada? Tenga en cuenta que se espera que esta sea la que pueda dar mejor información, es decir la menor cantidad de NAs.

R

incidencia_diaria <- incidence(COMPLETE)
incidencia_diaria

El resultado es un objeto de clase incidencia (incidence) que contiene el recuento de casos para cada intervalo de tiempo, lo que facilita su visualización y análisis posterior. Como puede observar la función produjo los siguientes datos:

SALIDA

<incidence object>
[166 cases from days 2014-04-07 to 2014-06-29]

$counts: matrix with 84 rows and 1 columns
$n: 166 cases in total
$dates: 84 dates marking the left-side of bins
$interval: 1 day
$timespan: 84 days
$cumulative: FALSE

R

incidencia_diaria <- incidence(casos$fecha_inicio_sintomas)
incidencia_diaria

SALIDA

<incidence object>
[166 cases from days 2014-04-07 to 2014-06-29]

$counts: matrix with 84 rows and 1 columns
$n: 166 cases in total
$dates: 84 dates marking the left-side of bins
$interval: 1 day
$timespan: 84 days
$cumulative: FALSE

Como resultado de la función se produjo un objeto tipo lista. Este objeto arroja estos datos: 166 casos contemplados entre los días 2014-04-07 al 2014-06-29 para un total de 84 días; se menciona que el intervalo es de 1 día, dado que no se utilizo específico explicitamente el parámetro por lo cual quedó su valor por defecto. Finalmente se menciona “cumulative : FALSE” lo que quiere decir que no se esta haciendo el acumulado de la incidencia, es decir que los casos corresponden a los del intervalo interval: 1 day, es decir a los casos nuevos cada día en específico.

Ahora haga una gráfica de la incidencia diaria.

R

plot(incidencia_diaria, border = "black")

En el Eje X (Fechas): Se puede observar fechas van desde el 7 de abril de 2014 hasta una fecha posterior al 21 de junio de 2014. Estas fechas representan el período de observación del brote.

En el Eje Y (Incidencia Diaria): La altura de las barras indica el número de nuevos casos reportados cada fecha según el tipo de fecha escogido.

Dado que no se agregó el parámetro interval la incidencia quedó por defecto diaria, produciéndose un histograma en el que cada barra representa la incidencia de un día, es decir, los casos nuevos. Los días sin barras sugieren que no hubo casos nuevos para esa fecha o que los datos podrían no estar disponibles para esos días.

A pesar de que hay una curva creciente, hay periodos con pocos o ningún caso. ¿Porque cree que podrían darse estos periodos de pocos a pesar de la curva creciente?

Usualmente al inicio de la transmisión en la fase exponencial, y dependiendo el periodo de incubación y el intervalo serial, se van a ver días sin casos. Eso no significa que la curva no sea creciente. Usualmente, al agrupar por semana ya no se verá la ausencia de casos.

3.2. Cálculo de la incidencia semanal

Teniendo en cuenta lo aprendido con respecto a la incidencia diaria, cree una variable para incidencia semanal. Luego, interprete el resultado y haga una gráfica. Para escoger la fecha que utilizará como última fecha debe asignarle un valor al argumento last_date de la función incidence ¿Qué fecha sería la más adecuada? Tenga en cuenta que la fecha debe ser posterior a la fecha que se haya escogido como el primer argumento.

Desafío 4

R

incidencia_semanal <- incidence(PRIMER ARGUMENTO,  #COMPLETE
                                SEGUNDO ARGUMENTO, #COMPLETE 
                                TERCER ARGUMENTO)  #COMPLETE

SALIDA

<incidence object>
[166 cases from days 2014-04-07 to 2014-06-30]
[166 cases from ISO weeks 2014-W15 to 2014-W27]

$counts: matrix with 13 rows and 1 columns
$n: 166 cases in total
$dates: 13 dates marking the left-side of bins
$interval: 7 days
$timespan: 85 days
$cumulative: FALSE

R

incidencia_semanal <- incidence(casos$fecha_inicio_sintomas, 
                                interval = 7, 
                                last_date = max(casos$fecha_de_hospitalizacion,
                                              na.rm = TRUE))
incidencia_semanal

SALIDA

<incidence object>
[166 cases from days 2014-04-07 to 2014-06-30]
[166 cases from ISO weeks 2014-W15 to 2014-W27]

$counts: matrix with 13 rows and 1 columns
$n: 166 cases in total
$dates: 13 dates marking the left-side of bins
$interval: 7 days
$timespan: 85 days
$cumulative: FALSE

R

plot(incidencia_semanal, border = "black")

Compare la gráfica de incidencia diaria con la de incidencia semanal. ¿Qué observa? ¿Los datos se comportan diferente? ¿Es lo que esperaba? ¿Logra observar alguna tendencia?

4. Tasa de crecimiento


4.1. Modelo log-lineal

Estimación de la tasa de crecimiento mediante un modelo log-lineal

Para observar mejor las tendencias de crecimiento en el número de casos se puede visualizar la incidencia semanal en una escala logarítmica. Esto es particularmente útil para identificar patrones exponenciales en los datos.

Grafique la incidencia transformada logarítmicamente:

R

ggplot(as.data.frame(incidencia_semanal)) + 
  geom_point(aes(x = dates, y = log(counts))) + 
  scale_x_incidence(incidencia_semanal) +
  xlab("Semana") +
  ylab("Incidencia semanal logarítmica") + 
  theme_minimal()

Ajuste un modelo log-lineal a los datos de incidencia semanal

R

ajuste_modelo <- incidence::fit(incidencia_semanal)
ajuste_modelo

SALIDA

<incidence_fit object>

$model: regression of log-incidence over time

$info: list containing the following items:
  $r (daily growth rate):
[1] 0.04145251

  $r.conf (confidence interval):
          2.5 %     97.5 %
[1,] 0.02582225 0.05708276

  $doubling (doubling time in days):
[1] 16.72148

  $doubling.conf (confidence interval):
        2.5 %   97.5 %
[1,] 12.14285 26.84302

  $pred: data.frame of incidence predictions (12 rows, 5 columns)

Desafío 5

¿Qué observa en este resultado?

$model: Indica que se ha realizado una regresión logarítmica de la incidencia en función del tiempo. Esto implica que la relación entre el tiempo y la incidencia de la enfermedad ha sido modelada como una función lineal en escala logarítmica en la incidencia con el fin de entender mejor las tendencias de crecimiento.

$info: Contiene varios componentes importantes del análisis:

  1. $r (daily growth rate) Tasa de crecimiento diaria: 0.04145251

La tasa de crecimiento diaria estimada del brote es de 0.0415. Esto significa que cada día la cantidad de casos está creciendo en un 4.15% con respecto al día anterior, bajo la suposición de un crecimiento exponencial constante durante el periodo modelado.

Si quisiera acceder a esta información sin ingresar al modelo podría hacerlo con el siguiente código:

R

tasa_crecimiento_diaria <- ajuste_modelo$info$r
cat("La tasa de crecimiento diaria es:", tasa_crecimiento_diaria, "\n")

SALIDA

La tasa de crecimiento diaria es: 0.04145251 
  1. $r.conf (confidence interval): 2.5 % 0.02582225 97.5 % 0.05708276

El intervalo de confianza del 95% para la tasa de crecimiento diaria está entre 0.0258 (2.58%) y 0.0571 (5.71%).

$doubling (doubling time in days): 16.72148

  1. El tiempo de duplicación estimado del número de casos nuevos es de aproximadamente 16.72 días. Esto significa que, bajo el modelo actual y con la tasa de crecimiento estimada, se espera que el número de casos de la curva epidémica actual se duplique cada 16.72 días.

$doubling.conf (confidence interval): 2.5 % 12.14285 97.5 % 26.84302

  1. El intervalo de confianza del 95% para el tiempo de duplicación está entre aproximadamente 12.14 y 26.84 días. Este amplio rango refleja la incertidumbre en la estimación y puede ser consecuencia de la variabilidad en los datos o de un tamaño de muestra pequeño.

$pred: Contiene las predicciones de incidencia observada. Incluye las fechas, la escala de tiempo en días desde el inicio del brote, los valores ajustados (predicciones) y los límites inferior y superior del intervalo de confianza para las predicciones.

Si quiere conocer un poco más de este componente puede explorarlo con la función glimpse.

R

glimpse(ajuste_modelo$info$pred)

¿El modelo se ajusta bien a los datos? Verifique el \(R^2\)

R

AjusteR2modelo <- summary(ajuste_modelo$model)$adj.r.squared
cat("El R cuadrado ajustado es:", AjusteR2modelo, "\n")

SALIDA

El R cuadrado ajustado es: 0.7551113 

Antes de continuar ¿Considera más adecuado usar una gráfica semanal para buscar un ajuste de los datos? ¿Por qué?

¿Es preferible calcular la tasa de crecimiento diaria con el ajuste semanal y no con el ajuste diario?

Es preferible estimar la tasa de crecimiento diaria utilizando el ajuste de la incidencia semanal en lugar de la incidencia diaria debido a que los datos diarios pueden ser muy volátiles en los primeros días de la curva exponencial. Esto puede suceder por varias razones:

  • Las fluctuaciones naturales, ciclos de informes, retrasos en el reporte y los errores de medición, que pueden no reflejar cambios reales en la transmisión de la enfermedad.

  • Los datos diarios pueden tener más lagunas o inexactitudes.

  • Eventos de superdispersión o las intervenciones de control.

El uso de datos semanales puede suavizar estas fluctuaciones, dando una mejor idea de la tendencia subyacente. Al utilizar una media móvil semanal, se suavizan estas fluctuaciones, lo que proporciona una imagen más clara de la tendencia subyacente. Esto permite mejorar la precisión de la estimación y evitar el sesgo de los días de la semana, así como mejorar el modelo al reducir el número total de puntos, dado que puede ayudar a evitar el sobreajuste y mejorar la generalización del modelo.

Ejemplo: Algunos fenómenos pueden variar sistemáticamente según el día de la semana. Por ejemplo, el número de pruebas de COVID-19 realizadas podría ser menor los fines de semana, lo que podría afectar a la incidencia reportada. Al utilizar una media móvil semanal, se evita este tipo de sesgo.

Grafique la incidencia incluyendo una línea que represente el modelo.

Con plot

R

plot(incidencia_semanal, fit = ajuste_modelo)

Tras ajustar el modelo log-lineal a la incidencia semanal para estimar la tasa de crecimiento de la epidemia, el gráfico muestra la curva de ajuste superpuesta a la incidencia semanal observada.

Al final del gráfico se puede observar que la incidencia semanal disminuye.

¿Porqué cree que podría estar pasando esto? ¿Cómo lo solucionaría?

Si se grafica por fecha de inicio de síntomas mientras el brote está creciendo, siempre se va a ver un descenso artificial en la curva de la incidencia en fechas recientes. Este descenso sólo corresponde al rezago administrativo (del diagnóstico y reporte de casos), pero no indica necesariamente una reducción de la incidencia real.

4.2. Modelo log-lineal con datos truncados

Encuentre una fecha límite adecuada para el modelo log-lineal, en función de los rezagos (biológicos y administrativos).

Dado que esta epidemia es de Ébola y la mayoría de los casos van a ser hospitalizados, es muy probable que la mayoría de las notificaciones ocurran en el momento de la hospitalización. De tal manera que podríamos examinar cuánto tiempo transcurre entre la fecha de inicio de síntomas y la fecha de hospitalización para hacernos una idea del rezago para esta epidemia.

R

summary(as.numeric(casos$fecha_de_hospitalizacion - casos$fecha_inicio_sintomas))

SALIDA

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
   0.00    1.00    2.00    3.53    5.00   22.00 

Al restar la fecha de hospitalización a la fecha de inicio de síntomas podría haber valores negativos. ¿Cuál cree que sea su significado? ¿Ocurre en este caso?

Para evitar el sesgo debido a rezagos en la notificación, se pueden truncar los datos de incidencia. Pruebe descartar las últimas dos semanas. Este procedimiento permite concentrarse en el periodo en que los datos son más completos para un análisis más fiable.

Semanas a descartar al final de la epicurva

R

semanas_a_descartar <- 2
fecha_minima <- min(incidencia_diaria$dates)
fecha_maxima <- max(incidencia_diaria$dates) - semanas_a_descartar * 7

# Para truncar la incidencia semanal
incidencia_semanal_truncada <- subset(incidencia_semanal, 
                         from = fecha_minima, 
                         to = fecha_maxima) # descarte las últimas semanas de datos

# Incidencia diaria truncada. No la usamos para la regresión lineal pero se puede usar más adelante
incidencia_diaria_truncada <- subset(incidencia_diaria, 
                        from = fecha_minima, 
                        to = fecha_maxima) # eliminamos las últimas dos semanas de datos

Desafío 6

Ahora utilizando los datos truncados incidencia_semanal_truncada vuelva a ajustar el modelo logarítmico lineal.

SALIDA

<incidence_fit object>

$model: regression of log-incidence over time

$info: list containing the following items:
  $r (daily growth rate):
[1] 0.05224047

  $r.conf (confidence interval):
          2.5 %    97.5 %
[1,] 0.03323024 0.0712507

  $doubling (doubling time in days):
[1] 13.2684

  $doubling.conf (confidence interval):
        2.5 %   97.5 %
[1,] 9.728286 20.85893

  $pred: data.frame of incidence predictions (10 rows, 5 columns)

R

ajuste_modelo_truncado <- incidence::fit(incidencia_semanal_truncada)
ajuste_modelo_truncado

SALIDA

<incidence_fit object>

$model: regression of log-incidence over time

$info: list containing the following items:
  $r (daily growth rate):
[1] 0.05224047

  $r.conf (confidence interval):
          2.5 %    97.5 %
[1,] 0.03323024 0.0712507

  $doubling (doubling time in days):
[1] 13.2684

  $doubling.conf (confidence interval):
        2.5 %   97.5 %
[1,] 9.728286 20.85893

  $pred: data.frame of incidence predictions (10 rows, 5 columns)

R

AjusteR2modelo <- summary(ajuste_modelo_truncado$model)$adj.r.squared 

cat("El R cuadrado ajustado es:", AjusteR2modelo, "\n")

SALIDA

El R cuadrado ajustado es: 0.8131106 

¿Cámo interpreta estos resultados? ¿Compare los \(R^2\)?

Desafío 7

Ahora utilizando los datos truncados incidencia_semanal_truncada vuelva a graficar el modelo logarítmico lineal.

R

plot(incidencia_semanal_truncada, fit = ajuste_modelo_truncado)

¿Qué cambios observa?

Observe las estadísticas resumidas del ajuste:

R

summary(ajuste_modelo_truncado$model)

SALIDA


Call:
stats::lm(formula = log(counts) ~ dates.x, data = df)

Residuals:
     Min       1Q   Median       3Q      Max
-0.73474 -0.31655 -0.03211  0.41798  0.65311

Coefficients:
            Estimate Std. Error t value Pr(>|t|)
(Intercept) 0.186219   0.332752   0.560 0.591049
dates.x     0.052240   0.008244   6.337 0.000224 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.5241 on 8 degrees of freedom
Multiple R-squared:  0.8339,	Adjusted R-squared:  0.8131
F-statistic: 40.16 on 1 and 8 DF,  p-value: 0.0002237

El modelo muestra que hay una relación significativa (R-squared: 0.8131) entre el tiempo (dates.x) y la incidencia de la enfermedad, por lo que concluimos que la enfermedad muestra un crecimiento exponencial a lo largo del tiempo.

4.3. Tasa de crecimiento y tasa de duplicación: extracción de datos

Estimacion de la tasa de crecimiento

Para estimar la tasa de crecimiento de una epidemia utilizando un modelo log-lineal es necesario realizar un ajuste de regresión a los datos de incidencia. Dado que ya tiene un objeto de incidencia truncado y un modelo log-lineal ajustado, puede proceder a calcular la tasa de crecimiento diaria y el tiempo de duplicación de la epidemia.

El modelo log-lineal proporcionará los coeficientes necesarios para estos cálculos. Note que el coeficiente asociado con el tiempo (la pendiente de la regresión) se puede interpretar como la tasa de crecimiento diaria cuando el tiempo se expresa en días.

Con el modelo ajustado truncado, es hora de realizar la estimación de la tasa de crecimiento. Estos datos los puede encontrar en el objeto ajuste modelo semana, que tiene los datos ajustados de incidencia semanal truncada.

Desafío 8

Por favor escriba el código para obtener los siguientes valores:

SALIDA

La tasa de crecimiento diaria es: 0.05224047 

SALIDA

Intervalo de confianza de la tasa de crecimiento diaria (95%): 0.03323024 0.0712507 

R

# Estimación de la tasa de crecimiento diaria
tasa_crecimiento_diaria <- ajuste_modelo_truncado$info$r

cat("La tasa de crecimiento diaria es:", tasa_crecimiento_diaria, "\n")

SALIDA

La tasa de crecimiento diaria es: 0.05224047 

R

# Intervalo de confianza de la tasa de crecimiento diaria
tasa_crecimiento_IC <- ajuste_modelo_truncado$info$r.conf

cat("Intervalo de confianza de la tasa de crecimiento diaria (95%):", tasa_crecimiento_IC, "\n")

SALIDA

Intervalo de confianza de la tasa de crecimiento diaria (95%): 0.03323024 0.0712507 

Si no lo recuerda, vuelva por pistas a la sección Ajuste un modelo log-lineal a los datos de incidencia semanal

Ahora que ya ha obtenido la tasa de crecimiento diaria y sus intervalos de confianza, puede pasar a estimar el tiempo de duplicación.

Estimación del tiempo de duplicación

Esta información también la encontrará calculada y lista para utilizar en el objeto ajuste_modelo_truncado, que tiene los datos ajustados de incidencia semanal truncada.

Desafío 9

Por favor escriba el código para obtener los siguientes valores:

SALIDA

El tiempo de duplicación de la epidemia es 13.2684 días

SALIDA

Intervalo de confianza del tiempo de duplicación (95%): 9.728286 20.85893 

R

# Estimación del tiempo de duplicación en días
tiempo_duplicacion_dias <- ajuste_modelo_truncado$info$doubling
cat("El tiempo de duplicación de la epidemia es", tiempo_duplicacion_dias, "días\n")

SALIDA

El tiempo de duplicación de la epidemia es 13.2684 días

R

# Intervalo de confianza del tiempo de duplicación
tiempo_duplicacion_IC <- ajuste_modelo_truncado$info$doubling.conf
cat("Intervalo de confianza del tiempo de duplicación (95%):", tiempo_duplicacion_IC, "\n")

SALIDA

Intervalo de confianza del tiempo de duplicación (95%): 9.728286 20.85893 

Si no lo recuerda vuelva por pistas a la sección Ajuste un modelo log-lineal a los datos de incidencia semanal

5. Estimación de número de reproducción


Evaluar la velocidad a la que se propaga una infección en una población es una tarea importante a la hora de informar la respuesta de salud pública a una epidemia.

Los números de reproducción son métricas típicas para monitorear el desarrollo de epidemias y son informativos sobre su velocidad de propagación. El Número de reproducción básico \(R_0\), por ejemplo, mide el número promedio de casos secundarios producidos por un individuo infeccioso dada una población completamente susceptible. Esta hipótesis suele ser válida solo al inicio de una epidemia.

Para caracterizar el la propagación en tiempo real es más común utilizar el Número de reproducción instantáneo \(R_t\), el cual describe el número promedio de casos secundarios generados por un individuo infeccioso en el tiempo \(t\) dado que no han habido cambios en las condiciones actuales.

En esta sección exploraremos los conceptos necesarios para calcular el Número de reproducción instantáneo, así como los pasos a seguir para estimarlo por medio del paquete de R EpiEstim.

5.1. Intervalo serial (SI)

¿Qué es el intervalo serial?

El intervalo serial en epidemiología se refiere al tiempo que transcurre entre el momento en que una persona infectada (el caso primario) comienza a mostrar síntomas y el momento en que la persona que fue infectada por ella (el caso secundario) comienza a mostrar síntomas.

Este intervalo es importante porque ayuda a entender qué tan rápido se está propagando una enfermedad y a diseñar estrategias de control como el rastreo de contactos y la cuarentena. Si el intervalo serial es corto, puede significar que la enfermedad se propaga rápidamente y que es necesario actuar con urgencia para contenerla. Si es largo, puede haber más tiempo para intervenir antes de que la enfermedad se disemine ampliamente.

Para este brote de Ébola asumiremos que el intervalo serial está descrito por una distribución Gamma de media (mean_si) de 8.7 días y con una desviación estándar (std_si) de 6.1 días. En la práctica del día 4 estudiaremos cómo estimar el intervalo serial.

R

# Parametros de la distribución gamma para el invertavlo serial
mean_si <- 8.7
std_si <-  6.1

config <- make_config(list(mean_si = mean_si, std_si = std_si)) 
# t_start y t_end se configuran automáticamente para estimar R en ventanas deslizantes para 1 semana de forma predeterminada.

5.2. Estimación de la transmisibilidad variable en el tiempo, R(t)

Cuando la suposición de que (\(R\)) es constante en el tiempo se vuelve insostenible, una alternativa es estimar la transmisibilidad variable en el tiempo utilizando el Número de reproducción instantáneo (\(R_t\)). Este enfoque, introducido por Cori et al. (2013), se implementa en el paquete EpiEstim, el cual estima el \(R_t\) para ventanas de tiempo personalizadas, utilizando una distribución de Poisson. A continuación, estimamos la transmisibilidad para ventanas de tiempo deslizantes de 1 semana (el valor predeterminado de estimate_R):


R

config <- make_config(list(mean_si = mean_si, std_si = std_si)) 
# t_start y t_end se configuran automáticamente para estimar R en ventanas deslizantes para 1 semana de forma predeterminada.

R

# use estimate_R using method = "parametric_si"
estimacion_rt <- estimate_R(incidencia_diaria_truncada, method = "parametric_si", 
                            si_data = si_data,
                            config = config)
# Observamos las primeras estimaciones de R(t)
head(estimacion_rt$R[, c("t_start", "t_end", "Median(R)", 
                         "Quantile.0.025(R)", "Quantile.0.975(R)")])

SALIDA

  t_start t_end Median(R) Quantile.0.025(R) Quantile.0.975(R)
1       2     8        NA                NA                NA
2       3     9  2.173592         0.3136801          7.215718
3       4    10  2.148673         0.3100840          7.132996
4       5    11  2.060726         0.2973920          6.841036
5       6    12  1.960940         0.2829915          6.509775
6       7    13  1.869417         0.2697834          6.205943

Grafique la estimación de \(R\) como función del tiempo:

R

plot(estimacion_rt, legend = FALSE)

Sobre este documento

Este documento ha sido una adaptación de los materiales originales disponibles en RECON Learn

Contribuciones

Autores originales:

  • Anne Cori

  • Natsuko Imai

  • Finlay Campbell

  • Zhian N. Kamvar

  • Thibaut Jombart

Cambios menores y adaptación a español:

  • José M. Velasco-España

  • Cándida Díaz-Brochero

  • Nicolas Torres

  • Zulma M. Cucunubá

Puntos clave

Revise si al final de esta lección adquirió estas competencias:

  • Identificar los parámetros necesarios en casos de transmisión de enfermedades infecciosas de persona a persona.

  • Estimar la probabilidad de muerte (CFR).

  • Calcular y graficar la incidencia.

  • Estimar e interpretar la tasa de crecimiento y el tiempo en el que se duplica la epidemia.

  • Estimar e interpretar el número de reproducción instantáneo de la epidemia.

Content from Construyendo un modelo matemático simple para Zika


Última actualización: 2024-11-19 | Mejora esta página

Tiempo estimado: 84 minutos

Hoja de ruta

Preguntas

  • ¿Cómo construir un modelo simplificado de zika?

Objetivos

Al final de este taller usted podrá:

  • Aplicar conceptos como parámetros, \(R_0\) e inmunidad de rebaño, aprendidos en la sesión A del taller
  • Traducir fórmulas matemáticas de las interacciones entre los parámetros del modelo a código de R
  • Realizar un modelo simple en R para una enfermedad transmitida por vector
  • Discutir cambios en las proyecciones del modelo cuando se instauran diferentes estrategias de control de la infección

1. Introducción


En este taller usted aplicará los conceptos básicos del modelamiento de Enfermedades Transmitidas por Vectores (ETV) mediante el uso del lenguaje R con énfasis en el funcionamiento de los métodos, utilizando como ejemplo un modelo básico de infección por un arbovirus: el virus del Zika.

2. Agenda


  • Instrucciones (5 mins)
  • Desarrollo taller Zika Parte B (45 mins con acompañamiento de monitores)
  • Revisión grupal del código (10 mins)
  • Revisión de resultados (20 mins)
  • Descanso (25 mins)
  • Discusión final (60 mins)

3. Conceptos básicos a desarrollar


En esta práctica se desarrollarán los siguientes conceptos:

  • Modelo SIR para ETV Zika
  • Parametrización de un modelo dinámico
  • Evaluación de un modelo dinámico
  • Parametrización de intervenciones de control (fumigación, mosquiteros y vacunación) para una ETV

4. Paquetes requeridos


Cargue los paquetes necesarios ingresando en R los siguientes comandos:

R

library(deSolve)   # Paquete deSolve para resolver las ecuaciones diferenciales
library(tidyverse) # Paquetes ggplot2 y dplyr de tidyverse
library(cowplot) # Paquete gridExtra para unir gráficos.
  • Si desea puede tomar notas en el script de R, para esto se recomienda usar el símbolo de comentario # después de cada línea de código (ver ejemplo arriba). O podría utilizar un archivo Rmd para tener un aspecto similar al del taller.

5. Compartimentos del modelo básico de Zika


  • \(S_h\) : Humanos susceptibles
  • \(I_h\) : Humanos infecciosos
  • \(R_h\) : Humanos recuperados de la infección (inmunizados frente a nueva infección)
  • \(S_v\) : Vectores susceptibles
  • \(E_v\) : Vectores expuestos
  • \(I_v\) : Vectores infecciosos

6. Parámetros del modelo


Ahora se usarán los parámetros que discutimos en la parte A del taller. Si aún no los tiene, estos se pueden encontrar en la guía de aprendizaje de la parte A del taller.

Desafío 1

Busque los valores de los parámetros del modelo y diligéncielos en el recuadro de abajo. Tenga en cuenta que todos los parámetros usados tienen la misma unidad de tiempo (días).

R

Lv       <-        # Esperanza de vida de los mosquitos (en días)
Lh       <-        # Esperanza de vida de los humanos (en días)
PIh      <-        # Periodo infeccioso en humanos (en días)
PIv      <-        # Periodo infeccioso en vectores (en días)
PEI      <-        # Período extrínseco de incubación en mosquitos adultos (en días)
muv      <-        # Tasa per capita de mortalidad del vector (1/Lv)
muh      <-        # Tasa per capita de mortalidad del hospedador (1/Lh)
alphav   <-        # Tasa per capita de natalidad del vector
alphah   <-        # Tasa per capita de natalidad del hospedador
gamma    <-        # Tasa de recuperación en humanos (1/PIh)
delta    <-        # Tasa extrínseca de incubación (1/PEI)
ph       <-        # Probabilidad de transmisión del vector al hospedador dada una picadura por un mosquito infeccioso a un humano susceptible
pv       <-        # Probabilidad de transmisión del hospedador al vector dada una picadura por un mosquito susceptible a un humano infeccioso
Nh       <-        # Número de humanos
m        <-        # Densidad de mosquitos hembra por humano
Nv       <-        # Número de vectores (m * Nh)
R0       <-        # Número reproductivo básico
b        <-        sqrt((R0 * muv*(muv+delta) * (muh+gamma)) /
                   (m * ph * pv * delta)) # Tasa de picadura
betah    <-        # Coeficiente de transmisión del mosquito al humano
betav    <-        # Coeficiente de transmisión del humano al mosquito
TIME     <-        # Número de años que se va a simular 

R

# Parámetros
ph       <- 0.7       # Probabilidad de transmisión del vector al hospedador dada una picadura por un mosquito infeccioso a un humano susceptible
pv       <- 0.7       # Probabilidad de transmisión del hospedador al vector dada una picadura por un mosquito susceptible a un humano infeccioso
Lv       <- 10        # Esperanza de vida de los mosquitos (en días)
Lh       <- 50 * 365  # Esperanza de vida de los humanos (en días)
Iph      <- 7         # Periodo infeccioso en humanos (en días)
IP       <- 6         # Periodo infeccioso en vectores (en días)
EIP      <- 8.4       # Período extrínseco de incubación en mosquitos adultos
muv      <- 1/Lv      # Tasa per capita de mortalidad del vector (1/Lv)
muh      <- 1/Lh      # Tasa per capita de mortalidad del hospedador (1/Lh)
alphav   <- muv       # Tasa per capita de natalidad del vector
alphah   <- muh       # Tasa per capita de natalidad del hospedador
gamma    <- 1/Iph     # Tasa de recuperación en humanos
delta    <- 1/EIP     # Tasa extrínseca de incubación



# Tamaño de la población
Nh       <- 100000   # Número de humanos
m        <- 2         # Proporción vector a humano
Nv       <- m * Nh    # Número de vectores
R0       <- 3         # Número reproductivo
b        <- sqrt((R0 * muv*(muv+delta) * (muh+gamma)) /
                   (m * ph * pv * delta)) # tasa de picadura

betah    <- ph*b     # Coeficiente de transmisión del mosquito al humano
betav    <- pv*b     # Coeficiente de transmisión del humano al mosquito

TIME     <- 100       # Número de años a simular

7. Ecuaciones del modelo


Para este modelo emplearemos las siguientes ecuaciones diferenciales:

7.1 Humanos

\[\ \frac{dSh}{dt} = \alpha_h N_h - \beta_h \frac {I_v}{N_h}S_h - \mu_h S_h \]

\[\ \frac{dIh}{dt} = \beta_h \frac {I_v}{N_h}S_h - (\gamma + \mu_h) I_h \]

\[\ \frac{dRh}{dt} = \gamma I_h - \mu_h R_h\]

7.2 Vectores

\[\ \frac{dSv}{dt} = \alpha_v N_v - \beta_v \frac{ I_h} {N_h}S_v - \mu_v Sv\]

\[\ \frac{dE_v}{dt} = \beta_v \frac{I_h} {N_h}S_v- (\delta + \mu_v) Ev\]

\[\ \frac{dI_v}{dt} = \delta Ev - \mu_v I_v\]

8. Fórmula para calcular \(R_0\) (Número reproductivo básico)


Fórmula necesaria para estimar \(R_0\):

\[ R_0 = \frac{mb^2 p_h p_v \delta}{\mu_v (\mu_v+\delta)(\mu_h+\gamma)} \]

9. Modelo en R


Es hora de implementar el modelo en R. Para lograrlo, usaremos la función ode del paquete desolve. Para el ejercicio se emplearán 4 argumentos de la función esta función: el primero son las condiciones iniciales del modelo (argumento y); el segundo es la secuencia temporal donde se ejecutará el modelo (argumento times); el tercero es una función que contiene las ecuaciones diferenciales que entrarán al sistema (argumento fun); por último un vector que contiene los parámetros con los que se calculará el sistema (argumento parms).

R

# NO la copie a R sólo tiene fines ilustrativos.
ode(y      = # Condiciones iniciales,
    times  = # Tiempo,
    fun    = # Modelo o función que lo contenga,
    parms  = # Parámetros
)

Desafío 2

En esta sección se empezará por crear la función (argumento fun), para ello es necesario traducir las ecuaciones del modelo a R. Abajo encontrará la función ya construida, por favor reemplace los parámetros faltantes (Cambie PAR por los parámetro correspondientes) en las ecuaciones:

R

arbovmodel <- function(t, x, params) {
  
  Sh <- x[1]    # Humanos susceptibles
  Ih <- x[2]    # Humanos infecciosos 
  Rh <- x[3]    # Humanos recuperados
  Sv <- x[4]    # Vectores susceptibles
  Ev <- x[5]    # Vectores expuestos
  Iv <- x[6]    # Vectores infecciosos
  
  with(as.list(params), # entorno local para evaluar derivados
       {
         # Humanos
         dSh   <-  PAR * Nh - PAR * (Iv/Nh) * Sh - PAR * Sh   
         dIh   <-  PAR * (Iv/Nh) * Sh - (PAR + PAR) * Ih
         dRh   <-  PAR * Ih  - PAR * Rh
         
         # Vectores
         dSv  <-  alphav * Nv - PAR * (Ih/Nh) * Sv - PAR * Sv 
         dEv  <-  PAR * (Ih/Nh) * Sv - (PAR + PAR)* Ev
         dIv  <-  PAR * Ev - PAR * Iv
         
         dx   <- c(dSh, dIh, dRh, dSv, dEv, dIv)
         list(dx)
       }
  )
}

R

arbovmodel <- function(t, x, params) {
  
  Sh <- x[1]    # Humanos susceptibles
  Ih <- x[2]    # Humanos infecciosos 
  Rh <- x[3]    # Humanos recuperados
  Sv <- x[4]    # Vectores susceptibles
  Ev <- x[5]    # Vectores expuestos
  Iv <- x[6]    # Vectores infecciosos
  
  with(as.list(params), # entorno local para evaluar derivados
       {
         # Humanos
         dSh   <-  alphah * Nh - betah * (Iv/Nh) * Sh - muh * Sh
         dIh   <-  betah * (Iv/Nh) * Sh  - (gamma + muh) * Ih
         dRh   <-  gamma * Ih  - muh * Rh
         
         # Vectores
         dSv   <-  alphav * Nv - betav * (Ih/Nh) * Sv - muv * Sv 
         dEv   <-  betav * (Ih/Nh) * Sv - (delta + muv)* Ev
         dIv   <-  delta * Ev - muv * Iv
         
         dx    <- c(dSh, dIh, dRh, dSv, dEv, dIv)
         list(dx)
       }
  )
}

10. Resuelva el Sistema


En esta sección se crearán los tres argumentos faltantes para usar la función ode y se creará un objeto de clase data.frame con los resultados de la función ode. Por favor complete y comente el código para:

  • Los VALORES de las condiciones iniciales del sistema.

  • Los ARGUMENTOS de la función ode en el paquete deSolve.

Desafío 3

R

# Secuencia temporal (times)
times  <- seq(1, 365 * TIME , by = 1)
# Los parámetros (parms)
params <- c(
  muv      = muv,     
  muh      = muh, 
  alphav   = alphav,
  alphah   = alphah,
  gamma    = gamma,   
  delta    = delta,   
  betav    = betav,       
  betah    = betah,   
  Nh       = Nh,      
  Nv       = Nv
)
# Condiciones iniciales del sistema (y)
xstart <- c(Sh = VALOR?,        # COMPLETE Y COMENTE
            Ih = VALOR?,        # COMPLETE Y COMENTE
            Rh = VALOR?,        # COMPLETE Y COMENTE
            Sv = VALOR?,        # COMPLETE Y COMENTE
            Ev = VALOR?,        # COMPLETE Y COMENTE
            Iv = VALOR?)        # COMPLETE Y COMENTE
# Resuelva las ecuaciones
out <- as.data.frame(ode(y      = ARGUMENTO?,   # COMPLETE Y COMENTE
                         times  = ARGUMENTO?,   # COMPLETE Y COMENTE
                         fun    = ARGUMENTO?,   # COMPLETE Y COMENTE
                         parms  = ARGUMENTO?))  # COMPLETE Y COMENTE

R

# ----------- Resuelva el modelo
#Tiempo
times  <- seq(1, 365 * TIME , by = 1)
# Especifique los parámetros
params <- c(
  muv      = muv,     
  muh      = muh, 
  alphav   = alphav,
  alphah   = alphah,
  gamma    = gamma,   
  delta    = delta,   
  betav    = betav,       
  betah    = betah,   
  Nh       = Nh,      
  Nv       = Nv
)
# Condiciones iniciales del sistema
xstart<- c(Sh = Nh ,      # Número inicial de Sh en T0
           Ih = 0,        # Número inicial de Ih en T0
           Rh = 0,        # Número inicial de Rh en T0
           Sv = Nv-1,     # Número inicial de Sv en T0
           Ev = 0,        # Número inicial de Ev en T0
           Iv = 1)        # Número inicial de Iv en TO
# Resuelva las ecuaciones
out <- as.data.frame(ode(y      = xstart,     # Condiciones iniciales
                         times  = times,      # Tiempo
                         fun    = arbovmodel, # Modelo
                         parms  = params))    # Parámetros

11. Resultados


Desafío 4

Para tener una visualización más significativa de los resultados, convierta las unidades de tiempo de días a años y a semanas.

R

# Cree las opciones de tiempo para años y semanas 
out$years <- 
out$weeks <- 

R

# Cree las opciones de tiempo a mostrar 
out$years <- out$time / 365
out$weeks <- out$time / 7

Observe el comportamiento del modelo en distintas escalas de tiempo (semanas y años):

11.1 Comportamiento General (Población humana)

R

# Revise el comportamiento general del modelo para 100 años
p1h <- ggplot(data = out, aes(y = (Rh + Ih + Sh), x = years)) +
  geom_line(color = 'grey68', size = 1) +
  ggtitle('Población humana total') +
  theme_bw() + ylab('Número') + xlab('Años')
p2h <- ggplot(data = out, aes(y = Sh, x = years)) +
  geom_line(color = 'royalblue', size = 1) +
  ggtitle('Población humana susceptible') +
  theme_bw() + ylab('Número') + xlab('Años')
p3h <- ggplot(data = out, aes(y = Ih, x = years)) +
  geom_line(color = 'firebrick', size = 1) +
  ggtitle('Población humana infecciosa') +
  theme_bw() + ylab('Número') + xlab('Años')
p4h <- ggplot(data = out, aes(y = Rh, x = years)) +
  geom_line(color = 'olivedrab', size = 1) +
  ggtitle('Población humana recuperada') +
  theme_bw() + ylab('Número') + xlab('Años')
plot_grid(p1h, p2h, p3h, p4h, ncol = 2)

11.2 Comportamiento General (Población de vectores)

R

# Revise el comportamiento general del modelo
p1v <- ggplot(data = out, aes(y = (Sv + Ev + Iv), x = years)) +
  geom_line(color = 'grey68', size = 1) +
  ggtitle('Población total de mosquitos') +
  theme_bw() + ylab('Número') + xlab('Años')
p2v <- ggplot(data = out, aes(y = Sv, x = years)) +
  geom_line(color = 'royalblue', size = 1) +
  ggtitle('Población susceptible de mosquitos') +
  theme_bw() + ylab('Número') + xlab('Años')
p3v <- ggplot(data = out, aes(y = Ev, x = years)) +
  geom_line(color = 'orchid', size = 1) +
  ggtitle('Población expuesta de mosquitos') +
  theme_bw() + ylab('Número') + xlab('Años')
p4v <- ggplot(data = out, aes(y = Iv, x = years)) +
  geom_line(color = 'firebrick', size = 1) +
  ggtitle('Población infecciosa de mosquitos') +
  theme_bw() + ylab('Número') + xlab('Años')
plot_grid(p1v, p2v, p3v, p4v, ncol = 2)

11.3 Proporción

Por favor dé una mirada más cuidadosa a las proporciones y discútalas

R

p1 <- ggplot(data = out, aes(y = Sh/(Sh+Ih+Rh), x = years)) +
  geom_line(color = 'royalblue', size = 1) +
  ggtitle('Población humana susceptible') +
  theme_bw() + ylab('Proporción') + xlab('Años') +
  coord_cartesian(ylim = c(0,1))
p2 <- ggplot(data = out, aes(y = Ih/(Sh+Ih+Rh), x = years)) +
  geom_line(color = 'firebrick', size = 1) +
  ggtitle('Población humana infecciosa') +
  theme_bw() + ylab('Proporción') + xlab('Años') +
  coord_cartesian(ylim = c(0,1))
p3 <- ggplot(data = out, aes(y = Rh/(Sh+Ih+Rh), x = years)) +
  geom_line(color = 'olivedrab', size = 1) +
  ggtitle('Población humana recuperada') +
  theme_bw() + ylab('Proporción') + xlab('Años') +
  coord_cartesian(ylim = c(0,1))
plot_grid(p1, p2, p3, ncol = 2) 

11.4 La primera epidemia

R

# Revise la primera epidemia
dat <- out %>% filter(weeks < 54)
p1e <- ggplot(dat, aes(y = Ih, x = weeks)) +
  geom_line(color = 'firebrick', size = 1) +
  ggtitle('Población de humanos infecciosos') +
  theme_bw() + ylab('Número') + xlab('Semanas')
p2e <- ggplot(dat, aes(y = Rh, x = weeks)) +
  geom_line(color = 'olivedrab', size = 1) +
  ggtitle('Población humana recuperada') +
  theme_bw() + ylab('Número') + xlab('Semanas')
plot_grid(p1e, p2e)

11.5 Algunos aspectos por discutir

  • ¿Qué tan sensible es el modelo a cambios en el \(R_0\)?
  • ¿Qué razones hay (según el modelo) para el intervalo de tiempo entre estas epidemias simuladas?
  • ¿Cómo se puede calcular la tasa de ataque?

11.6 Modele una o más intervenciones de control

Ahora, utilizando este modelo básico, modelar por grupos el impacto de las siguientes intervenciones:

  1. Grupo 1. Fumigación contra mosquitos
  2. Grupo 2. Mosquiteros/angeos
  3. Grupo 3. Vacunación que previene frente a infecciones

Para cada intervención:

  1. Indique en qué parte del modelo haría los cambios.

  2. De acuerdo a la literatura que explique estas intervenciones y describa cómo parametrizará el modelo. ¿Todas estas intervenciones son viables en la actualidad?

Sobre este documento


Contribuciones

  • Zulma Cucunuba & Pierre Nouvellet: Versión inicial
  • Kelly Charinga & Zhian N. Kamvar: Edición
  • José M. Velasco-España: Traducción de Inglés a Español y Edición
  • Andree Valle-Campos: Ediciones menores

Contribuciones son bienvenidas vía pull requests. El archivo fuente del documento original puede ser encontrado aquí.

Asuntos legales

Licencia: CC-BY Copyright: Zulma Cucunuba & Pierre Nouvellet, 2017

Puntos clave

Revise si al final de esta lección adquirió estas competencias:

  • Aplicar conceptos como parámetros, \(R_0\) e inmunidad de rebaño, aprendidos en la sesión A del taller
  • Traducir fórmulas matemáticas de las interacciones entre los parámetros del modelo a código de R
  • Realizar un modelo simple en R para una enfermedad transmitida por vector
  • Discutir cambios en las proyecciones del modelo cuando se instauran diferentes estrategias de control de la infección

Content from Estimación de las distribuciones de rezagos epidemiológicos: Enfermedad X


Última actualización: 2024-11-19 | Mejora esta página

Tiempo estimado: 98 minutos

Hoja de ruta

Preguntas

  • ¿Cómo responder ante un brote de una enfermedad desconocida?

Objetivos

Al final de este taller usted podrá:

  • Comprender los conceptos clave de las distribuciones de rezagos epidemiológicos para la Enfermedad X.

  • Comprender las estructuras de datos y las herramientas para el análisis de datos de rastreo de contactos.

  • Aprender a ajustar las estimaciones del intervalo serial y el período de incubación de la Enfermedad X teniendo en cuenta la censura por intervalo utilizando un marco de trabajo Bayesiano.

  • Aprender a utilizar estos parámetros para informar estrategias de control en un brote de un patógeno desconocido.

1. Introducción


La Enfermedad X representa un hipotético, pero plausible, brote de una enfermedad infecciosa en el futuro. Este término fue acuñado por la Organización Mundial de la Salud (OMS) y sirve como un término general para un patógeno desconocido que podría causar una epidemia grave a nivel internacional. Este concepto representa la naturaleza impredecible de la aparición de enfermedades infecciosas y resalta la necesidad de una preparación global y mecanismos de respuesta rápida. La Enfermedad X simboliza el potencial de una enfermedad inesperada y de rápida propagación, y resalta la necesidad de sistemas de salud flexibles y adaptables, así como capacidades de investigación para identificar, comprender y combatir patógenos desconocidos.

En esta práctica, va a aprender a estimar los rezagos epidemiológicos, el tiempo entre dos eventos epidemiológicos, utilizando un conjunto de datos simulado de la Enfermedad X.

La Enfermedad X es causada por un patógeno desconocido y se transmite directamente de persona a persona. Específicamente, la practica se centrará en estimar el período de incubación y el intervalo serial.

2. Agenda


Parte 1. Individual o en grupo.

Parte 2. En grupos de 6 personas. Construir estrategia de rastreo de contactos y aislamiento y preparar presentación de máximo 10 mins.

3. Conceptos claves


3.1. Rezagos epidemiológicos: Período de incubación e intervalo serial

En epidemiología, las distribuciones de rezagos se refieren a los retrasos temporales entre dos eventos clave durante un brote. Por ejemplo: el tiempo entre el inicio de los síntomas y el diagnóstico, el tiempo entre la aparición de síntomas y la muerte, entre muchos otros.

Este taller se enfocará en dos rezagos clave conocidos como el período de incubación y el intervalo serial. Ambos son cruciales para informar la respuesta de salud pública.

El período de incubación es el tiempo entre la infección y la aparición de síntomas.

El intervalo serial es el tiempo entre la aparición de síntomas entre el caso primario y secundario.

La relación entre estos parámetros tiene un impacto en si la enfermedad se transmite antes (transmisión pre-sintomática) o después de que los síntomas (transmisión sintomática) se hayan desarrollado en el caso primario (Figura 1).

Figura 1. Relación entre el período de incubación y el intervalo serial en el momento de la transmisión (Adaptado de Nishiura et al. 2020)

3.2. Distribuciones comunes de rezagos y posibles sesgos

3.2.1 Sesgos potenciales

Cuando se estiman rezagos epidemiológicos, es importante considerar posibles sesgos:

Censura significa que sabemos que un evento ocurrió, pero no sabemos exactamente cuándo sucedió. La mayoría de los datos epidemiológicos están “doblemente censurados” debido a la incertidumbre que rodea tanto los tiempos de eventos primarios como secundarios. No tener en cuenta la censura puede llevar a estimaciones sesgadas de la desviación estándar del resago (Park et al. en progreso).

Truncamiento a la derecha es un tipo de sesgo de muestreo relacionado con el proceso de recolección de datos. Surge porque solo se pueden observar los casos que han sido reportados. No tener en cuenta el truncamiento a la derecha durante la fase de crecimiento de una epidemia puede llevar a una subestimación del rezago medio (Park et al. en progreso).

El sesgo dinámico (o de fase epidémica) es otro tipo de sesgo de muestreo. Afecta a los datos retrospectivos y está relacionado con la fase de la epidemia: durante la fase de crecimiento exponencial, los casos que desarrollaron síntomas recientemente están sobrerrepresentados en los datos observados, mientras que durante la fase de declive, estos casos están subrepresentados, lo que lleva a la estimación de intervalos de retraso más cortos y más largos, respectivamente (Park et al. en progreso).

3.2.2 Distribuciones de rezagos

Tres distribuciones de probabilidad comunes utilizadas para caracterizar rezagos en epidemiología de enfermedades infecciosas (Tabla 1):

Tabla 1. Tres de las distribuciones de probabilidad más comunes para rezagos epidemiológicos.
Distribución Parámetros
Weibull shape y scale (forma y escala)
gamma shape y scale (forma y escala)
log normal log mean y log standard deviation (media y desviación estándar logarítmica)

4. Paquetes de R para la practica


En esta practica se usarán los siguientes paquetes de R:

  • dplyr para manejo de datos

  • epicontacts para visualizar los datos de rastreo de contactos

  • ggplot2 y patchwork para gráficar

  • incidence para visualizar curvas epidemicas

  • rstan para estimar el período de incubación

  • coarseDataTools via EpiEstim para estimar el intervalo serial

Instrucciones de instalación para los paquetes:

Para cargar los paquetes, escriba:

R

library(dplyr)
library(epicontacts)
library(incidence)
library(coarseDataTools)
library(EpiEstim)
library(ggplot2)
library(loo)
library(patchwork)
library(rstan)

Para este taller, las autoras han creado algunas funciones que serán necesarias para el buen funcionamiento del mismo. Por favor, copie el siguiente texto, selecciónelo y ejecútelo para tener estas funciones en su ambiente global y poderlas utilizar.

R

## Calcule la verosimilitud DIC mediante integración
diclik <- function(par1, par2, EL, ER, SL, SR, dist){
  
  ## Si la ventana de síntomas es mayor que la ventana de exposición
  if(SR-SL>ER-EL){
    dic1 <- integrate(fw1, lower=SL-ER, upper=SL-EL,
              subdivisions=10,
              par1=par1, par2=par2,
              EL=EL, ER=ER, SL=SL, SR=SR,
              dist=dist)$value
    if (dist == "W"){
      dic2 <- (ER-EL)*
        (pweibull(SR-ER, shape=par1, scale=par2) - pweibull(SL-EL, shape=par1, scale=par2))
    } else if (dist == "off1W"){
      dic2 <- (ER-EL)*
        (pweibullOff1(SR-ER, shape=par1, scale=par2) - pweibullOff1(SL-EL, shape=par1, scale=par2))
    } else if (dist == "G"){
      dic2 <- (ER-EL)*
        (pgamma(SR-ER, shape=par1, scale=par2) - pgamma(SL-EL, shape=par1, scale=par2))
    } else if (dist == "off1G"){
      dic2 <- (ER-EL)*
        (pgammaOff1(SR-ER, shape=par1, scale=par2) - pgammaOff1(SL-EL, shape=par1, scale=par2))
    } else if (dist == "L") {
      dic2 <- (ER-EL)*
        (plnorm(SR-ER, par1, par2) - plnorm(SL-EL, par1, par2))
    } else if (dist == "off1L") {
      dic2 <- (ER-EL)*
        (plnormOff1(SR-ER, par1, par2) - plnormOff1(SL-EL, par1, par2))
    } else {
      stop("distribution not supported")
    }
    dic3 <- integrate(fw3, lower=SR-ER, upper=SR-EL,
              subdivisions=10,
              par1=par1, par2=par2,
              EL=EL, ER=ER, SL=SL, SR=SR,
              dist=dist)$value
    return(dic1 + dic2 + dic3)
  }
  
## Si la ventana de exposición es mayor que la ventana de síntomas
  else{
    dic1 <- integrate(fw1, lower=SL-ER, upper=SR-ER, subdivisions=10,
              par1=par1, par2=par2,
              EL=EL, ER=ER, SL=SL, SR=SR,
              dist=dist)$value
    if (dist == "W"){
      dic2 <- (SR-SL)*
        (pweibull(SL-EL, shape=par1, scale=par2) - pweibull(SR-ER, shape=par1, scale=par2))
    } else if (dist == "off1W"){
      dic2 <- (SR-SL)*
        (pweibullOff1(SL-EL, shape=par1, scale=par2) - pweibullOff1(SR-ER, shape=par1, scale=par2))
    } else if (dist == "G"){
      dic2 <- (SR-SL)*
        (pgamma(SL-EL, shape=par1, scale=par2) - pgamma(SR-ER, shape=par1, scale=par2))
    } else if (dist == "off1G"){
      dic2 <- (SR-SL)*
        (pgammaOff1(SL-EL, shape=par1, scale=par2) - pgammaOff1(SR-ER, shape=par1, scale=par2))
    } else if (dist == "L"){
      dic2 <- (SR-SL)*
        (plnorm(SL-EL, par1, par2) - plnorm(SR-ER, par1, par2))
    } else if (dist == "off1L"){
      dic2 <- (SR-SL)*
        (plnormOff1(SL-EL, par1, par2) - plnormOff1(SR-ER, par1, par2))
    } else {
      stop("distribution not supported")
    }
    dic3 <- integrate(fw3, lower=SL-EL, upper=SR-EL,
              subdivisions=10,
              par1=par1, par2=par2,
              EL=EL, ER=ER, SL=SL, SR=SR,
              dist=dist)$value
    return(dic1 + dic2 + dic3)
  }
}

## Esta verosimilitud DIC está diseñada para datos que tienen intervalos superpuestos
diclik2 <- function(par1, par2, EL, ER, SL, SR, dist){
  if(SL>ER) {
    
    return(diclik(par1, par2, EL, ER, SL, SR, dist))
  } else {
    
    lik1 <- integrate(diclik2.helper1, lower=EL, upper=SL,
              SL=SL, SR=SR, par1=par1, par2=par2, dist=dist)$value
    lik2 <- integrate(diclik2.helper2, lower=SL, upper=ER,
              SR=SR, par1=par1, par2=par2, dist=dist)$value
    return(lik1+lik2)
  }
}

## Funciones de verosimilitud para diclik2
diclik2.helper1 <- function(x, SL, SR, par1, par2, dist){
  if (dist =="W"){
    pweibull(SR-x, shape=par1, scale=par2) - pweibull(SL-x, shape=par1, scale=par2)
  } else if (dist =="off1W") {
    pweibullOff1(SR-x, shape=par1, scale=par2) - pweibullOff1(SL-x, shape=par1, scale=par2)
  } else if (dist =="G") {
    pgamma(SR-x, shape=par1, scale=par2) - pgamma(SL-x, shape=par1, scale=par2)
  } else if (dist=="off1G"){
    pgammaOff1(SR-x, shape=par1, scale=par2) - pgammaOff1(SL-x, shape=par1, scale=par2)
  } else if (dist == "L"){
    plnorm(SR-x, par1, par2) - plnorm(SL-x, par1, par2)
  } else if (dist == "off1L"){
    plnormOff1(SR-x, par1, par2) - plnormOff1(SL-x, par1, par2)
  } else {
    stop("distribution not supported")     
  }
}

diclik2.helper2 <- function(x, SR, par1, par2, dist){
  if (dist =="W"){
    pweibull(SR-x, shape=par1, scale=par2)
  } else if (dist =="off1W") {
    pweibullOff1(SR-x, shape=par1, scale=par2)
  } else if (dist =="G") {
    pgamma(SR-x, shape=par1, scale=par2)
  } else if (dist =="off1G") {
    pgammaOff1(SR-x, shape=par1, scale=par2)
  } else if (dist=="L"){
    plnorm(SR-x, par1, par2)
  } else if (dist=="off1L"){
    plnormOff1(SR-x, par1, par2)
  } else {
    stop("distribution not supported")     
  }
}


## Funciones que manipulan/calculan la verosimilitud para los datos censurados
## Las funciones codificadas aquí se toman directamente de las
## notas de verosimilitud censurada por intervalos dobles.
fw1 <- function(t, EL, ER, SL, SR, par1, par2, dist){
  ## Función que calcula la primera función para la integral DIC
  if (dist=="W"){
    (ER-SL+t) * dweibull(x=t,shape=par1,scale=par2)
  } else if (dist=="off1W") {
    (ER-SL+t) * dweibullOff1(x=t,shape=par1,scale=par2)
  } else if (dist=="G") {
    (ER-SL+t) * dgamma(x=t, shape=par1, scale=par2)
  } else if (dist=="off1G") {
    (ER-SL+t) * dgammaOff1(x=t, shape=par1, scale=par2)
  } else if (dist =="L"){
    (ER-SL+t) * dlnorm(x=t, meanlog=par1, sdlog=par2)
  } else if (dist =="off1L"){
    (ER-SL+t) * dlnormOff1(x=t, meanlog=par1, sdlog=par2)
  } else {
    stop("distribution not supported")
  }
}

fw3 <- function(t, EL, ER, SL, SR, par1, par2, dist){
## Función que calcula la tercera función para la integral DIC
  if (dist == "W"){
    (SR-EL-t) * dweibull(x=t, shape=par1, scale=par2)
  } else if (dist == "off1W"){
    (SR-EL-t) * dweibullOff1(x=t, shape=par1, scale=par2)
  }  else if (dist == "G"){
    (SR-EL-t) * dgamma(x=t, shape=par1, scale=par2)
  }  else if (dist == "off1G"){
    (SR-EL-t) * dgammaOff1(x=t, shape=par1, scale=par2)
  } else if (dist == "L") {
    (SR-EL-t) * dlnorm(x=t, meanlog=par1, sdlog=par2)
  } else if (dist == "off1L"){
    (SR-EL-t) * dlnormOff1(x=t, meanlog=par1, sdlog=par2)
  } else {
    stop("distribution not supported")
  }
}

5. Datos


Esta práctica está partida en dos grupos para abordar dos enfermedades desconocidas con diferentes modos de transmisión.

Cargue los datos simulados que están guardados como un archivo .RDS, de acuerdo a su grupo asignado. Puede encontrar esta información en la carpeta Enfermedad X. Descargue la carpeta, extráigala en el computador y abra el proyecto de R.

Hay dos elementos de interés:

  • linelist, un archivo que contiene una lista de casos de la Enfermedad X, un caso por fila.

  • contacts, un archivo con datos de rastreo de contactos que contiene información sobre pares de casos primarios y secundarios.

R

# Grupo 1
dat <- readRDS("data/practical_data_group1.RDS") 
linelist <- dat$linelist 
contacts <- dat$contacts

6. Exploración de los datos


6.1. Exploración de los datos en linelist

Empiece con linelist. Estos datos fueron recolectados como parte de la vigilancia epidemiológica rutinaria. Cada fila representa un caso de la Enfermedad X, y hay 7 variables:

  • id: número único de identificación del caso

  • date_onset: fecha de inicio de los síntomas del paciente

  • sex: : M = masculino; F = femenino

  • age: edad del paciente en años

  • exposure: información sobre cómo el paciente podría haber estado expuesto

  • exposure_start: primera fecha en que el paciente estuvo expuesto

  • exposure_end: última fecha en que el paciente estuvo expuesto

💡 Preguntas (1)

  • ¿Cuántos casos hay en los datos de linelist?

  • ¿Qué proporción de los casos son femeninos?

  • ¿Cuál es la distribución de edades de los casos?

  • ¿Qué tipo de información sobre la exposición está disponible?

R

# Inspecionar los datos
head(linelist)

SALIDA

  id date_onset sex age                    exposure exposure_start exposure_end
1  1 2023-10-01   M  34 Close, skin-to-skin contact           <NA>         <NA>
2  2 2023-10-03   F  38 Close, skin-to-skin contact     2023-09-29   2023-09-29
3  3 2023-10-06   F  38 Close, skin-to-skin contact     2023-09-28   2023-09-28
4  4 2023-10-10   F  37                        <NA>     2023-09-25   2023-09-27
5  5 2023-10-11   F  33                        <NA>     2023-10-05   2023-10-05
6  6 2023-10-12   F  34 Close, skin-to-skin contact     2023-10-10   2023-10-10

R

# P1
nrow(linelist)

SALIDA

[1] 166

R

# P2
table(linelist$sex)[2]/nrow(linelist)

SALIDA

        M
0.6144578 

R

# P3
summary(linelist$age)

SALIDA

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
  22.00   33.00   36.00   35.51   38.00   47.00 

R

# P4
table(linelist$exposure, exclude = F)[1]/nrow(linelist)

SALIDA

Close, skin-to-skin contact
                  0.6626506 

💡 Discusión

  • ¿Por qué cree que falta la información de exposición en algunos casos?

  • Ahora, grafique la curva epidémica. ¿En qué parte del brote cree que está (principio, medio, final)?

R

i <- incidence(linelist$date_onset)
plot(i) + 
  theme_classic() + 
  scale_fill_manual(values = "purple") +
  theme(legend.position = "none")

Parece que la epidemia todavía podría esta creciendo.

6.2. Exploración de los datos de rastreo de contactos

Ahora vea los datos de rastreo de contactos, que se obtuvieron a través de entrevistas a los pacientes. A los pacientes se les preguntó sobre actividades e interacciones recientes para identificar posibles fuentes de infección. Se emparejaron pares de casos primarios y secundarios si el caso secundario nombraba al caso primario como un contacto. Solo hay información de un subconjunto de los casos porque no todos los pacientes pudieron ser contactados para una entrevista.

Note que para este ejercicio, se asumirá que los casos secundarios solo tenían un posible infectante. En realidad, la posibilidad de que un caso tenga múltiples posibles infectantes necesita ser evaluada.

Los datos de rastreo de contactos tienen 4 variables:

  • primary_case_id: número de identificación único para el caso primario (infectante)

  • secondary_case_id: número de identificación único para el caso secundario (infectado)

  • primary_onset_date: fecha de inicio de síntomas del caso primario

  • secondary_onset_date: fecha de inicio de síntomas del caso secundario

R

x <- make_epicontacts(linelist = linelist,
                       contacts = contacts,
                       from = "primary_case_id",
                       to = "secondary_case_id",
                       directed = TRUE) # Esto indica que los contactos son directos (i.e., este gráfico traza una flecha desde los casos primarios a los secundarios)

plot(x)

💡 Preguntas (2)

  • Describa los grupos (clusters).
  • ¿Ve algún evento potencial de superpropagación (donde un caso propaga el patógeno a muchos otros casos)?

_______Pausa 1 _________


7. Estimación del período de incubación

Ahora, enfoquese en el período de incubación. Se utilizará los datos del linelist para esta parte. Se necesitan ambos el tiempo de inicio de sintomas y el timpo de la posible exposición. Note que en los datos hay dos fechas de exposición, una de inicio y una de final. Algunas veces la fecha exacta de exposición es desconocida y en su lugar se obtiene la ventana de exposición durante la entrevista.

💡 Preguntas (3)

  • ¿Para cuántos casos tiene datos tanto de la fecha de inicio de síntomas como de exposición?

  • Calcule las ventanas de exposición. ¿Cuántos casos tienen una única fecha de exposición?

R

ip <- filter(linelist, !is.na(exposure_start) &
               !is.na(exposure_end))
nrow(ip)

SALIDA

[1] 50

R

ip$exposure_window <- as.numeric(ip$exposure_end - ip$exposure_start)

table(ip$exposure_window)

SALIDA


 0  1  2
34 11  5 

7.1. Estimación naive del período de incubación

Empiece calculando una estimación naive del período de incubación.

R

# Máximo tiempo de período de incubación
ip$max_ip <- ip$date_onset - ip$exposure_start
summary(as.numeric(ip$max_ip))

SALIDA

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
   1.00    3.00    4.50    6.38    7.75   20.00 

R

# Mínimo tiempo de período de incubación
ip$min_ip <- ip$date_onset - ip$exposure_end
summary(as.numeric(ip$min_ip))

SALIDA

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
   1.00    2.00    4.00    5.96    7.75   19.00 

7.2. Censura estimada ajustada del período de incubación

Ahora, ajuste tres distribuciones de probabilidad a los datos del período de incubación teniendo en cuenta la censura doble. Adapte un código de stan que fue publicado por Miura et al. durante el brote global de mpox de 2022. Este método no tiene en cuenta el truncamiento a la derecha ni el sesgo dinámico.

Recuerde que el interés principal es considerar tres distribuciones de probabilidad: Weibull, gamma y log normal (Ver Tabla 1).

Stan es un programa de software que implementa el algoritmo Monte Carlo Hamiltoniano (HMC por su siglas en inglés de Hamiltonian Monte Carlo). HMC es un método de Monte Carlo de cadena de Markov (MCMC) para ajustar modelos complejos a datos utilizando estadísticas bayesianas.

7.1.1. Corra el modelo en Stan

Ajuste las tres distribuciones en este bloque de código.

R

# Prepare los datos
earliest_exposure <- as.Date(min(ip$exposure_start))

ip <- ip |>
  mutate(date_onset = as.numeric(date_onset - earliest_exposure),
         exposure_start = as.numeric(exposure_start - earliest_exposure),
         exposure_end = as.numeric(exposure_end - earliest_exposure)) |>
  select(id, date_onset, exposure_start, exposure_end)

# Configure algunas opciones para ejecutar las cadenas MCMC en paralelo
# Ejecución de las cadenas MCMC en paralelo significa que se ejecutaran varias cadenas al mismo tiempo usando varios núcleos de su computador
options(mc.cores=parallel::detectCores())

input_data <- list(N = length(ip$exposure_start), # NNúmero de observaciones
              tStartExposure = ip$exposure_start,
              tEndExposure = ip$exposure_end,
              tSymptomOnset = ip$date_onset)

# tres distribuciones de probabilidad
distributions <- c("weibull", "gamma", "lognormal") 

# Código de Stan 
code <- sprintf("
  data{
    int<lower=1> N;
    vector[N] tStartExposure;
    vector[N] tEndExposure;
    vector[N] tSymptomOnset;
  }
  parameters{
    real<lower=0> par[2];
    vector<lower=0, upper=1>[N] uE;  // Uniform value for sampling between start and end exposure
  }
  transformed parameters{
    vector[N] tE;   // infection moment
    tE = tStartExposure + uE .* (tEndExposure - tStartExposure);
  }
model{
    // Contribution to likelihood of incubation period
    target += %s_lpdf(tSymptomOnset -  tE  | par[1], par[2]);
  }
  generated quantities {
    // likelihood for calculation of looIC
    vector[N] log_lik;
    for (i in 1:N) {
      log_lik[i] = %s_lpdf(tSymptomOnset[i] -  tE[i]  | par[1], par[2]);
    }
  }
", distributions, distributions)
names(code) <- distributions

# La siguiente línea puede tomar ~1.5 min
models <- mapply(stan_model, model_code = code)

# Toma ~40 sec.
fit <- mapply(sampling, models, list(input_data), 
              iter=3000, # Número de iteraciones (largo de la cadena MCMC)
              warmup=1000, # Número de muestras a descartar al inicio de MCMC
              chain=4) # Número de cadenas MCMC a ejecutar

pos <- mapply(function(z) rstan::extract(z)$par, fit, SIMPLIFY=FALSE) # muestreo posterior 

7.1.2. Revisar si hay convergencia

Ahora verifique la convergencia del modelo. Observe los valores de r-hat, los tamaños de muestra efectivos y las trazas MCMC. R-hat compara las estimaciones entre y dentro de cadenas para los parámetros del modelo; valores cercanos a 1 indican que las cadenas se han mezclado bien (Vehtari et al. 2021). El tamaño de muestra efectivo estima el número de muestras independientes después de tener en cuenta la dependencia en las cadenas MCMC (Lambert 2018). Para un modelo con 4 cadenas MCMC, se recomienda un tamaño total de muestra efectiva de al menos 400 (Vehtari et al. 2021).

Para cada modelo con distribución ajustada:

💡 Preguntas (4)

  • ¿Los valores de r-hat son cercanos a 1?

  • ¿Las 4 cadenas MCMC generalmente se superponen y permanecen alrededor de los mismos valores (se ven como orugas peludas)?

7.1.2.1. Convergencia para Gamma

R

print(fit$gamma, digits = 3, pars = c("par[1]","par[2]")) 

SALIDA

Inference for Stan model: anon_model.
4 chains, each with iter=3000; warmup=1000; thin=1;
post-warmup draws per chain=2000, total post-warmup draws=8000.

        mean se_mean    sd  2.5%   25%   50%   75% 97.5% n_eff Rhat
par[1] 1.973   0.003 0.359 1.344 1.717 1.952 2.200 2.746 11996    1
par[2] 0.324   0.001 0.066 0.205 0.278 0.320 0.366 0.465 12183    1

Samples were drawn using NUTS(diag_e) at Tue Nov 19 02:21:20 2024.
For each parameter, n_eff is a crude measure of effective sample size,
and Rhat is the potential scale reduction factor on split chains (at
convergence, Rhat=1).

R

rstan::traceplot(fit$gamma, pars = c("par[1]","par[2]"))

7.1.2.2. Convergencia para log normal

R

print(fit$lognormal, digits = 3, pars = c("par[1]","par[2]")) 

SALIDA

Inference for Stan model: anon_model.
4 chains, each with iter=3000; warmup=1000; thin=1;
post-warmup draws per chain=2000, total post-warmup draws=8000.

        mean se_mean    sd  2.5%   25%   50%   75% 97.5% n_eff Rhat
par[1] 1.529   0.001 0.113 1.303 1.455 1.531 1.604 1.754  9446    1
par[2] 0.800   0.001 0.084 0.657 0.740 0.793 0.853 0.982  9348    1

Samples were drawn using NUTS(diag_e) at Tue Nov 19 02:21:27 2024.
For each parameter, n_eff is a crude measure of effective sample size,
and Rhat is the potential scale reduction factor on split chains (at
convergence, Rhat=1).

R

rstan::traceplot(fit$lognormal, pars = c("par[1]","par[2]")) 

7.1.2.3. Convergencia para Weibull

R

print(fit$weibull, digits = 3, pars = c("par[1]","par[2]")) 

SALIDA

Inference for Stan model: anon_model.
4 chains, each with iter=3000; warmup=1000; thin=1;
post-warmup draws per chain=2000, total post-warmup draws=8000.

        mean se_mean    sd  2.5%   25%   50%   75% 97.5% n_eff Rhat
par[1] 1.374   0.001 0.144 1.100 1.278 1.371 1.469 1.665  9735    1
par[2] 6.951   0.008 0.780 5.553 6.406 6.909 7.434 8.655  8765    1

Samples were drawn using NUTS(diag_e) at Tue Nov 19 02:21:13 2024.
For each parameter, n_eff is a crude measure of effective sample size,
and Rhat is the potential scale reduction factor on split chains (at
convergence, Rhat=1).

R

rstan::traceplot(fit$weibull, pars = c("par[1]","par[2]")) 

7.1.3. Calcule los criterios de comparación de los modelos

Calcule el criterio de información ampliamente aplicable (WAIC) y el criterio de información de dejar-uno-fuera (LOOIC) para comparar los ajustes de los modelos. El modelo con mejor ajuste es aquel con el WAIC o LOOIC más bajo. En esta sección también se resumirá las distribuciones y se hará algunos gráficos.

💡 Preguntas (5)

  • ¿Qué modelo tiene mejor ajuste?

R

# Calcule WAIC para los tres modelos
waic <- mapply(function(z) waic(extract_log_lik(z))$estimates[3,], fit)
waic

SALIDA

          weibull     gamma lognormal
Estimate 278.0739 276.10315 272.80030
SE        11.9724  13.42642  13.86909

R

# Para looic, se necesita proveer los tamaños de muestra relativos
# al llamar a loo. Este paso lleva a mejores estimados de los tamaños de 
# muestra PSIS efectivos y del error de Monte Carlo 

# Extraer la verosimilitud puntual logarítmica para la distribución Weibull
loglik <- extract_log_lik(fit$weibull, merge_chains = FALSE)
# Obtener los tamaños de muestra relativos efectivos
r_eff <- relative_eff(exp(loglik), cores = 2)
# Calcula LOOIC
loo_w <- loo(loglik, r_eff = r_eff, cores = 2)$estimates[3,]
# Imprimir los resultados
loo_w[1]

SALIDA

Estimate
278.0957 

R

# Extraer la verosimilitud puntual logarítmica para la distribución gamma 
loglik <- extract_log_lik(fit$gamma, merge_chains = FALSE)
r_eff <- relative_eff(exp(loglik), cores = 2)
loo_g <- loo(loglik, r_eff = r_eff, cores = 2)$estimates[3,]
loo_g[1]

SALIDA

Estimate
276.1223 

R

# Extraer la verosimilitud puntual logarítmica para la distribución log normal 
loglik <- extract_log_lik(fit$lognormal, merge_chains = FALSE)
r_eff <- relative_eff(exp(loglik), cores = 2)
loo_l <- loo(loglik, r_eff = r_eff, cores = 2)$estimates[3,]
loo_l[1]

SALIDA

Estimate
272.8194 

7.1.4. Reporte los resultados

La cola derecha de la distribución del período de incubación es importante para diseñar estrategias de control (por ejemplo, cuarentena), los percentiles del 25 al 75 informan sobre el momento más probable en que podría ocurrir la aparición de síntomas, y la distribución completa puede usarse como una entrada en modelos matemáticos o estadísticos, como para pronósticos (Lessler et al. 2009).

Obtenga las estadísticas resumidas.

R

# Necesitamos convertir los parámetros de las distribuciones a la media y desviación estándar del rezago

# En Stan, los parámetros de las distribuciones son:
# Weibull: forma y escala
# Gamma: forma e inversa de la escala (aka rate)
# Log Normal: mu y sigma
# Referencia: https://mc-stan.org/docs/2_21/functions-reference/positive-continuous-distributions.html

# Calcule las medias
means <- cbind(
  pos$weibull[, 2] * gamma(1 + 1 / pos$weibull[, 1]), # media de Weibull
  pos$gamma[, 1] / pos$gamma[, 2], # media de gamma
  exp(pos$lognormal[, 1] + pos$lognormal[, 2]^2 / 2) # media de log normal
)

# Calcule las desviaciones estándar
standard_deviations <- cbind(
  sqrt(pos$weibull[, 2]^2 * (gamma(1 + 2 / pos$weibull[, 1]) - (gamma(1 + 1 / pos$weibull[, 1]))^2)),
  sqrt(pos$gamma[, 1] / (pos$gamma[, 2]^2)),
  sqrt((exp(pos$lognormal[, 2]^2) - 1) * (exp(2 * pos$lognormal[, 1] + pos$lognormal[, 2]^2)))
)

# Imprimir los rezagos medios e intervalos creíbles del 95%
probs <- c(0.025, 0.5, 0.975)

res_means <- apply(means, 2, quantile, probs)
colnames(res_means) <- colnames(waic) 
res_means

SALIDA

       weibull    gamma lognormal
2.5%  5.156190 5.019654  5.003490
50%   6.337041 6.116210  6.340963
97.5% 7.911724 7.528438  8.404233

R

res_sds <- apply(standard_deviations, 2, quantile, probs)
colnames(res_sds) <- colnames(waic) 
res_sds

SALIDA

       weibull    gamma lognormal
2.5%  3.696478 3.437871  3.994825
50%   4.653564 4.354253  5.914118
97.5% 6.343806 5.869400  9.978338

R

# Informe la mediana e intervalos creíbles del 95% para los cuantiles de cada distribución

quantiles_to_report <- c(0.025, 0.05, 0.5, 0.95, 0.975, 0.99)

# Weibull
cens_w_percentiles <- sapply(quantiles_to_report, function(p) quantile(qweibull(p = p, shape = pos$weibull[,1], scale = pos$weibull[,2]), probs = probs))
colnames(cens_w_percentiles) <- quantiles_to_report
print(cens_w_percentiles)

SALIDA

          0.025      0.05      0.5     0.95    0.975     0.99
2.5%  0.2227841 0.4195682 4.102952 12.51060 14.42423 16.75763
50%   0.4719745 0.7904135 5.295055 15.35355 17.87013 21.03653
97.5% 0.8422117 1.2911878 6.694759 20.15773 23.92182 28.82866

R

# Gamma
cens_g_percentiles <- sapply(quantiles_to_report, function(p) quantile(qgamma(p = p, shape = pos$gamma[,1], rate = pos$gamma[,2]), probs = probs))
colnames(cens_g_percentiles) <- quantiles_to_report
print(cens_g_percentiles)

SALIDA

          0.025      0.05      0.5     0.95    0.975     0.99
2.5%  0.3363189 0.5692259 4.080980 11.86245 13.84178 16.34970
50%   0.7110475 1.0522926 5.103455 14.57507 17.12274 20.41608
97.5% 1.1906940 1.6146939 6.293569 18.87232 22.47027 27.22704

R

# Log normal
cens_ln_percentiles <- sapply(quantiles_to_report, function(p) quantile(qlnorm(p = p, meanlog = pos$lognormal[,1], sdlog= pos$lognormal[,2]), probs = probs))
colnames(cens_ln_percentiles) <- quantiles_to_report
print(cens_ln_percentiles)

SALIDA

          0.025      0.05      0.5     0.95    0.975     0.99
2.5%  0.6216407 0.8392404 3.679201 12.58918 15.68893 20.11991
50%   0.9764770 1.2543310 4.621208 16.98961 21.80720 29.16939
97.5% 1.3595817 1.6855825 5.776808 24.94531 33.53503 47.58508

Para cada modelo, encuentre estos elementos para el período de incubación estimado en la salida de arriba y escribalos abajo.

  • Media e intervalo de credibilidad del 95%

  • Desviación estándar e intervalo de credibilidad del 95%

  • Percentiles (e.g., 2.5, 5, 25, 50, 75, 95, 97.5, 99)

  • Los parámetros de las distribuciones ajustadas (e.g., shape y scale para distribución gamma)

7.1.5. Grafique los resultados

R

# Prepare los resultados para graficarlos
df <- data.frame(
#Tome los valores de las medias para trazar la función de densidad acumulatica empirica
  inc_day = ((input_data$tSymptomOnset-input_data$tEndExposure)+(input_data$tSymptomOnset-input_data$tStartExposure))/2
)

x_plot <- seq(0, 30, by=0.1) # Esto configura el rango del eje x (número de días)

Gam_plot <- as.data.frame(list(dose= x_plot, 
                               pred= sapply(x_plot, function(q) quantile(pgamma(q = q, shape = pos$gamma[,1], rate = pos$gamma[,2]), probs = c(0.5))),
                               low = sapply(x_plot, function(q) quantile(pgamma(q = q, shape = pos$gamma[,1], rate = pos$gamma[,2]), probs = c(0.025))),
                               upp = sapply(x_plot, function(q) quantile(pgamma(q = q, shape = pos$gamma[,1], rate = pos$gamma[,2]), probs = c(0.975)))
))

Wei_plot <- as.data.frame(list(dose= x_plot, 
                               pred= sapply(x_plot, function(q) quantile(pweibull(q = q, shape = pos$weibull[,1], scale = pos$weibull[,2]), probs = c(0.5))),
                               low = sapply(x_plot, function(q) quantile(pweibull(q = q, shape = pos$weibull[,1], scale = pos$weibull[,2]), probs = c(0.025))),
                               upp = sapply(x_plot, function(q) quantile(pweibull(q = q, shape = pos$weibull[,1], scale = pos$weibull[,2]), probs = c(0.975)))
))

ln_plot <- as.data.frame(list(dose= x_plot, 
                              pred= sapply(x_plot, function(q) quantile(plnorm(q = q, meanlog = pos$lognormal[,1], sdlog= pos$lognormal[,2]), probs = c(0.5))),
                              low = sapply(x_plot, function(q) quantile(plnorm(q = q, meanlog = pos$lognormal[,1], sdlog= pos$lognormal[,2]), probs = c(0.025))),
                              upp = sapply(x_plot, function(q) quantile(plnorm(q = q, meanlog = pos$lognormal[,1], sdlog= pos$lognormal[,2]), probs = c(0.975)))
))

# Grafique las curvas de la distribución acumulada 
gamma_ggplot <- ggplot(df, aes(x=inc_day)) +
  stat_ecdf(geom = "step")+ 
  xlim(c(0, 30))+
  geom_line(data=Gam_plot, aes(x=x_plot, y=pred), color=RColorBrewer::brewer.pal(11, "RdBu")[11], linewidth=1) +
  geom_ribbon(data=Gam_plot, aes(x=x_plot,ymin=low,ymax=upp), fill = RColorBrewer::brewer.pal(11, "RdBu")[11], alpha=0.1) +
  theme_bw(base_size = 11)+
  labs(x="Incubation period (days)", y = "Proportion")+
  ggtitle("Gamma")

weibul_ggplot <- ggplot(df, aes(x=inc_day)) +
  stat_ecdf(geom = "step")+ 
  xlim(c(0, 30))+
  geom_line(data=Wei_plot, aes(x=x_plot, y=pred), color=RColorBrewer::brewer.pal(11, "RdBu")[11], linewidth=1) +
  geom_ribbon(data=Wei_plot, aes(x=x_plot,ymin=low,ymax=upp), fill = RColorBrewer::brewer.pal(11, "RdBu")[11], alpha=0.1) +
  theme_bw(base_size = 11)+
  labs(x="Incubation period (days)", y = "Proportion")+
  ggtitle("Weibull")

lognorm_ggplot <- ggplot(df, aes(x=inc_day)) +
  stat_ecdf(geom = "step")+ 
  xlim(c(0, 30))+
  geom_line(data=ln_plot, aes(x=x_plot, y=pred), color=RColorBrewer::brewer.pal(11, "RdBu")[11], linewidth=1) +
  geom_ribbon(data=ln_plot, aes(x=x_plot,ymin=low,ymax=upp), fill = RColorBrewer::brewer.pal(11, "RdBu")[11], alpha=0.1) +
  theme_bw(base_size = 11)+
  labs(x="Incubation period (days)", y = "Proportion")+
  ggtitle("Log normal")

(lognorm_ggplot|gamma_ggplot|weibul_ggplot) + plot_annotation(tag_levels = 'A') 

En los gráficos anteriores, la línea negra es la distribución acumulativa empírica (los datos), mientras que la curva azul es la distribución de probabilidad ajustada con los intervalos de credibilidad del 95%. Asegúrese de que la curva azul esté sobre la línea negra.

💡 Preguntas (6)

  • ¿Son los ajustes de las distribuciones lo que espera?

_______Pausa 2 _________

8. Estimación del intervalo serial

Ahora, estime el intervalo serial. Nuevamente, se realizará primero una estimación navie calculando la diferencia entre la fecha de inicio de síntomas entre el par de casos primario y secundario.

  1. ¿Existen casos con intervalos seriales negativos en los datos (por ejemplo, el inicio de los síntomas en el caso secundario ocurrió antes del inicio de los síntomas en el caso primario)?

  2. Informe la mediana del intervalo serial, así como el mínimo y el máximo.

  3. Grafique la distribución del intervalo serial.

8.1. Estimación naive

R

contacts$diff <- as.numeric(contacts$secondary_onset_date - contacts$primary_onset_date)
summary(contacts$diff)

SALIDA

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
  1.000   4.000   6.000   7.625  10.000  23.000 

R

hist(contacts$diff, xlab = "Serial interval (days)", breaks = 25, main = "", col = "pink")

8.2. Estimación ajustada por censura

Ahora se estimará el intervalo serial utilizando una implementación del paquete courseDataTools dentro del paquete R EpiEstim. Este método tiene en cuenta la censura doble y permite comparar diferentes distribuciones de probabilidad, pero no se ajusta por truncamiento a la derecha o sesgo dinámico.

Se considerará tres distribuciones de probabilidad y deberá seleccionar la que mejor se ajuste a los datos utilizando WAIC o LOOIC. Recuerde que la distribución con mejor ajuste tendrá el WAIC o LOOIC más bajo.

Ten en cuenta que en coarseDataTools, los parámetros para las distribuciones son ligeramente diferentes que en rstan. Aquí, los parámetros para la distribución gamma son shape y scale (forma y escala) (https://cran.r-project.org/web/packages/coarseDataTools/coarseDataTools.pdf).

Solo se ejecutará una cadena MCMC para cada distribución en interés del tiempo, pero en la práctica debería ejecutar más de una cadena para asegurarse de que el MCMC converge en la distribución objetivo. Usará las distribuciones a priori predeterminadas, que se pueden encontrar en la documentación del paquete (ver ‘detalles’ para la función dic.fit.mcmc aquí: (https://cran.r-project.org/web/packages/coarseDataTools/coarseDataTools.pdf).

8.2.1. Preparación de los datos

R

# Formatee los datos de intervalos censurados del intervalo serial

# Cada línea representa un evento de transmisión
# EL/ER muestran el límite inferior/superior de la fecha de inicio de los síntomas en el caso primario (infector)
# SL/SR muestran lo mismo para el caso secundario (infectado)
# type tiene entradas 0 que corresponden a datos censurados doblemente por intervalo
# (ver Reich et al. Statist. Med. 2009)



si_data <- contacts |>
  select(-primary_case_id, -secondary_case_id, -primary_onset_date, -secondary_onset_date,) |>
  rename(SL = diff) |>
  mutate(type = 0, EL = 0, ER = 1, SR = SL + 1) |>
  select(EL, ER, SL, SR, type)

8.2.2. Ajuste una distribución gamma para el SI

Primero, ajuste una distribución gamma al intervalo serial.

R

overall_seed <- 3 # semilla para el generador de números aleatorios para MCMC
MCMC_seed <- 007

# Ejecutaremos el modelo durante 4000 iteraciones con las primeras 1000 muestras descartadas como burning
n_mcmc_samples <- 3000 # número de muestras a extraer de la posterior (después del burning)

params = list(
  dist = "G", # Ajuste de una distribución Gamma para el Intervalo Serial (SI)
  method = "MCMC", # MCMC usando coarsedatatools
  burnin = 1000, # número de muestras de burning (muestras descartadas al comienzo de MCMC) 
  n1 = 50, # n1 es el número de pares de media y desviación estándar del SI que se extraen
  n2 = 50) # n2 es el tamaño de la muestra posterior extraída para cada par de media y desviación estándar del SI


mcmc_control <- make_mcmc_control(
seed = MCMC_seed,
burnin = params$burnin)

dist <- params$dist

config <- make_config(
list(
si_parametric_distr = dist,
mcmc_control = mcmc_control,
seed = overall_seed,
n1 = params$n1,
n2 = params$n2))

# Ajuste el SI
si_fit_gamma <- coarseDataTools::dic.fit.mcmc(
dat = si_data,
dist = dist,
init.pars = init_mcmc_params(si_data, dist),
burnin = mcmc_control$burnin,
n.samples = n_mcmc_samples,
seed = mcmc_control$seed)

SALIDA

Running 4000 MCMC iterations
MCMCmetrop1R iteration 1 of 4000
function value = -201.95281
theta =
   1.04012
   0.99131
Metropolis acceptance rate = 0.00000

MCMCmetrop1R iteration 1001 of 4000
function value = -202.42902
theta =
   0.95294
   1.05017
Metropolis acceptance rate = 0.55445

MCMCmetrop1R iteration 2001 of 4000
function value = -201.88547
theta =
   1.18355
   0.82124
Metropolis acceptance rate = 0.56522

MCMCmetrop1R iteration 3001 of 4000
function value = -202.11109
theta =
   1.26323
   0.77290
Metropolis acceptance rate = 0.55148



@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
The Metropolis acceptance rate was 0.55500
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Ahora observe los resultados.

R

# Verificar convergencia de las cadenas MCMC 
converg_diag_gamma <- check_cdt_samples_convergence(si_fit_gamma@samples)

SALIDA


Gelman-Rubin MCMC convergence diagnostic was successful.

R

converg_diag_gamma

SALIDA

[1] TRUE

R

# Guardar las muestras MCMC en un dataframe
si_samples_gamma <- data.frame(
type = 'Symptom onset',
shape = si_fit_gamma@samples$var1,
scale = si_fit_gamma@samples$var2,
p50 = qgamma(
p = 0.5, 
shape = si_fit_gamma@samples$var1, 
scale = si_fit_gamma@samples$var2)) |>
mutate( # La ecuación de conversión se encuentra aquí: https://en.wikipedia.org/wiki/Gamma_distribution
mean = shape*scale,
sd = sqrt(shape*scale^2)
) 

# Obtener la media, desviación estándar y 95% CrI

si_summary_gamma <- 
  si_samples_gamma %>%
summarise(
mean_mean = quantile(mean,probs=.5),
mean_l_ci = quantile(mean,probs=.025),
mean_u_ci = quantile(mean,probs=.975),
sd_mean = quantile(sd, probs=.5),
sd_l_ci = quantile(sd,probs=.025),
sd_u_ci = quantile(sd,probs=.975)
)
si_summary_gamma

SALIDA

  mean_mean mean_l_ci mean_u_ci  sd_mean  sd_l_ci  sd_u_ci
1  7.659641   6.65395  8.707372 4.342087 3.602425 5.481316

R

# Obtenga las mismas estadísticas de resumen para los parámetros de la distribución
si_samples_gamma |>
summarise(
shape_mean = quantile(shape, probs=.5),
shape_l_ci = quantile(shape, probs=.025),
shape_u_ci = quantile(shape, probs=.975),
scale_mean = quantile(scale, probs=.5),
scale_l_ci = quantile(scale, probs=.025),
scale_u_ci = quantile(scale, probs=.975)
)

R

# Necesita esto para hacer gráficos más tarde
gamma_shape <- si_fit_gamma@ests['shape',][1]
gamma_rate <- 1 / si_fit_gamma@ests['scale',][1]

8.2.3. Ajuste de una distribución log normal para el intervalo serial

Ahora, ajuste una distribución log normal a los datos del intervalo serial.

R

# Ejecute el modelo durante 4000 iteraciones, descartando las primeras 1000 muestras como burning
n_mcmc_samples <- 3000 # número de muestras a extraer de la posterior (después del burning)

params = list(
  dist = "L", # Ajustando una distribución log-normal para el Intervalo Serial (SI)
  method = "MCMC", # MCMC usando coarsedatatools
  burnin = 1000, # número de muestras de burning (muestras descartadas al comienzo de MCMC) 
  n1 = 50, # n1 es el número de pares de media y desviación estándar de SI que se extraen
  n2 = 50) # n2 es el tamaño de la muestra posterior extraída para cada par de media y desviación estándar de SI


mcmc_control <- make_mcmc_control(
seed = MCMC_seed,
burnin = params$burnin)

dist <- params$dist

config <- make_config(
list(
si_parametric_distr = dist,
mcmc_control = mcmc_control,
seed = overall_seed,
n1 = params$n1,
n2 = params$n2))

# Ajuste del intervalo serial
si_fit_lnorm <- coarseDataTools::dic.fit.mcmc(
dat = si_data,
dist = dist,
init.pars = init_mcmc_params(si_data, dist),
burnin = mcmc_control$burnin,
n.samples = n_mcmc_samples,
seed = mcmc_control$seed)

SALIDA

Running 4000 MCMC iterations
MCMCmetrop1R iteration 1 of 4000
function value = -216.54581
theta =
   1.94255
  -0.47988
Metropolis acceptance rate = 1.00000

MCMCmetrop1R iteration 1001 of 4000
function value = -216.21549
theta =
   1.81272
  -0.47418
Metropolis acceptance rate = 0.56643

MCMCmetrop1R iteration 2001 of 4000
function value = -215.86791
theta =
   1.85433
  -0.52118
Metropolis acceptance rate = 0.57221

MCMCmetrop1R iteration 3001 of 4000
function value = -216.32347
theta =
   1.93196
  -0.52205
Metropolis acceptance rate = 0.55948



@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
The Metropolis acceptance rate was 0.56875
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Revise los resultados.

R

# Revise la convergencia de las cadenas MCMC 
converg_diag_lnorm <- check_cdt_samples_convergence(si_fit_lnorm@samples)

SALIDA


Gelman-Rubin MCMC convergence diagnostic was successful.

R

converg_diag_lnorm

SALIDA

[1] TRUE

R

# Guarde las muestras de MCMC en un dataframe
si_samples_lnorm <- data.frame(
type = 'Symptom onset',
meanlog = si_fit_lnorm@samples$var1,
sdlog = si_fit_lnorm@samples$var2,
p50 = qlnorm(
p = 0.5, 
meanlog = si_fit_lnorm@samples$var1, 
sdlog = si_fit_lnorm@samples$var2)) |>
mutate( # La ecuación para la conversión está aquí https://en.wikipedia.org/wiki/Log-normal_distribution
mean = exp(meanlog + (sdlog^2/2)), 
sd = sqrt((exp(sdlog^2)-1) * (exp(2*meanlog + sdlog^2)))
)

R

# Obtenga la media, desviación estándar e intervalo de credibilidad del 95% 
si_summary_lnorm <- 
  si_samples_lnorm %>%
summarise(
mean_mean = quantile(mean,probs=.5),
mean_l_ci = quantile(mean,probs=.025),
mean_u_ci = quantile(mean,probs=.975),
sd_mean = quantile(sd, probs=.5),
sd_l_ci = quantile(sd,probs=.025),
sd_u_ci = quantile(sd,probs=.975)
)
si_summary_lnorm

SALIDA

  mean_mean mean_l_ci mean_u_ci  sd_mean  sd_l_ci  sd_u_ci
1  7.780719  6.692829  9.300588 5.189349 3.936678 7.289224

R

# Obtenga las estadísticas resumen para los parámetros de la distribución
si_samples_lnorm |>
summarise(
meanlog_mean = quantile(meanlog, probs=.5),
meanlog_l_ci = quantile(meanlog, probs=.025),
meanlog_u_ci = quantile(meanlog, probs=.975),
sdlog_mean = quantile(sdlog, probs=.5),
sdlog_l_ci = quantile(sdlog, probs=.025),
sdlog_u_ci = quantile(sdlog, probs=.975)
)

SALIDA

  meanlog_mean meanlog_l_ci meanlog_u_ci sdlog_mean sdlog_l_ci sdlog_u_ci
1     1.866779     1.724069     2.015255  0.6086726  0.5107204   0.725803

R

lognorm_meanlog <- si_fit_lnorm@ests['meanlog',][1]
lognorm_sdlog <- si_fit_lnorm@ests['sdlog',][1]

8.2.4. Ajuste de una distribución Weibull para el intervalo serial

Finalmente, ajuste de una distribución Weibull para los datos del intervalo serial.

R

# Ejecutaremos el modelo durante 4000 iteraciones, descartando las primeras 1000 muestras como burning
n_mcmc_samples <- 3000 # número de muestras a extraer de la posterior (después del burning)

params = list(
  dist = "W", # Ajustando una distribución Weibull para el Intervalo Serial (SI)
  method = "MCMC", # MCMC usando coarsedatatools
  burnin = 1000, # número de muestras de burning (muestras descartadas al comienzo de MCMC) 
  n1 = 50, # n1 es el número de pares de media y desviación estándar de SI que se extraen
  n2 = 50) # n2 es el tamaño de la muestra posterior extraída para cada par de media y desviación estándar de SI

mcmc_control <- make_mcmc_control(
seed = MCMC_seed,
burnin = params$burnin)

dist <- params$dist

config <- make_config(
list(
si_parametric_distr = dist,
mcmc_control = mcmc_control,
seed = overall_seed,
n1 = params$n1,
n2 = params$n2))

# Ajuste el intervalo serial 
si_fit_weibull <- coarseDataTools::dic.fit.mcmc(
dat = si_data,
dist = dist,
init.pars = init_mcmc_params(si_data, dist),
burnin = mcmc_control$burnin,
n.samples = n_mcmc_samples,
seed = mcmc_control$seed)

Revise los resultados.

R

# Revise covengencia
converg_diag_weibull <- check_cdt_samples_convergence(si_fit_weibull@samples)

SALIDA


Gelman-Rubin MCMC convergence diagnostic was successful.

R

converg_diag_weibull

SALIDA

[1] TRUE

R

# Guarde las muestra MCMC en un dataframe
si_samples_weibull <- data.frame(
type = 'Symptom onset',
shape = si_fit_weibull@samples$var1,
scale = si_fit_weibull@samples$var2,
p50 = qweibull(
p = 0.5, 
shape = si_fit_weibull@samples$var1, 
scale = si_fit_weibull@samples$var2)) |>
mutate( # La ecuación para conversión está aquí https://en.wikipedia.org/wiki/Weibull_distribution
mean = scale*gamma(1+1/shape),
sd = sqrt(scale^2*(gamma(1+2/shape)-(gamma(1+1/shape))^2))
)

R

# Obtenga las estadísticas resumen
si_summary_weibull <- 
  si_samples_weibull %>%
summarise(
mean_mean = quantile(mean,probs=.5),
mean_l_ci = quantile(mean,probs=.025),
mean_u_ci = quantile(mean,probs=.975),
sd_mean = quantile(sd, probs=.5),
sd_l_ci = quantile(sd,probs=.025),
sd_u_ci = quantile(sd,probs=.975)
)
si_summary_weibull

SALIDA

  mean_mean mean_l_ci mean_u_ci  sd_mean  sd_l_ci  sd_u_ci
1  7.692701  6.677548  8.809715 4.418743 3.797158 5.456492

R

# Obtenga las estadísticas resumen para los parámetros de la distribución.
si_samples_weibull |>
summarise(
shape_mean = quantile(shape, probs=.5),
shape_l_ci = quantile(shape, probs=.025),
shape_u_ci = quantile(shape, probs=.975),
scale_mean = quantile(scale, probs=.5),
scale_l_ci = quantile(scale, probs=.025),
scale_u_ci = quantile(scale, probs=.975)
)

SALIDA

  shape_mean shape_l_ci shape_u_ci scale_mean scale_l_ci scale_u_ci
1   1.791214    1.50812   2.090071   8.631295   7.481234   9.943215

R

weibull_shape <- si_fit_weibull@ests['shape',][1]
weibull_scale <- si_fit_weibull@ests['scale',][1]

8.2.5. Grafique los resultados para el intervalo serial

Ahora, grafique el ajuste de las tres distribuciones. Asegúrese que la distribución ajuste bien los datos del intervalo serial.

R

ggplot()+
  theme_classic()+
  geom_bar(data = contacts, aes(x=diff), fill = "#FFAD05") +
  scale_y_continuous(limits = c(0,14), breaks = c(0,2,4,6,8,10,12,14), expand = c(0,0)) +
  stat_function(
    linewidth=.8,
    fun = function(z, shape, rate)(dgamma(z, shape, rate) * length(contacts$diff)),
    args = list(shape = gamma_shape, rate = gamma_rate),
    aes(linetype  = 'Gamma')
  ) +
  stat_function(
    linewidth=.8,
    fun = function(z, meanlog, sdlog)(dlnorm(z, meanlog, sdlog) * length(contacts$diff)),
    args = list(meanlog = lognorm_meanlog, sdlog = lognorm_sdlog),
    aes(linetype ='Log normal')
  ) +
  stat_function(
    linewidth=.8,
    fun = function(z, shape, scale)(dweibull(z, shape, scale) * length(contacts$diff)),
    args = list(shape = weibull_shape, scale = weibull_scale),
    aes(linetype ='Weibull')
  ) +
  scale_linetype_manual(values = c('solid','twodash','dotted')) +
  labs(x = "Days", y = "Number of case pairs") + 
  theme(
    legend.position = c(0.75, 0.75),
    plot.margin = margin(.2,5.2,.2,.2, "cm"),
    legend.title = element_blank(),
  ) 

ADVERTENCIA

Warning: A numeric `legend.position` argument in `theme()` was deprecated in ggplot2
3.5.0.
ℹ Please use the `legend.position.inside` argument of `theme()` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
generated.

Ahora calcule el WAIC y LOOIC. coarseDataTools no tiene una forma integrada de hacer esto, por lo que se necesita calcular la verosimilitud a partir de las cadenas MCMC y utilizar el paquete loo en R.

R

# Cargue las funciones de verosimilitud de coarseDataTools
calc_looic_waic <- function(symp, symp_si, dist){
# Prepare los datos y parámetros para el paquete loo
# Se necesita: una matriz de tamaño S por N, donde S es el tamaño de la muestra posterior (con todas las cadenas fusionadas)
# y N es el número de puntos de datos

  
  mat <- matrix(NA, nrow = length(symp_si@samples$var1), ncol = nrow(si_data)) 
  
for (i in 1:nrow(symp)) {
  for (j in 1:length(symp_si@samples$var1)){
    L <- diclik2(par1 = symp_si@samples$var1[j],
                 par2 = symp_si@samples$var2[j], 
                 EL = symp$EL[i], ER = symp$ER[i], SL = symp$SL[i], SR = symp$SR[i], 
                 dist = dist)
    mat[j,i] <- L
  }
}
  return(list(waic = waic(log(mat)),
              looic = loo(log(mat)))) # now we have to take the log to get log likelihood

}

compare_gamma <- calc_looic_waic(symp = si_data, symp_si = si_fit_gamma, dist = "G")
compare_lnorm <- calc_looic_waic(symp = si_data, symp_si = si_fit_lnorm, dist = "L")
compare_weibull <- calc_looic_waic(symp = si_data, symp_si = si_fit_weibull, dist = "W")

# Imprima resultados
compare_gamma[["waic"]]$estimates

SALIDA

             Estimate         SE
elpd_waic -202.009546  7.0118463
p_waic       1.936991  0.4409454
waic       404.019093 14.0236925

R

compare_lnorm[["waic"]]$estimates

SALIDA

             Estimate         SE
elpd_waic -202.215546  7.0178113
p_waic       1.874728  0.4106866
waic       404.431092 14.0356226

R

compare_weibull[["waic"]]$estimates

SALIDA

             Estimate         SE
elpd_waic -203.941362  6.9020056
p_waic       2.083623  0.6633186
waic       407.882723 13.8040112

R

compare_gamma[["looic"]]$estimates

SALIDA

            Estimate         SE
elpd_loo -202.015856  7.0144149
p_loo       1.943301  0.4437589
looic     404.031713 14.0288297

R

compare_lnorm[["looic"]]$estimates

SALIDA

            Estimate         SE
elpd_loo -202.218954  7.0182826
p_loo       1.878136  0.4114664
looic     404.437909 14.0365651

R

compare_weibull[["looic"]]$estimates

SALIDA

            Estimate         SE
elpd_loo -203.956018  6.9097884
p_loo       2.098279  0.6725387
looic     407.912036 13.8195768

Incluya lo siguiente cuando reporte el intervalo serial:

  • Media e intervalo de credibilidad del 95%

  • Desviación estándar y e intervalo de credibilidad del 95%

  • Los parámetros de la distribución ajustada (e.g., shape y scale para distribución gamma)

💡 Preguntas (7)

  • ¿Qué distribución tiene el menor WAIC y LOOIC??

R

si_summary_gamma

SALIDA

  mean_mean mean_l_ci mean_u_ci  sd_mean  sd_l_ci  sd_u_ci
1  7.659641   6.65395  8.707372 4.342087 3.602425 5.481316

R

si_summary_lnorm

SALIDA

  mean_mean mean_l_ci mean_u_ci  sd_mean  sd_l_ci  sd_u_ci
1  7.780719  6.692829  9.300588 5.189349 3.936678 7.289224

R

si_summary_weibull

SALIDA

  mean_mean mean_l_ci mean_u_ci  sd_mean  sd_l_ci  sd_u_ci
1  7.692701  6.677548  8.809715 4.418743 3.797158 5.456492

_______Pausa 3 _________

9. Medidas de control

9.1 Analicemos el resultado juntos

Ahora ha finalizado el análisis estadístico 🥳

  • Compare el período de incubación y el intervalo serial.

  • ¿Cuál es más largo?

  • ¿Ocurre la transmisión pre-sintomática con la Enfermedad X?

  • ¿Serán medidas efectivas aislar a los individuos sintomáticos y rastrear y poner en cuarentena a sus contactos?

  • Si no lo son, ¿qué otras medidas se pueden implementar para frenar el brote?

9.2 Diseño de una estrategía de rastreo de contactos

Basado en los rezagos estimados así como la demografía de los casos, diseñe una estrategia de rastreo de contactos para el brote, incluyendo un plan de comunicaciones (Pista: piense sobre la logística).

10. Valores verdaderos

Los valores verdaderos usados para simular las epidemías fueron:

Tabla 2. Valores verdaderos del período de incubación.
Distribución Media (días) | Desviación estándar (días)
Grupo 1 Log normal 5.7 4.6
Grupo 2 Weibull 7.1 3.7
Tabla 3. Valores verdaderos del intervalo serial.
Distribución Media (días) | Desviación estándar (días)
Group 1 Gamma 8.4 4.9
Group 2 Gamma 4.8 3.3

¿Cómo se comparan sus estimaciones con los valores verdaderos? Discuta las posibles razones para las diferencias.

Puntos Clave

Revise si al final de esta lección adquirió estas competencias:

  • Comprender los conceptos clave de las distribuciones de retrasos epidemiológicos para la Enfermedad X.

  • Entender las estructuras de datos y las herramientas para el análisis de datos de rastreo de contactos.

  • Aprender cómo ajustar las estimaciones del intervalo serial y del período de incubación de la Enfermedad X teniendo en cuenta la censura por intervalo usando un marco de trabajo Bayesiano.

  • Aprender a utilizar estos parámetros para informar estrategias de control en un brote de un patógeno desconocido.

11. Recursos adicionales

En este práctica, en gran medida se ignoraron los sesgos de truncamiento a la derecha y sesgo dinámico, en parte debido a la falta de herramientas fácilmente disponibles que implementen las mejores prácticas. Para aprender más sobre cómo estos sesgos podrían afectar la estimación de las distribuciones de retraso epidemiológico en tiempo real, recomendamos un tutorial sobre el paquete dynamicaltruncation en R de Sam Abbott y Sang Woo Park (https://github.com/parksw3/epidist-paper).

12. Contribuidores

Kelly Charniga, Zachary Madewell, Zulma M. Cucunubá

13. Referencias

  1. Reich NG et al. Estimating incubation period distributions with coarse data. Stat Med. 2009;28:2769–84. PubMed https://doi.org/10.1002/sim.3659
  2. Miura F et al. Estimated incubation period for monkeypox cases confirmed in the Netherlands, May 2022. Euro Surveill. 2022;27(24):pii=2200448. https://doi.org/10.2807/1560-7917.ES.2022.27.24.2200448
  3. Abbott S, Park Sang W. Adjusting for common biases in infectious disease data when estimating distributions. 2022 [cited 7 November 2023]. https://github.com/parksw3/dynamicaltruncation
  4. Lessler J et al. Incubation periods of acute respiratory viral infections: a systematic review, The Lancet Infectious Diseases. 2009;9(5):291-300. https://doi.org/10.1016/S1473-3099(09)70069-6.
  5. Cori A et al. Estimate Time Varying Reproduction Numbers from Epidemic Curves. 2022 [cited 7 November 2023]. https://github.com/mrc-ide/EpiEstim
  6. Lambert B. A Student’s Guide to Bayesian Statistics. Los Angeles, London, New Delhi, Singapore, Washington DC, Melbourne: SAGE, 2018.
  7. Vehtari A et al. Rank-normalization, folding, and localization: An improved R-hat for assessing convergence of MCMC. Bayesian Analysis 2021: Advance publication 1-28. https://doi.org/10.1214/20-BA1221
  8. Nishiura H et al. Serial interval of novel coronavirus (COVID-19) infections. Int J Infect Dis. 2020;93:284-286. https://doi.org/10.1016/j.ijid.2020.02.060

Content from Generar reportes a partir de bases de datos de vigilancia epidemiológica usando sivirep


Última actualización: 2024-11-19 | Mejora esta página

Tiempo estimado: 55 minutos

Hoja de ruta

Preguntas

  • ¿Cómo obtener un informe automatico de datos de SIVIGILA usando sivirep?

Objetivos

Al final de este taller usted podrá:

  • Conocer el paquete sivirep

  • Identificar las funcionalidades principales de sivirep para generar un reporte básico.

  • Hacer uso de sivirep para el análisis de una de las enfermedades con mayor impacto en la región, como herramienta para la toma de decisiones y análisis.

Introducción


Colombia ha mejorado a lo largo de los años la calidad, la accesibilidad y la transparencia de su sistema oficial de vigilancia epidemiológica, SIVIGILA. Este sistema está regulado por el Instituto Nacional de Salud de Colombia y es operado por miles de trabajadores de la salud en las secretarías de salud locales, hospitales y unidades primarias generadoras de datos.

Sin embargo, todavía existen desafíos, especialmente a nivel local, en cuanto a la oportunidad y la calidad del análisis epidemiológico y de los informes epidemiológicos. Estas tareas pueden requerir una gran cantidad de trabajo manual debido a limitaciones en el entrenamiento para el análisis de datos, el tiempo que se requiere invertir, la tecnología y la calidad del acceso a internet en algunas regiones de Colombia.

Objetivos


  • Conocer el paquete sivirep
  • Identificar las funcionalidades principales de sivirep para generar un reporte básico.
  • Hacer uso de sivirep para el análisis de una de las enfermedades con mayor impacto en la región, como herramienta para la toma de decisiones y análisis.

Conceptos básicos a desarrollar


En esta práctica se desarrollarán los siguientes conceptos:

  • Función: conjunto de instrucciones que se encargan de transformar las entradas en los resultados deseados.

  • Modulo: cojunto de funciones que son agrupadas debido a su relación conceptual, resultados proporcionados y definición de responsabilidades.

  • R Markdown: es una extensión del formato Markdown que permite combinar texto con código R incrustado en el documento. Es una herramienta para generar informes automatizados y documentos técnicos interactivos.

  • SIVIGILA: Sistema de Notificación y Vigilancia en Salud Pública de Colombia.

  • Microdatos: Son los datos sobre las características de las unidades de estudio de una población (individuos, hogares, establecimientos, entre otras) que se encuentran consolidados en una base de datos.

  • Evento: Conjunto de sucesos o circunstancias que pueden modificar o incidir en la situación de salud de una comunidad.

  • Incidencia: Es la cantidad de casos nuevos de un evento o una enfermedad que se presenta durante un período de tiempo específico. Usualmente, se presentá como el número de casos por población a riesgo, y por ello el denominador podría variar dependiendo del evento o enfermedad.

  • Departamento: En Colombia, existen 32 unidades geográficas administrativas (adm1) llamadas departamentos.

  • Municipio: Corresponden al segundo nivel de división administrativa en Colombia, que mediante agrupación conforman los departamentos. Colombia posee 1104 municipios registrados.

  • Reporte: análisis descriptivo de una enfermedad o evento del SIVIGILA.

Contenido del taller


Ha llegado el momento de explorar sivirep, de conocer cómo generar reportes automatizados con el paquete y sus funcionalidades principales.

El evento que analizaremos es Dengue en el departamento del Cauca para el año 2022, ya que ha sido una de las regiones más afectadas en Colombia a lo largo del tiempo por esta enfermedad.

Iniciaremos instalando e importando el paquete a través de los siguientes comandos:

R

remove.packages("sivirep")
if (!require("pak")) install.packages("pak")
pak::pak("epiverse-trace/sivirep") # Comando para instalar sivirep
rm(list = ls()) # Comando para limpiar el ambiente de R
library(sivirep) # Comando para importar sivirep

Verificar que el evento o enfermedad se encuentren disponibles para su descarga en la lista que provee sivirep, la cual se puede obtener ejecutando el siguiente comando:

Reporte automatizado

Ahora generaremos un reporte automatizado a partir de la plantilla que provee el paquete llamada Reporte Básico {sivirep}, la cual contiene seis secciones y recibe los siguientes parámetros de entrada: el nombre del evento o enfermedad, el año, el nombre de departamento (opcional) y nombre del municipio (opcional) para descargar los datos de la fuente de SIVIGILA.

Para hacer uso de la plantilla se deben seguir los siguientes pasos:

  1. En RStudio hacer click ‘File/New File/R’ Markdown:
  1. Selecciona la opción del panel izquierdo: ‘From Template’, después haz clic en el template llamado Reporte Básico {sivirep}, indica el nombre que deseas para el reporte (i.e. Reporte_Laura), la ubicación donde deseas guardarlo y presiona ‘Ok’.
  1. En la parte superior de **‘RStudio’*, presiona el botón ‘Knit’, despliega las opciones y selecciona ‘Knit with parameters’.
  1. A continuación, aparecerá una pantalla donde podrás indicar el nombre de la enfermedad o evento, el año y el departamento del reporte. Esta acción descargará los datos deseados y también proporcionará la plantilla en un archivo R Markdown (.Rmd), al hacer clic en el botón ‘Knit’.
  1. Espera unos segundos mientras el informe se genera en un archivo HTML.

!Felicitaciones has generado tu primer reporte automatizado con sivirep!

Actividad exploratoria

Para conocer las funciones principales del paquete realizaremos una actividad exploratoria siguiendo el flujo de datos de sivirep.

Construiremos un informe en R Markdown para Dengue, departamento del Cauca, año 2022 (no se debe utilizar la plantilla de reporte vista en la sección anterior) que de respuesta a las siguientes preguntas:

  1. ¿Cómo es la distribución por sexo y semana epidemiológica de la enfermedad?
  2. ¿Esta distribución sugiere que la enfermedad afecta más a un sexo o a otro? ¿sí, no y por qué?
  3. ¿Cómo afecta la enfermedad los distintos grupos etarios?
  4. ¿Cuál es el municipio que más se ve afectado por la enfermedad en la región?

1. Preparación y configuración del documento R Markdown

Realizaremos la configuración y preparación del documento en R Markdown a través de los siguientes pasos:

  1. Crear un documento en R Mardown vacio:
  2. Insertar un chunk en el documento con las siguientes opciones de configuración:

R

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.path = "figures/",
  include = TRUE,
    error = FALSE,
  warning = FALSE,
  message = FALSE
)

2. Importación de datos de SIVIGILA

Iniciaremos con la importación de los datos del evento o enfermedad utilizando la función import_data_event la cual permite descargar los datos desde la fuente de SIVIGILA utilizando un formato parametrizado basado en el nombre del evento y el año.

R

data_dengue <-  import_data_event(nombre_event = "dengue",
                                  year = 2022)

3. Exploración de la base de datos

Recomendamos explorar la base de datos, sus variables, tipos de datos, registros y otras caracteristicas que pueden ser relavantes para su análisis y permitan responder correctamente a las preguntas planteadas en la actividad.

Si tiene alguna duda respecto a las variables o desconoce su significado puede dirigirse al diccionario de datos del SIVIGILA.

4. Limpieza de datos de SIVIGILA

Para limpiar los datos utilizaremos una función genérica que proporciona sivirep llamada limpiar_data_sivigila, la cual envuelve diversas tareas para identificar y corregir errores, inconsistencias y discrepancias en los conjuntos de datos con el fin de mejorar su calidad y precisión. Este proceso puede incluir la corrección de errores tipográficos, el reemplazo de valores faltantes y la validación de datos, entre otras tareas, como eliminar fechas improbables, limpiar códigos de geolocalización y estandarizar los nombres de las columnas y las categorías de edad.

R

data_limpia <- limpiar_data_sivigila(data_event = data_dengue)
data_limpia

5. Filtrar casos

Ahora debemos filtrar los datos del evento o enfermedad por el departamento de Cauca, utilizando la función de sivirep llamada geo_filtro. Esta función permite al usuario crear informes a nivel subnacional, seleccionando casos específicos basados en la ubicación geográfica.

R

data_filtrada <- geo_filtro(data_event = data_limpia, dpto = "Cauca")
data_filtrada

6. Variable de sexo

Agruparemos los datos de la enfermedad por la variable sexo para poder visualizar su distribución y obtener los porcentajes a través de la función que proporciona sivirep:

R

casos_sex <- agrupar_sex(data_event = data_filtrada,
                         porcentaje = TRUE)
casos_sex

Además, sivirep cuenta con una función para generar el gráfico por esta variable llamada plot_sex:

R

plot_sex(data_agrupada = casos_sex)

La distribución de casos por sexo y semana epidemiológica se puede generar utilizando la función agrupar_sex_semanaepi proporcionada por sivirep.

R

casos_sex_semanaepi <- agrupar_sex_semanaepi(data_event = data_filtrada)
casos_sex_semanaepi

La función de visualización correspondiente es plot_sex_semanaepi, que sivirep proporciona para mostrar la distribución de casos por sexo y semana epidemiológica.

R

plot_sex_semanaepi(data_agrupada = casos_sex_semanaepi)

7. Variable de edad

sivirep proporciona una función llamada agrupar_edad, que puede agrupar los datos de enfermedades por grupos de edad. De forma predeterminada, esta función produce rangos de edad con intervalos de 10 años.

R

casos_edad <- agrupar_edad(data_event = data_limpia, interval_edad = 10)
casos_edad

La función de visualización correspondiente es plot_edad.

R

plot_edad(data_agrupada = casos_edad)

8. Distribución espacial de casos

Obtener la distribución espacial de los casos es útil para identificar áreas con una alta concentración de casos, agrupaciones de enfermedades y factores de riesgo ambientales o sociales.

En Colombia, existen 32 unidades geográficas administrativas (adm1) llamadas departamentos. sivirep proporciona una función llamada agrupar_mpio que permite obtener un data.frame de casos agrupados por departamento o municipio.

R

dist_esp_dept <- agrupar_mpio(data_event = data_filtrada)
dist_esp_dept

Con la función llamada plot_map, podremos generar un mapa estático que muestra la distribución de casos por municipios.

R

mapa <- plot_map(data_agrupada = dist_esp_dept)
mapa

9. Análisis de resultados

Analiza los resultados obtenidos en la ejecución de las funciones y responde las preguntas planteadas en el enunciado de la actividad exploratoria.

Dentro del R Markdown desarrolla dos (2) conclusiones pertinentes a la enfermedad teniendo en cuenta el contexto de la región y las estrategias que podrían contribuir a su mitigación.

Reflexión


Conformaremos grupos de 4-5 personas y discutiremos sobre la disponibilidad de los datos y el impacto que esto tiene en la construcción de análisis y en las acciones que se pueden emprender para mitigar el efecto de esta enfermedad sobre la población.

Desafio


En el siguiente enlace podrán encontrar el desafío que debérán desarrollar con sivirep: - https://docs.google.com/document/d/1_79eyXHTaQSPSvUzlikx9DKpBvICaiqy/edit?usp=sharing&ouid=113064166206309718856&rtpof=true&sd=true


Sobre este documento

Este documento ha sido diseñado por Geraldine Gómez Millán para el Curso Internacional: Análisis de Brotes y Modelamiento en Salud Pública, Bogotá 2023. TRACE-LAC/Javeriana.

Contribuciones

  • Geraldine Gómez Millán
  • Jaime Pavlich-Mariscal
  • Andrés Moreno

Content from Estima la fuerza de infección a partir de encuestas serológicas usando serofoi


Última actualización: 2024-11-19 | Mejora esta página

Tiempo estimado: 55 minutos

Hoja de ruta

Preguntas

  • ¿Cómo estimar retrospectivamente la Fuerza de Infección de un patógeno a partir de encuestas serológicas poblacionales de prevalencia desagregadas por edad mediante la implementación de modelos Bayesianos usando serofoi?

Objetivos

Al final de este taller usted podrá:

  • Explorar y analizar una encuesta serológica típica.

  • Aprender a estimar la Fuerza de Infección en un caso de uso específico.

  • Visualizar e interpretar los resultados.

1. Introducción


serofoi es un paquete de R para estimar retrospectivamente la Fuerza de Infección de un patógeno a partir de encuestas serológicas poblacionales de prevalencia desagregadas por edad mediante la implementación de modelos Bayesianos. Para ello, serofoi utiliza el paquete Rstan, que proporciona una interfaz para el lenguaje de programación estadística Stan, mediante el cual se implementan métodos de Monte-Carlo basados en cadenas de Markov.

Como un caso particular, estudiaremos un brote de Chikungunya, una enfermedad viral descubierta en Tanzania en el año 1952 que se caracteriza por causar fuertes fiebres, dolor articular, jaqueca, sarpullidos, entre otros síntomas. Desde el año 2013, casos de esta enfermedad comenzaron a ser reportados en América y desde entonces la enfermedad ha sido endémica en varios países latinoamericanos. Durante este tutorial, vamos a analizar una encuesta serológica realizada entre octubre y diciembre de 2015 en Bahía, Brasil, la cual fue realizada poco tiempo después de una epidemia de esta enfermedad, con el fin de caracterizar los patrones endémicos o epidémicos en la zona.

2. Objetivos


  • Explorar y analizar una encuesta serológica típica.

  • Aprender a estimar la Fuerza de Infección en un caso de uso específico.

  • Visualizar e interpretar los resultados.

3. Conceptos básicos a desarrollar


En esta práctica se desarrollarán los siguientes conceptos:

  • Encuestas serologícas (sero)

  • Fuerza de infección (foi)

  • Modelos serocatalíticos

  • Estadística Bayesiana

  • Visualización e interpretación de resultados de modelos FoI

La Fuerza de Infección (FoI, por sus siglas en inglés), también conocida cómo la tasa de riesgo o la presión de infección, representa la tasa a la que los individuos susceptibles se infectan dado que estuvieron expuestos a un patógeno. En otras palabras, la FoI cuantifica el riesgo de que un individuo susceptible se infecte en un periodo de tiempo.

Como veremos a continuación, este concepto es clave en el modelamiento de enfermedades infecciosas. Para ilustrar el concepto, consideremos una población expuesta a un patógeno y llamemos \(P(t)\) a la proporción de individuos que han sido infectados al tiempo \(t\). Suponiendo que no hay sero-reversión, la cantidad \(1 - P(t)\) representa la cantidad de individuos susceptibles a la enfermedad, de tal forma que la velocidad de infección está dada por:

\[ \tag{1} \frac{dP(t)}{d t} = \lambda(t) (1- P(t)),\]

en donde \(\lambda(t)\) representa la tasa a la que los individuos susceptibles se infectan por unidad de tiempo (días, meses, años, …), es decir la FoI. La ecuación diferencial (1) se asemeja a la de una reacción química en donde \(P(t)\) representa la proporción de sustrato que no ha entrado en contacto con un reactivo catalítico, por lo que este tipo de modelos se conocen como modelos serocatalíticos (Muench, 1959).

A pesar de la simpleza del modelo representado por la ecuación (1), en comparación con modelos compartimentales (por ejemplo), la dificultad para conocer una condición inicial para la seropositividad en algún punto del pasado imposibilita su uso práctico. Para sortear esta limitación, es común aplicar el modelo para cohortes de edad en lugar de para el total de la población. Para ello, etiquetemos cada cohorte de edad de acuerdo con su año de nacimiento \(\tau\), y supongamos que los individuos son seronegativos al nacer: \[ \frac{dP^\tau (t)}{dt} = \lambda(t) (1 - P^\tau(t)). \] Con condiciones iniciales dadas por \(P^\tau(\tau) = 0\). Esta ecuación se puede resolver analíticamente, dando como resultado (Hens et al, 2010):

\[ P^{\tau} (t) = 1 - \exp\left(-\int_{\tau}^{t} \lambda (t') dt' \right). \]

Suponiendo que la FoI se mantiene constante a lo largo de cada año, la versión discreta de esta ecuación es:

\[ \tag{2} P^\tau(t) = 1 - \exp\left(-\sum_{i = \tau}^{t} \lambda_i \right), \] Como un ejemplo, consideremos la FoI representada en la siguiente figura:

A partir de esta FoI, es posible calcular la seroprevalencia para distintas cohortes por medio de la ecuación (2):

Cuando conocemos los datos de una encuesta serológica, la información a la que tenemos acceso es a una fotografía de la seroprevalencia en el momento su realización \(t_{sur}\) como función de la edad de los individuos en ese momento, la cual coincide con la ecuación (2) ya que los individuos envejecen al mismo ritmo al que pasa el tiempo; es decir:

\[ \tag{3} P^{t_{sur}}(a^\tau) = 1 - \exp\left(-\sum_{i = \tau}^{t_{sur}} \lambda_i \right). \] En el caso del ejemplo, esto nos da la siguiente gráfica: Note que los valores de seroprevalencia de cada edad en esta gráfica corresponden a los valores de seroprevalencia al momento de la encuesta (2020) en la gráfica anterior.

La misión que cumple serofoi es estimar la fuerza de infección histórica \(\lambda(t)\) a partir de esta fotografía del perfil serológico de la población al momento de la encuesta. para lo cual se requieren encuestas serológicas que cumplan con los siguientes criterios de inclusión:

  • Basadas en poblacionales (no hospitalaria).
  • Estudio de tipo transversal (un solo tiempo) .
  • Que indique la prueba diagnóstica utilizada.
  • Que identifique la edad del paciente en el momento de la encuesta.

En casos donde la FoI pueda considerarse constante, la ecuación (3) da como resultado:

\[ \tag{4} P_{sur}(a^\tau(t_{sur})) = 1 - \exp (-\lambda a^\tau), \]

en donde se tuvo en cuenta qué, al momento de la introducción del patógeno (\(t = 0\)), la proporción de individuos infectados fue \(P(0) = 0\) (condición inicial). Note que el término de la suma en la ecuación (3) da como resultado la edad de cada cohorte al considerar la fuerza de infección constante.

Figura 1. Curvas de prevalencia en función de la edad para distintos valores de FoI constante.

En este ejemplo hemos escogido, por simplicidad, que la FoI fuese constante; sin embargo, este no es necesariamente el caso. Identificar si la FoI sigue una tendencia constante o variante en el tiempo puede ser de vital importancia para la identificación y caracterización de la propagación de una enfermedad. Es acá donde el paquete de R serofoi juega un papel importante, ya que este permite estimar retrospectivamente la FoI de un patógeno, recuperando así su evolución temporal, por medio de modelos Bayesianos pre-establecidos.

Los modelos actuales del paquete serofoi asumen los siguientes supuestos biológicos:

  • No hay sero-reversión (sin pérdida de inmunidad).
  • La FoI no depende de la edad.
  • Bajos o nulos niveles de migración en las poblaciones.
  • Diferencias pequeñas entre las tasas de mortalidad de susceptibles e infectados.

3.2. Modelos Bayesianos


A diferencia del enfoque frecuentista, donde la probabilidad se asocia con la frecuencia relativa de la ocurrencia de los eventos, la estadística Bayesiana se basa en el uso de la probabilidad condicional de los eventos respecto al conocimiento (o estado de información) que podamos tener sobre los datos o sobre los parámetros que queramos estimar.

Por lo general, cuando proponemos un modelo lo que buscamos es disminuir la incertidumbre sobre algún parámetro, de tal forma que nos aproximemos a su valor tan óptimamente como nos lo permita nuestro conocimiento previo del fenómeno y de las mediciones realizadas (datos).

La inferencia Bayesiana se sustenta en el teorema de Bayes, el cual establece que: dado un conjunto de datos \(\vec{y} = (y_1, …, y_N)\), el cual representa un único evento, y la variable aleatoria \(\theta\), que representa un parámetro de interés para nosotros (en nuestro caso, la FoI \(\lambda\)), la distribución de probabilidad conjunta de las variables aleatorias asociadas está dada por:

\[ \tag{4} p(\vec{y}, \theta) = p(\vec{y} | \theta) p(\theta) = p(\theta | \vec{y}) p(\vec{y}), \]

de donde se desprende la distribución aposteriori de \(\theta\), es decir una versión actualizada de la distribución de probabilidad de la FoI condicionada a nuestros datos:

\[\tag{5} p(\theta, \vec{y}) = \frac{p(\vec{y} | \theta) p(\theta)}{p(\vec{y})},\]

La distribución \(p(\vec{y} | \theta)\), que corresponde a la información interna a los datos condicionada al valor del parámetro \(\theta\), suele estar determinada por la naturaleza del experimento: no es lo mismo escoger pelotas dentro de una caja reemplazándolas que dejándolas por fuera de la caja (e.g.). En el caso particular de la FoI, contamos con datos como el número total de encuestas por edad y el número de casos positivos, por lo que es razonable asignar una distribución binomial a la probabilidad, como veremos a continuación.

3.3.1. Modelo FoI constante

En nuestro caso particular, el parámetro que queremos estimar es la FoI (\(\lambda\)). La distribución de probabilidad apriori de \(\lambda\) representa nuestras suposiciones informadas o el conocimiento previo que tengamos sobre el comportamiento de la FoI. En este contexto, el estado de mínima información sobre \(\lambda\) está representado por una distribución uniforme:

\[\tag{6} \lambda \sim U(0, 2),\]

lo que significa que partimos de la premisa de que todos los valores de la fuerza de infección entre \(0\) y \(2\) son igualmente probables. Por otro lado, de la teoría de modelos sero-catalíticos sabemos que la seroprevalencia en un año dado está descrita por un proceso cumulativo con la edad (Hens et al, 2010):

\[\tag{7} P(a, t) = 1 - \exp\left( -\sum_{i=t-a+1}^{t} \lambda_i \right), \] en donde \(\lambda_i\) corresponde a la FoI al tiempo \(t\). Como en este caso la FoI es constante, la ec. (7) se reduce a:

\[\tag{8} P(a, t) = 1 - \exp\left( -\lambda a \right),\]

Si \(n(a, t_{sur})\) es el número de muestras positivas por edad obtenidas en un estudio serológico desarrollado en el año \(t_{sur}\), entonces podemos estimar la distribución de los casos seropositivos por edad condicionada al valor de \(\lambda\) como una distribución binomial:

\[\tag{9} p(a, t) \sim Binom(n(a, t), P(a, t)) \\ \lambda \sim U(0, 2)\]

3.3.2. Modelo FOI dependientes del tiempo

Actualmente, serofoi permite la implementación de dos modelos dependientes del tiempo: uno de variación lenta de la FoI (tv-normal) y otro de variación rápida (tv-normal-log) de la FoI.

Cada uno de ellos se basa en distintas distribuciones previas para \(\lambda\), las cuales se muestran en la tabla 1.

Model Option Probability of positive case at age \(a\) Prior distribution
constant \(\sim binom(n(a,t), P(a,t))\) \(\lambda\sim uniform(0,2)\)
tv_normal \(\sim binom(n(a,t), P(a,t))\) \(\lambda\sim normal(\lambda(t-1),\sigma)\\ \lambda(t=1)\sim normal(0,1)\)
tv_normal_log \(\sim binom(n(a,t), P(a,t))\) \(\lambda\sim normal(log(\lambda(t-1)),\sigma)\\ \lambda(t=1)\sim normal(-6,4)\)

Tabla 1. Distribuciones a priori de los distintos modelos soportados por serofoi. \(\sigma\) representa la desviación estándar.

Como se puede observar, las distribuciones previas de \(\lambda\) en ambos casos están dadas por distribuciones Gaussianas con desviación estándar \(\sigma\) y centradas en \(\lambda\) (modelo de variación lenta - tv_normal) y \(\log(\lambda)\) (modelo de variación rápida - tv_normal_log). De esta manera, la FoI en un tiempo \(t\) está distribuida de acuerdo a una distribución normal alrededor del valor que esta tenía en el tiempo inmediatamente anterior. El logaritmo en el modelo tv_normal_log permite identificar cambios drásticos en la tendencia temporal de la FoI.

4. Contenido del taller

4.1 Instalación de serofoi

Previo a la instalación de serofoi, cree un proyecto en R en la carpeta de su escogencia en su máquina local; esto con el fin de organizar el espacio de trabajo en donde se guardarán los códigos que desarrolle durante la sesión.

Antes de realizar la instalación de serofoi, es necesario instalar y configurar C++ Toolchain (instrucciones para windows/ mac/linux). Después de haber configurado C++ Toolchain, ejecute las siguientes líneas de código para instalar el paquete:

R

if(!require("pak")) install.packages("pak")
pak::pak("epiverse-trace/serofoi")

Opcionalmente, es posible modificar la configuración de R para que los modelos a implementar corran en paralelo, aprovechando los núcleos del procesador de su computador. Esto tiene el efecto de disminuir los tiempos de cómputo de las implementaciones en Stan. Para activar esta opción ejecute:

R

options(mc.cores=parallel::detectCores())

Finalmente, cargue el paquete ejecutando:

R

library(serofoi)

4.2 Caso de uso: Chikungunya

En esta sección vamos a analizar una encuesta serológica realizada entre octubre y diciembre de 2015 en Bahía, Brasil, la cual fue realizada poco tiempo después de una epidemia de esta enfermedad en la zona. Nuestro objetivo es caracterizar la propagación de la enfermedad por medio de la implementación de los distintos modelos y determinar cuál de estos describe mejor la situación. Primero, carguemos y preparemos los datos que utilizaremos en este análisis. La base chik2015 contiene los datos correspondientes a esta encuesta serológica:

R

data(chik2015)
chik2015p <- prepare_serodata(chik2015)
str(chik2015p)

SALIDA

'data.frame':	4 obs. of  16 variables:
 $ survey        : chr  "BRA 2015(S019)" "BRA 2015(S019)" "BRA 2015(S019)" "BRA 2015(S019)"
 $ total         : int  45 109 144 148
 $ counts        : num  17 55 63 69
 $ age_min       : num  1 20 40 60
 $ age_max       : num  19 39 59 79
 $ tsur          : num  2015 2015 2015 2015
 $ country       : Factor w/ 256 levels "ABW","AFG","AGO",..: 33 33 33 33
 $ test          : chr  "ELISA" "ELISA" "ELISA" "ELISA"
 $ antibody      : chr  "IgG anti-CHIKV" "IgG anti-CHIKV" "IgG anti-CHIKV" "IgG anti-CHIKV"
 $ reference     : chr  "Dias et al. 2018" "Dias et al. 2018" "Dias et al. 2018" "Dias et al. 2018"
 $ age_mean_f    : num  10 29 49 69
 $ sample_size   : int  446 446 446 446
 $ birth_year    : num  2005 1986 1966 1946
 $ prev_obs      : num  0.378 0.505 0.438 0.466
 $ prev_obs_lower: num  0.238 0.407 0.355 0.384
 $ prev_obs_upper: num  0.535 0.602 0.523 0.55

Para correr el modelo de FoI constante y visualizar los resultados del mismo, corra las siguientes líneas de código:

R

chik_constant <- run_seromodel(serodata = chik2015p,
                               foi_model = "constant",
                               iter = 1000)

chik_constant_plot <- plot_seromodel(chik_constant, 
                                     serodata = chik2015p, 
                                     size_text = 12)

Ahora, corra los modelos dependientes del tiempo con iter =1500. Luego visualice conjuntamente las tres gráficas por medio de la función plot_grid() del paquete cowplot:

R

install.packages("cowplot")

cowplot::plot_grid(chik_constant_plot, 
                   chik_normal_plot, 
                   chik_normal_log_plot,
                   ncol = 3)

NOTA: Debido a que el número de trayectorias es relativamente alto, con el fin de asegurar la convergencia de los modelos, el tiempo de cómputo de los modelos dependientes del tiempo puede tardar varios minutos.

Pista: Debería obtener la siguiente gráfica:

Explicación de elpd (expected log pointwise predictive density)

5. Reflexión


Según los criterios explicados anteriormente, responda:

¿Cuál de los tres modelos se ajusta mejor a esta encuesta serológica?




¿Cómo interpreta estos resultados?



Reto

Puntos Clave

Revise si al final de esta lección adquirió estas competencias:

  • Explorar y analizar una encuesta serológica típica.

  • Aprender a estimar la Fuerza de Infección en un caso de uso específico.

  • Visualizar e interpretar los resultados.

Sobre este documento

Este documento ha sido diseñado por NOMBRE_DE_AUTORES para el Curso Internacional: Análisis de Brotes y Modelamiento en Salud Pública, Bogotá 2023. TRACE-LAC/Javeriana.

Contribuciones

  • Nicolás Torres Domínguez
  • Zulma M. Cucunuba

Contribuciones son bienvenidas vía pull requests.

Referencias


Muench, H. (1959). Catalytic models in epidemiology. Harvard University Press.

Hens, N., Aerts, M., Faes, C., Shkedy, Z., Lejeune, O., Van Damme, P., & Beutels, P. (2010). Seventy-five years of estimating the force of infection from current status data. Epidemiology & Infection, 138(6), 802–812.

Cucunubá, Z. M., Nouvellet, P., Conteh, L., Vera, M. J., Angulo, V. M., Dib, J. C., … Basáñez, M. G. (2017). Modelling historical changes in the force-of-infection of Chagas disease to inform control and elimination programmes: application in Colombia. BMJ Global Health, 2(3). doi:10.1136/bmjgh-2017-000345

Content from Introducción al cálculo de efectividad vacunal con cohortes usando vaccineff


Última actualización: 2024-11-19 | Mejora esta página

Tiempo estimado: 55 minutos

Hoja de ruta

Preguntas

  • ¿Cómo realizar el cálculo de efectividad vacunal con cohortes usando vaccineff?

Objetivos

Al final de este taller usted podrá:

  • Estudiar algunos de los conceptos claves para la implementación de estudios observacionales con diseño de cohortes

  • Calcular las métricas claves para llevar a cabo un estudio de cohortes

  • Discutir las ventajas y desventajas de los métodos de estimación no paramétricos y semi-paramétricos de modelos de superviviencia

  • Aplicar estrategias de emparejamiento de casos y control y discutir su potencial efecto sobre los estudios de cohortes

  • Estimar la efectividad vacunal haciendo uso de un diseño de cohortes

1. Introducción


En esta sesión se espera que los participantes se familiaricen con uso del paquete de Rvaccineff. Utilizaremos el diseño de cohortes para estimar la efectividad vacunal de un conjunto de datos de prueba y analizaremos los resultados obtenidos.

2. Objetivos


  • Estudiar algunos de los conceptos claves para la implementación de estudios observacionales con diseño de cohortes

  • Calcular las métricas claves para llevar a cabo un estudio de cohortes

  • Discutir las ventajas y desventajas de los métodos de estimación no paramétricos y semi-paramétricos de modelos de superviviencia

  • Aplicar estrategias de emparejamiento de casos y control y discutir su potencial efecto sobre los estudios de cohortes

  • Estimar la efectividad vacunal haciendo uso de un diseño de cohortes

3. Conceptos básicos a desarrollar


En esta práctica se desarrollarán los siguientes conceptos:

  • Eficacia y Efectividad vacunal

  • Tiempo de inducción o “delays” en el análisis de efectividad de vacunas

  • Diseños observacionales para el estudio de la efectividad vacunal

  • Diseño de cohortes para estimación de la efectividad vacunal

  • Estimador no paramétrico Kaplan-Meier

  • Modelo semi-paramétrico de Cox

  • Riesgos proporcionales

3.1. Eficacia vs Efectividad vacunal

Eficacia:

  • Se refiere a la capacidad de lograr un efecto deseado o esperado bajo condiciones ideales o controladas.

  • Es una medida teórica. En experimentos clínicos, por ejemplo, la eficacia de un medicamento se evalúa en condiciones controladas, donde todos los factores externos están cuidadosamente regulados para determinar el rendimiento puro del medicamento.

  • Puede considerarse como “lo que puede hacer” una intervención en las mejores circunstancias posibles.

Efectividad:

  • Se refiere a qué tan bien funciona una intervención en condiciones “reales” o prácticas, fuera de un entorno controlado.

  • Es una medida práctica. En el contexto de los medicamentos, la efectividad se refiere a qué tan bien funciona un medicamento en el “mundo real”, donde las condiciones no están tan controladas y hay muchos factores variables.

  • Puede considerarse como “lo que realmente hace” una intervención en circunstancias típicas o cotidianas.

Un ejemplo podría ser un medicamento que en experimentos clínicos (en condiciones ideales) mostró una eficacia del 95% en prevenir el desenlace. Sin embargo, cuando se usó en el contexto general, considerando factores como la adherencia al tratamiento, interacciones con otros medicamentos y variabilidad en la población, su efectividad fue del 85%. Aquí, la eficacia muestra el potencial del medicamento bajo condiciones ideales, mientras que la efectividad muestra cómo funciona en situaciones cotidianas.

En el caso de las vacunas la efectividad hace referencia a la disminución proporcional de casos de una enfermedad o evento en una población vacunada comparada con una población no vacunada, en condiciones del mundo real.

3.2. Tiempo de inducción

El tiempo de inducción o “delay” se refiere al periodo necesario para que la vacuna administrada induzca una respuesta inmune protectora frente a la infección o la enfermedad provocada por un patógeno. Si ocurre una infección entre el momento de la vacunación y el momento donde se prevé exista una respuesta inmune protectora, ocurrirá la infección de manera natural y no se considera un fallo de la vacuna en cuestión, puesto que la persona inmunizada aún no contaba con la protección conferida por la vacuna.

3.3. Diseños observacionales para el estudio de la efectividad vacunal

Por múltiples razones, los estudios observacionales son los más utilizados en la estimación de la efectividad vacunal. Existen múltiples diseños, entre ellos: diseño de cohortes, diseño de casos y controles, diseño de test-negativo, diseño “screening-method”. La elección del diseño, dependerá de la disponibilidad de información y los recursos disponibles para llevar a cabo el estudio.

3.4. Diseño de cohorte para el estudio de la efectividad vacunal

El término cohorte se refiere a un grupo de personas que comparten una característica común. En los estudios con diseño de cohorte, existen dos o más poblaciones que difieren en una característica o exposición y se realiza un seguimiento a ambos grupos para evaluar la aparición de uno o más desenlaces de interés y comparar la incidencia de los eventos entre ambos grupos. Para el caso de los estudios de la efectividad de vacunas, la exposición de interés corresponde a la aplicación de la vacuna y los desenlaces pueden incluir:

  • Infección: contagio con el patógeno de interés tras una exposición a alguien infeccioso

  • Enfermedad: desarrollo de signos y síntomas relacionados con la infección con un patógeno. Puede tener diferentes grados de severidad, requerir ingreso a hospitalización, UCI o incluso conducir a la muerte.

  • A la hora de llevar a cabo estos estudios se debe garantizar que las personas incluidas en cada población no tengan el desenlace de interés que será evaluado durante el seguimiento. Así mismo, es importante que el seguimiento se lleve a cabo de manera igual de rigurosa y por el mismo tiempo en ambos grupos.

La efectividad vacunal corresponde a la reducción relativa del riesgo de desarrollar la infección y/o enfermedad. En los estudios de cohorte se puede estimar el riesgo relativo, los odds ratio o los hazard ratio que expresa el riesgo que tiene la cohorte no expuesta comparado con la cohorte de los expuestos. La efectividad vacunal será, por lo tanto, el complemento del riesgo proporcional en la cohorte de expuestos.

Veamos esto con un ejemplo: para evaluar la efectividad de una vacuna para prevenir el desarrollo de enfermedad en una población, al cabo del seguimiento la incidencia en la cohorte vacunada fue de 1 por 1000 habitantes y en la cohorte no vacunada fue de 5 por 1000 habitantes. En este caso, tenemos que el riesgo relativo en la población vacunada fue 0.001/0.005 = 0,2. Es decir, la población vacunada tuvo una incidencia correspondiente al 20% de la incidencia de la población no vacunada. Esta reducción se puede atribuir a la vacuna y para estimarla usaríamos el complemento, que corresponde a 1 - 0,2= 0,8. Es decir que la reducción que se observó corresponde a un 80% y se puede atribuir a la vacuna, por lo tanto, la efectividad de la vacunación fue del 80% para evitar la enfermedad.

3.5. Métodos estadísticos para análisis de efectividad vacunal

En el diseño de cohortes se utilizan principalmente dos métodos: Estimación no paramétrica de Kapplan-Meier y Modelo semi-paramétrico de Cox

Estimador Kaplan-Meier

El estimador Kaplan-Meier utiliza las tasas de incidencia de un evento para cuantificar la probabilidad de supervivencia \(S(t)\) de forma no paramétrica, es decir, sin una representación matemática funcional. En general, la incidencia acumulada de cada grupo permite cuantificar el riesgo de presentar el evento como \(1 - S(t)\). Asimismo, la comparación entre el riesgo de ambos grupos en un punto del tiempo determina el riesgo relativo entre grupos (\(RR\)). A partir de esta se estima la efectividad vacunal como: \[V_{EFF} = 1 - RR\]

Modelo de Regresión de Cox

El modelo Cox, permite estimar de forma semi-paramétrica la función de riesgo instantáneo \(\lambda(t|x_i)\) de un individuo \(i\) en terminos de sus características, las cuales se asocian a un conjunto de covariables \(x_1, x_2, ..., x_n\), con lo cual se tiene:

\[\lambda(t|x_i) = \lambda_0(t) \exp(\beta_1 x_{i1} + \beta_2 x_{i2} + ....+\beta_n x_{in})\] donde \(\beta_1, \beta_2, ..., \beta_n\) son los coeficientes de la regresión y \(\lambda_0\) es una función basal no especificada, por lo que el modelo se asume como semi-paramétrico. En el caso en el que la función de riesgo instantáneo es completamente especificada, es decir, se conoce la forma funcional de \(\lambda_0(t)\), es posible calcular matemáticamente la probabilidad de supervivencia. Sin embargo, ninguna de estas funciones puede ser estimada directamente del modelo de Cox. Por el contrario, con este se busca estimar el hazard ratio, una medida de riesgos comparativa entre dos individuos \(i\) y \(j\) con características \(x_{i1}, x_{i2}, ..., x_{in}\) y \(x_{j1}, x_{j2}, ..., x_{jn}\), respectivamente. En este orden, al evaluar el riesgo de un individuo con respecto al otro se tiene:

\[HR = \frac{\lambda(t|x_i)}{\lambda(t|x_j)} = \frac{\lambda_0(t)\exp(\beta x_{i})}{\lambda_0(t)\exp(\beta x_{j})} = \exp(\beta(x_i-x_j))\] Riesgos proporcionales: Nótese que la dependencia temporal de las funciones de riesgo instantáneo individuales se canceló dentro de la expresion anterior para el Hazard ratio, por tanto se dice que los individuos \(i\) y \(j\) exhiben riesgos proporcionales en el tiempo. Esta hipótesis constituye un pilar importante de los estudios de efectividad bajo diseño de cohorte.

En el contexto de los estudios de cohorte, el modelo de Cox puede utilizarse para estimar el riesgo comparativo entre ambos grupos de presentar un desenlace de interés. En este sentido la pertenecia a un grupo se utiliza como covariable dentro de la regresión. Particularmente, para el cálculo de la efectividad vacunal se puede utilizar el \(HR\) de forma análoga al caso discutido para el estimador Kaplan-Meier, con lo que se define:

\[V_{EFF} = 1 - HR\]

4. Caso de uso del paquete


Para esta sesión se trabajará con el conjunto de datos de ejemplo incluido en el paquete vaccineff. Adicionalmente, se utilizarán algunas funciones de los paquetes ggplot2 y cowplot para vizualizar algunos resultados.

Para installar vaccineff ejecute las siguientes líneas en R:

R

# if(!require("pak")) install.packages("pak")
# pak::pak("epiverse-trace/vaccineff@dev")

Posteriormente, para cargar los paquetes en el espacio de trabajo y el set de datos de ejemplo del vaccineff ejecute:

R

library("vaccineff")
library("ggplot2")
library("cowplot")
data(cohortdata)
head(cohortdata)

SALIDA

        id sex age death_date death_other_causes vaccine_date_1 vaccine_date_2
1 afade1b2   F  37       <NA>               <NA>           <NA>           <NA>
2 556c8c76   M  19       <NA>               <NA>           <NA>           <NA>
3 04edf85a   M  50       <NA>               <NA>           <NA>           <NA>
4 7e51a18e   F   8       <NA>               <NA>           <NA>           <NA>
5 c5a83f56   M  66       <NA>               <NA>           <NA>           <NA>
6 7f675ec3   M  29       <NA>               <NA>     2044-04-09     2044-04-30
  vaccine_1 vaccine_2
1      <NA>      <NA>
2      <NA>      <NA>
3      <NA>      <NA>
4      <NA>      <NA>
5      <NA>      <NA>
6    BRAND1    BRAND1

Este tabla contiene información de una epidemia simulada ocurrida en 2044 sobre una población hipotética, compuesta por 100.000 habitantes, que recibió dos dosis de una vacuna para disminuir el riesgo de muerte por una enfermedad respiratoria viral. Sobre esta población se realizó seguimiento por 1 año, entre el 1 de enero y 31 de diciembre de 2044. En el conjunto de datos se presenta la información desagregada para cada uno de los participantes.

4.1 Funciones básicas de exploración de datos

4.1.2 Grupo etario

Para agrupar la población por grupo etario y graficar la distribución por edad utilizaremos la función “get_age_group”. Para esto, ejectute el siguiente comando en R:

R

cohortdata$age_group <- get_age_group(
  data = cohortdata,
  col_age = "age",
  max_val = 80,
  min_val = 0,
  step = 9)

Esta función permite agrupar la población en intervalos etarios a partir de la variable “age” del conjunto de datos. En particular, para construir la agrupación por decenios se asigna el ancho del intervalo con step = 9, el mínimo valor de edad en 0 con “min_val” y el máximo valor en 80 con “max_val”. Esto significa que cualquier registro con edad mayor o igual a 80 años se agrupará en “>80”

Puede graficar de forma rápida la distribución etaria asociada a la variable “age_group” utilizando la función “geom_bar” de la librería ggplot2. Para esto, ejecute el siguiente comando en R:

R

ggplot(data = cohortdata,
  aes(x = age_group)) +
  geom_bar() +
  theme_classic()

4.1.2. Cobertura vacunal en las cohortes

En un conjunto de datos típico de un estudio de cohorte se tiene información desagregada por persona de la(s) dosis aplicada(s) durante el seguimiento. En este sentido, es importante dimensionar el nivel de cobertura en la vacunación alcanzado durante el seguimiento. vaccineff ofrece la función “plot_coverage” que permite llevar a cabo esta tarea con facilidad. Para esto, ejecute el siguiente comando en R:

R

plot_coverage(
  data = cohortdata,
  vacc_date_col = "vaccine_date_1",
  unit = "month",
  doses_count_color = "steelblue",
  coverage_color = "mediumpurple",
  date_interval = NULL,
  cumulative = FALSE)

Esta función calcula la cobertura con base en alguna dosis, la cual se especifica en el parámetro “vacc_date_col”. En particular, en el ejemplo anterior se utiliza la primera dosis del conjunto de datos de ejemplo (“vaccine_date_1”). Además, es posible agrupar el conteo de dosis y el cálculo de cobertura en días, meses y años utilizando el parámetro “unit” y personalizar los colores de la gráfica con los parámetros “doses_count_color” y “coverage_color”. El parámetro “date_interval” se utiliza para personalizar el intervalo de datos que se quiere observar, en caso de usar “FALSE” la función toma las fechas mínima y máxima de la tabla. Finalmente, el parámetro “cumulative” permite graficar el conteo de dosis de forma acumulada en el tiempo.

4.2 Seguimiento de la cohorte y tiempos al evento

Para llevar a cabo la estimación de la efectividad vacunal primero debe definir la fecha de inicio y fin del seguimiento de cohortes, esto con base en su exploración de las bases de datos. Para el caso particular del conjunto de datos ejemplo contenidos es vaccineff se utiliza:

R

start_cohort <- as.Date("2044-01-01")
end_cohort <- as.Date("2044-12-31")

Para determinar la fecha de immunización, de un individuo vaccineff cuenta con la función get_immunization_date(). Esta calcula la fecha de inmunización seleccionando la vacuna que cumpla con el criterio de fecha límite presentado en la siguiente figura.

Esquema de fecha límite para selección de vacunas
Esquema de fecha límite para selección de vacunas

4.2.2 Tiempo de inducción del efecto (delay)

En la figura anterior se introdujo el concepto de delay. El delay del desenlace hace referencia al tiempo característico al desenlace desde que se un individuo se infecta. Por otra parte, el delay de la immunización se refiere al tiempo que se espera que transcurra antes de considerar que existe inmunidad protectora. Cada uno de estas variables puede controlarse en la función mediante los parámetros outcome_delay e immunization_delay, respectivamente. El valor que se asigne a estas depende de la naturaleza del estudio. En particular, para nuestra epidemia ejemplo utilizaremos outcome_delay = 0 dado que no es considerado como una variable relevante para determinar la fecha límite en que la vacuna podría hacer efecto. Por otra parte, el valor asumido para el de immunización es de 14 días.

Para calcular la fecha de immunización de cada individiduo escriba la siguiente instrucción en R y ejecútela:

R

cohortdata$immunization <-
  get_immunization_date(
    data = cohortdata,
    outcome_date_col = "death_date",
    outcome_delay = 0,
    immunization_delay = 14,
    vacc_date_col = c("vaccine_date_1", "vaccine_date_2"),
    end_cohort = end_cohort,
    take_first = FALSE)

Al ejecutar este comando, se añadirá una columna nueva al conjunto de datos del caso de estudio, esta columna contiene una variable de tipo “Date” que corresponde la fecha 14 días posteriores a la última dosis de cualquiera de las dos vacunas utilizadas en el caso de estudio, que cumpla con el criterio de fecha límite. Puede evidenciar la nueva columna en el conjunto de datos.

4.2.3. Definir la exposición: cohorte vacunada y cohorte no vacunada

A continuación, cree una columna nueva donde categorice cada uno de los sujetos según su estado vacunal, vacunados como “v” y no vacunados como “u” (unvaccinated, en inglés). Para esto ejecute el siguiente comando en R:

R

cohortdata$vaccine_status <- set_status(
  data = cohortdata,
  col_names = "immunization",
  status = c("vacc", "unvacc"))

Al ejecutar el comando encontrará que se ha creado una nueva columna en el conjunto de datos con los valores “vacc” o “unvacc”, para cada uno de los registros de acuerdo con su estado de vacunación, el cual se determina a partir de la variable "immunization".

4.2.3 Definir el desenlace: estado vivo vs estado fallecido

El desenlace para este ejercicio va a ser la condición vital al final del estudio. En otros ejercicios el desenlace de interés podría ser la hospitalización o la infección, dependiendo del conjunto de datos utilizado.

Tenga en cuenta que en conjunto de datos del caso de estudio todas las defunciones son debidas a la infección por el virus de interés. Para definir el desenlace de interés de acuerdo a la condición vital de los participantes en la cohorte, ejecute el siguiente comando en R:

R

cohortdata$death_status <- set_status(
  data = cohortdata,
  col_names = "death_date")

Al ejecutar este comando, nuevamente aparecerá una nueva columna en el conjunto de datos con el que se está realizando el caso de estudio. Al explorar la base de datos completa, se verifica que en la nueva columna aparece 0 = vivo y 1 = fallecido.

4.2.4. Calcular el tiempo al evento (desde exposición hasta desenlace)

Ahora, calcule para cada una de las personas el tiempo que fue seguido en el estudio. Recuerde que el seguimiento se hace igual para ambas cohortes (vacunada y no vacunada) y finaliza cuando termina el estudio o cuando se presenta el desenlace de interés. Para obtener este dato ejecute el siguiente comando en R:

R

cohortdata$time_to_death <- get_time_to_event(
  data = cohortdata,
  outcome_date_col = "death_date",
  start_cohort = start_cohort,
  end_cohort = end_cohort,
  start_from_immunization = FALSE)

Nuevamente, al ejecutar este comando se creará una nueva variable en el conjunto de datos y como se puede evidenciar al explorar los datos completos, la nueva variable estima los días de seguimiento para cada una de las personas. Esta función permite crear tiempos al evento desde el inicio del seguimiento en el caso de estudio, en este caso se utiliza la opción “start_from_immunization = FALSE” o desde la fecha de inmunización. Para este último caso es necesario asignar “start_from_immunization = TRUE” y pasar el nombre de la columna con la información sobre la fecha de inmunización en el parámetro “immunization_date_col”. En la figura a continuación se presenta un esquema de los dos métodos de cálculo del tiempo al evento.

a) Tiempo al evento evaludado desde inicio de la cohorte. b) tiempo al evento desde la fecha de immunización
a) Tiempo al evento evaludado desde inicio de la cohorte. b) tiempo al evento desde la fecha de immunización

4.3. Curvas de riesgo acumulado y supervivencia al desenlace en cada cohorte

Como primera aproximación a la efectividad vacunal, graficaremos la curva de riesgos acumulados (1 menos la supervivencia de las poblaciones). Esta nos permite entender de forma cualitativa cuál de las poblaciones acumula mayor riesgo durante el estudio de cohorte. Y por tanto, evaluar hipótesis sobre el potencial efecto de la vacunación sobre la cohorte. Para graficar el riesgo acumulado ejecute el siguiente comando en R:

R

plot_survival(data = cohortdata,
  outcome_status_col = "death_status",
  time_to_event_col = "time_to_death",
  vacc_status_col = "vaccine_status",
  vaccinated_status = "vacc",
  unvaccinated_status = "unvacc",
  vaccinated_color = "steelblue",
  unvaccinated_color = "darkred",
  start_cohort = start_cohort,
  end_cohort = end_cohort,
  percentage = TRUE,
  cumulative = TRUE)

A partir de la función anterior también es posible realizar la gráfica de supervivencia de las cohortes o gráfica de Kaplan-Meier en la cual se parte del 100% de ambas cohortes en el eje de las ordenadas y en el eje de las abscisas se representa el tiempo y se observa la disminución que hay debido a las defunciones en cada cohorte. Tenga en cuenta que este no es el estimador de Kaplan-Meir, es sólo la gráfica sobre las curvas de supervivencia y de riesgo acumulado. Para obtener este gráfico, se ejecuta la misma función y se cambia el argumento “percentage” por FALSE:

R

plt_surv <- plot_survival(data = cohortdata,
  outcome_status_col = "death_status",
  time_to_event_col = "time_to_death",
  vacc_status_col = "vaccine_status",
  vaccinated_status = "vacc",
  unvaccinated_status = "unvacc",
  vaccinated_color = "steelblue",
  unvaccinated_color = "darkred",
  start_cohort = start_cohort,
  end_cohort = end_cohort,
  percentage = TRUE,
  cumulative = FALSE)
plt_surv

4.4. Evaluación de la hipótesis riesgos proporcionales

Previo a la estimación de la efectividad vacunal es importante testear la hipótesis de riesgos proporcionales entre las cohortes de vacunados y no vacunados, con el fin de identificar posibles sesgos muestrales que afecten la validez de los resultados y definir estrategias de cálculo de cohortes dinámicas, emparejamiento o estratificación, entre otras, que permitar contrarestar este efecto. Un método cualitativo utilizado ampliamente en la literatura es la transformación de la probabilidad de supervivencia mediante la función logaritmo natural como \(\log[-\log[S(t)]]\). Al graficar esta función contra el logaritmo del tiempo al evento, es posible identificar si los grupos representan riesgos proporcionales cuando sus curvas se comportan de forma aproximadamente paralela.

A continuación se presentan las gráficas loglog obtenidas al construir los tiempos al evento de forma dinámica (panel de la izquiera) y con punto de inicio fijo (panel de la derecha). Particularmente, se observa que el cálculo dinámico de los tiempos no satisface riesgos proporcionales, por lo que pontencialmente necesitará algún tipo de estrategia para contrarestar los posibles sesgos muestrales.

4.5. Estimación de la efectividad vacunal

Modelo de Cox

En todos los estudios observacionales de efectividad vacunal de cohorte se compara el riesgo (o tasa de incidencia) entre los vacunados y no vacunados de desarrollar el desenlace de interés (infección, enfermedad, muerte, etc). La medida de riesgo a utilizar dependerá de las características de la cohorte y puede expresarse en términos de relative risk (RR) o hazard ratio (HR).

En este ejercicio utilizaremos el modelo de regresión de riesgos proporcionales de Cox para estimar el hazar ratio y con esta medida estimar la efectividad mediante

VE= 1-HR

Para estimar la efectividad vacunal en términos de HR, ejecute el siguiente comando en R:

R

coh_eff_noconf(
  data = cohortdata,
  outcome_status_col = "death_status",
  time_to_event_col = "time_to_death",
  status_vacc_col = "vaccine_status")

SALIDA

      HR HR_low HR_high  V_eff V_eff_low V_eff_high     PH      p_value
1 0.2684 0.1855  0.3884 0.7316    0.6116     0.8145 reject 0.0009088627

4.6 Cohorte dinámica - Emparejamiento poblacional

El comando anterior arrojó la efectividad vacunal obtenida mediante el model de Cox. Sin embargo, en la columna PH aparece la palabra rejected. Esto significa que pese a que las curvas de ambos grupos son paralelas la evaluación de la hipótesis de riesgos proporcionales haciendo uso del test de Schoenfeld no es satisfactoria. Esto debido a los potenciales sesgos muestrales inducidos por la estrategia de vacunación desplegada sobre la población. Una estrategia para contrarestas dichos sesgos es el emparejamiento dinámico de la población. En la siguiente figura se presenta un esquema de emparejamiento orientado a disminuir los sesgos muestrales.

4.7 Próximos características

  • Estimador Kaplan-Meier

  • Curva loglog para evaluación de riesgos proporcionales

  • Matching dinámico

  • Censura

5. Reflexión


En esta sesión se llevó a cabo una estimación básica de la efectividad vacunal mediante un estudio de cohorte, haciendo uso de los estimador Kaplan-Meier y del modelo de Cox. Adicionalmente, se estudió la hipótesis de riesgos proporcionales para los grupos vacunado y no vacunado comparando la construcción de tiempos al evento de forma dinámica y fija. Se observó que pese a que la hipótesis de riesgos proporcionales se corrobora gráficamente en la consrucción de los tiempos al evento con fecha de inicio fija, al evaluar el modelo de riesgos proporcionales de Cox el test de Schoenfeld rechaza esta hipótesis. En general la decisión sobre aceptar o no los resultados es altamente dependiente del acceso a información adicional relevante que permita realizar ajustes a la estimación y capturar dinámicas relevantes.


Reto


Reto


Puntos Clave

  • Estudiar algunos de los conceptos claves para la implementación de estudios observacionales con diseño de cohortes

  • Calcular las métricas claves para llevar a cabo un estudio de cohortes

  • Discutir las ventajas y desventajas de los métodos de estimación no paramétricos y semi-paramétricos de modelos de superviviencia

  • Aplicar estrategias de emparejamiento de casos y control y discutir su potencial efecto sobre los estudios de cohortes

  • Estimar la efectividad vacunal haciendo uso de un diseño de cohortes

Sobre este documento

Este documento ha sido diseñado por David Santiago Quevedo para el Curso Internacional: Análisis de Brotes y Modelamiento en Salud Pública, Bogotá 2023. TRACE-LAC/Javeriana.

Contribuciones

  • David Santiago Quevedo

  • Santiago Loaiza

  • Zulma Cucunubá

Content from Indicadores demográficos, canales endémicos y clusters de incidencia usando epiCo


Última actualización: 2024-11-19 | Mejora esta página

Tiempo estimado: 55 minutos

Hoja de ruta

Preguntas

  • ¿Cómo simular una sala de análisis de riesgo?

Objetivos

Al final de este taller usted podrá:

  1. Navegar por la codificación de la División Política Administrativa de Colombia (DIVIPOLA).
  2. Consultar, visualizar e interpretar pirámides poblacionales a distintas escalas administrativas.
  3. Familiarizarse con la información epidemiológica proporcionada por el SIVIGILA.
  4. Describir las características demográficas de la información epidemiológica.
  5. Estimar tasas de incidencia acumulada a distintas escalas espaciales y temporales.
  6. Realizar una evaluación del riesgo etario integrando información epidemiológica con información sobre la estructura poblacional de una región.

epiCo

epiCo ofrece herramientas estadísticas y de visualización para el análisis de indicadores demográficos, comportamiento espaciotemporal como mapas de riesgo, y caracterización de los brotes a través de canales endémicos de las ETVs en Colombia.

Instalación

Puede descargar la última versión estable de epiCo a través del repositorio en GitHub con los siguientes comandos:

R

# if(!require("remotes")) install.packages("remotes")
# if(!require("epico")) remotes::install_github("epiverse-trace/epiCo")

Motivación

  • Incluir evaluaciones de riesgos espaciales y demográficos en informes epidemiológicos rutinarios para mejorar la identificación de grupos vulnerables.
  • Facilitar la comprensión de los diferentes perfiles epidemiológicos dentro de una región de Colombia en cuanto al inicio, duración, magnitud y frecuencia de los brotes.
  • Reforzar la transparencia de los métodos utilizados para el análisis de brotes.

Indicadores demográficos en epiCo

El módulo demográfico de epiCo es una herramienta de análisis descriptivo y evaluación del riesgo basado en las variables reportadas por el Sistema de Vigilancia en Salud Pública en Colombia (SIVIGILA) y en datos del Departamento Administrativo Nacional de Estadística (DANE).

El sistema DIVIPOLA es una nomenclatura estándar diseñada por el DANE para la identificación de entidades territoriales (departamentos, distritos y municipios), áreas no municipalizadas, y centros poblados a través de un código numérico único.

Colombia posee:

  • 32 departamentos (División administrativa nivel 1)
  • 1102 municipios (División administrativa nivel 2)
  • 1 isla
  • 18 áreas no municipalizadas
  • 6678 centros poblados

Dos dígitos son usados para codificar los departamentos, y tres dígitos adicionales son usados para codificar los municipios. epiCo proporciona la lista completa de códigos a través de un dataset interno llamado divipola_table.

R

# Se cargan las librerías necesarias.
library(epiCo)
library(incidence)

# Listado de códigos para cada uno de los municipios.
data("divipola_table")
head(divipola_table)

SALIDA

  COD_DPTO  NOM_DPTO COD_REG          NOM_REG COD_MPIO   NOM_MPIO  LATITUD
1       05 ANTIOQUIA      03 CENTRO_OCCIDENTE    05001   MEDELLIN 6.257590
2       05 ANTIOQUIA      03 CENTRO_OCCIDENTE    05002  ABEJORRAL 5.803728
3       05 ANTIOQUIA      03 CENTRO_OCCIDENTE    05004   ABRIAQUI 6.627569
4       05 ANTIOQUIA      03 CENTRO_OCCIDENTE    05021 ALEJANDRIA 6.365534
5       05 ANTIOQUIA      03 CENTRO_OCCIDENTE    05030      AMAGA 6.032922
6       05 ANTIOQUIA      03 CENTRO_OCCIDENTE    05031     AMALFI 6.977789
   LONGITUD
1 -75.61103
2 -75.43847
3 -76.08598
4 -75.09060
5 -75.70800
6 -74.98124

2. Pirámides poblacionales

epiCo cuenta con datos internos sobre las proyecciones poblaciones de Colombia a nivel nacional, departamental y municipal (información proveniente del DANE).

Los usuarios pueden realizar consultas a epiCo utilizando la función population_pyramid, la cual requiere el codigo DIVIPOLA de la entidad de interés y el año que se desea consultar.

R

tolima_codigo <- "73" # Código DIVIPOLA para el departamento del Tolima.
año <- 2021 # Año a consultar.
tolima_piramide_2021 <- population_pyramid(tolima_codigo, año) # Pirámide 
# poblacional del departamento del tolima para el año 2021.
head(tolima_piramide_2021)

SALIDA

  age population sex
1   0      42889   F
2   5      47378   F
3  10      51141   F
4  15      54694   F
5  20      53866   F
6  25      50014   F

epiCo presenta los datos agrupados en intervalos de 5 años de forma predeterminada; sin embargo, puede modificar parámetros para tener rangos distintos, separar la pirámide por género binario, obtener los datos como conteos totales o proporciones y/o desplegar una gráfica de la pirámide.

R

tolima_codigo <- "73" # Código DIVIPOLA para el departamento del Tolima.
año <- 2021 # Año a consultar.
rango_edad <- 10 #Rango de edades o ventana.
# Pirámide poblacional del departamento del tolima para el año 2021.
tolima_piramide_2021 <- population_pyramid(divipola_code = tolima_codigo,
                                           year = año,
                                           range = rango_edad,
                                           sex = TRUE, total = TRUE,
                                           plot = TRUE)

3. Datos epidemiológicos

epiCo es una herramienta que produce análisis basados en datos epidemiológicos como los que provee el SIVIGILA. El paquete cuenta con un data set llamado epi_data, que cuenta con la información y estructura que requiere la librería para sus funciones y que utiliza los casos de dengue reportados en el departamento del Tolima del 2012 al 2021 a manera de ejemplo.

R

# Datos epimdeiológicos.
data("epi_data")
head(epi_data)

SALIDA

# A tibble: 6 × 16
  fec_not    cod_pais_o cod_dpto_o cod_mun_o cod_pais_r cod_dpto_r cod_mun_r
  <chr>      <chr>      <chr>      <chr>     <chr>      <chr>      <chr>
1 2012-02-04 170        73         73671     170        73         73671
2 2012-11-18 170        73         73268     170        73         73268
3 2012-07-12 170        73         73001     170        11         11001
4 2012-11-24 170        73         73275     170        73         73275
5 2012-03-29 170        73         73268     170        73         73268
6 2012-02-19 170        73         73001     170        73         73001
# ℹ 9 more variables: cod_dpto_n <chr>, cod_mun_n <chr>, edad <dbl>,
#   sexo <chr>, per_etn <dbl>, ocupacion <chr>, estrato <chr>, ini_sin <chr>,
#   cod_eve <dbl>

4. Variables demográficas

epiCo proporciona un diccionario para consultar las categorías étnicas usadas por el sistema SIVIGILA a través de la función describe_ethnicity.

R

# Descripción de las etnicidades reportadas en la base de datos.
describe_ethnicity(unique(epi_data$per_etn))

SALIDA

  code
1    1
2    2
3    3
4    4
5    5
6    6
                                                                                                                                                                                                                                                  description
1                                  A person of Amerindian descent who shares feelings of identification with their aboriginal past, maintaining traits and values of their traditional culture, as well as their own forms of organization and social control
2                                                                                      They are communities that have their own ethnic and cultural identity; They are characterized by a nomadic tradition, and have their own language, which is Romanesque
3 Population located in the Archipelago of San Andres, Providencia and Santa Catalina, with Afro-Anglo-Antillean cultural roots, whose members have clearly differentiated sociocultural and linguistic traits from the rest of the Afro-Colombian population
4                                                                                                                     Population located in the municipality of San Basilio de Palenque, department of Bolivar, where palenquero is spoken, a Creole language
5                                                                                                               Person of Afro-Colombian descent who have their own culture, and have their own traditions and customs within the rural-populatedrelationship
6                                                                                                                                                                                                                                                        <NA>

Para calcular la distribución de las ocupaciones en estos años, epiCo usa la columna que nos indica la ocupación de cada uno de los casos presentados en estos años. Esta ocupación está codificada por el sistema ISCO-88 como se puede observar en la tabla isco88_table, y epiCo cuenta con la función occupation_plot para visualizar de forma proporcional el número de casos pertenecientes a una labor.

R

# Tabla de ocupaciones del sistema ISCO-88.
data("isco88_table")
head(isco88_table)

SALIDA

  major                                 major_label sub_major
1     1 Legislators, Senior Officials and Managers         11
2     1 Legislators, Senior Officials and Managers         11
3     1 Legislators, Senior Officials and Managers         11
4     1 Legislators, Senior Officials and Managers         11
5     1 Legislators, Senior Officials and Managers         11
6     1 Legislators, Senior Officials and Managers         11
                    sub_major_label minor
1 Legislators and Senior Officials    111
2 Legislators and Senior Officials    112
3 Legislators and Senior Officials    113
4 Legislators and Senior Officials    114
5 Legislators and Senior Officials    114
6 Legislators and Senior Officials    114
                                          minor_label unit
1                                        Legislators  1110
2                        Senior Government Officials  1120
3           Traditional Chiefs and Heads of Villages  1130
4 Senior Officials of Special-Interest Organisations  1141
5 Senior Officials of Special-Interest Organisations  1142
6 Senior Officials of Special-Interest Organisations  1143
                                                                           unit_label
1                                                                        Legislators
2                                                        Senior government officials
3                                           Traditional chiefs and heads of villages
4                                  Senior officials of political-party organisations
5 Senior officials of employers', workers' and other economic-interest organisations
6          Senior officials of humanitarian and other special-interest organisations 

R

# Se calcula la distribución de ocupaciones.
occupation <- describe_occupation(isco_codes = as.integer(epi_data$ocupacion), sex = epi_data$sexo, plot = "treemap")

ADVERTENCIA

Warning in describe_occupation(isco_codes = as.integer(epi_data$ocupacion), :
NAs introduced by coercion

SALIDA

59870 codes are invalid.

5. Estimación de las tasas de incidencia

La función incidence_rate de epiCo requiere del paquete incidence para producir un objeto incidence modificado. En lugar de un vector (o matriz) de conteos, transforma el objeto para proporcionar un elemento de tasa que representa el número de casos en el periodo dividido por el número total de habitantes en la región en las fechas específicas.

epiCo usa como denominadores las proyecciones poblacionales del DANE; por lo que es necesario proporcionar el nivel de administración en el que se calculan las incidencias.

R

# Se crea el objeto incidence a partir de las fechas y municiíos presentadas en
# epi_data.
incidence_objecto <- incidence(
  dates = epi_data$fec_not,
  groups = epi_data$cod_mun_o,
  interval = "1 epiweek"
)
# Calcular la tasa de incidencia para cada uno de los municipios del Tolima.
incidence_tasa_objecto <- incidence_rate(incidence_objecto, level = 2)
head(incidence_tasa_objecto$counts)

SALIDA

     73001 73024 73026 73030 73043 73055 73067 73124 73148 73152 73168 73200
[1,]     7     0     0     0     0     0     0     0     0     0     2     0
[2,]    20     0     0     0     0     0     0     1     2     0     2     3
[3,]    17     0     1     1     0     0     0     0     2     0     3     0
[4,]    15     0     0     0     0     0     1     0     2     0     3     0
[5,]    18     0     0     0     0     0     0     0     4     0     8     0
[6,]    25     0     0     0     0     1     1     0     2     0     5     0
     73217 73226 73236 73268 73270 73275 73283 73319 73347 73349 73352 73408
[1,]     0     0     0     5     0     0     0     5     0     0     0     0
[2,]     1     0     0    10     0     2     2     6     0     1     0     1
[3,]     1     1     0    20     0     3     0     3     0     5     0     5
[4,]     0     0     0    16     0     4     0     2     0     1     0     3
[5,]     0     1     0    18     0     4     0     3     0     0     0     2
[6,]     0     0     0    12     0     1     0     4     0     2     0     5
     73411 73443 73449 73461 73483 73504 73520 73547 73555 73563 73585 73616
[1,]     0     1     1     0     0     1     0     0     0     0     0     0
[2,]     0     1     2     0     0     0     0     1     0     0     0     0
[3,]     0     1     1     0     1     0     0     0     0     0     0     0
[4,]     1     1     3     0     0     0     0     9     0     2     0     0
[5,]     0     1     2     0     2     2     0     0     0     0     0     0
[6,]     1     1     1     0     0     0     0     0     0     0     0     1
     73622 73624 73671 73675 73678 73686 73770 73854 73861 73870 73873
[1,]     0     0     0     0     0     0     0     0     0     0     0
[2,]     0     2     0     0     0     0     1     1     0     0     0
[3,]     0     1     0     1     0     0     0     0     4     0     0
[4,]     0     0     0     1     0     0     0     0     6     0     0
[5,]     0     0     2     0     0     0     0     0     1     0     0
[6,]     0     0     1     0     0     0     0     0     1     0     0

6. Estimación del riesgo etario

La normalización de los datos es un aspecto clave de la epidemiología. epiCo utiliza la distribución por edades de los casos y normaliza los datos epidemiológicos con la estructura de edades de una población a tarvés de la función age_risk. Esta normalización nos permite estimar el riesgo por edad de sufrir una enfermedad según la estructura de edad de la población general de un municipio, departamento o país en un año determinado.

R

# Se filtran los datos del SIVIGILA para el año 2021.
data_tolima_2021 <-  epi_data[lubridate::year(epi_data$fec_not) == 2021, ]

# Se calcula las tasas por edades para el año 2021.
incidence_rate_2019 <- age_risk(age = as.integer(data_tolima_2021$edad),
                                population_pyramid = tolima_piramide_2021,
                                sex = data_tolima_2021$sexo,
                                plot = TRUE)

Construir un canal endémico con epiCo

El módulo de canal endémico de epiCo es una herramienta que permite estimar una línea de tendencia central y los límites superior e inferior del número esperado de casos de una enfermedad a partir de los datos históricos proporcionados por el usuario y un conjunto de parámetros personalizados.

En el siguiente tutorial, los usuarios aprenderán:

  1. Qué es un canal endémico y cuáles son sus ventajas, inconvenientes y precauciones.
  2. Un ejemplo de los datos históricos necesarios para construir un canal endémico.
  3. Cómo utilizar la función endemic_channel.

1. ¿Qué es un canal endémico?

El uso de un canal endémico es una estrategia visual para representar el comportamiento histórico de una enfermedad en una región específica en una curva epidémica que define la tendencia central de los casos durante un año y los límites superior e inferior donde se espera que varíen los casos. Este gráfico proporciona tres zonas denominadas “\(\color{green}{\text{Safety - Seguridad}}\)”, “\(\color{yellow}{\text{Warning - Alerta}}\)” y “\(\color{red}{\text{Epidemic - Epidemia}}\)”, que posteriormente se utilizan para definir la situación epidemiológica de la región en función de los casos actuales.

La definición más amplia y la metodología para el canal endémico fueron proporcionadas por Bormant (1999).

Los datos necesarios para construir un canal endémico son la incidencia semanal o mensual de la enfermedad durante los años anteriores en la región de interés. epiCo pide al menos un año de datos, pero los canales endémicos suelen tener entre 5 y 7 años de información. Es natural suponer que más años proporcionan mejores estimaciones ya que los análisis estadísticos serán más robustos, pero es importante disponer de información contextual para asegurar que las condiciones de transmisión, vigilancia o demográficas (entre otros factores) no han cambiado durante este periodo de tiempo. En cuanto a la frecuencia de los datos, la incidencia semanal puede proporcionar información más útil sobre el comportamiento de la enfermedad y el momento en que se debe plantear una alerta epidemiológica; sin embargo, depende de la experiencia de los usuarios y de su contexto que se pueda lograr esta resolución de los datos. Otra decisión común mientras se construye un canal endémico es ignorar los años epidémicos anteriores para evitar tergiversar la dinámica de transmisión tradicional. epiCo no sugiere años epidémicos automáticamente, pero su función endemic_channel ofrece la opción de anotar los años atípicos y decidir si incluirlos, ignorarlos o ajustarlos. Más adelante en esta viñeta se ofrecen más consejos y fuentes para la recopilación de datos.

2. Datos históricos necesarios para construir un canal endémico

En esta sección se presentan algunas estrategias para obtener, manejar y recopilar los datos históricos necesarios para construir un canal endémico. Se asume que el usuario cuenta con los datos de incidencia de la enfermedad de interés o tiene la posibilidad de consultar datos históricos del SIVIGILA.

Independientemente del caso del usuario, el objetivo de esta sección es crear un objeto incidence con datos históricos de la enfermedad. Este objeto debe dar cuenta de la incidencia de una sola región (no se permiten grupos en la función endemic_channel), debe tener un intervalo semanal o mensual (también se permiten semanas epidemiológicas) y debe representar al menos un año de información.

Para comprender mejor el paquete de incidence, consulte sus viñetas.

Incidencia histórica a partir de los datos propios o de los datos SIVIGILA

El canal endémico es más útil cuando se dispone de datos hasta el año inmediatamente anterior; sin embargo, esto no siempre es posible para los usuarios ajenos a las instituciones de vigilancia. Como opción, los usuarios pueden acceder a los datos del SIVIGILA que normalmente se hacen públicos hasta el segundo semestre del año (es decir, los datos de 2022 se publicaron en el segundo semestre de 2023). Para este ejemplo se utilizarán los datos suministrados en la carpeta de datos del paquete. Estos datos presentan los casos de dengue para todos los municipios del Tolima para los años 2012 a 2021. En este caso, utilizaremos los casos del municipio de Espinal para calcular el respectivo canal endémico.

R

# Se cargan las librerías necesarias.
library(epiCo)
library(incidence)

#Se cargan los datos epidemiológicos del paquete.
data("epi_data")

data_espinal <- epi_data[epi_data$cod_mun_o == 73268, ]

# Se construyen el objeto incidence para el municipio del Espinal.
incidencia_espinal <- incidence(data_espinal$fec_not,
  interval = "1 epiweek"
)

# Se filtra el objeto incidence para obtener el historico de los 7 años 
# anteriores al año de interes. En este caso el 2021.
incidencia_historica <- incidencia_espinal[
  incidencia_espinal$date <= as.Date("2020-12-31") &
    incidencia_espinal$date >= as.Date("2014-01-01"), ]

print(incidencia_historica)

SALIDA

<incidence object>
[4808 cases from days 2014-01-05 to 2020-12-27]
[4808 cases from MMWR weeks 2014-W02 to 2020-W53]

$counts: matrix with 365 rows and 1 columns
$n: 4808 cases in total
$dates: 365 dates marking the left-side of bins
$interval: 1 week
$timespan: 2549 days
$cumulative: FALSE

Cuando los usuarios tienen sus propios datos, el objetivo es el mismo: limpiar y manejar los datos para construir el objeto incidence (llamado incidencia_histórica en el ejemplo anterior). El mensaje clave es que se necesita una lista de fechas que den cuenta del evento de interés para luego utilizarla en el paquete incidence.

3. Uso de la función canal_endémico de epico

Tras recopilar los datos de incidencia, los usuarios están listos para pedir a epiCo que construya un canal endémico.

La función endemic_channel tiene los siguientes parámetros:

  • incidence_historic: Un objeto de incidencia con los recuentos de casos de los años anteriores.

  • observations: Un vector numérico con las observaciones actuales (por defecto = NULL).

  • method: Una cadena con el método de cálculo de la media de preferencia (“median”, “mean” o “geometric”) o método “unusual_behavior” (Prueba de distribución de Poisson) (por defecto = “geométrica”).

  • geom_method: Una cadena con el método seleccionado para el cálculo de la media geométrica (véase la función geom_mean de epiCo) (por defecto = “shifted”).

  • outlier_years: Un vector numérico con los años atípicos (epidémicos) (por defecto = NULL).

  • outliers_handling: Una cadena con la decisión de tratamiento de los años atípicos:

    • ignored = los datos de los años atípicos no se tendrán en cuenta (por defecto).
    • included = se tendrán en cuenta los datos de los años atípicos.
    • replaced_by_median = los datos de los años atípicos se sustituirán por la mediana y se tendrán en cuenta.
    • replaced_by_mean = los datos de los años atípicos se sustituirán por la media y se tendrán en cuenta.
    • replaced_by_geom_mean = los datos de los años atípicos se sustituirán por la media geométrica y se tendrán en cuenta.
  • ci: Un valor numérico para especificar el intervalo de confianza a utilizar con el método geométrico (por defecto = 0.95).

  • plot: Un booleano para mostrar un gráfico (por defecto = FALSE).

La salida de la función endemic_channel de epiCo es una tabla de datos con las observaciones, la media histórica y los intervalos de confianza, y un gráfico que muestra las tres bandas epidemiológicas, las observaciones actuales (si se proporcionan) y los métodos y el manejo de valores atípicos establecidos por el usuario.

Ejemplo

El siguiente ejemplo utiliza la incidencia histórica a la que se ha accedido previamente desde la fuente de datos SIVIGILA para construir el canal endémico 2021 para el municipio del Espinal.

R

# Se toman el conteo de casos del 2020 como las observaciones.
observaciones <- incidencia_espinal[
  incidencia_espinal$date >= as.Date("2021-01-01") &
    incidencia_espinal$date <= as.Date("2021-12-31"), ]$counts

# Se especifican los años hiper endemicos que deben ser ignorados en la 
# constucción del canal endémico.
años_atipicos <- c(2016, 2019)

# Se construye el canal endémico y se plotea el resultado.
espinal_endemic_chanel <- endemic_channel(
  incidence_historic = incidencia_historica,
  observations = observaciones,
  outlier_years = años_atipicos,
  plot = TRUE
)

SALIDA

Data prior to 2015-01-04 were not used for the endemic channel calculation.

Análisis espaciotemporales con epico

El módulo espaciotemporal de epico es una herramienta que permite analizar la correlación espacial de los casos a partir de sus coordenadas de notificación y los tiempos reales de desplazamiento dentro del territorio colombiano.

En el siguiente tutorial aprenderás:

  1. A utilizar la función morans_index de epico.
  2. Cómo interpretar y comunicar los resultados.

1. La función morans_index de epico

epiCo proporciona una función para realizar un análisis del índice de Moran Local a partir de un objeto incidence con observaciones únicas para un conjunto de municipios colombianos.

Internamente, la función lee los grupos del objeto incidence como los códigos DIVIPOLA para:

  1. Estimar las tasas de incidencia utilizando la función incidence_rate de epico.
  2. Evaluarlas en la función neighborhoods de epico.

Es necesario que el usuario proporcione el nivel administrativo al que corresponden los grupos (0: nivel nacional, 1: nivel departamental, 2: nivel municipal), la escala a la que se deben estimar las tasas de incidencia (casos por número de habitantes) y el umbral de tiempo de viaje para la definición del barrio. En el siguiente ejemplo se utilizan los casos de los municipios del Tolima para el año 2021.

R

# Se cargan las librerías necesarias.
library(epiCo)
library(incidence)

data("epi_data")

# Se filtran los datos epidemiológicos para el año de interés.
data_tolima <- epi_data[lubridate::year(epi_data$fec_not) == 2021, ]

# Se crea el objeto incidence para los datos del tolima en el 2021.
incidence_object <- incidence(
  dates = data_tolima$fec_not,
  groups = data_tolima$cod_mun_o,
  interval = "12 months"
)

# Se realiza el analisis espaciotemporal, especificando la escala de nivel 
# municipal
monrans_tolima <- morans_index(incidence_object = incidence_object, threshold = 2)

SALIDA

Significant municipalities are:

SALIDA

73043 with Low-High (incidence - spatial correlation)
73168 with Low-High (incidence - spatial correlation)
73483 with High-Low (incidence - spatial correlation)
73504 with Low-Low (incidence - spatial correlation)

R

monrans_tolima$plot

2. Interpretación de los resultados del mapa

El metodo del indice de moran local divide los datos espaciales en 4 cuadrantes:

  • High-High: donde se ubican los municipios cuya incidencia está sobre la media de la región, y se encuentran rodeados de municipios que también se encuentran sobre la media de la región.
  • Low-High: municipios cuya incidencia está debajo de la media de la región, y se encuentran rodeados de municipios que se encuentran sobre la media de la región. Este cuadrante marca municipios candidatos a ser casos atípicos espaciales.
  • Low-Low: municipios cuya incidencia está debajo de la media de la región, y se encuentran rodeados de municipios que se encuentran debajo de la media media de la región.
  • High-Low, municipios cuya incidencia está sobre la media de la región, y se encuentran rodeados de municipios que se encuentran debajo de la media de la región. Este cuadrante marca municipios candidatos a ser casos atípicos espaciales.

Reto

Reto ***

Puntos Clave

Revise si al final de esta lección adquirió estas competencias:

  1. Navegar por la codificación de la División Política Administrativa de Colombia (DIVIPOLA).
  2. Consultar, visualizar e interpretar pirámides poblacionales a distintas escalas administrativas.
  3. Familiarizarse con la información epidemiológica proporcionada por el SIVIGILA.
  4. Describir las características demográficas de la información epidemiológica.
  5. Estimar tasas de incidencia acumulada a distintas escalas espaciales y temporales.
  6. Realizar una evaluación del riesgo etario integrando información epidemiológica con información sobre la estructura poblacional de una región.

Sobre este documento

Este documento ha sido diseñado por Juan Daniel Umaña Caro y Juan Montenegro-Torres para el Curso Internacional: Análisis de Brotes y Modelamiento en Salud Pública, Bogotá 2023. TRACE-LAC/Javeriana.

Contribuciones