1. Objetivo de la Actividad:

El objetivo principal de esta actividad es que aprendas a manejar y tratar datos incompletos o problemáticos en un conjunto de datos real, con el fin de poder realizar análisis posteriores confiables. Buscando:

  • Identificar y manejar valores faltantes.
  • Detectar y tratar valores atípicos.
  • Comprender como los datos faltantes y los datos atípicos afectan análisis posteriores.

2. Descripción de los datos:

  • Nombre del Dataset: ‘diabetes.csv’
  • Número de Variables: 9
  • Número de Observaciones: 768

El conjunto de datos contiene información médica de pacientes y se utiliza para predecir un resultado de interés de salud (variable dependiente). Incluye variables independientes como:

Variable Tipo Descripción
Pregnancies Numérica Número de embarazos de la paciente
Glucose Numérica Nivel de glucosa en sangre (mg/dL)
BloodPressure Numérica Presión arterial diastólica (mm Hg)
SkinThickness Numérica Espesor del pliegue cutáneo (mm)
Insulin Numérica Nivel de insulina en sangre (mu U/mL)
BMI Numérica Índice de masa corporal (peso en kg / altura en m²)
DiabetesPedigreeFunction Numérica Función de predisposición genética a la diabetes (herencia)
Age Numérica Edad de la paciente (años)
Outcome Categórica Resultado de interés (1 = diabetes, 0 = no diabetes)

3. Librerias:

library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.4.3
## Warning: package 'ggplot2' was built under R version 4.4.3
## Warning: package 'readr' was built under R version 4.4.3
## Warning: package 'dplyr' was built under R version 4.4.2
## Warning: package 'forcats' was built under R version 4.4.2
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.2     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(mice)
## Warning: package 'mice' was built under R version 4.4.3
## 
## Adjuntando el paquete: 'mice'
## 
## The following object is masked from 'package:stats':
## 
##     filter
## 
## The following objects are masked from 'package:base':
## 
##     cbind, rbind
library(ggplot2)
library(dplyr)
library(tidyr)
library(readr)
library(reshape2)
## 
## Adjuntando el paquete: 'reshape2'
## 
## The following object is masked from 'package:tidyr':
## 
##     smiths
library(visdat)
## Warning: package 'visdat' was built under R version 4.4.3
library(gridExtra)
## Warning: package 'gridExtra' was built under R version 4.4.3
## 
## Adjuntando el paquete: 'gridExtra'
## 
## The following object is masked from 'package:dplyr':
## 
##     combine
library(skimr)
## Warning: package 'skimr' was built under R version 4.4.3
library(ggcorrplot)
## Warning: package 'ggcorrplot' was built under R version 4.4.3
library(patchwork) 
## Warning: package 'patchwork' was built under R version 4.4.2
library(GGally)
## Warning: package 'GGally' was built under R version 4.4.3

4. Observar los Datos:

4.1 Cargar Dataset

df <- read_csv("diabetes.csv")
## Rows: 768 Columns: 9
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl (9): Pregnancies, Glucose, BloodPressure, SkinThickness, Insulin, BMI, D...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

4.2 Vistaso superficial de los Datos:

print(paste("Número de filas:",nrow(df) ) )      # Número de filas
## [1] "Número de filas: 768"
print(paste("Número de columnas:",ncol(df)))       # Número de columnas
## [1] "Número de columnas: 9"
head(df)
## # A tibble: 6 × 9
##   Pregnancies Glucose BloodPressure SkinThickness Insulin   BMI
##         <dbl>   <dbl>         <dbl>         <dbl>   <dbl> <dbl>
## 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
## # ℹ 3 more variables: DiabetesPedigreeFunction <dbl>, Age <dbl>, Outcome <dbl>

Obseervación inicial: Aparentemente no hay valores faltantes ni atípicos dentro de la definición o el contexto de cada variable observado.

str(df)        # Tipo de cada variable y primeros valores
## spc_tbl_ [768 × 9] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ Pregnancies             : num [1:768] 6 1 8 1 0 5 3 10 2 8 ...
##  $ Glucose                 : num [1:768] 148 85 183 89 137 116 78 115 197 125 ...
##  $ BloodPressure           : num [1:768] 72 66 64 66 40 74 50 0 70 96 ...
##  $ SkinThickness           : num [1:768] 35 29 0 23 35 0 32 0 45 0 ...
##  $ Insulin                 : num [1:768] 0 0 0 94 168 0 88 0 543 0 ...
##  $ BMI                     : num [1:768] 33.6 26.6 23.3 28.1 43.1 25.6 31 35.3 30.5 0 ...
##  $ DiabetesPedigreeFunction: num [1:768] 0.627 0.351 0.672 0.167 2.288 ...
##  $ Age                     : num [1:768] 50 31 32 21 33 30 26 29 53 54 ...
##  $ Outcome                 : num [1:768] 1 0 1 0 1 0 1 0 1 1 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   Pregnancies = col_double(),
##   ..   Glucose = col_double(),
##   ..   BloodPressure = col_double(),
##   ..   SkinThickness = col_double(),
##   ..   Insulin = col_double(),
##   ..   BMI = col_double(),
##   ..   DiabetesPedigreeFunction = col_double(),
##   ..   Age = col_double(),
##   ..   Outcome = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>

Observación: Podemos ver que nuestras 9 variables son tomadas como numéricas; sin embargo, la variable Outcome es categórica y debemos denotarla como tal.

df$Outcome <- as.factor(df$Outcome)
str(df)
## spc_tbl_ [768 × 9] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ Pregnancies             : num [1:768] 6 1 8 1 0 5 3 10 2 8 ...
##  $ Glucose                 : num [1:768] 148 85 183 89 137 116 78 115 197 125 ...
##  $ BloodPressure           : num [1:768] 72 66 64 66 40 74 50 0 70 96 ...
##  $ SkinThickness           : num [1:768] 35 29 0 23 35 0 32 0 45 0 ...
##  $ Insulin                 : num [1:768] 0 0 0 94 168 0 88 0 543 0 ...
##  $ BMI                     : num [1:768] 33.6 26.6 23.3 28.1 43.1 25.6 31 35.3 30.5 0 ...
##  $ DiabetesPedigreeFunction: num [1:768] 0.627 0.351 0.672 0.167 2.288 ...
##  $ Age                     : num [1:768] 50 31 32 21 33 30 26 29 53 54 ...
##  $ Outcome                 : Factor w/ 2 levels "0","1": 2 1 2 1 2 1 2 1 2 2 ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   Pregnancies = col_double(),
##   ..   Glucose = col_double(),
##   ..   BloodPressure = col_double(),
##   ..   SkinThickness = col_double(),
##   ..   Insulin = col_double(),
##   ..   BMI = col_double(),
##   ..   DiabetesPedigreeFunction = col_double(),
##   ..   Age = col_double(),
##   ..   Outcome = col_double()
##   .. )
##  - attr(*, "problems")=<externalptr>

