En este trabajo vamos a realizar un EDA y una limpieza de datos de esta base de datos.

En primer lugar cargar todas las librerias que usaremos en el trabajo

1) Cargar Datos:

Empezamos cargando los datos necesarios para realizar la actividad, para luego hacer una copia del df original, para asi luego hacer una comparación con los datos ya limpios, y finalmente ubicamos las columnas numericas del dataframe.

df <- read.csv("https://raw.githubusercontent.com/Kalbam/Datos/refs/heads/main/diabetes.csv")
df_original <- df
numeric_cols <- sapply(df, is.numeric)

2) Análisis Descriptivo Inicial

# reemplazar ceros imposibles por NA
cols_a_reemplazar <- c("Glucose", "BloodPressure", "SkinThickness", "Insulin", "BMI")
for (col in cols_a_reemplazar) {
  df[[col]][df[[col]] == 0] <- NA
}

En esta parte lo que hicimos fue reemplazar los valores sin sentido (valores igual a 0 en las columnas dichas por la profesora) y las reemplazamos por NA

# exploración general
head(df)
##   Pregnancies Glucose BloodPressure SkinThickness Insulin  BMI
## 1           6     148            72            35      NA 33.6
## 2           1      85            66            29      NA 26.6
## 3           8     183            64            NA      NA 23.3
## 4           1      89            66            23      94 28.1
## 5           0     137            40            35     168 43.1
## 6           5     116            74            NA      NA 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
tail(df)
##     Pregnancies Glucose BloodPressure SkinThickness Insulin  BMI
## 763           9      89            62            NA      NA 22.5
## 764          10     101            76            48     180 32.9
## 765           2     122            70            27      NA 36.8
## 766           5     121            72            23     112 26.2
## 767           1     126            60            NA      NA 30.1
## 768           1      93            70            31      NA 30.4
##     DiabetesPedigreeFunction Age Outcome
## 763                    0.142  33       0
## 764                    0.171  63       0
## 765                    0.340  27       0
## 766                    0.245  30       0
## 767                    0.349  47       1
## 768                    0.315  23       0
dim(df)
## [1] 768   9
colnames(df)
## [1] "Pregnancies"              "Glucose"                 
## [3] "BloodPressure"            "SkinThickness"           
## [5] "Insulin"                  "BMI"                     
## [7] "DiabetesPedigreeFunction" "Age"                     
## [9] "Outcome"
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 NA 70 96 ...
##  $ SkinThickness           : int  35 29 NA 23 35 NA 32 NA 45 NA ...
##  $ Insulin                 : int  NA NA NA 94 168 NA 88 NA 543 NA ...
##  $ BMI                     : num  33.6 26.6 23.3 28.1 43.1 25.6 31 35.3 30.5 NA ...
##  $ 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 ...
glimpse(df)
## Rows: 768
## Columns: 9
## $ Pregnancies              <int> 6, 1, 8, 1, 0, 5, 3, 10, 2, 8, 4, 10, 10, 1, …
## $ Glucose                  <int> 148, 85, 183, 89, 137, 116, 78, 115, 197, 125…
## $ BloodPressure            <int> 72, 66, 64, 66, 40, 74, 50, NA, 70, 96, 92, 7…
## $ SkinThickness            <int> 35, 29, NA, 23, 35, NA, 32, NA, 45, NA, NA, N…
## $ Insulin                  <int> NA, NA, NA, 94, 168, NA, 88, NA, 543, NA, NA,…
## $ BMI                      <dbl> 33.6, 26.6, 23.3, 28.1, 43.1, 25.6, 31.0, 35.…
## $ DiabetesPedigreeFunction <dbl> 0.627, 0.351, 0.672, 0.167, 2.288, 0.201, 0.2…
## $ Age                      <int> 50, 31, 32, 21, 33, 30, 26, 29, 53, 54, 30, 3…
## $ Outcome                  <int> 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, …
summary(df)
##   Pregnancies        Glucose      BloodPressure    SkinThickness  
##  Min.   : 0.000   Min.   : 44.0   Min.   : 24.00   Min.   : 7.00  
##  1st Qu.: 1.000   1st Qu.: 99.0   1st Qu.: 64.00   1st Qu.:22.00  
##  Median : 3.000   Median :117.0   Median : 72.00   Median :29.00  
##  Mean   : 3.845   Mean   :121.7   Mean   : 72.41   Mean   :29.15  
##  3rd Qu.: 6.000   3rd Qu.:141.0   3rd Qu.: 80.00   3rd Qu.:36.00  
##  Max.   :17.000   Max.   :199.0   Max.   :122.00   Max.   :99.00  
##                   NA's   :5       NA's   :35       NA's   :227    
##     Insulin            BMI        DiabetesPedigreeFunction      Age       
##  Min.   : 14.00   Min.   :18.20   Min.   :0.0780           Min.   :21.00  
##  1st Qu.: 76.25   1st Qu.:27.50   1st Qu.:0.2437           1st Qu.:24.00  
##  Median :125.00   Median :32.30   Median :0.3725           Median :29.00  
##  Mean   :155.55   Mean   :32.46   Mean   :0.4719           Mean   :33.24  
##  3rd Qu.:190.00   3rd Qu.:36.60   3rd Qu.:0.6262           3rd Qu.:41.00  
##  Max.   :846.00   Max.   :67.10   Max.   :2.4200           Max.   :81.00  
##  NA's   :374      NA's   :11                                              
##     Outcome     
##  Min.   :0.000  
##  1st Qu.:0.000  
##  Median :0.000  
##  Mean   :0.349  
##  3rd Qu.:1.000  
##  Max.   :1.000  
## 

