Entregable 1

Omar Medina y Andrés España.

Actividad 1 Visualización de Datos.

Usaremos un conjunto de datos que incluye varias variables médicas predictoras (independientes) y una variable objetivo (dependiente), que es el resultado de interés. Entre las variables independientes se encuentran el número de embarazos de la paciente, el índice de masa corporal (BMI, calculado como peso en kg(altura en m)2peso en kg(altura enm)2), el nivel de insulina, la edad, entre otras. En este ejercicio, sustituiremos todos los valores ausentes o nulos por NAN y realizaremos el análisis correspondiente de los datos faltantes.

Realizaremos un EDA

# Cargar librerías necesarias
library(dplyr)
Warning: package 'dplyr' was built under R version 4.4.3

Adjuntando el paquete: 'dplyr'
The following objects are masked from 'package:stats':

    filter, lag
The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union
library(ggplot2)
library(mice)

Adjuntando el paquete: 'mice'
The following object is masked from 'package:stats':

    filter
The following objects are masked from 'package:base':

    cbind, rbind
df <- read.csv("C:/Users/andre/Downloads/diabetes.csv")
# Revisar las primeras filas
head(df)
  Pregnancies Glucose BloodPressure SkinThickness Insulin  BMI
1           6     148            72            35       0 33.6
2           1      85            66            29       0 26.6
3           8     183            64             0       0 23.3
4           1      89            66            23      94 28.1
5           0     137            40            35     168 43.1
6           5     116            74             0       0 25.6
  DiabetesPedigreeFunction Age Outcome
1                    0.627  50       1
2                    0.351  31       0
3                    0.672  32       1
4                    0.167  21       0
5                    2.288  33       1
6                    0.201  30       0
summary(df)
  Pregnancies        Glucose      BloodPressure    SkinThickness  
 Min.   : 0.000   Min.   :  0.0   Min.   :  0.00   Min.   : 0.00  
 1st Qu.: 1.000   1st Qu.: 99.0   1st Qu.: 62.00   1st Qu.: 0.00  
 Median : 3.000   Median :117.0   Median : 72.00   Median :23.00  
 Mean   : 3.845   Mean   :120.9   Mean   : 69.11   Mean   :20.54  
 3rd Qu.: 6.000   3rd Qu.:140.2   3rd Qu.: 80.00   3rd Qu.:32.00  
 Max.   :17.000   Max.   :199.0   Max.   :122.00   Max.   :99.00  
    Insulin           BMI        DiabetesPedigreeFunction      Age       
 Min.   :  0.0   Min.   : 0.00   Min.   :0.0780           Min.   :21.00  
 1st Qu.:  0.0   1st Qu.:27.30   1st Qu.:0.2437           1st Qu.:24.00  
 Median : 30.5   Median :32.00   Median :0.3725           Median :29.00  
 Mean   : 79.8   Mean   :31.99   Mean   :0.4719           Mean   :33.24  
 3rd Qu.:127.2   3rd Qu.:36.60   3rd Qu.:0.6262           3rd Qu.:41.00  
 Max.   :846.0   Max.   :67.10   Max.   :2.4200           Max.   :81.00  
    Outcome     
 Min.   :0.000  
 1st Qu.:0.000  
 Median :0.000  
 Mean   :0.349  
 3rd Qu.:1.000  
 Max.   :1.000  
# Revisamos la estructura de los datos
str(df)
'data.frame':   768 obs. of  9 variables:
 $ Pregnancies             : int  6 1 8 1 0 5 3 10 2 8 ...
 $ Glucose                 : int  148 85 183 89 137 116 78 115 197 125 ...
 $ BloodPressure           : int  72 66 64 66 40 74 50 0 70 96 ...
 $ SkinThickness           : int  35 29 0 23 35 0 32 0 45 0 ...
 $ Insulin                 : int  0 0 0 94 168 0 88 0 543 0 ...
 $ BMI                     : num  33.6 26.6 23.3 28.1 43.1 25.6 31 35.3 30.5 0 ...
 $ DiabetesPedigreeFunction: num  0.627 0.351 0.672 0.167 2.288 ...
 $ Age                     : int  50 31 32 21 33 30 26 29 53 54 ...
 $ Outcome                 : int  1 0 1 0 1 0 1 0 1 1 ...