5. Limpieza de Datos:

5.1 Análisis de Valores Faltantes:

Como parte del ejercicio se plantea el ejercicio de sustituir todos los valores ausentes o nulos por NAN y realice el análisis de estos datos faltantes.

cols <- c("Glucose","BloodPressure","SkinThickness","Insulin","BMI")

for (col in cols) {
  df[[col]][df[[col]] == 0] <- NA
}
vis_miss(df)

Note que el porcentaje general en el dataset de valores faltantes es el 9.4% sin embargo es necesario revisar la proporción en cada variable para saber como tratarlos.

colSums(is.na(df))
##              Pregnancies                  Glucose            BloodPressure 
##                        0                        5                       35 
##            SkinThickness                  Insulin                      BMI 
##                      227                      374                       11 
## DiabetesPedigreeFunction                      Age                  Outcome 
##                        0                        0                        0

Luego de hacer el reemplazo, podemos ver la cantidad de valores faltantes por variable, encontrando una gran proporción en las variables Insulin y SkinThickness.

na_summary <- tibble(
  variable = names(df),
  n_na = sapply(df, function(x) sum(is.na(x))),
  pct_na = round(sapply(df, function(x) mean(is.na(x)) * 100), 2)
) %>%
  arrange(desc(pct_na))

print(na_summary)
## # A tibble: 9 × 3
##   variable                  n_na pct_na
##   <chr>                    <int>  <dbl>
## 1 Insulin                    374  48.7 
## 2 SkinThickness              227  29.6 
## 3 BloodPressure               35   4.56
## 4 BMI                         11   1.43
## 5 Glucose                      5   0.65
## 6 Pregnancies                  0   0   
## 7 DiabetesPedigreeFunction     0   0   
## 8 Age                          0   0   
## 9 Outcome                      0   0

Dado los porcentajes de Na por variable, encontramos que es necesario tratar con mayor cuidado los Na de las variables Insulin y SkinThickness, dado su alto porcentaje.

5.1.1 Imputación:

# --- Imputación con método pmm ---
imp_pmm <- mice(df, method = "pmm", m = 5, seed = 123)
## 
##  iter imp variable
##   1   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   1   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   1   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   1   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   1   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
df_pmm <- complete(imp_pmm)

# --- Imputación con método norm.predict ---
imp_norm_predict <- mice(df, method = "norm.predict", m = 5, seed = 123)
## 
##  iter imp variable
##   1   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   1   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   1   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   1   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   1   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
df_norm_predict <- complete(imp_norm_predict)

# --- Imputación con método norm.nob ---
imp_norm_nob <- mice(df, method = "norm.nob", m = 5, seed = 123)
## 
##  iter imp variable
##   1   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   1   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   1   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   1   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   1   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
df_norm_nob <- complete(imp_norm_nob)

# --- Imputación con método norm ---
imp_norm <- mice(df, method = "norm", m = 5, seed = 123)
## 
##  iter imp variable
##   1   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   1   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   1   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   1   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   1   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   2  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   3  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   4  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   5  Glucose  BloodPressure  SkinThickness  Insulin  BMI
df_norm <- complete(imp_norm)

5.1.1.1 Análisis del Método PMM

Estadísticos:

# Mostrar resumen de imputaciones realizadas
print("Original")
## [1] "Original"
summary(df[cols])
##     Glucose      BloodPressure    SkinThickness      Insulin      
##  Min.   : 44.0   Min.   : 24.00   Min.   : 7.00   Min.   : 14.00  
##  1st Qu.: 99.0   1st Qu.: 64.00   1st Qu.:22.00   1st Qu.: 76.25  
##  Median :117.0   Median : 72.00   Median :29.00   Median :125.00  
##  Mean   :121.7   Mean   : 72.41   Mean   :29.15   Mean   :155.55  
##  3rd Qu.:141.0   3rd Qu.: 80.00   3rd Qu.:36.00   3rd Qu.:190.00  
##  Max.   :199.0   Max.   :122.00   Max.   :99.00   Max.   :846.00  
##  NA's   :5       NA's   :35       NA's   :227     NA's   :374     
##       BMI       
##  Min.   :18.20  
##  1st Qu.:27.50  
##  Median :32.30  
##  Mean   :32.46  
##  3rd Qu.:36.60  
##  Max.   :67.10  
##  NA's   :11
print("Método pmm")
## [1] "Método pmm"
summary(df_pmm[cols])
##     Glucose      BloodPressure   SkinThickness      Insulin     
##  Min.   : 44.0   Min.   : 24.0   Min.   : 7.00   Min.   : 14.0  
##  1st Qu.: 99.0   1st Qu.: 64.0   1st Qu.:21.00   1st Qu.: 71.0  
##  Median :117.0   Median : 72.0   Median :29.00   Median :115.0  
##  Mean   :121.6   Mean   : 72.3   Mean   :28.74   Mean   :146.6  
##  3rd Qu.:141.0   3rd Qu.: 80.0   3rd Qu.:36.00   3rd Qu.:182.0  
##  Max.   :199.0   Max.   :122.0   Max.   :99.00   Max.   :846.0  
##       BMI       
##  Min.   :18.20  
##  1st Qu.:27.48  
##  Median :32.15  
##  Mean   :32.41  
##  3rd Qu.:36.60  
##  Max.   :67.10
  • Glucose

    • Min, Q1, Median, Mean, Max son prácticamente iguales en ambos datasets.
    • Solo se imputaron 5 valores faltantes; los cambios son mínimos.
  • BloodPressure

    • Valores centrales similares (mediana 72), pero la media baja ligeramente de 72.41 → 72.3.
    • Se imputaron 35 NA, lo que suaviza la media sin alterar la distribución general.
  • SkinThickness

    • Los NA eran 227, por lo que la imputación es importante.
    • Media disminuye ligeramente: 29.15 → 28.74.
    • Valores mínimos, máximos y mediana casi iguales → la imputación no introduce valores extremos.
  • Insulin

    • NA = 374, muchos valores faltantes.
    • Media disminuye de 155.55 → 146.6.
    • Mediana baja de 125 → 115.
    • Valores más “Conservadores”
  • BMI

    • Cambios mínimos: media 32.46 → 32.41, mediana 32.3 → 32.15.
    • Solo 11 NA imputados, por lo que el efecto es pequeño.

