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
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)
# 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
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.
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.
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.
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
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
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