viejos<-df %>% names
viejos
[1] "Pregnancies"              "Glucose"                 
[3] "BloodPressure"            "SkinThickness"           
[5] "Insulin"                  "BMI"                     
[7] "DiabetesPedigreeFunction" "Age"                     
[9] "Outcome"                 

Dado que nuestra base de datos tiene los nombres de las variables en ingles decidimos renombrar nuestras variables al español de la siguiente manera.

require(dplyr)  # Este paquete contiene la función rename
require(magrittr) 
Cargando paquete requerido: magrittr
df %<>% rename(
  Embarazo = viejos[1],
  Glucosa = viejos[2],
  Presion_Arterial = viejos[3],
  Grosor_de_Piel = viejos[4],
  Insulina = viejos[5],
  IMC = viejos[6],
  Función_Genética_de_Diabetes = viejos[7],
  Edad = viejos[8],
  Resultado = viejos[9])
df %>% names
[1] "Embarazo"                     "Glucosa"                     
[3] "Presion_Arterial"             "Grosor_de_Piel"              
[5] "Insulina"                     "IMC"                         
[7] "Función_Genética_de_Diabetes" "Edad"                        
[9] "Resultado"                   

Realizamos los siguientes graficos con el fin de mas adelante comparar las distribuciones con los valores imputados.

# Histogramas para variables numéricas
library(tidyr)

Adjuntando el paquete: 'tidyr'
The following object is masked from 'package:magrittr':

    extract
library(tidyselect)
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ forcats   1.0.0     ✔ readr     2.1.5
✔ lubridate 1.9.4     ✔ stringr   1.5.1
✔ purrr     1.0.2     ✔ tibble    3.2.1
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ tidyr::extract()   masks magrittr::extract()
✖ mice::filter()     masks dplyr::filter(), stats::filter()
✖ dplyr::lag()       masks stats::lag()
✖ purrr::set_names() masks magrittr::set_names()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
df_orig<-df
df_orig %>%
  select(-Resultado) %>%
  pivot_longer(cols = everything()) %>%
  ggplot(aes(value)) +
  geom_histogram(fill="blue", color="black", bins=30) +
  facet_wrap(~ name, scales="free") +
  theme_minimal()

Definimos las columnas donde 0 representa datos faltantes, los cuales posteriormente remplazaremos con NA.

cols_to_replace <- c("Glucosa", "Presion_Arterial", "Grosor_de_Piel", "Insulina", "IMC")

# Reemplazamos los ceros por NA en estas columnas
df[cols_to_replace] <- lapply(df[cols_to_replace], function(x) ifelse(x == 0, NA, x))

# Verificamos si ahora hay valores NA
colSums(is.na(df))  # Muestra cuántos valores NA hay en cada columna
                    Embarazo                      Glucosa 
                           0                            5 
            Presion_Arterial               Grosor_de_Piel 
                          35                          227 
                    Insulina                          IMC 
                         374                           11 
Función_Genética_de_Diabetes                         Edad 
                           0                            0 
                   Resultado 
                           0 

Queremos verificar estos datos faltantes, así que miramos el porcentaje de datos faltantes por variables de distintas formas.

# Instalar y cargar librerías necesarias

library(naniar)

gg_miss_var(df, show_pct = TRUE)  # Vemos el porcentaje de valores faltantes por variable

# Visualizamos los valores faltantes después de reemplazar 0 por NA
library(naniar)
vis_miss(df)

# Realizamos un Missing map de los NA
library(Amelia)
Cargando paquete requerido: Rcpp
## 
## Amelia II: Multiple Imputation
## (Version 1.8.3, built: 2024-11-07)
## Copyright (C) 2005-2025 James Honaker, Gary King and Matthew Blackwell
## Refer to http://gking.harvard.edu/amelia/ for more information
## 
missmap(df, col = c("red", "blue"), legend = TRUE)