Histogramas

    # Crear un dataframe combinado con una columna que indique el origen
    df_comb <- bind_rows(
      df %>% select(all_of(cols)) %>% mutate(Origen = "df"),
      df_pmm %>% select(all_of(cols)) %>% mutate(Origen = "df_pmm")
    )

    # Pivot largo para ggplot
    df_long <- df_comb %>%
      pivot_longer(cols = all_of(cols), names_to = "Variable", values_to = "Valor")

    # Histograma comparativo
    ggplot(df_long, aes(x = Valor, fill = Origen)) +
      geom_histogram(alpha = 0.5, position = "identity", bins = 30, color = "black") +
      facet_wrap(~ Variable, scales = "free") +
      scale_fill_manual(values = c("black", "red")) +
      theme_minimal() +
      ggtitle("Comparación de histogramas: df vs df_pmm")
## Warning: Removed 652 rows containing non-finite outside the scale range
## (`stat_bin()`).

Graficamente, en variables como BloodPressure, BMI y Glucose no se observan cambios radicales luego de imputar por el método PMM.

Sin embargo, para las variables Insulin y SkinThickness se observan cambios notables en las distribuciones, por lo que es recomendable evaluar si estos cambios son estadísticamente significativos.

Pruebas

for (col in cols) {
  cat("\nVariable:", col, "\n")
  print(wilcox.test(df[[col]], df_pmm[[col]]))
}
## 
## Variable: Glucose 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_pmm[[col]]
## W = 293526, p-value = 0.9508
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: BloodPressure 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_pmm[[col]]
## W = 283013, p-value = 0.8543
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: SkinThickness 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_pmm[[col]]
## W = 212193, p-value = 0.5087
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: Insulin 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_pmm[[col]]
## W = 159530, p-value = 0.1284
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: BMI 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_pmm[[col]]
## W = 291828, p-value = 0.8946
## alternative hypothesis: true location shift is not equal to 0

Se observa que el p-valor de cada prueba es mayor a 0.05, por lo que no se rechaza la hipótesis nula. Esto indica que no existen diferencias estadísticamente significativas entre las medianas de las variables antes y después de la imputación mediante el método PMM. Por lo que, este método es una buena opción para la imputación de nuestros datos.

5.1.1.2 Análisis del Método Norm Predict

Estadísticos:

# Mostrar resumen de imputaciones realizadas
print("Original")
## [1] "Original"
summary(df[cols])
##     Glucose      BloodPressure    SkinThickness      Insulin      
##  Min.   : 44.0   Min.   : 24.00   Min.   : 7.00   Min.   : 14.00  
##  1st Qu.: 99.0   1st Qu.: 64.00   1st Qu.:22.00   1st Qu.: 76.25  
##  Median :117.0   Median : 72.00   Median :29.00   Median :125.00  
##  Mean   :121.7   Mean   : 72.41   Mean   :29.15   Mean   :155.55  
##  3rd Qu.:141.0   3rd Qu.: 80.00   3rd Qu.:36.00   3rd Qu.:190.00  
##  Max.   :199.0   Max.   :122.00   Max.   :99.00   Max.   :846.00  
##  NA's   :5       NA's   :35       NA's   :227     NA's   :374     
##       BMI       
##  Min.   :18.20  
##  1st Qu.:27.50  
##  Median :32.30  
##  Mean   :32.46  
##  3rd Qu.:36.60  
##  Max.   :67.10  
##  NA's   :11
print("Método norm_predict")
## [1] "Método norm_predict"
summary(df_norm_predict[cols])
##     Glucose      BloodPressure    SkinThickness      Insulin      
##  Min.   : 44.0   Min.   : 24.00   Min.   : 7.00   Min.   :-22.18  
##  1st Qu.: 99.0   1st Qu.: 64.00   1st Qu.:22.00   1st Qu.: 88.00  
##  Median :117.0   Median : 72.00   Median :28.50   Median :130.00  
##  Mean   :121.7   Mean   : 72.35   Mean   :28.89   Mean   :151.74  
##  3rd Qu.:141.0   3rd Qu.: 80.00   3rd Qu.:35.00   3rd Qu.:190.47  
##  Max.   :199.0   Max.   :122.00   Max.   :99.00   Max.   :846.00  
##       BMI       
##  Min.   :18.20  
##  1st Qu.:27.50  
##  Median :32.05  
##  Mean   :32.44  
##  3rd Qu.:36.60  
##  Max.   :67.10

Se comparan las variables originales con los datos imputados mediante los métodos PMM y Norm_predict:

  • Glucose

    • Muy similares, ningún cambio relevante.La imputación no afecta Glucose; los pocos NA rellenados no alteran la distribución.
  • BloodPressure

    • Cambios insignificantes (media 72.41 → 72.35). La imputacion no afecta significativamente la distribución central de BloodPressure.
  • SkinThickness

    • Mediana baja un poco (29 → 28.5), media similar (29.15 → 28.89). Modifica levemente la media, pero la mediana se mantiene cercana; los valores imputados no generan extremos importantes.
  • Insulin

    • Media 155.55 → 151.74, mediana 125 → 130, mínimo negativo (-22.18).Norm_predict genera algunos valores inválidos negativos, lo que puede alterar la interpretación y no es recomendable para Insulin.
  • BMI

    • Cambios muy pequeños (media 32.46 → 32.44, mediana 32.3 → 32.05), la distribución se mantiene estable.

    Histogramas

    # Crear un dataframe combinado con una columna que indique el origen
    df_comb <- bind_rows(
      df %>% select(all_of(cols)) %>% mutate(Origen = "df"),
      df_norm_predict %>% select(all_of(cols)) %>% mutate(Origen = "df_norm_predict")
    )

    # Pivot largo para ggplot
    df_long <- df_comb %>%
      pivot_longer(cols = all_of(cols), names_to = "Variable", values_to = "Valor")

    # Histograma comparativo
    ggplot(df_long, aes(x = Valor, fill = Origen)) +
      geom_histogram(alpha = 0.5, position = "identity", bins = 30, color = "black") +
      facet_wrap(~ Variable, scales = "free") +
      scale_fill_manual(values = c("black", "red")) +
      theme_minimal() +
      ggtitle("Comparación de histogramas: df vs df_norm_predict")
