Parte 1: CDC Heart Disease information

Exploración inicial

  • Antes de empezar la exploración, guardo las bases en variables y descargo las librerías que me van a permitir leer los datos.

  • Buscamos las dimensiones de las 2 bases de datos provistas (2020 y 2022)

dim(data_2020)
## [1] 319795     18
dim(data_2022)
## [1] 445132     40
  • Indicamos los datos que posee cada tabla
colnames(data_2020)
##  [1] "HeartDisease"     "BMI"              "Smoking"          "AlcoholDrinking" 
##  [5] "Stroke"           "PhysicalHealth"   "MentalHealth"     "DiffWalking"     
##  [9] "Sex"              "AgeCategory"      "Race"             "Diabetic"        
## [13] "PhysicalActivity" "GenHealth"        "SleepTime"        "Asthma"          
## [17] "KidneyDisease"    "SkinCancer"
colnames(data_2022)
##  [1] "State"                     "Sex"                      
##  [3] "GeneralHealth"             "PhysicalHealthDays"       
##  [5] "MentalHealthDays"          "LastCheckupTime"          
##  [7] "PhysicalActivities"        "SleepHours"               
##  [9] "RemovedTeeth"              "HadHeartAttack"           
## [11] "HadAngina"                 "HadStroke"                
## [13] "HadAsthma"                 "HadSkinCancer"            
## [15] "HadCOPD"                   "HadDepressiveDisorder"    
## [17] "HadKidneyDisease"          "HadArthritis"             
## [19] "HadDiabetes"               "DeafOrHardOfHearing"      
## [21] "BlindOrVisionDifficulty"   "DifficultyConcentrating"  
## [23] "DifficultyWalking"         "DifficultyDressingBathing"
## [25] "DifficultyErrands"         "SmokerStatus"             
## [27] "ECigaretteUsage"           "ChestScan"                
## [29] "RaceEthnicityCategory"     "AgeCategory"              
## [31] "HeightInMeters"            "WeightInKilograms"        
## [33] "BMI"                       "AlcoholDrinkers"          
## [35] "HIVTesting"                "FluVaxLast12"             
## [37] "PneumoVaxEver"             "TetanusLast10Tdap"        
## [39] "HighRiskLastYear"          "CovidPos"

Podemos ver que la tabla del año 2022 es mucho más grande

  • Para combinar ambas tablas vamos a usar de base la tabla de menor cantidad de columnas

  • Para emparejar los datos, renombramos las columnas en la tabla 2022 para coincidir los nombres de las columnas con los mismos datos de la tabla 2020

  • Para hacer esto usamos la función _ rename_ del paquete “dplyr” previamente instalado

data_2022 <- rename(data_2022,
  HeartDisease = HadHeartAttack,
  Stroke = HadStroke,
  Asthma = HadAsthma,
  SkinCancer = HadSkinCancer,
  KidneyDisease = HadKidneyDisease,
  Diabetic = HadDiabetes,
  PhysicalHealth = PhysicalHealthDays,
  MentalHealth = MentalHealthDays,
  DiffWalking = DifficultyWalking,
  Race = RaceEthnicityCategory,
  PhysicalActivity = PhysicalActivities,
  GenHealth = GeneralHealth,
  SleepTime = SleepHours,
  Smoking = SmokerStatus,
  AlcoholDrinking = AlcoholDrinkers
)
  • Creamos un nuevo dataframe que contenga las filas de ambas tablas.

  • Para esto primero intersectamos las columnas, así separamos las que tienen igual nombre y las ponemos en nuevas variables

  • Compruebo que tenga +700.000 filas y 18 columnas

common_columns <- intersect(names(data_2020), names(data_2022))

combined_data<-bind_rows(select(data_2020, all_of(common_columns)),select(data_2022, all_of(common_columns)) )

dim(combined_data)
## [1] 764927     18

Análisis inicial

  • Para analizar cada columna primero vamos a crear una variable aplicando la función “sapply” para conocer la clase de cada columna

  • Con “colSums” vamos a sumar los datos NA de cada coluna

  • Primero vamos a convertir los espacios vacíos en la tabla a na

  • Calculamos el porcentaje que representa esta suma en cada columna

  • Presentamos estos datos en un df

#tipo de dato de cada columna
data_types <- sapply(combined_data, class)