Podemos observar que aproximademente el 9% de los datos, son valores faltantes, una gran parte de estos datos se encuentra concentrada en las variables Insulina y Grosor de Piel, por lo que tenemos que tener un tratamiento especial con esats variables.

#Revisar el porcentaje exacto 
sapply(df, function(x) mean(is.na(x)) * 100)
                    Embarazo                      Glucosa 
                   0.0000000                    0.6510417 
            Presion_Arterial               Grosor_de_Piel 
                   4.5572917                   29.5572917 
                    Insulina                          IMC 
                  48.6979167                    1.4322917 
Función_Genética_de_Diabetes                         Edad 
                   0.0000000                    0.0000000 
                   Resultado 
                   0.0000000 

Aqui podemos observar los porcentajes exactos de datos faltantes en esats variables, de aqui podemos notar que la presion arterial , el IMC y la glucosa tienen menos del 5% de datos faltantes por lo que no es necesaria una imputación, ya que la cantidad es pequeña, podemos simplemente usar la función drop_na.

library(tidyr)
df<- df %>% drop_na(Presion_Arterial, Glucosa, IMC)

Histogramas para comparar distribuciones antes y después de la modificación.

library(tidyr)
library(tidyselect)
library(tidyverse)

#  Histograma de las variables numéricas en el dataset después de la imputación
df %>%
  select(-Resultado) %>%  # Excluye la variable objetivo
  pivot_longer(cols = everything()) %>%  # Convierte columnas en una sola columna "name" con valores en "value"
  ggplot(aes(value)) +
  geom_histogram(fill="green", color="black", bins=30, na.rm=TRUE) +
  facet_wrap(~ name, scales="free") +
  theme_minimal()

#  Histograma del dataset original (antes de la imputación)
df_orig %>%
  select(-Resultado) %>%
  pivot_longer(cols = everything()) %>%
  ggplot(aes(value)) +
  geom_histogram(fill="blue", color="black", bins=30) +
  facet_wrap(~ name, scales="free") +
  theme_minimal()

ks.test(df$Glucosa, df_orig$Glucosa)
Warning in ks.test.default(df$Glucosa, df_orig$Glucosa): p-value will be
approximate in the presence of ties

    Asymptotic two-sample Kolmogorov-Smirnov test

data:  df$Glucosa and df_orig$Glucosa
D = 0.0081218, p-value = 1
alternative hypothesis: two-sided
ks.test(df$Presion_Arterial, df_orig$Presion_Arterial)
Warning in ks.test.default(df$Presion_Arterial, df_orig$Presion_Arterial):
p-value will be approximate in the presence of ties

    Asymptotic two-sample Kolmogorov-Smirnov test

data:  df$Presion_Arterial and df_orig$Presion_Arterial
D = 0.045688, p-value = 0.4181
alternative hypothesis: two-sided
ks.test(df$IMC, df_orig$IMC)
Warning in ks.test.default(df$IMC, df_orig$IMC): p-value will be approximate in
the presence of ties

    Asymptotic two-sample Kolmogorov-Smirnov test

data:  df$IMC and df_orig$IMC
D = 0.015798, p-value = 1
alternative hypothesis: two-sided

Como el p-valor (> 0.05) en todos los casos, entonces no hay evidencia para rechazar la hipótesis nula. De manera que la distribución de las variables no cambió significativamente después de la imputación/eliminación.

Tratamiento de Insulina y Grosor de Piel

Como la variable insulina tiene al rededor del 50% de datos faltantes debiamos encontrar una mejor manera de tratar estos datos, pero todos los métodos de imputación nos mostraron diferencias significativas entre las distribuciones antes y después de imputar los valores faltantes, y finalmente decidimos optar por usar method="pmm"