## Warning: Removed 652 rows containing non-finite outside the scale range
## (`stat_bin()`).

En general, se observa que las distribuciones antes y después de la imputación no presentan cambios radicales a simple vista, a excepción de las variables Insulin y SkinThickness, donde sí se notan diferencias más importantes que analizaremos con pruebas.

Pruebas

for (col in cols) {
  cat("\nVariable:", col, "\n")
  print(wilcox.test(df[[col]], df_norm_predict[[col]]))
}
## 
## Variable: Glucose 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_norm_predict[[col]]
## W = 293105, p-value = 0.9897
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: BloodPressure 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_norm_predict[[col]]
## W = 282272, p-value = 0.9241
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: SkinThickness 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_norm_predict[[col]]
## W = 210911, p-value = 0.6382
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: Insulin 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_norm_predict[[col]]
## W = 145705, p-value = 0.3019
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: BMI 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_norm_predict[[col]]
## W = 291082, p-value = 0.9635
## alternative hypothesis: true location shift is not equal to 0

No hay diferencias estadísticamente significativas entre las distribuciones de las variables antes y después de la imputación por el método Norm_predict. Esto indica que, aunque algunas variables como Insulin y SkinThickness presentan cambios visuales, dichos cambios no son suficientes para considerarlos estadísticamente relevantes.

Sin embargo, cabe destacar que para la variable Insulin, este método generó valores extremos erróneos; por lo tanto, no se considerará Norm_predict como alternativa de imputación para Insulin.

5.1.1.3 Análisis del Método Norm nob

Estadísticos:

# Mostrar resumen de imputaciones realizadas
print("Original")
## [1] "Original"
summary(df[cols])
##     Glucose      BloodPressure    SkinThickness      Insulin      
##  Min.   : 44.0   Min.   : 24.00   Min.   : 7.00   Min.   : 14.00  
##  1st Qu.: 99.0   1st Qu.: 64.00   1st Qu.:22.00   1st Qu.: 76.25  
##  Median :117.0   Median : 72.00   Median :29.00   Median :125.00  
##  Mean   :121.7   Mean   : 72.41   Mean   :29.15   Mean   :155.55  
##  3rd Qu.:141.0   3rd Qu.: 80.00   3rd Qu.:36.00   3rd Qu.:190.00  
##  Max.   :199.0   Max.   :122.00   Max.   :99.00   Max.   :846.00  
##  NA's   :5       NA's   :35       NA's   :227     NA's   :374     
##       BMI       
##  Min.   :18.20  
##  1st Qu.:27.50  
##  Median :32.30  
##  Mean   :32.46  
##  3rd Qu.:36.60  
##  Max.   :67.10  
##  NA's   :11
print("Método norm_nob")
## [1] "Método norm_nob"
summary(df_norm_nob[cols])
##     Glucose      BloodPressure    SkinThickness        Insulin       
##  Min.   : 44.0   Min.   : 24.00   Min.   :-0.1049   Min.   :-280.72  
##  1st Qu.: 99.0   1st Qu.: 64.00   1st Qu.:21.0000   1st Qu.:  71.75  
##  Median :117.0   Median : 72.00   Median :28.3273   Median : 127.57  
##  Mean   :121.6   Mean   : 72.42   Mean   :28.5705   Mean   : 149.95  
##  3rd Qu.:140.2   3rd Qu.: 80.00   3rd Qu.:35.6374   3rd Qu.: 201.28  
##  Max.   :199.0   Max.   :122.00   Max.   :99.0000   Max.   : 846.00  
##       BMI       
##  Min.   :18.20  
##  1st Qu.:27.50  
##  Median :32.35  
##  Mean   :32.44  
##  3rd Qu.:36.60  
##  Max.   :67.10
  • Glucose y BloodPressure: Los valores mínimos, máximos, medianas y medias son prácticamente iguales a los originales. Esto indica que la imputación no afecta significativamente la distribución de estas variables.

  • SkinThickness: La media y mediana son similares a los originales, aunque aparece un valor mínimo negativo (-0.1049). La imputación no altera la tendencia central, pero genera un valor atípico poco realista.

  • Insulin: Se observan valores mínimos extremadamente negativos (-280.72), mientras que la mediana y media son similares a los originales. Esto indica que la imputación introduce valores erróneos extremos, por lo que este método no es recomendable para Insulin.

  • BMI: Cambios mínimos, la distribución se mantiene estable. La imputación no altera la variable de manera relevante.

En general, el método Norm_nob mantiene relativamente estables las distribuciones de Glucose, BloodPressure, SkinThickness y BMI, pero genera valores extremos no válidos para SkinThickness e Insulin, lo que limita su uso para estas variables.

Histogramas

    # Crear un dataframe combinado con una columna que indique el origen
    df_comb <- bind_rows(
      df %>% select(all_of(cols)) %>% mutate(Origen = "df"),
      df_norm_nob %>% select(all_of(cols)) %>% mutate(Origen = "df_norm_nob")
    )

    # Pivot largo para ggplot
    df_long <- df_comb %>%
      pivot_longer(cols = all_of(cols), names_to = "Variable", values_to = "Valor")

    # Histograma comparativo
    ggplot(df_long, aes(x = Valor, fill = Origen)) +
      geom_histogram(alpha = 0.5, position = "identity", bins = 30, color = "black") +
      facet_wrap(~ Variable, scales = "free") +
      scale_fill_manual(values = c("blue", "red")) +
      theme_minimal() +
      ggtitle("Comparación de histogramas: df vs df_norm_nob")
## Warning: Removed 652 rows containing non-finite outside the scale range
## (`stat_bin()`).

Para Glucose, BloodPressure y BMI, la imputación mantiene la distribución original.Para Insulin y SkinThickness, la imputación introduce valores atípicos y negativos, por lo que no es recomendable usar Norm_nob en estas variables.

Pruebas

for (col in cols) {
  cat("\nVariable:", col, "\n")
  print(wilcox.test(df[[col]], df_norm_nob[[col]]))
}
## 
## Variable: Glucose 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_norm_nob[[col]]
## W = 293396, p-value = 0.9628
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: BloodPressure 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_norm_nob[[col]]
## W = 281102, p-value = 0.9648
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: SkinThickness 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_norm_nob[[col]]
## W = 213540, p-value = 0.3894
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: Insulin 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_norm_nob[[col]]
## W = 152569, p-value = 0.8142
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: BMI 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_norm_nob[[col]]
## W = 290750, p-value = 0.9943
## alternative hypothesis: true location shift is not equal to 0