#convierto los vacios en na
combined_data[combined_data == ""] <- NA

#sumo los vacios de cada columna
missing_values <- colSums(is.na(combined_data))

#lo paso a porcentaje
percentage_missing <- round(colMeans(is.na(combined_data)) * 100,2)

#lo presento como tabla
result <- data.frame(
  Data_type = data_types,
  Missing_val = missing_values,
  Percent_missing = percentage_missing
)

knitr::kable(result,caption="Result")
Result
Data_type Missing_val Percent_missing
HeartDisease character 3065 0.40
BMI numeric 48806 6.38
Smoking character 35462 4.64
AlcoholDrinking character 46574 6.09
Stroke character 1557 0.20
PhysicalHealth numeric 10927 1.43
MentalHealth numeric 9067 1.19
DiffWalking character 24012 3.14
Sex character 0 0.00
AgeCategory character 9079 1.19
Race character 14057 1.84
Diabetic character 1087 0.14
PhysicalActivity character 1093 0.14
GenHealth character 1198 0.16
SleepTime numeric 5453 0.71
Asthma character 1773 0.23
KidneyDisease character 1926 0.25
SkinCancer character 3143 0.41

Limpieza de datos

  • Vamos a ordenar las columnas de modo de presentar primero la información del paciente, después los aspectos generales y al final la historia clínica
combined_data<- select(combined_data, "Sex", "AgeCategory", "Race","BMI", "GenHealth", "PhysicalActivity", "PhysicalHealth",
  "MentalHealth", "SleepTime", "DiffWalking", "Smoking", "AlcoholDrinking", "HeartDisease", "Stroke", "Asthma", "KidneyDisease", "SkinCancer", "Diabetic")
  • Eliminamos los datos donde exista un NA
# Número total de filas antes de eliminar filas con NA
total_rows_before <- nrow(combined_data)

# Eliminamos las filas
combined_data <- na.omit(combined_data)

dim(combined_data)
## [1] 654934     18
  • Con este valor podemos conocer el porcentaje de información perdida
# Número total de filas después de eliminar filas con NA
total_rows_after <- nrow(combined_data)

# Porcentaje de filas eliminadas
percentage_rows_removed <- round(((total_rows_before - total_rows_after) / total_rows_before) * 100,2)

percentage_rows_removed
## [1] 14.38

Este tipo de limpieza es una práctica que conlleva una importante pérdida de datos presentes en las demás columnas y que aportaban información valiosa al conjunto de datos.

La eliminación de filas reduce un 14.38% el tamaño de la muestra, lo que puede afectar la significatividad estadística y la potencia de cualquier análisis realizado con los datos restantes.

Los modelos predictivos y estadísticos van a resultar menos generalizables y con un gran margen de error

Cambiar los NA por el promedio de los datos de esa columna, en lugar de eliminar toda la fila, sería una alternativa muy útil para mantener el tamaño original del data frame y conservar los datos de las demás columnas.

Con esta corrección, el valor y el promedio de la columna igualmente experimentan un cambio, pero no es tan significativo y refleja con mayor precisión los valores reales, lo que es útil para no variar los resultados y valores de los análisis; a diferencia de lo que sucede al eliminar filas de información.

  • Evaluamos la existencia de datos duplicados con la función duplicated
num_duplicated <- sum(duplicated(combined_data))

num_duplicated
## [1] 28215
porcentaje_duplicados <- round((num_duplicated / nrow(combined_data)) * 100,2)

porcentaje_duplicados
## [1] 4.31

Como podemos ver, tenemos más de 28.000 datos duplicados. Si bien, solo representa el 4,41% de la tabla de datos, es un valor muy influyente en los cálculos generales y modelos estadísticos

La eliminación de los datos duplicados sería un error muy grande ya que, un duplicado representa un paciente con las mismas caraterísticas. Por esto, significaría una importante pérdida de datos que podría distorsionar totalmente las estadísticas descriptivas, como promedios, desviaciones estándar y correlaciones, se alteraría la distribución y la representación de los datos.

Limpieza de variables categóricas

“gsub” es una función en R que se usa para reemplazar todas las coincidencias de un patrón dentro de una cadena.

Para la limpieza usaremos alternativas que ofrece esta función, para cambiar patrones, eliminar caracteres de más o reemplazar el nombre de las variables

  • Simplificamos las variables de la columna “Race”

  • Compruebo cada tipo de variable presente en la columna