library(mice)
# Imputación de la variable 'Insulina' usando el método pmm
imputed_data <- mice(df, method = "pmm", m = 5)

 iter imp variable
  1   1  Grosor_de_Piel  Insulina
  1   2  Grosor_de_Piel  Insulina
  1   3  Grosor_de_Piel  Insulina
  1   4  Grosor_de_Piel  Insulina
  1   5  Grosor_de_Piel  Insulina
  2   1  Grosor_de_Piel  Insulina
  2   2  Grosor_de_Piel  Insulina
  2   3  Grosor_de_Piel  Insulina
  2   4  Grosor_de_Piel  Insulina
  2   5  Grosor_de_Piel  Insulina
  3   1  Grosor_de_Piel  Insulina
  3   2  Grosor_de_Piel  Insulina
  3   3  Grosor_de_Piel  Insulina
  3   4  Grosor_de_Piel  Insulina
  3   5  Grosor_de_Piel  Insulina
  4   1  Grosor_de_Piel  Insulina
  4   2  Grosor_de_Piel  Insulina
  4   3  Grosor_de_Piel  Insulina
  4   4  Grosor_de_Piel  Insulina
  4   5  Grosor_de_Piel  Insulina
  5   1  Grosor_de_Piel  Insulina
  5   2  Grosor_de_Piel  Insulina
  5   3  Grosor_de_Piel  Insulina
  5   4  Grosor_de_Piel  Insulina
  5   5  Grosor_de_Piel  Insulina
df_imputed <- complete(imputed_data)

Debemos verificar que no haya una diferencia estadísticamente significativa entre las distribuciones de los datos de Insulina originales y los datos imputados.

library(ggplot2)

# Histograma para los datos originales
ggplot(df, aes(x = Insulina)) + 
  geom_histogram(fill = "blue", color = "black", bins = 30) +
  ggtitle("Distribución de Insulina Original")
Warning: Removed 332 rows containing non-finite outside the scale range
(`stat_bin()`).

# Histograma para los datos imputados
ggplot(df_imputed, aes(x = Insulina)) + 
  geom_histogram(fill = "red", color = "black", bins = 30) +
  ggtitle("Distribución de Insulina Imputada")

ks.test(df_imputed$Insulina, df$Insulina)  # Test de Kolmogorov-Smirnov para comparar distribuciones
Warning in ks.test.default(df_imputed$Insulina, df$Insulina): p-value will be
approximate in the presence of ties

    Asymptotic two-sample Kolmogorov-Smirnov test

data:  df_imputed$Insulina and df$Insulina
D = 0.035263, p-value = 0.9099
alternative hypothesis: two-sided

Dado que el p-valor es muy alto (1) y D=0, podemos concluir que no hay una diferencia estadísticamente significativa entre las distribuciones de los datos de Insulina antes y después de la imputación. Esto sugiere que el proceso de imputación no alteró significativamente la distribución de los datos de esa variable.

# Imputar solo la variable 'Grosor_de_Piel' usando el método 'norm.predict'
imputed_data <- mice(df, method = c("", "norm.predict", "", "", "", "", "", "", ""), m = 5)

 iter imp variable
  1   1
  1   2
  1   3
  1   4
  1   5
  2   1
  2   2
  2   3
  2   4
  2   5
  3   1
  3   2
  3   3
  3   4
  3   5
  4   1
  4   2
  4   3
  4   4
  4   5
  5   1
  5   2
  5   3
  5   4
  5   5
df <- complete(imputed_data)
sum(is.na(df_imputed$Insulina))
[1] 0
sum(is.na(df_imputed$Grosor_de_Piel))
[1] 0
#No hay valores nulos 

Hemos terminado con los datos faltantes.

##Datos Atipicos

library(ggplot2)
library(tidyr)

# Convertir a formato largo para ggplot
df_long <- df_imputed %>%
  pivot_longer(cols = c(Glucosa, Presion_Arterial, Grosor_de_Piel, Insulina, IMC),
               names_to = "Variable", values_to = "Valor")