En todas las variables, el p-valor > 0.05 indica que no existe evidencia estadísticamente significativa para afirmar que la imputación con norm.nob haya alterado la distribución de los datos originales.

Sin embargo, cabe destacar que para la variable Insulin y SkinThickness, este método generó valores extremos erróneos; por lo tanto, no es un método adecuado para estas variables.

5.1.1.3 Análisis del Método Norm

Estadísticos:

# Mostrar resumen de imputaciones realizadas
print("Original")
## [1] "Original"
summary(df[cols])
##     Glucose      BloodPressure    SkinThickness      Insulin      
##  Min.   : 44.0   Min.   : 24.00   Min.   : 7.00   Min.   : 14.00  
##  1st Qu.: 99.0   1st Qu.: 64.00   1st Qu.:22.00   1st Qu.: 76.25  
##  Median :117.0   Median : 72.00   Median :29.00   Median :125.00  
##  Mean   :121.7   Mean   : 72.41   Mean   :29.15   Mean   :155.55  
##  3rd Qu.:141.0   3rd Qu.: 80.00   3rd Qu.:36.00   3rd Qu.:190.00  
##  Max.   :199.0   Max.   :122.00   Max.   :99.00   Max.   :846.00  
##  NA's   :5       NA's   :35       NA's   :227     NA's   :374     
##       BMI       
##  Min.   :18.20  
##  1st Qu.:27.50  
##  Median :32.30  
##  Mean   :32.46  
##  3rd Qu.:36.60  
##  Max.   :67.10  
##  NA's   :11
print("Método norm")
## [1] "Método norm"
summary(df_norm[cols])
##     Glucose      BloodPressure    SkinThickness       Insulin       
##  Min.   : 44.0   Min.   : 24.00   Min.   : 6.209   Min.   :-196.00  
##  1st Qu.: 99.0   1st Qu.: 64.00   1st Qu.:21.954   1st Qu.:  73.75  
##  Median :117.0   Median : 72.00   Median :29.195   Median : 136.09  
##  Mean   :121.6   Mean   : 72.44   Mean   :29.260   Mean   : 156.85  
##  3rd Qu.:141.0   3rd Qu.: 80.00   3rd Qu.:36.000   3rd Qu.: 219.30  
##  Max.   :199.0   Max.   :122.00   Max.   :99.000   Max.   : 846.00  
##       BMI       
##  Min.   :18.20  
##  1st Qu.:27.48  
##  Median :32.25  
##  Mean   :32.44  
##  3rd Qu.:36.60  
##  Max.   :67.10
  • Glucose
    Mínimo, cuartiles, mediana, media y máximo prácticamente idénticos antes y después. Norm mantuvo muy bien la distribución.

  • BloodPressure
    Estadísticos idénticos (mín, Q1, mediana, media, Q3, máx). No hay alteración perceptible.

  • SkinThickness
    Muy parecido, pero el mínimo cambió de 7.0 → 6.209 (ligero descenso).
    Esto sugiere que el método generó un valor imputado menor que el mínimo original.

  • Insulin
    Mínimo cambió drásticamente de 14.0 → -196.0
    Esto es importante: el método norm puede generar valores fuera del rango plausible, incluso negativos (algo no fisiológico para insulina).
    Mediana, media y máximo son similares, pero este valor atípico negativo es una señal de alerta. Riesgo de valores no realistas.

  • BMI
    Muy estable: diferencias de redondeo mínimas. Conservada la estructura.

    Histogramas

    # Crear un dataframe combinado con una columna que indique el origen
    df_comb <- bind_rows(
      df %>% select(all_of(cols)) %>% mutate(Origen = "df"),
      df_norm %>% select(all_of(cols)) %>% mutate(Origen = "df_norm")
    )

    df_long <- df_comb %>%
      pivot_longer(cols = all_of(cols), names_to = "Variable", values_to = "Valor")

    # Histograma comparativo
    ggplot(df_long, aes(x = Valor, fill = Origen)) +
      geom_histogram(alpha = 0.5, position = "identity", bins = 30, color = "black") +
      facet_wrap(~ Variable, scales = "free") +
      scale_fill_manual(values = c("blue", "red")) +
      theme_minimal() +
      ggtitle("Comparación de histogramas: df vs df_norm")
## Warning: Removed 652 rows containing non-finite outside the scale range
## (`stat_bin()`).

El método norm preserva bastante bien las distribuciones, excepto en Insulin, donde genera valores no fisiológicos que no deberían aceptarse sin corrección posterior.

Pruebas

for (col in cols) {
  cat("\nVariable:", col, "\n")
  print(wilcox.test(df[[col]], df_norm[[col]]))
}
## 
## Variable: Glucose 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_norm[[col]]
## W = 293225, p-value = 0.9786
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: BloodPressure 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_norm[[col]]
## W = 281325, p-value = 0.986
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: SkinThickness 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_norm[[col]]
## W = 206068, p-value = 0.8034
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: Insulin 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_norm[[col]]
## W = 146395, p-value = 0.3655
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: BMI 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df[[col]] and df_norm[[col]]
## W = 291126, p-value = 0.9595
## alternative hypothesis: true location shift is not equal to 0

En general: el método norm conserva muy bien la forma y posición de las distribuciones, pero en variables como Insulin puede introducir valores fuera del rango plausible, por lo que es recomendable considerar otro método.

5.1.2 Escogencia del Método de Imputación:

imp_pmm <- mice(df[cols], method = "pmm", m = 1, seed = 123)
## 
##  iter imp variable
##   1   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   2   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   3   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   4   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
##   5   1  Glucose  BloodPressure  SkinThickness  Insulin  BMI
df_pmm <- complete(imp_pmm)

for (col in cols) {
  na_idx <- is.na(df[[col]])
  df[na_idx, col] <- df_pmm[na_idx, col]
}
vis_miss(df)

5.2 Análisis de Outliers:

Iniciaremos de forma clasica realizando Boxplot para cada variable.

# Seleccionar columnas numéricas
df_num <- df[sapply(df, is.numeric)]

# Calcular límites de outliers por variable usando la regla de Tukey
outliers_df <- df_num %>%
  pivot_longer(cols = everything(), names_to = "variable", values_to = "value") %>%
  group_by(variable) %>%
  mutate(
    Q1 = quantile(value, 0.25, na.rm = TRUE),
    Q3 = quantile(value, 0.75, na.rm = TRUE),
    IQR = Q3 - Q1,
    Lower = Q1 - 1.5 * IQR,
    Upper = Q3 + 1.5 * IQR,
    is_outlier = value < Lower | value > Upper
  )