combined_data$Race <- gsub("White only, Non-Hispanic", "White", combined_data$Race)

combined_data$Race <- gsub("Black only, Non-Hispanic", "Black", combined_data$Race)

combined_data$Race <- gsub("Other race only, Non-Hispanic", "Other", combined_data$Race)

#verifico
unique(combined_data$Race)
## [1] "White"                          "Black"                         
## [3] "Asian"                          "American Indian/Alaskan Native"
## [5] "Other"                          "Hispanic"                      
## [7] "Multiracial, Non-Hispanic"
  • Normalizar los datos de Categoría de Edad para que se resuman como “Edad inicial– Edad final”. Es decir, todos se deberían mostrar como “35-39” y no como “Age 35-39”.

  • Para esto vamos a cambiar las variables con la estructura incorrecta

#reemplazo
combined_data$AgeCategory<- gsub("Age (\\d+) to (\\d+)", "\\1-\\2", combined_data$AgeCategory)

combined_data$AgeCategory<- gsub("Age 80 or older", "80 or older", combined_data$AgeCategory)

#verifico
unique(combined_data$AgeCategory)
##  [1] "55-59"       "80 or older" "65-69"       "75-79"       "40-44"      
##  [6] "70-74"       "60-64"       "50-54"       "45-49"       "18-24"      
## [11] "35-39"       "30-34"       "25-29"
  • Normalizar los datos de diabetes para que únicamente sean “Yes” o “No”
combined_data$Diabetic<- gsub("^Yes.*", "Yes", combined_data$Diabetic)
combined_data$Diabetic<- gsub("^No.*", "No", combined_data$Diabetic)

#verifico
unique(combined_data$Diabetic)
## [1] "Yes" "No"

EDA (Exploratory Data Analysis)

Para realizar el EDA utilizaremos ggplot. Para ello, instalamos las librerias necesarias

  • Cantidad de casos de enfermedad cardíaca (infarto)
Cases_HeartDisease <- table(combined_data$HeartDisease)
Cases_HeartDisease
## 
##     No    Yes 
## 609008  45926
#Porcentaje de personas con enfermedad cardiaca
percentage_heart_disease <- round((sum(combined_data$HeartDisease == "Yes")/nrow(combined_data))*100,2)

percentage_heart_disease 
## [1] 7.01

Como podemos apreciar hay un desbalance significativo, ya que solo el 7% de las personas entrevistadas experimentó o experimenta enfermedades cardiacas

Aunque el 47% de la población cumple con al menos uno de los factores de riesgo según el enunciado, la tabla no muestra una inclinación clara hacia la incidencia de infartos. Es importante destacar que ser un factor de riesgo no necesariamente indica que la persona vaya a sufrir la enfermedad, sino que tiene una predisposición mayor que la media.

  • Analizamos la relación de target con el sexo, actividad física, consumo de tabaco y alcohol

  • Vamos a visualizar esto individualmente y posteriormente en un gráfico donde podamos apreciar la superposición de las relaciones. Para esto, previamente cargamos las librerías necesarias

Target vs sexo

Female Male 5.32 8.84

Se observa que los hombres tienen una mayor predisposición a sufrir infartos

Target vs Actividad física

## [1] "personas que no hacen ejercicio"
## [1] 22.52
## [1] "personas que no hacen ejercicio y tuvieron infartos"
## [1] 2.57

En este caso podemos ver que la actividad física no tiene una relación significativa, ya que solo el 2,6% de las personas que no hace ejercicio tiene enfermeddades cardiacas

Target vs Consumo de tabaco

## [1] "Porcentaje de personas que son fumadores:"
## [1] 20.14
## [1] "Porcentaje de fumadores que tuvieron infartos:"
## [1] 12.16

El porcentaje es mucho mayor, significando que el consumo de tabaco si se relaciona significativamente con las enfermedades cardiacas

**_Target vs Consumo de alcohol**

## [1] "Porcentaje de personas que consumen alcohol:"
## [1] 31.43
## [1] "Porcentaje de personas que consumen alcohol y tuvieron infartos:"
## [1] 4.08

Obtuvimos que el 4% de las personas que consumen alcohol tuvieron infartos, por lo que la relación es baja