# Crear boxplots con facetas
ggplot(df_long, aes(x = Variable, y = Valor)) +
  geom_boxplot(fill = "lightblue", color = "black") +
  facet_wrap(~ Variable, scales = "free") + 
  theme_minimal() +
  labs(title = "Boxplots")

Podemos observar que variables como la Insulina, presíón arterial, IMC, y grosor de piel hay detección de valores atípicos.

out <- boxplot.stats(df_imputed$Grosor_de_Piel)$out
out_ind <- which(df_imputed$Grosor_de_Piel %in% c(out))
out_ind
[1]  54 170 419 545 651
df_imputed[out_ind, ]
    Embarazo Glucosa Presion_Arterial Grosor_de_Piel Insulina  IMC
54         0     100               88             60      110 46.8
170        5     130               82             99      112 39.1
419        0     180               78             63       14 59.4
545        2     197               70             99      280 34.7
651       13     158              114             60      220 42.3
    Función_Genética_de_Diabetes Edad Resultado
54                         0.962   31         0
170                        0.956   37         1
419                        2.420   25         1
545                        0.575   62         1
651                        0.257   44         1

Aqui observamos las filas que contienen datos atipicos, con el summary podemos observar que el valor de la media y de la mediana estan muy cercanos, los que nos lleva a pensar que sigue un distribucion normal, y que estos valores se encuentran demasiado alejados de la media, por lo que son posibles valores atipicos.

summary(df_imputed$Grosor_de_Piel)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   7.00   21.00   29.00   29.34   36.00   99.00 

Entonces decidimos aplicar el metodo de percentiles. Este método de detección de valores atípicos se basa en los percentiles. Con el método de los percentiles, todas las observaciones que se encuentren fuera del intervalo formado por los percentiles 2.5 y 97.5 se considerarán como posibles valores atípicos 7

lower_bound <- quantile(df_imputed$Grosor_de_Piel, 0.025)
lower_bound
2.5% 
  11 
upper_bound <- quantile(df_imputed$Grosor_de_Piel, 0.975)
upper_bound
 97.5% 
49.925 

Entonces todos aquellos valores que queden por fuera de nuestro intervalo seran tomados como valores atipicos. (11,49)

out <- boxplot.stats(df_imputed$Insulina)$out
out_ind <- which(df_imputed$Insulina %in% c(out))
out_ind
 [1]   8  11  12  28 104 145 176 182 209 216 219 233 235 236 246 271 281 341 348
[20] 369 371 386 392 429 447 451 457 465 550 552 557 606 616 636 637 642 655 667
[39] 672 684 685 710
lower_bound <- quantile(df_imputed$Insulina, 0.025)
lower_bound
2.5% 
32.3 
upper_bound <- quantile(df_imputed$Insulina, 0.975)
upper_bound
97.5% 
  480 

Aquí podemos observar que los valores que estan fuera del intervalo [29,485] son los considerados atipicos.

summary(df_imputed$Insulina)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   14.0    74.0   122.0   150.2   188.0   846.0 
# Aplicar capping a todas las columnas numéricas en df_imputed
for (col in names(df_imputed)) {
  if (is.numeric(df_imputed[[col]])) {
    lower_bound <- quantile(df_imputed[[col]], 0.025, na.rm = TRUE)
    upper_bound <- quantile(df_imputed[[col]], 0.975, na.rm = TRUE)
    
    df_imputed[[col]][df_imputed[[col]] < lower_bound] <- lower_bound
    df_imputed[[col]][df_imputed[[col]] > upper_bound] <- upper_bound
  }
}