# 📌 Tabla con los outliers detectados
tabla_outliers <- outliers_df %>%
  filter(is_outlier) %>%
  select(variable, value) %>%
  arrange(variable, value)

# Mostrar tabla en consola
print(tabla_outliers)
## # A tibble: 115 × 2
## # Groups:   variable [7]
##    variable value
##    <chr>    <dbl>
##  1 Age       67  
##  2 Age       67  
##  3 Age       67  
##  4 Age       68  
##  5 Age       69  
##  6 Age       69  
##  7 Age       70  
##  8 Age       72  
##  9 Age       81  
## 10 BMI       52.3
## # ℹ 105 more rows
# 📌 Boxplots con outliers etiquetados
ggplot(outliers_df, aes(x = "", y = value)) +
  geom_boxplot(outlier.shape = NA) + # Evitar que el boxplot dibuje puntos duplicados
  geom_point(
    data = filter(outliers_df, is_outlier),
    aes(color = "Outlier"),
    size = 2
  ) +
  geom_text(
    data = filter(outliers_df, is_outlier),
    aes(label = round(value, 1)),
    vjust = -0.5,
    size = 2.5
  ) +
  facet_wrap(~ variable, scales = "free", ncol = 3) +
  theme_minimal() +
  labs(
    title = "Boxplots con valores atípicos identificados",
    x = "",
    y = "Valor"
  ) +
  theme(
    axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    legend.position = "none"
  )

De los boxplots en cuanto a outliers vemos que todas las variables los presentan a excepción de la variable Glucose. Entre las variables que mas valores atípicos tienen, destacan Insulin y DiabetesPedigreeFunction

# --- Función Hampel ---
hampel_filter <- function(x, t0 = 3) {
  med <- median(x, na.rm = TRUE)
  mad_val <- mad(x, constant = 1.4826, na.rm = TRUE)
  
  outliers <- abs(x - med) > (t0 * mad_val)
  x[outliers] <- med
  
  return(list(
    vector_imputado = x,
    indices_outliers = which(outliers),
    cantidad_outliers = sum(outliers)
  ))
}

# --- Dataset de ejemplo ---
# df <- tu_dataframe

# Copia de seguridad
df_original <- df
df_imputado <- df
resumen_outliers <- data.frame(Variable = character(),
                               Cantidad = integer(),
                               stringsAsFactors = FALSE)

# --- Aplicar Hampel a todas las variables numéricas ---
for (var in names(df_imputado)) {
  if (is.numeric(df_imputado[[var]])) {
    res <- hampel_filter(df_imputado[[var]])
    df_imputado[[var]] <- res$vector_imputado
    
    resumen_outliers <- rbind(resumen_outliers,
                              data.frame(Variable = var,
                                         Cantidad = res$cantidad_outliers))
  }
}

print(resumen_outliers)
##                   Variable Cantidad
## 1              Pregnancies       23
## 2                  Glucose        0
## 3            BloodPressure       11
## 4            SkinThickness        2
## 5                  Insulin       43
## 6                      BMI        4
## 7 DiabetesPedigreeFunction       40
## 8                      Age       27
# --- Preparar datos para gráficos ---
df_original_long <- df_original %>%
  pivot_longer(cols = where(is.numeric), names_to = "Variable", values_to = "Valor") %>%
  mutate(Tipo = "Antes")

df_imputado_long <- df_imputado %>%
  pivot_longer(cols = where(is.numeric), names_to = "Variable", values_to = "Valor") %>%
  mutate(Tipo = "Después")

df_comparacion <- bind_rows(df_original_long, df_imputado_long)

# --- Generar un gráfico por variable ---
graficos <- df_comparacion %>%
  group_split(Variable) %>%
  lapply(function(data_var) {
    ggplot(data_var, aes(x = Tipo, y = Valor, fill = Tipo)) +
      geom_boxplot(outlier.shape = 16, outlier.size = 1.5, alpha = 0.6) +
      labs(title = unique(data_var$Variable), x = "", y = "Valor") +
      theme_minimal() +
      theme(legend.position = "none",
            plot.title = element_text(size = 14, face = "bold"))
  })

# --- Combinar en una cuadrícula ---
wrap_plots(graficos) + plot_annotation(title = "Comparación Antes vs Después (Método Hampel)")

Análisis y tratamiento de outliers con el método de Hampel

En general,

  • En casi todas las variables, los puntos extremos (outliers) se reducen o desaparecen.
  • La mediana y el rango intercuartílico (la caja) prácticamente no cambian, lo que indica que el método es robusto y preserva la estructura central de los datos.
  • Algunos outliers siguen presentes porque Hampel no los considera extremos según su criterio estadístico (basado en MAD y mediana).

Variable por variable

Age
- Antes: valores de hasta ~81 años.
- Después: tope reducido a ~60, los casos muy extremos fueron reemplazados.

BMI
- Antes: valores de hasta ~67.1.
- Después: máximo reducido a ~50, outliers severos eliminados.

BloodPressure
- Antes: varios outliers altos (~122) y bajos (~24).
- Después: reducción en los valores extremos, especialmente los muy bajos.

DiabetesPedigreeFunction
- Antes: valores hasta ~2.5.
- Después: máximo cercano a ~1.0, gran limpieza de valores atípicos.

Glucose
- Antes: extremos cerca de ~200.
- Después: rango más controlado, pero aún hay algunos puntos que Hampel no considera outliers.

Insulin
- Antes: outliers muy extremos (hasta ~846).
- Después: máximo reducido a ~400, pero quedan algunos puntos altos que para Hampel no son atípicos bajo su umbral.

Pregnancies
- Antes: valores hasta 17 embarazos.
- Después: máximo alrededor de 10, reduciendo casos extremos.

SkinThickness
- Antes: valores hasta ~99 mm.
- Después: máximo cerca de ~60 mm, disminución importante de extremos.

A primera vista, podemos decir que el Filtro de Hampel logró detectar e imputar los outliers (utilizando la mediana) de forma exitosa, ya que gráficamente no se observan cambios drásticos en la distribución de los datos. Sin embargo, esto lo confirmaremos a continuación mediante una prueba no paramétrica (para datos que no siguen una distribución Normal), y así verificar si realmente no ha habido cambios estadísticamente significativos.

Prueba de Wilcox