Gráfico de apresiación

## `summarise()` has grouped output by 'variable'. You can override using the
## `.groups` argument.

A partir de este gráfico podemos visualizar mejor la relación entre los fcatores de riesgo y el porcentaje de estos que sufrieron infartos.

Como podemos ver, el consumo de alcohol es el factor que menor relación tiene con los infartos. Si bien no es un porcentaje bajo, ya que solo el 30% de las personas consumia alcohol, no hay más personas enfermas entre las que consumen

En el caso de los fumadores vemos lo contrario. Las personas que consumen tabaco tienen mucha más tendencia a sufrir infartos, el 10% de ellos experimentó o experimenta una enfermedad cardiaca, esto indica que es nuestro mayor factor de riesgo

Se puede ver que la actividad física es un gran factor de riego, en este caso, el porcentaje de las personas que no realizan actividad física y tuvieron infartos es casi el doble de las que si hacen ejercicio

  • Analizamos Target vs Edad.
  • Para este análisis resulta más eficaz la utilización de un gráfico debido a la variedad categorías

Podemos ver que el fator de la edad influye significativamente en la cantidad de casos, los cuales aumentan conforme también aumenta la edad En este caso, podemos aseguarar que la edad es un factor de riesgo

-Revición general de los campos de forma gráfica.

  • BMI

Lo normal es entre 18,5 y 24,9

Podemos observar que el pico de la curva está corrido ligeramente por encima de los valores “normales”

  • Stroke
##          No    Yes
##                   
## No   590991  18017
## Yes   37991   7935
  • PhysicalHealth

Podemos ver que hay una gran relación de la constancia de actividad física con los enfermedades cardiacas

Entre los que si sufieron infartos, la gran mayoria no realizaba actividad física. Por lo tanto, podemos concluir que es un factor de riesgo

  • MentalHealth

No se ve una relación

  • DiffWalking
##          No    Yes
##                   
## No   531929  77079
## Yes   28585  17341

No se ve una relación

  • Race

No hay relación

  • Diabetic
##          No    Yes
##                   
## No   533191  75817
## Yes   30405  15521

No hay relación

  • GenHealth
## `summarise()` has grouped output by 'GenHealth'. You can override using the
## `.groups` argument.

Podemos ver que la población que no tiene una buena salúd general es mucho más propensa a sufrir infartos

  • SleepTime
## `summarise()` has grouped output by 'SleepTime'. You can override using the
## `.groups` argument.

Aunque los casos se vean más altos en las 7-8 horas de sueño, tiene que ver con la cantidad mayor de población que duerme esa cantidad de horas. Por lo tanto, las horas de sueño no reflejan una relación con los casos de infartos

  • Asthma
##          No    Yes
##                   
## No   524150  84858
## Yes   37604   8322
  • KidneyDisease
##          No    Yes
##                   
## No   588234  20774
## Yes   39843   6083
  • SkinCancer
##          No    Yes
##                   
## No   558883  50125
## Yes   38332   7594

Análisis estadistico

  • Vamos a analizar los valores de las columnas numéricas de la tabla de datos
numeric_summary <- combined_data %>%
  summarise(across(where(is.numeric), list(mean = ~mean(.), sd = ~sd(.), median = ~median(.), min = ~min(.), max = ~max(.)), .names = "{col}_{fn}"))

print(numeric_summary)
##   BMI_mean   BMI_sd BMI_median BMI_min BMI_max PhysicalHealth_mean
## 1 28.45071 6.442805       27.4   12.02   97.65             3.79821
##   PhysicalHealth_sd PhysicalHealth_median PhysicalHealth_min PhysicalHealth_max
## 1          8.245473                     0                  0                 30
##   MentalHealth_mean MentalHealth_sd MentalHealth_median MentalHealth_min
## 1          4.126199        8.115427                   0                0
##   MentalHealth_max SleepTime_mean SleepTime_sd SleepTime_median SleepTime_min
## 1               30       7.059847     1.443783                7             1
##   SleepTime_max
## 1            24

Media (mean): Representa el valor promedio de la variable. Es útil para entender el valor central de los datos.

Desviación Estándar (sd): Mide la dispersión de los datos alrededor de la media. Una desviación estándar más alta indica mayor variabilidad.

