Empezamos instalando las librerías necesarias.
# Cargamos las librerías
library(dplyr)
library(ggplot2)
library(readxl)
library(tidyverse)
library(TTR)
Cargamos los datos de la base.
# Cargamos la información de la base
datos_covid <- read_excel("2. covid_example_data.xlsx")
datos_covid
Empezamos nuestros análisis con una descripción de la información de la tabla.
# Número de filas
nrow(datos_covid)
## [1] 82101
# Número de columnas
ncol(datos_covid)
## [1] 31
Con estás lineas de código podemos ver que tenemos una tabla con 82101 filas y 31 columnas.
# Nombres de columnas
colnames(datos_covid)
## [1] "PID" "reprt_creationdt_FALSE" "case_dob_FALSE"
## [4] "case_age" "case_gender" "case_race"
## [7] "case_eth" "case_zip" "Contact_id"
## [10] "sym_startdt_FALSE" "sym_fever" "sym_subjfever"
## [13] "sym_myalgia" "sym_losstastesmell" "sym_sorethroat"
## [16] "sym_cough" "sym_headache" "sym_resolved"
## [19] "sym_resolveddt_FALSE" "contact_household" "hospitalized"
## [22] "hosp_admidt_FALSE" "hosp_dischdt_FALSE" "died"
## [25] "died_covid" "died_dt_FALSE" "confirmed_case"
## [28] "covid_dx" "pos_sampledt_FALSE" "latitude_JITT"
## [31] "longitude_JITT"
El conjunto de datos de la tabla tiene una serie de columnas con el sufijo sym_, siendo estas variables el reporte del tipo de sintoma encontrado en el paciente.
#posibles faltantes
datos_covid %>%
summarise_all(funs(sum(is.na(.)))) %>%
t()
## [,1]
## PID 0
## reprt_creationdt_FALSE 0
## case_dob_FALSE 48
## case_age 48
## case_gender 63
## case_race 2630
## case_eth 2574
## case_zip 13
## Contact_id 32205
## sym_startdt_FALSE 37480
## sym_fever 31577
## sym_subjfever 37908
## sym_myalgia 32137
## sym_losstastesmell 50724
## sym_sorethroat 32241
## sym_cough 31630
## sym_headache 32018
## sym_resolved 42294
## sym_resolveddt_FALSE 65799
## contact_household 36737
## hospitalized 32482
## hosp_admidt_FALSE 77115
## hosp_dischdt_FALSE 78600
## died 36832
## died_covid 42302
## died_dt_FALSE 80394
## confirmed_case 9
## covid_dx 0
## pos_sampledt_FALSE 122
## latitude_JITT 94
## longitude_JITT 200
Evidenciamos los datos faltantes por columnas, siendo las columnas de died_dt_FALSE, hosp_admidt_FALSE y hosp_dischdt_FALSE las que más valores faltantes tienen.
Hacemos un analisis descriptivo de las variables de la tabla.
summary(datos_covid)
## PID reprt_creationdt_FALSE case_dob_FALSE
## Length:82101 Min. :2019-12-27 00:00:00 Min. :1914-12-28 00:00:00
## Class :character 1st Qu.:2020-08-05 00:00:00 1st Qu.:1968-05-21 00:00:00
## Mode :character Median :2020-12-04 00:00:00 Median :1984-05-13 00:00:00
## Mean :2020-11-08 10:56:26 Mean :1981-05-16 14:49:00
## 3rd Qu.:2021-01-22 00:00:00 3rd Qu.:1995-07-25 00:00:00
## Max. :2021-07-27 00:00:00 Max. :2042-06-07 00:00:00
## NA's :48
## case_age case_gender case_race case_eth
## Min. :-20.00 Length:82101 Length:82101 Length:82101
## 1st Qu.: 25.00 Class :character Class :character Class :character
## Median : 37.00 Mode :character Mode :character Mode :character
## Mean : 39.69
## 3rd Qu.: 53.00
## Max. :106.00
## NA's :48
## case_zip Contact_id sym_startdt_FALSE
## Min. :30000 Length:82101 Min. :1941-03-08 00:00:00
## 1st Qu.:30213 Class :character 1st Qu.:2020-07-25 00:00:00
## Median :30312 Mode :character Median :2020-11-22 00:00:00
## Mean :30250 Mean :2020-10-23 00:14:19
## 3rd Qu.:30331 3rd Qu.:2021-01-14 00:00:00
## Max. :31707 Max. :2030-12-03 00:00:00
## NA's :13 NA's :37480
## sym_fever sym_subjfever sym_myalgia sym_losstastesmell
## Length:82101 Length:82101 Length:82101 Length:82101
## Class :character Class :character Class :character Class :character
## Mode :character Mode :character Mode :character Mode :character
##
##
##
##
## sym_sorethroat sym_cough sym_headache sym_resolved
## Length:82101 Length:82101 Length:82101 Length:82101
## Class :character Class :character Class :character Class :character
## Mode :character Mode :character Mode :character Mode :character
##
##
##
##
## sym_resolveddt_FALSE contact_household hospitalized
## Min. :2001-11-08 00:00:00 Length:82101 Length:82101
## 1st Qu.:2020-07-29 00:00:00 Class :character Class :character
## Median :2020-11-27 00:00:00 Mode :character Mode :character
## Mean :2020-11-03 16:14:50
## 3rd Qu.:2021-01-20 00:00:00
## Max. :2102-02-03 00:00:00
## NA's :65799
## hosp_admidt_FALSE hosp_dischdt_FALSE died
## Min. :2019-12-26 00:00:00 Min. :2019-12-04 00:00:00 Length:82101
## 1st Qu.:2020-06-30 00:00:00 1st Qu.:2020-07-06 00:00:00 Class :character
## Median :2020-10-14 00:00:00 Median :2020-10-24 00:00:00 Mode :character
## Mean :2020-10-06 21:48:35 Mean :2020-10-14 14:17:35
## 3rd Qu.:2021-01-12 18:00:00 3rd Qu.:2021-01-22 00:00:00
## Max. :2032-05-22 00:00:00 Max. :2029-12-29 00:00:00
## NA's :77115 NA's :78600
## died_covid died_dt_FALSE confirmed_case
## Length:82101 Min. :2020-02-21 00:00:00 Length:82101
## Class :character 1st Qu.:2020-07-23 00:00:00 Class :character
## Mode :character Median :2020-12-11 00:00:00 Mode :character
## Mean :2020-11-03 05:00:18
## 3rd Qu.:2021-02-06 00:00:00
## Max. :2021-07-01 00:00:00
## NA's :80394
## covid_dx pos_sampledt_FALSE latitude_JITT
## Length:82101 Min. :2020-02-01 00:00:00 Min. :-0.003998
## Class :character 1st Qu.:2020-08-03 00:00:00 1st Qu.:33.690308
## Mode :character Median :2020-12-03 00:00:00 Median :33.783085
## Mean :2020-11-05 18:46:09 Mean :32.578711
## 3rd Qu.:2021-01-17 00:00:00 3rd Qu.:33.977716
## Max. :2021-07-23 00:00:00 Max. :34.188697
## NA's :122 NA's :94
## longitude_JITT
## Min. :-84.800289
## 1st Qu.:-84.460319
## Median :-84.384400
## Mean :-81.394127
## 3rd Qu.:-84.347641
## Max. : 0.003999
## NA's :200
#Distribución por case_gender (n)
ggplot(datos_covid, aes(x = case_gender)) +
geom_bar(fill = "steelblue") +
geom_text(
stat = "count",
aes(label = after_stat(count)),
vjust = -0.5
) +
labs(
title = "Distribución por Generos (n)",
x = "Genero",
y = "Frecuencia"
) +
theme_minimal()
Analizando el gráfico podemos observar que hay dos grupos predominante de la categoría genero, estos se corresponden con los de hombre y mujer. También se puede evidenciar que hay más registros de mujeres que de hombres.
#Distribución por case_gender (%)
datos_covid %>%
count(case_gender) %>%
mutate(pct = n / sum(n) * 100) %>%
ggplot(aes(x = case_gender, y = pct)) +
geom_col(fill = "seagreen") +
geom_text(aes(label = sprintf("%.1f%%", pct)), vjust = -0.5) +
labs(
title = "Distribución porcentual por Generos (%)",
x = "Genero",
y = "Porcentaje (%)"
) +
theme_minimal()
Analizando el gráfico por sexo, pero como distribución porcentual vemos que la diferencia del más representado (mujer) sobre el segundo (hombre) es de aproximadamente el 7 %. También evidenciamos que fuera de las dos categorias predominantes la suma de los demas generos no llega ni al 1%.
#Distribución por case_race (n)
ggplot(datos_covid, aes(x = case_race)) +
geom_bar(fill = "steelblue") +
geom_text(
stat = "count",
aes(label = after_stat(count)),
vjust = -0.5
) +
labs(
title = "Distribución por Raza (n)",
x = "Raza",
y = "Frecuencia"
) +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 0.5))
De forma similar a como sucede con el caso del genero, para la raza se evidencian dos categorias predominates, estás son las razas de negros y blancosm siendo mayor para la raza negra.
#Distribución por case_race (%)
datos_covid %>%
count(case_race) %>%
mutate(pct = n / sum(n) * 100) %>%
ggplot(aes(x = case_race, y = pct)) +
geom_col(fill = "seagreen") +
geom_text(aes(label = sprintf("%.1f%%", pct)), vjust = -0.5) +
labs(
title = "Distribución Porcentual por Raza (%)",
x = "Raza",
y = "Porcentaje (%)"
) +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 0.5))
Analizando el gráfico por raza, pero como distribución porcentual vemos que la diferencia del más representado (raza negra) con el segundo más representado (raza blanca) es del 4%. Adicionalmente se observa que las razas fueras de las dominantes suman aproximadamente un 20%, siendo este número no despreciable.
#Distribución por case_eth (n)
ggplot(datos_covid, aes(x = case_eth)) +
geom_bar(fill = "steelblue") +
geom_text(
stat = "count",
aes(label = after_stat(count)),
vjust = -0.5
) +
labs(
title = "Distribución por Etnia (n)",
x = "ETH",
y = "Frecuencia"
) +
theme_minimal()
Analizando el gráfico podemos observar que hay una categoría predominante (etnia no hispana/latina), siendo este valor al menos 7 veces más grande que las siguientes categorias.
#Distribución por case_eth (%)
datos_covid %>%
count(case_eth) %>%
mutate(pct = n / sum(n) * 100) %>%
ggplot(aes(x = case_eth, y = pct)) +
geom_col(fill = "seagreen") +
geom_text(aes(label = sprintf("%.1f%%", pct)), vjust = -0.5) +
labs(
title = "Distribución Porcentual por Etnia (%)",
x = "Etnia",
y = "Porcentaje (%)"
) +
theme_minimal()
Analizando el gráfico por etnia, pero como distribución porcentual vemos que la diferencia del más representado (etnia no hispana/latina) con el segundo más representado (hispano/latino) es de aproximadamente el 65%.
# Creamos la columna age_group
datos_covid <- datos_covid %>%
mutate(
age_group = case_when(
case_age >= 0 & case_age <= 17 ~ "0-17",
case_age >= 18 & case_age <= 29 ~ "18-29",
case_age >= 30 & case_age <= 44 ~ "30-44",
case_age >= 45 & case_age <= 64 ~ "45-64",
case_age >= 65 ~ "65+",
TRUE ~ "Desconocido/Inválido"
)
)
datos_covid
# Tabla cruzada de Grupo Etario por Edad
tabla_cruzada <- table(
datos_covid$age_group,
datos_covid$case_gender
)
tabla_cruzada
##
## Female Male Unknown
## 0-17 4014 3948 38
## 18-29 11227 9333 84
## 30-44 11935 10639 96
## 45-64 10969 10432 79
## 65+ 5132 4026 38
## Desconocido/Inválido 22 15 11
Al realizar el análisis de la tabla cruzada podemos observar que los valores más altos por genero es para las mujeres en todos los grupos etarios.
# Porcentaje de personas que presentaron fiebre
datos_covid %>%
filter(!is.na(sym_fever)) %>%
summarise(
porcentaje_fiebre = mean(sym_fever == "Yes") * 100
)
# Porcentaje de personas que presentaron fiebre subjetiva
datos_covid %>%
filter(!is.na(sym_subjfever)) %>%
summarise(
porcentaje_fiebre_subjetiva = mean(sym_subjfever == "Yes") * 100
)
# Porcentaje de personas que presentaron dolor muscular
datos_covid %>%
filter(!is.na(sym_myalgia)) %>%
summarise(
porcentaje_dolor_muscular = mean(sym_myalgia == "Yes") * 100
)
# Porcentaje de personas que presentaron perdida del gusto y olfato
datos_covid %>%
filter(!is.na(sym_losstastesmell)) %>%
summarise(
porcentaje_perdida_gus_olf = mean(sym_losstastesmell == "Yes") * 100
)
# Porcentaje de personas que presentaron dolor de garganta
datos_covid %>%
filter(!is.na(sym_sorethroat)) %>%
summarise(
porcentaje_dolor_garganta = mean(sym_sorethroat == "Yes") * 100
)
# Porcentaje de personas que presentaron tos
datos_covid %>%
filter(!is.na(sym_cough)) %>%
summarise(
porcentaje_tos = mean(sym_cough == "Yes") * 100
)
# Porcentaje de personas que presentaron dolor de cabeza
datos_covid %>%
filter(!is.na(sym_headache)) %>%
summarise(
porcentaje_dolor_cabeza = mean(sym_headache == "Yes") * 100
)
De los sintomas encontrados en los pacientes, en orden de los más comunes a los menos comunes tenemos: tos, dolor de cabeza, perdida del gusto/olfato, dolor muscular, fiebre, fiebre subjetiva y dolor de garganta.
# Tabla de Hospitalización
tabla_hospitalizacion <- datos_covid %>%
filter(!is.na(hospitalized)) %>%
group_by(age_group) %>%
summarise(total_si = sum(hospitalized == "Yes"), .groups = "drop") %>%
mutate(tasa_hospitalizacion = 100 * total_si / sum(total_si))
tabla_hospitalizacion <- tabla_hospitalizacion %>% select(age_group, tasa_hospitalizacion)
tabla_hospitalizacion
Para la tasa de hospitalización se observa que el grupo de 65+ es el más afectado con un 41,42 % seguido con grupo etario de 45-64 con un 33,40%. Así podemos evidenciar que en estos grupos etarios el valor es de 74,81 %.
# Tabla de Letalidad
vals_muerto_validos <- c("Yes", "No")
denominador_global <- datos_covid %>%
filter(confirmed_case == "Yes", died %in% vals_muerto_validos) %>%
nrow()
tabla_letalidad <- datos_covid %>%
filter(confirmed_case == "Yes", died %in% vals_muerto_validos) %>%
group_by(age_group) %>%
summarise(
total_muerto_si = sum(died == "Yes"),
total_en_grupo_con_dato = n(),
.groups = "drop"
) %>%
mutate(
denominador_global = denominador_global,
porcentaje_letalidad = ifelse(denominador_global > 0,
100 * total_muerto_si / denominador_global,
NA_real_)
)
tabla_letalidad <- tabla_letalidad %>% select(age_group, porcentaje_letalidad)
tabla_letalidad
Analizando la tasa de letalidad por grupo etario podemos evidenciar que el valor más alto es para el grupo etario de 65+ y el ese valor es casi 5 veces más grande que el valor del segundo grupo etario más afectado (45-64) y nuevamente es casi 6 veces mayor al observado con el siguiente grupo etario (30-44).
df_diario <- datos_covid %>%
group_by(reprt_creationdt_FALSE) %>%
summarise(conteo = n()) %>%
arrange(reprt_creationdt_FALSE)
df_diario <- df_diario %>%
mutate(media_movil_7d = zoo::rollmean(conteo, k = 7, fill = NA, align = "right"))
ggplot(df_diario, aes(x = reprt_creationdt_FALSE)) +
geom_col(aes(y = conteo), fill = "steelblue", alpha = 0.7) +
geom_line(aes(y = media_movil_7d), color = "red", size = 0.9) +
scale_x_date(
date_breaks = "1 month",
date_labels = "%b %d",
expand = c(0, 0)
) +
labs(
title = "Evolución de registros diarios con media móvil de 7 días",
x = "Fecha del reporte",
y = "Número de casos"
) +
theme_minimal(base_size = 14) +
theme(
plot.title = element_text(face = "bold", hjust = 0.5),
axis.text.x = element_text(angle = 45, hjust = 1)
)
En el analisis del gráfico podemos ver que la media movil se comporta de una forma muy particular en donde tiende a subestimar cuando empiezan a crecer los casos, así como también tiende a sobreestimar cuando decrecen los casos. Este fenomeno se encuentra en las dos picos de brote.
grafico_apilado_conteo <- ggplot(datos_covid, aes(x = age_group, fill = case_gender)) +
geom_bar(color = "black") +
labs(title = "Distribución de Casos de COVID por Rango de Edad y Género",
x = "Rango de Edad",
y = "Frecuencia Absoluta (n)",
fill = "Género") +
scale_x_discrete(limits = c("0-17", "18-29", "30-44", "45-64", "65+", "Desconocido/Inválido")) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
print(grafico_apilado_conteo)
Podemos ver que la distribución de casos por sexo y grupo etario se comporta de forma muy similar tanto para hombres como para mujeres siendo ligeramente mayor en el caso de las mujeres, adicionalmente los valores de desconocido no apreciable en la gráfica.
agrupamiento = merge(tabla_hospitalizacion, tabla_letalidad, by = "age_group")
agrupamiento <- agrupamiento %>% rename(tasa_letalidad = porcentaje_letalidad)
df_long <- agrupamiento %>%
pivot_longer(cols = c(tasa_hospitalizacion, tasa_letalidad),
names_to = "variable",
values_to = "valor")
ggplot(df_long, aes(x = age_group, y = valor, fill = variable)) +
geom_bar(stat = "identity", position = position_dodge(width = 0.8)) +
geom_text(aes(label = sprintf("%.2f", valor)),
position = position_dodge(width = 0.8),
vjust = -0.5, size = 3) +
labs(
title = "Relación de hospitalización y letalidad por grupo etario",
x = "Grupo etario",
y = "Tasa",
fill = "Variable"
) +
theme(axis.text.x = element_text(angle = 45, vjust = 0.5, hjust = 0.5))
Podemos observar que el pico de hospitalización está en el grupo etario de 65+ en donde a medida que disminuye la edad, la tasa de hospitalización decrece, adicionalmente observamos de igual forma que la tasa de letalidad tiene su pico en el grupo de 65+ y este disminuye con la edad.
df_long <- datos_covid %>%
pivot_longer(
cols = c(sym_cough, sym_headache, sym_losstastesmell, sym_myalgia, sym_fever),
names_to = "sintoma",
values_to = "valor"
)
tabla_share <- df_long %>%
filter(!is.na(valor)) %>%
group_by(sintoma, age_group) %>%
summarise(
total_yes = sum(valor == "Yes"),
.groups = "drop_last"
) %>%
group_by(sintoma) %>%
mutate(
total_yes_sintoma = sum(total_yes),
share_pct = ifelse(total_yes_sintoma > 0,
100 * total_yes / total_yes_sintoma,
0)
) %>%
ungroup()
ggplot(tabla_share, aes(x = age_group, y = share_pct, fill = sintoma)) +
geom_col(position = position_dodge(width = 0.9), width = 0.8) +
geom_text(aes(label = sprintf("%.1f%%", share_pct)),
position = position_dodge(width = 0.9),
angle = 90,
vjust = 0.5,
hjust = 0.5,
size = 3) +
labs(
title = "Frecuencia de prevalencia de los síntomas por grupos etarios",
x = "Grupo etario",
y = "Frecuencia de prevalencia de los síntomas (%)",
fill = "Síntoma"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", hjust = 0.5),
axis.text.x = element_text(angle = 45, hjust = 1)
)
Para el grupo etario de 0-17 años la fiebre es el síntoma más común, para los grupos etarios de 18-29 y 30-44 la perdida del olfato y gusto es el síntoma más común, para el grupo etario de 45-64 el dolor muscular es el síntoma más común y para el grupo etario 65+ la tos es el síntoma más común.