cols <- c("Glucose","BloodPressure","SkinThickness","Insulin","BMI", "Age", "DiabetesPedigreeFunction","Pregnancies")
for (col in cols) {
  cat("\nVariable:", col, "\n")
  print(wilcox.test(df_original[[col]], df_imputado[[col]]))
}
## 
## Variable: Glucose 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df_original[[col]] and df_imputado[[col]]
## W = 294912, p-value = 1
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: BloodPressure 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df_original[[col]] and df_imputado[[col]]
## W = 296843, p-value = 0.824
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: SkinThickness 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df_original[[col]] and df_imputado[[col]]
## W = 295666, p-value = 0.9309
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: Insulin 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df_original[[col]] and df_imputado[[col]]
## W = 310457, p-value = 0.07369
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: BMI 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df_original[[col]] and df_imputado[[col]]
## W = 296438, p-value = 0.8607
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: Age 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df_original[[col]] and df_imputado[[col]]
## W = 304983, p-value = 0.246
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: DiabetesPedigreeFunction 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df_original[[col]] and df_imputado[[col]]
## W = 309472, p-value = 0.09391
## alternative hypothesis: true location shift is not equal to 0
## 
## 
## Variable: Pregnancies 
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  df_original[[col]] and df_imputado[[col]]
## W = 303422, p-value = 0.3241
## alternative hypothesis: true location shift is not equal to 0

Nótese que, para cada una de nuestras variables, el p-valor es mayor que 0.05; por lo tanto, no se rechaza la hipótesis nula y se concluye que no existen diferencias estadísticamente significativas entre las distribuciones de las variables antes y después del tratamiento de los outliers. En consecuencia, podemos afirmar que, desde el punto de vista estadístico, la imputación realizada fue adecuada.

6. EDA:

6.1 Análisis Univariado:

skim(df_imputado)
Data summary
Name df_imputado
Number of rows 768
Number of columns 9
_______________________
Column type frequency:
factor 1
numeric 8
________________________
Group variables None

Variable type: factor

skim_variable n_missing complete_rate ordered n_unique top_counts
Outcome 0 1 FALSE 2 0: 500, 1: 268

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
Pregnancies 0 1 3.55 2.96 0.00 1.00 3.00 6.00 11.00 ▇▃▂▂▂
Glucose 0 1 121.54 30.51 44.00 99.00 117.00 140.25 199.00 ▁▇▇▃▂
BloodPressure 0 1 72.22 11.42 38.00 64.00 72.00 80.00 106.00 ▁▅▇▅▁
SkinThickness 0 1 28.61 10.10 7.00 20.00 29.00 36.00 63.00 ▅▇▇▃▁
Insulin 0 1 130.27 71.52 14.00 75.00 125.00 168.00 360.00 ▆▇▅▂▁
BMI 0 1 32.31 6.67 18.20 27.48 32.30 36.52 53.20 ▃▇▇▃▁
DiabetesPedigreeFunction 0 1 0.42 0.23 0.08 0.24 0.37 0.56 1.11 ▇▇▅▂▁
Age 0 1 31.95 9.97 21.00 24.00 29.00 38.25 60.00 ▇▃▂▂▁

Pregnancies (Embarazos):
La media es de 3.55 embarazos con desviación estándar de 2.96, lo que indica variabilidad en la cantidad de embarazos. La mayoría de los casos se encuentra entre 1 y 6 embarazos, con mediana en 3, y pocos casos en 0 o valores más altos.

Glucose (Glucosa):
Presenta una media de 121.54 mg/dL y desviación estándar de 30.51. La mayoría de los valores se concentra entre 99 y 140, aunque hay algunos valores más bajos (44) y más altos (140), mostrando cierta dispersión algo tipico en la población.

BloodPressure (Presión arterial):
Con media de 72.22 mmHg y desviación estándar de 11.42, la mayoría de los individuos se encuentra dentro de rangos normales, aunque existen casos con presión más baja (38), que podrían ser considerados atípicos.

SkinThickness (Grosor de piel):
La media es de 28.61 mm y desviación estándar de 10.10, indicando dispersión moderada. La mediana es 29 mm, y la mayoría se concentra entre 20 y 36 mm, con algunos valores bajos (7 mm) que podrían considerarse outliers leves.

Insulin (Insulina):
Con una media de 130.27 µU/mL y desviación estándar de 71.52, los niveles de insulina muestran alta variabilidad. Los valores se encuentran entre 14 y 168, con la mediana en 125, reflejando heterogeneidad fisiológica y algunos posibles outliers.

BMI (Índice de masa corporal):
La media es 32.31 kg/m² con desviación estándar de 6.67. La mayoría de los individuos tiene sobrepeso u obesidad, concentrándose entre 27.48 y 36.53, con la mediana en 32.30, mientras que valores más bajos (18.2) son menos frecuentes.

DiabetesPedigreeFunction (Función de pedigree diabético):
Presenta media 0.416 y desviación estándar 0.229. La mayoría de los casos tiene riesgo genético bajo a moderado, con percentiles 25% en 0.244, mediana 0.372 y 75% en 0.560, existiendo algunos individuos con mayor predisposición.

Age (Edad):
La media es 31.95 años con desviación estándar 9.97. La población es relativamente joven, concentrándose entre 24 y 38 años, con mediana en 29 y pocos casos mayores.