Realizamos un analisis exploratorio general de cada dato, aqui algunas caracteristicas que vi.

Tenemos 768 filas y 9 columnas, con variables como pregnancies, glucose, bloodpressure etc.

Tambien podemos ver la media, mediana, maximo, minimo de cada variable.

Vemos que una las variables tiene una gran cantidad de valores NA, como por ejemplo insulina, el cual tiene 374 na´s, mas adelante analizaremos esta parte.

# summary individual
summary_var <- lapply(df, function(col) {
  col <- na.omit(col)
  summary(col)
})
summary_var
## $Pregnancies
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   0.000   1.000   3.000   3.845   6.000  17.000 
## 
## $Glucose
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    44.0    99.0   117.0   121.7   141.0   199.0 
## 
## $BloodPressure
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   24.00   64.00   72.00   72.41   80.00  122.00 
## 
## $SkinThickness
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    7.00   22.00   29.00   29.15   36.00   99.00 
## 
## $Insulin
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   14.00   76.25  125.00  155.55  190.00  846.00 
## 
## $BMI
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   18.20   27.50   32.30   32.46   36.60   67.10 
## 
## $DiabetesPedigreeFunction
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.0780  0.2437  0.3725  0.4719  0.6262  2.4200 
## 
## $Age
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   21.00   24.00   29.00   33.24   41.00   81.00 
## 
## $Outcome
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   0.000   0.000   0.000   0.349   1.000   1.000
# Conteo de NAs
na_count <- sapply(df, function(col) sum(is.na(col)))
na_count
##              Pregnancies                  Glucose            BloodPressure 
##                        0                        5                       35 
##            SkinThickness                  Insulin                      BMI 
##                      227                      374                       11 
## DiabetesPedigreeFunction                      Age                  Outcome 
##                        0                        0                        0
# Porcentaje de NAs
na_percent <- colSums(is.na(df)) / nrow(df) * 100
sort(na_percent, decreasing = TRUE)
##                  Insulin            SkinThickness            BloodPressure 
##               48.6979167               29.5572917                4.5572917 
##                      BMI                  Glucose              Pregnancies 
##                1.4322917                0.6510417                0.0000000 
## DiabetesPedigreeFunction                      Age                  Outcome 
##                0.0000000                0.0000000                0.0000000
# Mapa de valores faltantes
missmap(df)

Lo que hacemos aqui es ccalcular cuántos valores faltantes hay por columna, muestra las filas incompletas, obtiene el porcentaje de NAs y visualiza su distribución con un mapa de valores faltantes.

vemos que las variables,glocose, bloodpressure, skinthickness, insulin y bmi presentan datos con NA.

Al tener el porcentaje de NAs sabremos con que metodo tendriamos que imputarlos, en este caso podriamos con la media o la mediana, mas adelante analizaremos su distribucion, pero ahora la variable insulina se tendra que aplicar otros metodos, como lo puede ser pmm

3) Visualización Inicial

plot_histograms <- function(data, title){
  plots <- lapply(names(data)[numeric_cols], function(col){
    ggplot(data, aes_string(x = col)) +
      geom_histogram(bins = 30, fill = "skyblue", color = "black") +
      theme_minimal() +
      labs(title = paste(title, col))
  })
  do.call(grid.arrange, c(plots, ncol = 3))
}

