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
combined_data <- na.omit(combined_data)

dim(combined_data)
## [1] 654934     18

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 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

Como podemos ver, tenemos más de 28.000 datos duplicados. Si bien, no significa un gran porcentaje 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

Como podemos apreciar hay un desbalance significativo, ya que una mayor proporción de personas no experimentó un infarto

Si bien el enunciado nos dice que el 47% de la población cumple con al menos uno de los factores de riesgo, la tabla no muestra una inclinación de la población a sufrir infartos. Sin embargo, esta distribución podría reflejar la realidad observada en la población general.

  • 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

No 321930 287078 Yes 18077 27849

Target vs Actividad física

##          No    Yes
##                   
## No   130631 478377
## Yes   16861  29065

Target vs Consumo de tabaco

## 
##     No    Yes 
## 389493 265441

Target vs Consumo de alcohol

##          No    Yes
##                   
## No   411595 197413
## Yes   37524   8402

Gráfico de apresiación

La cantidad de casos con infartos es baja en todas las categorías de las variables.

La poca diferencia sugiere que, estas variables categóricas por sí solas no parecen tener una fuerte asociación con los enfermedades cardiacas. Es decir, no parecen representar factores importantes de riesgo

  • 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
  • Race No hay relación

  • Diabetic

##          No    Yes
##                   
## No   533191  75817
## Yes   30405  15521
  • 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, 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, no se ve una relación directa con las enfermedades cardiacas

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 y el BMI 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 o tienen un alto BMI, sufren un riesgo más alto de padecer infartos.

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.

Al contrario de lo esperado, el consumo de alcohol y tabaco no mostró una relación. Esto no quiere decir que no representen un factor de riesgo, sino que, a partir de nuestra muestra, no se puede corroborar una relación.

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, el sexo, la raza, las horas de sueño, el asma, la diabetes o el cáncer de piel no representan un factor de riego.

Parte 2: MIT-BIH Arrhythmia Database