for (var in names(df_imputado)) {
  if (is.numeric(df_imputado[[var]])) {
    
    # Histograma + densidad
    p1 <- ggplot(df_imputado, aes(x = .data[[var]])) +
      geom_histogram(aes(y = ..density..), bins = 30, fill = "skyblue", color = "black", alpha = 0.7) +
      geom_density(color = "red", size = 1) +
      labs(title = paste("Histograma y densidad de", var), x = var, y = "Densidad") +
      theme_minimal()
    
    # Boxplot
    p2 <- ggplot(df_imputado, aes(y = .data[[var]])) +
      geom_boxplot(fill = "lightgreen", color = "black", alpha = 0.7) +
      labs(title = paste("Boxplot de", var), y = var) +
      theme_minimal()
    
    graficos[[var]] <- grid.arrange(p1, p2, ncol = 2)
  }
}
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: The dot-dot notation (`..density..`) was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(density)` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

Pregnancies: En el histograma se observa que los datos presentan un sesgo positivo, evidenciado por una cola más extendida hacia la derecha. Esto se confirma en el boxplot, donde la mediana aparece ligeramente desplazada hacia la izquierda y el bigote correspondiente al tercer cuartil es más largo, reflejando el mismo sesgo identificado en el histograma.

Glucose: El histograma muestra una distribución con sesgo positivo, evidenciado por una cola más alargada hacia la derecha. Esto coincide con el boxplot, donde la mediana se encuentra centrada pero el bigote superior es más extenso que el inferior, lo que confirma la presencia del sesgo observado en el histograma.

BloodPressure: En el histograma observamos que la distribución es aproximadamente simétrica, con una ligera concentración de valores alrededor de la media (~70–75). La curva de densidad confirma esta tendencia, mostrando una forma cercana a la normalidad. En el boxplot, la mediana se encuentra centrada dentro de la caja, y los bigotes son relativamente equilibrados, lo que refuerza la idea de simetría. Sin embargo, se identifican algunos valores atípicos tanto por encima como por debajo de los límites, que podrían corresponder a casos clínicamente relevantes de presión arterial inusualmente alta o baja.

SkinThickness: El histograma muestra una distribución con ligera asimetría positiva, evidenciada por una cola más extendida hacia la derecha. En el boxplot, la mediana se encuentra centrada, pero se observa un valor atípico en el extremo superior, lo que refuerza la presencia de algunos registros elevados.

Insulin: Presenta una marcada asimetría positiva, con una cola larga hacia valores altos. El boxplot confirma esta observación, mostrando múltiples valores atípicos por encima del tercer cuartil, lo que indica que existen casos con niveles de insulina considerablemente más altos que el resto.

BMI: La distribución presenta una ligera asimetría positiva, con la mayoría de los valores concentrados alrededor de la media y una cola hacia la derecha. El boxplot muestra algunos valores atípicos en el extremo superior, aunque en menor cantidad que en la variable Insulin.

DiabetesPedigreeFunction: La variable tiene una asimetría positiva notable, con una gran concentración de observaciones en valores bajos y una cola extendida hacia la derecha. El boxplot refleja la presencia de varios valores atípicos superiores, lo que sugiere que en algunos casos el historial familiar de diabetes influye de manera excepcionalmente elevada.

Age: Tiene una asimetría positiva muy clara, con datos concentrados entre los 20-30 años y una cola que se extiende hacia la derecha. EL boxplot confirma lo observado en el histograma pues es claro como la mediana se encuentra mas hacia la izquierda y asi mismo el bigote superior es bastante alargado.

library(ggplot2)

# Suponiendo que tu dataframe se llama df
ggplot(df_imputado, aes(x = factor(Outcome), fill = factor(Outcome))) +
  geom_bar() +
  labs(title = "Distribución de Outcome",
       x = "Outcome",
       y = "Cantidad") +
  scale_fill_manual(values = c("lightblue", "salmon")) +
  theme_minimal()

En el caso de la variable Outcome, se observa que la clase más prevalente es 0 (no tener diabetes), con aproximadamente 500 observaciones, mientras que la clase 1 (tener diabetes) presenta alrededor de 260–270 observaciones. Esto indica un desequilibrio de clases, ya que hay casi el doble de casos sin diabetes que con diabetes.

6.2 Análisis Bivariado:

Correlación:

cor_matrix <- cor(df_imputado[,cols], use = "complete.obs")
ggcorrplot(cor_matrix, hc.order = TRUE, type = "lower",
           lab = TRUE, lab_size = 3, colors = c("blue", "white", "red"))

El análisis de la matriz de correlación revela varias relaciones destacables entre las variables estudiadas. Se observa que BMI y SkinThickness presentan una correlación positiva fuerte (r = 0.69), lo que indica que a medida que aumenta el índice de masa corporal, también tiende a incrementarse el grosor de la piel. Por otra parte, la variable Insulin muestra una correlación positiva moderada con Glucose (r = 0.47), sugiriendo una relación directa entre los niveles de insulina y glucosa en sangre. Asimismo, se identifica una correlación positiva moderada-fuerte entre Pregnancies y Age, lo que refleja que, generalmente, a mayor edad, se registra un mayor número de embarazos. En contraste, las demás variables presentan, en términos generales, correlaciones positivas débiles, indicando relaciones poco marcadas entre ellas.

Graficos de Dispersión:

pairs(df_imputado[,cols],
      main = "Matriz de Gráficos de Dispersión",
      pch = 19,        # tipo de punto
      col = "red")    # color de puntos

# Seleccionar variables numéricas
numeric_vars <- c("Pregnancies", "Glucose", "BloodPressure", 
                  "SkinThickness", "Insulin", "BMI", 
                  "DiabetesPedigreeFunction", "Age")

# Convertir a formato largo
df_long <- df %>%
  select(all_of(numeric_vars), Outcome) %>%
  pivot_longer(cols = all_of(numeric_vars),
               names_to = "Variable",
               values_to = "Valor")

# Crear matriz de boxplots
ggplot(df_long, aes(x = factor(Outcome), y = Valor, fill = factor(Outcome))) +
  geom_boxplot() +
  facet_wrap(~Variable, scales = "free_y") +  # un boxplot por variable
  labs(title = "Distribución de variables numéricas según Outcome",
       x = "Outcome",
       y = "Valor") +
  scale_fill_manual(values = c("lightblue", "salmon")) +
  theme_minimal()

El análisis de la matriz de boxplots muestra diferencias notables entre los grupos de Outcome 0 y 1. La variable Glucose presenta valores significativamente más altos en los individuos con diabetes (Outcome 1), confirmando que la glucosa elevada es un fuerte indicador de la enfermedad. De manera similar, el BMI es ligeramente mayor en este grupo, lo que sugiere que un mayor índice de masa corporal puede estar asociado a un mayor riesgo de diabetes. La edad también muestra una mediana superior en los individuos con diabetes, indicando que la probabilidad de presentar la enfermedad aumenta con la edad. En cuanto a Insulin, se observa una tendencia a valores más altos en Outcome 1, aunque con gran dispersión, lo que podría reflejar resistencia a la insulina; sin embargo, la presencia de muchos valores atípicos limita la interpretación precisa. SkinThickness es ligeramente mayor en los individuos con diabetes, posiblemente relacionado con adiposidad subcutánea, mientras que BloodPressure muestra diferencias mínimas entre los grupos, sugiriendo que no es un factor diferenciador relevante en este dataset. La DiabetesPedigreeFunction es algo mayor en Outcome 1, lo que indica una posible influencia genética, y finalmente, los individuos con diabetes tienden a haber tenido un mayor número de embarazos, reflejando que, en este conjunto de datos, más embarazos se asocian a un mayor riesgo de diabetes.