# Verificar algunos valores después del capping
summary(df_imputed)
    Embarazo         Glucosa       Presion_Arterial Grosor_de_Piel 
 Min.   : 0.000   Min.   : 74.00   Min.   :50.00    Min.   :11.00  
 1st Qu.: 1.000   1st Qu.: 99.75   1st Qu.:64.00    1st Qu.:21.00  
 Median : 3.000   Median :117.00   Median :72.00    Median :29.00  
 Mean   : 3.837   Mean   :121.95   Mean   :72.38    Mean   :29.17  
 3rd Qu.: 6.000   3rd Qu.:142.00   3rd Qu.:80.00    3rd Qu.:36.00  
 Max.   :12.000   Max.   :189.00   Max.   :97.85    Max.   :49.92  
    Insulina          IMC        Función_Genética_de_Diabetes      Edad      
 Min.   : 32.3   Min.   :21.00   Min.   :0.1260               Min.   :21.00  
 1st Qu.: 74.0   1st Qu.:27.50   1st Qu.:0.2450               1st Qu.:24.00  
 Median :122.0   Median :32.40   Median :0.3790               Median :29.00  
 Mean   :148.3   Mean   :32.38   Mean   :0.4660               Mean   :33.26  
 3rd Qu.:188.0   3rd Qu.:36.60   3rd Qu.:0.6275               3rd Qu.:41.00  
 Max.   :480.0   Max.   :46.48   Max.   :1.3160               Max.   :62.92  
   Resultado     
 Min.   :0.0000  
 1st Qu.:0.0000  
 Median :0.0000  
 Mean   :0.3439  
 3rd Qu.:1.0000  
 Max.   :1.0000  
# Convertir a formato largo para ggplot
df_long <- df_imputed %>%
  pivot_longer(cols = c(Glucosa, Presion_Arterial, Grosor_de_Piel, Insulina, IMC),
               names_to = "Variable", values_to = "Valor")

# Crear boxplots con facetas
ggplot(df_long, aes(x = Variable, y = Valor)) +
  geom_boxplot(fill = "lightblue", color = "black") +
  facet_wrap(~ Variable, scales = "free") + 
  theme_minimal() +
  labs(title = "Boxplots")

Como podemos observar despues de aplicar el capping , vemos que aun hay datos atipicos presentes en nuestra variable Insulina , esto es debido a que los percentiles elegidos (2.5% y 97.5%) no son suficientes para eliminar todos los valores extremos.

Por ello, usaremos el criterio de 1.5*IQR en lugar de los percentiles

for (col in names(df_imputed)) {
  if (is.numeric(df_imputed[[col]])) {
    qnt <- quantile(df_imputed[[col]], probs = c(0.25, 0.75), na.rm = TRUE) # Cuartiles Q1 y Q3
    IQR_value <- IQR(df_imputed[[col]], na.rm = TRUE) # Rango intercuartílico
    lower_bound <- qnt[1] - 1.5 * IQR_value
    upper_bound <- qnt[2] + 1.5 * IQR_value

    # Aplicar capping
    df_imputed[[col]][df_imputed[[col]] < lower_bound] <- lower_bound
    df_imputed[[col]][df_imputed[[col]] > upper_bound] <- upper_bound
  }
}

# Revisar si todavía hay atípicos en Insulina
boxplot(df_imputed$Insulina, col="lightblue")

Con este boxplot observamos que ya no hay presentes valores atipicos en nuestra variable Insulina.

df_long <- df_imputed %>%
  pivot_longer(cols = c(Glucosa, Presion_Arterial, Grosor_de_Piel, Insulina, IMC),
               names_to = "Variable", values_to = "Valor")

# Crear boxplots con facetas
ggplot(df_long, aes(x = Variable, y = Valor)) +
  geom_boxplot(fill = "lightblue", color = "black") +
  facet_wrap(~ Variable, scales = "free") + 
  theme_minimal() +
  labs(title = "Boxplots")

Con una vista más general, podemos observar que ya no hay valores atípicos presentes en ninguna de nuestras variables, lo que indica que el proceso de tratamiento ha sido exitoso. Esto nos permite asegurar que los datos son ahora más representativos y adecuados para su análisis, reduciendo la influencia de valores extremos que podrían sesgar los resultados.

En conclusión, hemos completado satisfactoriamente la imputación de los datos faltantes (NA) y la corrección de valores atípicos en nuestra base de datos de diabetes, garantizando así una mayor calidad y fiabilidad en los análisis posteriores que se realicen sobre este conjunto de datos.