plot_boxplots <- function(data, title){
  plots <- lapply(names(data)[numeric_cols], function(col){
    ggplot(data, aes_string(y = col)) +
      geom_boxplot(fill = "orange", outlier.color = "red") +
      theme_minimal() +
      labs(title = paste(title, col))
  })
  do.call(grid.arrange, c(plots, ncol = 3))
}

plot_histograms(df, "Antes de imputación - ")

plot_boxplots(df, "Antes de imputación - ")

Con estos graficos notamos que ninguo presenta distribucion normal, esto lo calcularemos mas adelante con la prueba de shapiro wilk, y tambien vemos la cantidad de datos atipicos en cada una de las variables, mas adelante calcularemos cada una.

4) Prueba de Normalidad (Shapiro-Wilk)

normalidad <- lapply(df_original[, numeric_cols], function(col) {
  col <- na.omit(col)
  col_std <- scale(col)
  shapiro.test(col_std)
})
normalidad
## $Pregnancies
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.90428, p-value < 2.2e-16
## 
## 
## $Glucose
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.9701, p-value = 1.986e-11
## 
## 
## $BloodPressure
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.81892, p-value < 2.2e-16
## 
## 
## $SkinThickness
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.90463, p-value < 2.2e-16
## 
## 
## $Insulin
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.72202, p-value < 2.2e-16
## 
## 
## $BMI
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.94999, p-value = 1.842e-15
## 
## 
## $DiabetesPedigreeFunction
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.83652, p-value < 2.2e-16
## 
## 
## $Age
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.87477, p-value < 2.2e-16
## 
## 
## $Outcome
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.60251, p-value < 2.2e-16

Luego de haber aplicado la prueba de shapiro wilk notamos que ninguno de los datos presentan normalidad, por lo que realizaremos imputacion por la mediana y en el caso de la variable Insulina utilizaremos pmm, ya que este tiene un 49% de NAs.

5) Imputación de Valores Faltantes

cols_a_imputar <- setdiff(names(df), "Insulin")
for (col in cols_a_imputar) {
  df[[col]][is.na(df[[col]])] <- median(df[[col]], na.rm = TRUE)
}


imp_pmm <- mice(df, method = "pmm", m = 5, seed = 123)
## 
##  iter imp variable
##   1   1  Insulin
##   1   2  Insulin
##   1   3  Insulin
##   1   4  Insulin
##   1   5  Insulin
##   2   1  Insulin
##   2   2  Insulin
##   2   3  Insulin
##   2   4  Insulin
##   2   5  Insulin
##   3   1  Insulin
##   3   2  Insulin
##   3   3  Insulin
##   3   4  Insulin
##   3   5  Insulin
##   4   1  Insulin
##   4   2  Insulin
##   4   3  Insulin
##   4   4  Insulin
##   4   5  Insulin
##   5   1  Insulin
##   5   2  Insulin
##   5   3  Insulin
##   5   4  Insulin
##   5   5  Insulin
df_pmm <- complete(imp_pmm, 1)

# Usaremos PMM
df <- df_pmm

Ahora imputamos los datos faltantes usando la media, y luego utilizamos pmm de la libreria mice para los valores NAs de insulina.

6) Detección de Outliers

outliers_iqr <- function(x, k = 1.5){
  if(!is.numeric(x)) return(integer(0))
  x2 <- na.omit(x)
  Q1 <- quantile(x2, 0.25)
  Q3 <- quantile(x2, 0.75)
  IQRv <- Q3 - Q1
  lower <- Q1 - k * IQRv
  upper <- Q3 + k * IQRv
  which(x < lower | x > upper)
}