Mediana (median): Es el valor que se encuentra en el centro de un conjunto de datos ordenados. Es útil cuando los datos no siguen una distribución normal.

Mínimo (min) y Máximo (max): Representan los valores más pequeño y más grande en el conjunto de datos, respectivamente.

La media de MBI está dentro de los valores normales (entre 18,5 y 24,9), por lo que podemos asumir que la mayor parte de la población está dentro del rango considerado sano. Sin embargo, como ya habiamos aclarado, la curva indica que aquellas personas por encima de este valor son más propensas a sufriri infartos

La salud física es el dato donde existe una mayor desviación, lo cual indica una variabilidad muy alta en la población total. Por esta razón vemos que la media se achica

La salúd mental no muestra una relación con los infartos, pero tenemos en cuenta que las conclusiones que podriamos sacar de estos datos son muy ambiguas puesto que no sabemos qué consideraciones se tomó para la medición y que, la salud mental abarca una variedad de factores muy amplia que podrian o no tener que ver con los paros cardiacos individualmente

Las horas de sueño son el factor en que mejores datos se obtuvieron. La media se encuentra dentro de los valores recomendado y hay una disviación muy baja. Aún así, no se ven relaciones con los paros cardiacos

  • Los datos “outliers” son valores que se desvían significativamente de la tendencia general del conjunto de datos. En la medición de factores de riesgo poblacional para enfermedades cardiacas, los outliers pueden afectar la precisión y la interpretación de los resultados.

  • Detectarlos es importante para garantizar que los análisis sean representativos. Para hacerlo vamos a utilizar el rango intercuartil (IQR). Los valores que se encuentran a una distancia mayor de 1.5 * IQR del primer y tercer cuartil son considerados outliers.

  • Analizamos estos datos gráficamente

En BMI vemos varios outliers mayores a 60. No se observa una clara separación entre los casos con y sin enfermedad cardíaca en función del BMI.

Los valores de Salud Mental se agrupan principalmente entre 0 y 30 días (considerando un mes). No hay outliers significativos en esta variable.

No hay outliers significativos en la variable de salúd física. La distribución de los casos con y sin enfermedad cardíaca es bastante uniforme, con algunos puntos más dispersos hacia la derecha

Hay algunos outliers con valores de sueño muy altos (más de 15 horas). No se observa una clara separación entre los casos con y sin enfermedad cardíaca en función del tiempo de sueño.

  • Pudimos apreciar que no hay una diferencia entre la distribución de las personas con y sin enfermedad cardiaca en las variables analizadas

  • Elimiar los outliers impata en el análisis de los datos, ya que influyen en las estadísticas descriptivas, los modelos predictivos y la interpretación general

  • Aunque la eliminación de estos datos puede mejorar la presición de algunos modelos, en los casos como este donde analizamos muestras y factores reales implica un perdida de información valiosa Igualmente se debe analizar que no se trate de pocos casos aislados que muevan significativamente el valor de los cálculos

  • Una mejor forma de trabajar esos casos es reemplazar estos valores por la media del conjunto de datos o, en lo posible, reemplazar por los valores más cercanos en un rango.

Conclusiones

Después del análisis de nuestros datos, concluimos que si bien todos los datos son considerados factores de riesgo por CDC, no todos ellos muestran una relación significativa con las enfermedades cardiacas.

La edad, la actividad física, la mala salud general, el BMI y el consumo de tabaco son los factores con la influencia más fuerte. Las personas mayores, aquellos que no realizan actividad física frecuente, no tienen una buena salud general, presentan un alto BMI o fuman sufren un riesgo más alto de padecer infartos.

En el análisis también pudimos apreciar la tendencia de los hombres a sufrir más infartos en comparación con las mujeres.

Muchos datos son ambiguos, esto quiere decir que, si bien son muy útiles, no permiten sacar conclusiones muy acertadas por la generalización que implican o el desconocimiento de lo que se tuvo en cuenta para la evaluación. Esto se puede apreciar en las variables de salud general y salud mental.

Se cuenta con muchos datos que realmente no tienen ninguna influencia ni podrían implicar factores de riesgo. Sin embargo, se llevó a cabo un conteo de la presencia de enfermedad cardiaca, discriminando por estos factores. Así, podemos decir con seguridad que, la raza, las horas de sueño, el asma, la diabetes o el cáncer de piel no representan un factor de riego.