outliers_lista <- lapply(df_original[, numeric_cols], outliers_iqr)
outliers_lista
## $Pregnancies
## [1]  89 160 299 456
## 
## $Glucose
## [1]  76 183 343 350 503
## 
## $BloodPressure
##  [1]   8  16  19  44  50  61  79  82  85 107 126 173 178 194 223 262 267 270 301
## [20] 333 337 348 358 363 427 431 436 454 469 485 495 523 534 536 550 590 598 602
## [39] 605 620 644 692 698 704 707
## 
## $SkinThickness
## [1] 580
## 
## $Insulin
##  [1]   9  14  55 112 140 154 187 221 229 232 248 249 259 287 297 361 371 376 393
## [20] 410 416 481 487 520 575 585 613 646 656 696 708 711 716 754
## 
## $BMI
##  [1]  10  50  61  82 121 126 146 178 194 248 304 372 427 446 495 523 674 685 707
## 
## $DiabetesPedigreeFunction
##  [1]   5  13  40  46  59 101 148 188 219 229 244 246 260 293 309 331 371 372 384
## [20] 396 446 535 594 607 619 622 623 660 662
## 
## $Age
## [1] 124 364 454 460 490 538 667 675 685
## 
## $Outcome
## integer(0)
# Rosner para Insulin
rosnerTest(na.omit(df_original$Insulin), k = 5)
## $distribution
## [1] "Normal"
## 
## $statistic
##      R.1      R.2      R.3      R.4      R.5 
## 6.648507 5.942333 5.503371 4.873759 4.755937 
## 
## $sample.size
## [1] 768
## 
## $parameters
## k 
## 5 
## 
## $alpha
## [1] 0.05
## 
## $crit.value
## lambda.1 lambda.2 lambda.3 lambda.4 lambda.5 
## 3.974092 3.973762 3.973432 3.973102 3.972771 
## 
## $n.outliers
## [1] 5
## 
## $alternative
## [1] "Up to 5 observations are not\n                                 from the same Distribution."
## 
## $method
## [1] "Rosner's Test for Outliers"
## 
## $data
##   [1]   0   0   0  94 168   0  88   0 543   0   0   0   0 846 175   0 230   0
##  [19]  83  96 235   0   0   0 146 115   0 140 110   0   0 245  54   0   0 192
##  [37]   0   0   0 207  70   0   0 240   0   0   0   0   0   0  82  36  23 300
##  [55] 342   0 304 110   0 142   0   0   0 128   0   0   0   0  38 100  90 140
##  [73]   0 270   0   0   0   0   0   0   0   0  71   0   0 125   0  71 110   0
##  [91]   0 176  48   0  64 228   0  76  64 220   0   0   0  40   0 152   0 140
## [109]  18  36 135 495  37   0 175   0   0   0   0  51 100   0 100   0   0  99
## [127] 135  94 145   0 168   0 225   0  49 140  50  92   0 325   0   0  63   0
## [145] 284   0   0 119   0   0 204   0 155 485   0   0  94 135  53 114   0 105
## [163] 285   0   0 156   0   0   0  78   0 130   0  48  55 130   0 130   0   0
## [181]   0  92  23   0   0   0 495  58 114 160   0  94   0   0   0 210   0  48
## [199]  99 318   0   0   0  44 190   0 280   0  87   0   0   0   0 130 175 271
## [217] 129 120   0   0 478   0   0 190  56  32   0   0 744  53   0 370  37   0
## [235]  45   0 192   0   0   0   0  88   0 176 194   0   0 680 402   0   0   0
## [253]  55   0 258   0   0   0 375 150 130   0   0   0   0  67   0   0   0   0
## [271]   0  56   0  45   0  57   0 116   0 278   0 122 155   0   0 135 545 220
## [289]  49  75  40  74 182 194   0 120 360 215 184   0   0 135  42   0   0 105
## [307] 132 148 180 205   0 148  96  85   0  94  64   0 140   0 231   0   0  29
## [325]   0 168 156   0 120  68   0  52   0   0  58 255   0   0 171   0 105  73
## [343]   0   0   0 108  83   0  74   0   0   0   0  43   0   0 167   0  54 249
## [361] 325   0   0   0 293  83   0   0  66 140 465  89  66  94 158 325  84  75
## [379]   0  72  82   0 182  59 110  50   0   0 285  81 196   0 415  87   0 275
## [397] 115   0   0   0   0   0  88   0   0 165   0   0   0 579   0 176 310  61
## [415] 167 474   0   0   0 115 170  76  78   0 210 277   0 180 145 180   0  85
## [433]  60   0   0   0   0   0   0   0   0  50 120   0   0  14  70  92  64  63
## [451]  95   0 210   0 105   0   0  71 237  60  56   0  49   0   0 105  36 100
## [469]   0 140   0   0   0   0   0   0 191 110  75   0 328   0  49 125   0 250
## [487] 480 265   0   0  66   0   0 122   0   0   0  76 145 193  71   0   0  79
## [505]   0   0  90 170  76   0   0 210   0   0  86 105 165   0   0 326  66 130
## [523]   0   0   0   0  82 105 188   0 106   0  65   0  56   0   0   0 210 155
## [541] 215 190   0  56  76 225 207 166  67   0   0 106   0  44 115 215   0   0
## [559]   0   0   0 274  77  54   0  88  18 126 126 165   0   0  44 120 330  63
## [577] 130   0   0   0   0   0   0   0 600   0   0   0 156   0   0 140   0 115
## [595] 230 185   0  25   0 120   0   0   0 126   0   0 293  41 272 182 158 194
## [613] 321   0 144   0   0  15   0   0 160   0   0 115   0  54   0   0   0   0
## [631]   0  90   0 183   0   0   0  66  91  46 105   0   0   0 152 440 144 159
## [649] 130   0 100 106  77   0 135 540  90 200   0  70   0   0 231 130   0 132
## [667]   0   0 190 100 168   0  49 240   0   0   0   0   0 265  45   0 105   0
## [685]   0 205   0   0 180 180   0   0  95 125   0 480 125   0 155   0 200   0
## [703]   0   0 100   0   0 335   0 160 387  22   0 291   0 392 185   0 178   0
## [721]   0 200 127 105   0   0 180   0   0   0  79   0 120 165   0   0 120   0
## [739] 160   0 150  94 116   0 140 105   0  57 200   0   0  74   0 510   0 110
## [757]   0   0   0   0  16   0   0 180   0 112   0   0
## 
## $data.name
## [1] "na.omit(df_original$Insulin)"
## 
## $bad.obs
## [1] 0
## 
## $all.stats
##   i   Mean.i     SD.i Value Obs.Num    R.i+1 lambda.i+1 Outlier
## 1 0 79.79948 115.2440   846      14 6.648507   3.974092    TRUE
## 2 1 78.80052 111.9425   744     229 5.942333   3.973762    TRUE
## 3 2 77.93211 109.3998   680     248 5.503371   3.973432    TRUE
## 4 3 77.14510 107.2796   600     585 4.873759   3.973102    TRUE
## 5 4 76.46073 105.6657   579     410 4.755937   3.972771    TRUE
## 
## attr(,"class")
## [1] "gofOutlier"

Ahora detectamos outliers con el metodo del rango intercuartilico y aplicamos Rosner para verificar los resultados, y notamos que las variables si presentan datos atipicos

7) Capping de Outliers

cap_outliers <- function(x, k = 1.5){
  if(!is.numeric(x)) return(x)
  
  # Usar na.rm = TRUE para evitar errores con NA/NaN
  Q1 <- quantile(x, 0.25, na.rm = TRUE)
  Q3 <- quantile(x, 0.75, na.rm = TRUE)
  IQRv <- Q3 - Q1
  lower <- Q1 - k * IQRv
  upper <- Q3 + k * IQRv
  
  x[x < lower] <- lower
  x[x > upper] <- upper
  
  return(x)
}

df[, numeric_cols] <- lapply(df[, numeric_cols], cap_outliers)

Utilizamos capping en este caso, lo que hace es los valores atipicos los reemplaza por el limite de estos

8) Análisis Final

plot_histograms(df, "Después de imputación - ")

plot_boxplots(df, "Después de imputación - ")

ggcorr(df[, numeric_cols], label = TRUE)

Realizamos un mini EDA final para ver como cambiaron los datos en comparacion al EDA del incio y notamos que mantiene la distribucion y ya no presenta datos atipicos

normalidad <- lapply(df_original[, numeric_cols], function(col) {
  col <- na.omit(col)
  col_std <- scale(col)
  shapiro.test(col_std)
})
normalidad
## $Pregnancies
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.90428, p-value < 2.2e-16
## 
## 
## $Glucose
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.9701, p-value = 1.986e-11
## 
## 
## $BloodPressure
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.81892, p-value < 2.2e-16
## 
## 
## $SkinThickness
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.90463, p-value < 2.2e-16
## 
## 
## $Insulin
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.72202, p-value < 2.2e-16
## 
## 
## $BMI
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.94999, p-value = 1.842e-15
## 
## 
## $DiabetesPedigreeFunction
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.83652, p-value < 2.2e-16
## 
## 
## $Age
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.87477, p-value < 2.2e-16
## 
## 
## $Outcome
## 
##  Shapiro-Wilk normality test
## 
## data:  col_std
## W = 0.60251, p-value < 2.2e-16

Volvemos a calcular la normalidad para saber si cambio la distribucion y como vemos todas siguen siendo no normales