1 Enunciado


Todo estudio analítico debe nacer de una necesidad por parte del negocio o de una voluntad de dotarle de un conocimiento contenido en los datos y que solo podremos obtener a través de una colección de buenas prácticas basadas en la Minería de Datos.

El mundo de la analítica de datos se sustenta en 3 ejes:

A. Uno de ellos es el profundo conocimiento que deberíamos tener del negocio al que tratamos de dar respuestas mediante los estudios analíticos.

B. El otro gran eje es sin duda las capacidades analíticas que seamos capaces de desplegar y en este sentido, las dos prácticas de esta asignatura pretenden que el estudiante realice un recorrido sólido por este segundo eje.

C. El tercer eje son los datos. Las necesidades del Negocio deben concretarse con preguntas analíticas que a su vez sean viables responder a partir de los datos de que disponemos. La tarea de analizar los datos es sin duda importante, pero la tarea de identificarlos y obtenerlos va a ser para un analista un reto permanente.

Como primera parte del estudio analítico que nos disponemos a realizar, se pide al estudiante que complete los siguientes pasos:

  1. Plantear un problema de analítica de datos detallando los objetivos analíticos y explica una metodología para resolverlos de acuerdo con lo que se ha practicado en las PEC y lo que se ha aprendido en el material didáctico.

  2. Seleccionar un juego de datos y justificar su elección. El juego de datos deberá tener capacidades para que se le puedan aplicar algoritmos supervisados y algoritmos no supervisados en la PRA2 y deberá estar alineado con el problema analítico planteado en el paso anterior.

Adjuntamos aquí una lista de portales de datos abiertos para seleccionar el juego de datos. Se pueden usar otras fuentes para obtener vuestro juego de datos, pero recordad de citarlas:

  1. Realizar un análisis exploratorio del juego de datos seleccionado.

  2. Realizar tareas de limpieza y acondicionado para poder ser usado en procesos de modelado.

  3. Realizar métodos de discretización.

  4. Aplicar un estudio PCA sobre el juego de datos. A pesar de no estar explicado en el material didáctico, se valorará si en lugar de PCA investigáis por vuestra cuenta y aplicáis SVD (Single Value Decomposition).

Recordad que para todas las PRA es necesario documentar en cada apartado del ejercicio práctico que se ha hecho, por qué se ha hecho y cómo se ha hecho. Asimismo, todas las decisiones y conclusiones deberán ser presentados de forma razonada y clara, contextualizando los resultados, es decir, especificando todos y cada uno de los pasos que se hayan llevado a cabo para su resolución. Por último, incluid una conclusión final resumiendo los resultados obtenidos en la práctica e indicad eventuales citaciones bibliográficas, fuentes internas/externas y materiales de investigación.


2 Recursos de programación



3 INTRODUCCIÓN


El presente documento describe un flujo analítico completo y contextualizado de un proyecto de Data Mining para su entrega como PRA1.


4 EJERCICIO 1: objetivos analíticos y metodología


A partir del juego de datos seleccionado: **Diabetes_prediction_dataset* se plantea un proyecto de clasificación que seguirá en su ejecución la metodolOgia CRISP-DM (Cross Industry Standard Process for Data Mining) que se compone de las 6 fases principales y responde a las siguientes preguntas:

  1. Comprensión del negocio - ¿Qué necesita el negocio?
  2. Comprensión de los datos - ¿Qué datos tenemos/necesitamos? ¿Están limpios?
  3. Preparación de los datos - ¿Cómo organizamos los datos para el modelado?
  4. Modelado - ¿Qué técnicas de modelado debemos aplicar?
  5. Evaluación - ¿Qué modelo cumple mejor con los objetivos del negocio?
  6. Implementación - ¿Cómo acceden los interesados a los resultados?

4.1 Comprensión del Negocio


Precedente

La diabetes es una condición crónica definida por un nivel elevado de glucosa en sangre. La diabetes causa daño progresivo a los riñones, los ojos y el corazón con el tiempo. La detección temprana de la diabetes es una tarea desafiante. La enfermedad de la diabetes se puede dividir en tres categorías:

  • Diabetes tipo 1: También conocida como diabetes juvenil o diabetes insulinodependiente, ocurre cuando el sistema inmunitario del cuerpo daña las células liberadoras de insulina, deteniendo la producción de insulina.
  • Diabetes tipo 2: También conocida como diabetes no insulinodependiente, ocurre cuando el cuerpo desarrolla resistencia a la insulina o deja de producir insulina. Puede ocurrir a cualquier edad.
  • Diabetes gestacional: Es una forma de diabetes que afecta a las mujeres embarazadas.

Contexto y Objetivos

Este proyecto responde a una necesidad creciente de desarrollar sistemas predictivos capaces de identificar pacientes en riesgo de desarrollar diabetes de tipo 2 basados en factores clínicos, de estilo de vida y antecedentes médicos. Sabido es, que la diabetes es la causante de enfermedades cardíacas, problemas renales y dificultades oculares, y es por ello, que es fundamental prevenir, controlar y crear conciencia al respecto.

El objetivo es la mejora de la predicción y diagnóstico temprano de diabetes tipo 2 proporcionando a profesionales de la salud y sistemas sanitarios una herramienta predictiva que permita identificar personas en riesgo antes de que desarrollen síntomas graves. Este conocimiento permitirá reducir costos sanitarios, optimizar recursos preventivos y diseñar campañas de concienciación más efectivas en una primera instancia.

Además, se plantea como objetivo secundario, la autoalimentación o agregación de datos recopilados de los pacientes detectados con la variante de tipo 2, con la finalidad de construir una base de conocimiento que pueda ser utilizada para apoyar al diseño computacional de nuevas moléculas terapéuticas o la optimización y mejora de los tratamientos actuales.

Para ello, se desarrollará un modelo de clasificación para predecir si una persona padecerá diabetes (binomial 0-1) a partir de variables como edad, sexo, hiertensión, enfermedades cardíacas, IMC, niveles de hemoglobina glicosilada, niveles de glucosa en sangre y hábitos de tabaquismo.

Además, se explora los patrones ocultos e la población mediante técnicas de clustering no supervisado, con el fin de identificar perfiles de riesgo.

Las medidas de éxtito se basa en: + La precisión predictiva del modelo + La interpretabilidad de los resultados + Su aplicación clínica en la toma de decisiones preventivas

Con la entrega del presente documento se incluye la definición del problema desde la perspectiva de negocio, incluyendo objetivos, impacto esperado y justificación.


4.2 Comprensión de los Datos


El conjunto de datos utilizado en este estudio es el

El archivo está en formato CSV (Comma Separated Values) y contiene 10000 observaciones y 21 variables distintas y bien estructuradas. El dataset presenta una combinación de variables numéricas continuas, categóricas y binarias.

El conjunto de las variables nos permite abordar tanto los problemas de modelado supervisado (clasificación de riesgo de diabetes) como los no supervisado (identificación de perfiles de riesgo mediante técnicas de agrupamiento).

El dataset de Diabetes Prediction es apropiado para el objetivo de este proyecto, ya que ofrece una representación realista. Contiene más de 5 variables numéricas continuas: Age, BMI, Waist_Circumference, Fasting_Blood_Glucose, HbA1c, entre otras, al menos 2 variables categóricas: Sex, Ethnicity, Physical_Activity_Level, etc., y más de 1 variable binaria: Family_History_of_Diabetes, Previous_Gestational_Diabetes, cumpliendo todos los requisitos mínimos solicitados para la elección de los datos.


4.3 Preparación de los datos


La fase de preparación de los datos tiene como objetivo transformar el conjunto de datos crudo en una estructura limpia, balanceada y normalizada para el aprendizaje automático, tanto supervisado como no supervisado. Para ello, en primer lugar se procederá a la eliminación o de valores faltantes, garantizando así la integridad del dataset. Posteriormente, se realizará la conversión de las variables categóricas, como Sex y Ethnicity, asegurando su correcto tratamiento en los modelos de clasificación. Las variables numéricas continuas Age, BMI, HbA1c, Fasting_Blood_Glucose serán normalizadas si fueran necesario. Además, se analiza el balance de clases de la variable objetivo diabetes y, si se detectara un desbalanceo significativo, se aplicarán técnicas de sobremuestreo como SMOTE para equilibrar la representación de las clases. El producto final de esta fase será un dataset limpio, codificado, balanceado y estructurado, preparado para el modelado predictivo y de segmentación.


4.4 Modelado


El objetivo es construir y comparar diferentes modelos de segmentación que permitan identificar perfiles de pacientes en función de sus características clínicas, demográficas y de estilo de vida. En este proyecto se priorizarán técnicas de aprendizaje no supervisado, dejando de lado por el momento los modelos de clasificación supervisada como regresiones o redes neuronales.

Para llevar a cabo la segmentación de los datos, se aplicarán distintos métodos de agrupamiento: Hierarchical Clustering, que permite descubrir estructuras jerárquicas naturales en los datos; DBSCAN, Density-Based Spatial Clustering of Applications with Noise, y OPTICS, Ordering Points To Identify the Clustering Structure, ambos métodos basados en la densidad, que son especialmente útiles para encontrar clusters de formas arbitrarias y manejar datos con ruido. Asimismo, se utilizará el método de agregación k-means para particionar los datos en grupos.

Para determinar el número óptimo de clusters, se aplicarán criterios de evaluación como el Método del Codo Elbow Method y el índice de Silhouette, que ayudan a valorar la calidad de las agrupaciones obtenidas. Además, con el objetivo de mejorar la eficiencia del modelado y facilitar la interpretación visual, se llevarán a cabo técnicas de reducción de dimensionalidad, como el Análisis de Componentes Principales PCA y la Descomposición en Valores Singulares SVD.

Todos los modelos son validados mediante métricas de cohesión y separación de clusters. El producto final de esta fase será un conjunto de segmentaciones validadas, permitiendo así descubrir patrones en la población que podrían ser utilizados en posteriores estudios predictivos.


4.5 Evaluación


El objetivo se centra en deteterminar cuál de los modelos desarrollados contempla todos los objetivos de negocio y obtiene la mayor precisión predictiva y mejor interpretabilidad clínica. Para ello, evaluamos los médelos utilizando métricas como Accuracy, Recall, Precision, F1-score y área bajo la curva ROC (AUC-ROC). Estas métricas permiten valorar la tasa de aciertos pero también la capacidad para detectar correctamente a los pacientes diabéticos Recall y su equilibrio entre sensibilidad y especificidad F1-score. Adicionalmente, se comprueba la robustez frente a la posibilidad de un desequilibrio de clases y se analiza su aplicación práctica. El producto final de esta fase es la selección del modelo óptimo justificado mediante las matrices de confusión y curvas ROC.


4.6 Implementación


La fase de implementación tiene como objetivo presentar los resultados de forma accesible, transparente y reproducible para todos los interesados, que en nuestro caso son principalmente profesionales de la salud y responsables de políticas públicas relacionadas con la salut. Para ello, se elabora un documento .Rmd en RMarkdown/LaTeX que documente todo el flujo del trabajo de este estudio, y éste genere un informe ejecutable en formato HTML. Además, se propone un sistema de mantenimiento y autoalimentación que permita actualizar periódicamente el modelo a medida que se incorporen nuevos datos de pacientes, asegurando la mejora continua del sistema predictivo.(Este último punto queda fuera del alcance de este proyecto).

# Limpiamos el entorno
rm(list = ls())

5 EJERCICIO 2: Descripción del origen del conjunto de datos


En este apartado se describen y analizan los datos extraídos del open data donde se descarga a través del API el dataset y tal como hemos mencionado en el apartado anterior de comprensión de los datos, este dataset fue creado para estudiar los factores de riesgo asociados a la diabetes tipo 2, proporcionando una base de datos que contiene características demográficas, clínicas y de hábitos de vida de los pacientes.

La recopilación de los datos se realizó mediante registros médicos y encuestas clínicas, integrando las variables de la edad, género, antecedentes de hipertensión, enfermedades cardíacas, niveles de glucosa en sangre, niveles de colesterol, consumo de alcohol, entre otros. El propósito original de este conjunto de datos es apoyar el desarrollo de modelos predictivos, así como la detección de patrones asociados al riesgo de desarrollar diabetes.

El dataset está disponible públicamente bajo una , lo que permite su uso libre para fines académicos, de investigación y de desarrollo de proyectos de ciencia de datos. Su estructura, número de observaciones y la diversidad de variables permiten la aplicación de técnicas de aprendizaje automático y supervisadas como no supervisadas.

Instalación y carga de librerías necesarias para descargar los datos y realizar el estudio.

# Librerías necesarias:
if (!require(httr)) install.packages("httr")
if (!require(jsonlite)) install.packages("jsonlite")
if (!require(utils)) install.packages("utils")
if (!require(dplyr)) install.packages("dplyr")
if (!require(GGally)) install.packages("GGally")
if (!require(corrplot)) install.packages("corrplot")
if (!require(tidyr)) install.packages("tidyr")
if (!require(broom)) install.packages("broom")
if (!require(ggridges)) install.packages("ggridges")
if (!require("fmsb")) install.packages("fmsb")
if (!require("factoextra")) install.packages("factoextra")
if (!require(cluster)) install.packages("cluster")



library(httr)      # para conectar con APIs externas a través de dolicitudes http
library(jsonlite)  # para conversión de objetos R a Json y viceversa
library(utils)     # para importar y exportar datos
library(dplyr)     # para preprocesar y limpiar datos
library(ggplot2)   # para gráficos (violin plot, boxplot, scatterplot...)
library(ggridges)  # Para density ridge plots
library(GGally)    # Para ggpairs scatterplot matrices
library(corrplot)  # para matrices de correlación
library(tidyr)     # para reordenar y transformar estructuras de datos
library(broom)     # para presentar resultados estadísticos en tabla
library(fmsb)      # para gráficos de radar (spider plots)
library(factoextra)  # para visualizar analisis multivariados
library(cluster)

A posteriori, configuramos la API KAGGLE para la descarga del dataset.

# API Key
kaggle_api <- fromJSON("kaggle.json")

# Credenciales
Sys.setenv(KAGGLE_USERNAME = kaggle_api$username)
Sys.setenv(KAGGLE_KEY = kaggle_api$key)

Creamos un nuevo directorio / carpeta donde guardaremos el archivo CSV

# Crearemos un nuevo directorio donde guardar el archivo a descargar
if (!dir.exists("data")) {
  dir.create("data")
}
Descargamos el archivo con el que vamos a trabajar con los sigueintes comandos:
# Definimos el nombre del archivo zip y la ruta de extracción
zip_path <- "data/diabetes-prediction-dataset.zip"

# Descargamos el dataset en la carpeta creada solo si nO existe
if (!file.exists(zip_path)) {
  system("kaggle datasets download -d marshalpatel3558/diabetes-prediction-dataset -p data")
} else {
  message("El archivo fue descargado previamente.")
}

# Descomprimimos solo si no ha sido descomprimido antes
if (!file.exists(file.path("data", "diabetes_dataset.csv"))) {
  unzip(zipfile = zip_path, exdir = "data")
} else {
  message("El archivo ZIP fue descomprimido previamente..")
}


# Listar archivos de la carpeta data
list.files("data", recursive = TRUE)
## [1] "diabetes-prediction-dataset.zip" "diabetes_dataset.csv"

Exploración del conjunto de datos

#
df_origin <- read.csv("data/diabetes_dataset.csv")

# Dimensiones
cat("Número de observaciones:", nrow(df_origin), "\n")
## Número de observaciones: 10000
cat("Número de variables:", ncol(df_origin), "\n")
## Número de variables: 21
# Imprimimos las primeras filas
head(df_origin)
##   X Age    Sex Ethnicity  BMI Waist_Circumference Fasting_Blood_Glucose HbA1c
## 1 0  58 Female     White 35.8                83.4                 123.9  10.9
## 2 1  48   Male     Asian 24.1                71.4                 183.7  12.8
## 3 2  34 Female     Black 25.0               113.8                 142.0  14.5
## 4 3  62   Male     Asian 32.7               100.4                 167.4   8.8
## 5 4  27 Female     Asian 33.5               110.8                 146.4   7.1
## 6 5  40 Female     Asian 33.6                96.1                  75.0  13.5
##   Blood_Pressure_Systolic Blood_Pressure_Diastolic Cholesterol_Total
## 1                     152                      114             197.8
## 2                     103                       91             261.6
## 3                     179                      104             261.0
## 4                     176                      118             183.4
## 5                     122                       97             203.2
## 6                     170                       90             152.3
##   Cholesterol_HDL Cholesterol_LDL  GGT Serum_Urate Physical_Activity_Level
## 1            50.2            99.2 37.5         7.2                Moderate
## 2            62.0           146.4 88.5         6.1                Moderate
## 3            32.1           164.1 56.2         6.9                     Low
## 4            41.1            84.0 34.4         5.4                     Low
## 5            53.9            92.8 81.9         7.4                Moderate
## 6            44.5           190.0 77.5         6.4                     Low
##   Dietary_Intake_Calories Alcohol_Consumption Smoking_Status
## 1                    1538            Moderate          Never
## 2                    2653            Moderate        Current
## 3                    1684               Heavy         Former
## 4                    3796            Moderate          Never
## 5                    3161               Heavy        Current
## 6                    3460                None          Never
##   Family_History_of_Diabetes Previous_Gestational_Diabetes
## 1                          0                             1
## 2                          0                             1
## 3                          1                             0
## 4                          1                             0
## 5                          0                             0
## 6                          1                             1

Verificamos la estructura del juego de datos, el número de columnas y el tipo de datos que contiene, así como un ejemplo de los valores.

# Mostramos la estructura de los datos
str(df_origin)
## 'data.frame':    10000 obs. of  21 variables:
##  $ X                            : int  0 1 2 3 4 5 6 7 8 9 ...
##  $ Age                          : int  58 48 34 62 27 40 58 38 42 30 ...
##  $ Sex                          : chr  "Female" "Male" "Female" "Male" ...
##  $ Ethnicity                    : chr  "White" "Asian" "Black" "Asian" ...
##  $ BMI                          : num  35.8 24.1 25 32.7 33.5 33.6 33.2 26.9 27 24 ...
##  $ Waist_Circumference          : num  83.4 71.4 113.8 100.4 110.8 ...
##  $ Fasting_Blood_Glucose        : num  124 184 142 167 146 ...
##  $ HbA1c                        : num  10.9 12.8 14.5 8.8 7.1 13.5 13.3 10.9 7 14 ...
##  $ Blood_Pressure_Systolic      : int  152 103 179 176 122 170 131 121 132 146 ...
##  $ Blood_Pressure_Diastolic     : int  114 91 104 118 97 90 80 83 118 83 ...
##  $ Cholesterol_Total            : num  198 262 261 183 203 ...
##  $ Cholesterol_HDL              : num  50.2 62 32.1 41.1 53.9 44.5 77.9 69.7 73.2 53.3 ...
##  $ Cholesterol_LDL              : num  99.2 146.4 164.1 84 92.8 ...
##  $ GGT                          : num  37.5 88.5 56.2 34.4 81.9 77.5 52.1 72 76.4 14.5 ...
##  $ Serum_Urate                  : num  7.2 6.1 6.9 5.4 7.4 6.4 4.7 5.6 6.2 6.9 ...
##  $ Physical_Activity_Level      : chr  "Moderate" "Moderate" "Low" "Low" ...
##  $ Dietary_Intake_Calories      : int  1538 2653 1684 3796 3161 3460 3107 2390 3844 2230 ...
##  $ Alcohol_Consumption          : chr  "Moderate" "Moderate" "Heavy" "Moderate" ...
##  $ Smoking_Status               : chr  "Never" "Current" "Former" "Never" ...
##  $ Family_History_of_Diabetes   : int  0 0 1 1 0 1 0 0 1 1 ...
##  $ Previous_Gestational_Diabetes: int  1 1 0 0 0 1 0 1 0 0 ...

Descripción de las variables contenidas en el fichero y contruimos un diccionario de datos utilizando la documentación auxiliar.


6 EJERCICIO 3: Análisis Exploratorio


Resumen estadístico básico del conjunto de los datos

Para las variables numéricas proporciona los valores del min, primer cuartil (25%), mediana (50%), mean, tercer cuartil (75%) y máx. Y, para las variables categóricas (de tipo factor o character) devuelve el recuento de observaciones de cada categoría.

# Estadísticos básicos
summary(df_origin)
##        X             Age            Sex             Ethnicity        
##  Min.   :   0   Min.   :20.00   Length:10000       Length:10000      
##  1st Qu.:2500   1st Qu.:32.00   Class :character   Class :character  
##  Median :5000   Median :45.00   Mode  :character   Mode  :character  
##  Mean   :5000   Mean   :44.62                                        
##  3rd Qu.:7499   3rd Qu.:57.00                                        
##  Max.   :9999   Max.   :69.00                                        
##       BMI        Waist_Circumference Fasting_Blood_Glucose     HbA1c       
##  Min.   :18.50   Min.   : 70.0       Min.   : 70.0         Min.   : 4.000  
##  1st Qu.:24.10   1st Qu.: 82.2       1st Qu.:102.2         1st Qu.: 6.800  
##  Median :29.50   Median : 94.9       Median :134.5         Median : 9.500  
##  Mean   :29.42   Mean   : 94.8       Mean   :134.8         Mean   : 9.508  
##  3rd Qu.:34.70   3rd Qu.:107.0       3rd Qu.:167.8         3rd Qu.:12.300  
##  Max.   :40.00   Max.   :120.0       Max.   :200.0         Max.   :15.000  
##  Blood_Pressure_Systolic Blood_Pressure_Diastolic Cholesterol_Total
##  Min.   : 90.0           Min.   : 60.00           Min.   :150.0    
##  1st Qu.:112.0           1st Qu.: 75.00           1st Qu.:187.9    
##  Median :134.0           Median : 89.00           Median :225.5    
##  Mean   :134.2           Mean   : 89.56           Mean   :225.2    
##  3rd Qu.:157.0           3rd Qu.:105.00           3rd Qu.:262.4    
##  Max.   :179.0           Max.   :119.00           Max.   :300.0    
##  Cholesterol_HDL Cholesterol_LDL      GGT          Serum_Urate   
##  Min.   :30.00   Min.   : 70.0   Min.   : 10.00   Min.   :3.000  
##  1st Qu.:42.30   1st Qu.:101.7   1st Qu.: 32.60   1st Qu.:4.200  
##  Median :55.20   Median :134.4   Median : 55.45   Median :5.500  
##  Mean   :55.02   Mean   :134.4   Mean   : 55.17   Mean   :5.503  
##  3rd Qu.:67.90   3rd Qu.:166.4   3rd Qu.: 77.50   3rd Qu.:6.800  
##  Max.   :80.00   Max.   :200.0   Max.   :100.00   Max.   :8.000  
##  Physical_Activity_Level Dietary_Intake_Calories Alcohol_Consumption
##  Length:10000            Min.   :1500            Length:10000       
##  Class :character        1st Qu.:2129            Class :character   
##  Mode  :character        Median :2727            Mode  :character   
##                          Mean   :2742                               
##                          3rd Qu.:3368                               
##                          Max.   :3999                               
##  Smoking_Status     Family_History_of_Diabetes Previous_Gestational_Diabetes
##  Length:10000       Min.   :0.000              Min.   :0.0000               
##  Class :character   1st Qu.:0.000              1st Qu.:0.0000               
##  Mode  :character   Median :1.000              Median :1.0000               
##                     Mean   :0.507              Mean   :0.5165               
##                     3rd Qu.:1.000              3rd Qu.:1.0000               
##                     Max.   :1.000              Max.   :1.0000

Distribución de los datos

Evaluar la distribución permite identificar patrones como asimetrías, presencia de outliers, concentraciones de valores, o desviaciones respecto a una distribución normal, lo que tiene un impacto directo en la selección de las técnicas de modelado.

# Creamos una copia del original antes de tratar los datos
df <- df_origin

# Eliminamos la primera columna (índice=X) que no necesitamos
df <- df[, -1]

# Dividimos el dataset en v. numéricas y categóricas
df_num <- df[sapply(df, is.numeric)]
cat("Variables numéricas:\n")
## Variables numéricas:
print(names(df_num))
##  [1] "Age"                           "BMI"                          
##  [3] "Waist_Circumference"           "Fasting_Blood_Glucose"        
##  [5] "HbA1c"                         "Blood_Pressure_Systolic"      
##  [7] "Blood_Pressure_Diastolic"      "Cholesterol_Total"            
##  [9] "Cholesterol_HDL"               "Cholesterol_LDL"              
## [11] "GGT"                           "Serum_Urate"                  
## [13] "Dietary_Intake_Calories"       "Family_History_of_Diabetes"   
## [15] "Previous_Gestational_Diabetes"
df_cat <- df[sapply(df, is.character)]
cat("\nVariables categóricas:\n")
## 
## Variables categóricas:
print(names(df_cat))
## [1] "Sex"                     "Ethnicity"              
## [3] "Physical_Activity_Level" "Alcohol_Consumption"    
## [5] "Smoking_Status"
#Identificamos las variables binarias
binarias <- names(df_num)[sapply(df_num, function(x) length(unique(x)) == 2)]

cat("\nVariables binarias detectadas:\n")
## 
## Variables binarias detectadas:
print(binarias)
## [1] "Family_History_of_Diabetes"    "Previous_Gestational_Diabetes"
# Contar los valores por variable categórica
cat("Conteo de valores únicos por variable categórica:\n")
## Conteo de valores únicos por variable categórica:
for (colname in names(df_cat)) {
  cat("\n", colname, ":\n")
  print(table(df_cat[[colname]]))
}
## 
##  Sex :
## 
## Female   Male 
##   5005   4995 
## 
##  Ethnicity :
## 
##    Asian    Black Hispanic    White 
##     2503     2539     2476     2482 
## 
##  Physical_Activity_Level :
## 
##     High      Low Moderate 
##     3341     3372     3287 
## 
##  Alcohol_Consumption :
## 
##    Heavy Moderate     None 
##     3307     3373     3320 
## 
##  Smoking_Status :
## 
## Current  Former   Never 
##    3364    3330    3306
# Lista de variables categóricas
variables_cat <- names(df_cat)

# Gráficamos para cada variable
for (colname in variables_cat) {
  
  # Creamos data frame
  temp_df <- as.data.frame(table(df_cat[[colname]]))
  colnames(temp_df) <- c("Categoria", "Frecuencia")
  
  # Graficamos
  p <- ggplot(temp_df, aes(x = reorder(Categoria, -Frecuencia), y = Frecuencia, fill = Frecuencia)) +
    geom_bar(stat = "identity") +
    geom_text(aes(label = Frecuencia), vjust = -0.5, size = 3) +
    scale_fill_gradient(low = "skyblue", high = "blue") +  # Colores según frecuencia
    labs(title = paste("Distribución de", colname),
         x = colname,
         y = "Frecuencia") +
    theme_minimal() +
    theme(axis.text.x = element_text(angle = 45, hjust = 1),
          plot.title = element_text(face = "bold", size = 14)) +
    guides(fill = "none")  # Quitar leyenda de colores
  
  print(p)
}

# Variables binarias
binarias <- c("Family_History_of_Diabetes", "Previous_Gestational_Diabetes")

# Graficar cada binaria
for (colname in binarias) {
  
  # Data frame temporal para graficar
  temp_df <- as.data.frame(table(df[[colname]]))
  colnames(temp_df) <- c("Categoria", "Frecuencia")
  
  # Creamos el gráfico
  p <- ggplot(temp_df, aes(x = as.factor(Categoria), y = Frecuencia, fill = Categoria)) +
    geom_bar(stat = "identity") +
    geom_text(aes(label = Frecuencia), vjust = -0.5, size = 4) +
    scale_fill_manual(values = c("0" = "skyblue", "1" = "salmon")) +
    labs(title = paste("Distribución de", colname),
         x = colname,
         y = "Frecuencia") +
    theme_minimal() +
    theme(axis.text.x = element_text(angle = 0, hjust = 0.5),
          plot.title = element_text(face = "bold", size = 14),
          legend.position = "none")  # No mostrar leyenda
  
  # Visualizamos
  print(p)
}

# Lista de variables numéricas
variables_num <- names(df_num)

# Conteo de valores únicos por cada variable numérica
cat("Número de valores únicos por variable numérica:\n")
## Número de valores únicos por variable numérica:
for (colname in variables_num) {
  num_unicos <- length(unique(df_num[[colname]]))
  cat(colname, "=", num_unicos, "\n")
}
## Age = 50 
## BMI = 216 
## Waist_Circumference = 501 
## Fasting_Blood_Glucose = 1301 
## HbA1c = 111 
## Blood_Pressure_Systolic = 90 
## Blood_Pressure_Diastolic = 60 
## Cholesterol_Total = 1499 
## Cholesterol_HDL = 501 
## Cholesterol_LDL = 1301 
## GGT = 901 
## Serum_Urate = 51 
## Dietary_Intake_Calories = 2451 
## Family_History_of_Diabetes = 2 
## Previous_Gestational_Diabetes = 2
# Filtro df_num: eliminamos la columna X y las v.binarias
df_num_filtrado <- df_num %>%
  select( -Family_History_of_Diabetes, -Previous_Gestational_Diabetes)

# Lista de variables numéricas
variables_num <- names(df_num_filtrado)

# Espacio para gráficos (2 filas, 2 columnas)
par(mfrow = c(2, 2))

# Loop para cada variable
for (colname in variables_num) {
  
  # Histograma
  hist(df_num_filtrado[[colname]],
       main = paste("Histograma de", colname),
       xlab = colname,
       col = "tomato",
       breaks = 20,
       probability = TRUE)  # Importante: para que la escala sea de densidad
  
  # Línea de densidad
  lines(density(df_num_filtrado[[colname]], na.rm = TRUE),
        col = "blue",
        lwd = 2)
  
  # Boxplot
  boxplot(df_num_filtrado[[colname]],
          main = paste("Boxplot de", colname),
          col = "lightblue",
          horizontal = TRUE)
}

# Restauramos espacio gráfico normal
par(mfrow = c(1, 1))

Respecto a la mayoría de las variables numéricas como BMI, Waist_Circumference, Fasting_Blood_Glucose, HbA1c, Blood_Pressure_Systolic, Blood_Pressure_Diastolic, Cholesterol_Total, Cholesterol_HDL, Cholesterol_LDL, GGT, Serum_Urate y Dietary_Intake_Calories muestran distribuciones cercanas a uniformes o levemente asimétricas, lo que indica que los datos están bien distribuidos a lo largo de sus rangos. En particular, variables como Age, BMI, Waist_Circumference, Fasting_Blood_Glucose y Dietary_Intake_Calories muestran una distribución más plana, lo que sugiere que no hay una concentración muy fuerte de individuos en rangos específicos. En los boxplots vemos que no existen outliers extremos muy destacados, en su mayoría de los datos caen dentro del rango de los bigotes de los boxplots, excepto en las variables como Fasting_Blood_Glucose, Cholesterol_Total, GGT y Dietary_Intake_Calories que muestran ligeramente indicios de valores atípicos. En general, no se obaerva distribuciones extremadamente sesgadas, y no existe grandes concentraciones de valores ni presencia de masiva de outliers, lo que sugiere calidad en los datos.

Limpieza de los datos

La presencia de valores ausentes o nulos puede distorsionar los resultados de los modelos predictivos, afectar las estadísticas descriptivas y generar sesgos en las conclusiones, por tanto, es crucial su detección, tratamiento o eliminación.

cat('Conteo de NA:\n')
## Conteo de NA:
print(colSums(is.na(df)))
##                           Age                           Sex 
##                             0                             0 
##                     Ethnicity                           BMI 
##                             0                             0 
##           Waist_Circumference         Fasting_Blood_Glucose 
##                             0                             0 
##                         HbA1c       Blood_Pressure_Systolic 
##                             0                             0 
##      Blood_Pressure_Diastolic             Cholesterol_Total 
##                             0                             0 
##               Cholesterol_HDL               Cholesterol_LDL 
##                             0                             0 
##                           GGT                   Serum_Urate 
##                             0                             0 
##       Physical_Activity_Level       Dietary_Intake_Calories 
##                             0                             0 
##           Alcohol_Consumption                Smoking_Status 
##                             0                             0 
##    Family_History_of_Diabetes Previous_Gestational_Diabetes 
##                             0                             0
cat('\nConteo de blancos (""):\n')
## 
## Conteo de blancos (""):
print(colSums(df == ""))
##                           Age                           Sex 
##                             0                             0 
##                     Ethnicity                           BMI 
##                             0                             0 
##           Waist_Circumference         Fasting_Blood_Glucose 
##                             0                             0 
##                         HbA1c       Blood_Pressure_Systolic 
##                             0                             0 
##      Blood_Pressure_Diastolic             Cholesterol_Total 
##                             0                             0 
##               Cholesterol_HDL               Cholesterol_LDL 
##                             0                             0 
##                           GGT                   Serum_Urate 
##                             0                             0 
##       Physical_Activity_Level       Dietary_Intake_Calories 
##                             0                             0 
##           Alcohol_Consumption                Smoking_Status 
##                             0                             0 
##    Family_History_of_Diabetes Previous_Gestational_Diabetes 
##                             0                             0

Observaciones: Tras revisar el dataset, no existen valores nulos (NA) ni blancos (““) en ninguna de las variables. Por tanto, no será necesario aplicar técnicas de limpieza de datos.

Estudio de Correlación

El objetivo de realizar el análisis de correlación sobre las variables numéricas es identificar posibles relaciones lineales entre ellas ya que puedan ser relevantes para el modelado. También permite evaluar la existencia de multicolinealidad, que puede ser problemática en modelos lineales. Desde una perspectiva médica, el análisis de correlaciones ayuda a comprender si los factores de riesgo clínicos como el índice de masa corporal, los niveles de glucosa, los parámetros de lípidos y la presión arterial presentan relaciones esperadas o si por el contrario muestran comportamientos atípicos.

corr_matrix <- cor(df_num_filtrado, use = "complete.obs")

# Visualizar la matriz de correlación
corrplot(corr_matrix, 
         method = "color",     # Representación como mapa de calor
         type = "upper",       # Parte superior de la matriz
         tl.col = "black",     # Texto de las etiquetas en negro
         tl.cex = 0.8,         # Tamaño de fuente de las etiquetas
         tl.srt = 30,          # Rotación de etiquetas
         order = "AOE",        # Ordenar agrupando variables similares
         number.cex = 0.65,    # Tamaño de los números dentro de las celdas
         sig.level = 0.01,     # valor p < 0.01
         addCoef.col = "black" # Color de los coeficientes de correlación
)

+ Observaciones: La matriz muestra que las variables numéricas presentan una correlación lineal baja o nula. Por tanto no existen relaciones fuertes ni colinealidad significativa, y podemos decir que existe una independencia entre las variables, aunque se podría explorar relaciones no lineales. A la interpretación médica añadimos que diferentes pacientes pueden tener alteraciones (glucemia, lípidos, presión arterial) de forma aislada sin necesariamente compartir patrones comunes, lo cual se da en estudios de prevención donde los individuos no presentan enfermedades avanzadas. Esta conclusión lo corrobora la ausencia de valores extremos en los datos.

Análisis de relaciones significativas

El propósito es identificar diferencias significativas en marcadores clínicos como glucemia, presión arterial y perfil lipídico…etc (v.númericas) entre los diferentes grupos definidos por v.categóricas como sexo, etnia y el nivel de actividad física o los antecedentes familiares de diabetes. Información relevante a obtener:

Se respondera las siguientes preguntas:

  1. Sexo (Sex) ¿Presentan hombres y mujeres diferencias significativas en niveles de glucosa, lípidos, o IMC?

  2. Etnia (Ethnicity) ¿Existen variaciones en marcadores de riesgo metabólico entre grupos étnicos?

  3. Historial familiar de diabetes (Family_History_of_Diabetes) ¿Las personas con antecedentes familiares pueden presentar un mayor ratio de IMC, glucosa o HbA1c?

  4. Diabetes gestacional previa (Previous_Gestational_Diabetes) ¿Muestran las mujeres embarazadas valores más alterados en glucosa o lípidos?

  5. Nivel de actividad física (Physical_Activity_Level) e Ingesta calórica (Dietary_Intake_Calories) ¿Las personas que realizan más actividad física y cuidan su dieta presentan un mejor perfil glucémico, menos grasa corporal y un perfil lipídico más saludable?

  6. Consumo de alcohol (Alcohol_Consumption) ¿Las personas con mayor consumo de alcohol presentan alteraciones en variables metabólicas como glucosa, presión arterial, perfil lipídico o función hepática?

Esperamos obtener la sigueinte información:

# Relación entre variables categóricas y numéricas predefinidas
relaciones <- list(
  Sex = c("BMI", "Waist_Circumference", "Fasting_Blood_Glucose", "HbA1c", "Blood_Pressure_Systolic", "Blood_Pressure_Diastolic", "Dietary_Intake_Calories"),
  Ethnicity = c("Fasting_Blood_Glucose", "HbA1c", "BMI", "Waist_Circumference", "Cholesterol_Total", "Cholesterol_HDL", "Cholesterol_LDL"),
  Family_History_of_Diabetes = c("Fasting_Blood_Glucose", "HbA1c", "BMI", "Cholesterol_Total", "GGT", "Blood_Pressure_Systolic"),
  Previous_Gestational_Diabetes = c("Fasting_Blood_Glucose", "HbA1c", "BMI", "Waist_Circumference"),
  Physical_Activity_Level = c("Fasting_Blood_Glucose", "HbA1c", "BMI", "Waist_Circumference", "Cholesterol_Total", "GGT", "Dietary_Intake_Calories"),
  Alcohol_Consumption = c("GGT", "Cholesterol_Total", "Cholesterol_HDL", "BMI")
)

# Tabla final de resultados
tabla_resultados <- data.frame()

# Evaluación de pruebas estadísticas
for (cat_var in names(relaciones)) {
  for (num_var in relaciones[[cat_var]]) {
    
    datos <- df[, c(cat_var, num_var)]
    datos <- datos[complete.cases(datos), ]
    
    n_grupos <- length(unique(datos[[cat_var]]))
    
    if (n_grupos == 2) {
      test <- t.test(as.formula(paste(num_var, "~", cat_var)), data = datos)
      tipo_test <- "t-test"
      p_valor <- test$p.value
    } else {
      test <- aov(as.formula(paste(num_var, "~", cat_var)), data = datos)
      tipo_test <- "ANOVA"
      p_valor <- summary(test)[[1]][["Pr(>F)"]][1]
    }
    
    tabla_resultados <- rbind(tabla_resultados,
      data.frame(Categoria = cat_var,
                 Variable_Numerica = num_var,
                 Test_Usado = tipo_test,
                 p_valor = round(p_valor, 4),
                 Significativo = ifelse(p_valor < 0.05, "Sí", "No"))
    )
  }
}

# Mostrar tabla ordenada
tabla_resultados <- tabla_resultados %>% arrange(Categoria, p_valor)
print(tabla_resultados)
##                        Categoria        Variable_Numerica Test_Usado p_valor
## 1            Alcohol_Consumption                      BMI      ANOVA  0.0093
## 2            Alcohol_Consumption                      GGT      ANOVA  0.0372
## 3            Alcohol_Consumption        Cholesterol_Total      ANOVA  0.6915
## 4            Alcohol_Consumption          Cholesterol_HDL      ANOVA  0.9717
## 5                      Ethnicity                    HbA1c      ANOVA  0.0956
## 6                      Ethnicity    Fasting_Blood_Glucose      ANOVA  0.1623
## 7                      Ethnicity      Waist_Circumference      ANOVA  0.2775
## 8                      Ethnicity          Cholesterol_HDL      ANOVA  0.2831
## 9                      Ethnicity                      BMI      ANOVA  0.4003
## 10                     Ethnicity        Cholesterol_Total      ANOVA  0.5511
## 11                     Ethnicity          Cholesterol_LDL      ANOVA  0.8671
## 12    Family_History_of_Diabetes                      GGT     t-test  0.0102
## 13    Family_History_of_Diabetes        Cholesterol_Total     t-test  0.3923
## 14    Family_History_of_Diabetes                      BMI     t-test  0.4396
## 15    Family_History_of_Diabetes  Blood_Pressure_Systolic     t-test  0.8307
## 16    Family_History_of_Diabetes    Fasting_Blood_Glucose     t-test  0.9478
## 17    Family_History_of_Diabetes                    HbA1c     t-test  0.9509
## 18       Physical_Activity_Level        Cholesterol_Total      ANOVA  0.0945
## 19       Physical_Activity_Level                      BMI      ANOVA  0.4084
## 20       Physical_Activity_Level                    HbA1c      ANOVA  0.4251
## 21       Physical_Activity_Level      Waist_Circumference      ANOVA  0.5509
## 22       Physical_Activity_Level    Fasting_Blood_Glucose      ANOVA  0.7831
## 23       Physical_Activity_Level                      GGT      ANOVA  0.7926
## 24       Physical_Activity_Level  Dietary_Intake_Calories      ANOVA  0.8397
## 25 Previous_Gestational_Diabetes    Fasting_Blood_Glucose     t-test  0.0192
## 26 Previous_Gestational_Diabetes      Waist_Circumference     t-test  0.1295
## 27 Previous_Gestational_Diabetes                    HbA1c     t-test  0.8645
## 28 Previous_Gestational_Diabetes                      BMI     t-test  0.9101
## 29                           Sex                      BMI     t-test  0.0511
## 30                           Sex    Fasting_Blood_Glucose     t-test  0.1747
## 31                           Sex                    HbA1c     t-test  0.3682
## 32                           Sex  Blood_Pressure_Systolic     t-test  0.3971
## 33                           Sex      Waist_Circumference     t-test  0.5978
## 34                           Sex Blood_Pressure_Diastolic     t-test  0.7662
## 35                           Sex  Dietary_Intake_Calories     t-test  0.9220
##    Significativo
## 1             Sí
## 2             Sí
## 3             No
## 4             No
## 5             No
## 6             No
## 7             No
## 8             No
## 9             No
## 10            No
## 11            No
## 12            Sí
## 13            No
## 14            No
## 15            No
## 16            No
## 17            No
## 18            No
## 19            No
## 20            No
## 21            No
## 22            No
## 23            No
## 24            No
## 25            Sí
## 26            No
## 27            No
## 28            No
## 29            No
## 30            No
## 31            No
## 32            No
## 33            No
## 34            No
## 35            No

Las relaciones que resultaron estadísticamente significativas (p < 0.0375):

El consumo de alcohol muestra las diferencias en dos marcadores clínicos: el índice de masa corporal (IMC) con un p-valor de 0.0093, y los niveles de la enzima hepática GGT con un p-valor de 0.0372. Esto sugiere que los individuos con mayor consumo de alcohol tienden a tener un IMC más alto y posibles alteraciones hepáticas, lo cual es coherente con los efectos metabólicos del alcohol.

En cuanto a las mujeres con antecedentes de diabetes gestacional (Previous_Gestational_Diabetes), se observa una diferencia en la glucosa en ayunas (Fasting_Blood_Glucose) con un p-valor de 0.0192. Lo que refuerza la evidencia clínica que indica un mayor riesgo de desarrollar diabetes tipo 2 en este grupo.

Individuos con antecedentes familiares de diabetes (Family_History_of_Diabetes) muestran niveles de GGT con p-valor = 0.0102, significativamente más altos, lo que sugiere posible alteración hepática asociada a riesgo metabólico.

Graficamos los resultados

# Añadimos columna de significancia
tabla_resultados <- tabla_resultados %>%
  mutate(Significativo = ifelse(p_valor < 0.05, "Sí", "No"))

# Convertimos columnas en factores para mejor ordenación
tabla_resultados$Categoria <- factor(tabla_resultados$Categoria, levels = unique(tabla_resultados$Categoria))
tabla_resultados$Variable_Numerica <- factor(tabla_resultados$Variable_Numerica, levels = unique(tabla_resultados$Variable_Numerica))

# Heatmap de p-valores
ggplot(tabla_resultados, aes(x = Categoria, y = Variable_Numerica, fill = p_valor)) +
  geom_tile(color = "white") +
  geom_text(aes(label = round(p_valor, 3)), size = 3) +
  scale_fill_gradient2(low = "tomato", mid = "white", high = "lightblue",
                       midpoint = 0.05, limits = c(0, 1)) +
  labs(title = "Mapa de significancia estadística (p-valor)",
       x = "Variable Categórica", y = "Variable Numérica", fill = "p-valor") +
  theme_minimal(base_size = 12) +
  theme(axis.text.x = element_text(angle = 30, hjust = 1),
        plot.title = element_text(face = "bold", size = 14))

El heatmap de p-valores revela que las variables categóricas Alcohol_Consumption, Physical_Activity_Level y Family_History_of_Diabetes presentan asociaciones estadísticamente significativas con múltiples biomarcadores clínicos, lo que indica que son factores relevantes para la predicción del riesgo de diabetes. En paralelo, los biomarcadores BMI, GGT y Fasting_Blood_Glucose destacan por su sensibilidad a los cambios mencionados, mostrando una alta discriminación entre subgrupos. Esta visualización permite identificar patrones complejos de dependencia que no son evidentes en tablas individuales y proporciona una base para la selección de variables en modelos supervisados o segmentaciones no supervisadas, optimizando así la capacidad predictiva y la interpretabilidad clínica del progrma.

# Definir variable categórica y lista de variables numéricas
cat_alcohol <- "Alcohol_Consumption"
num_alcohol <- c("Fasting_Blood_Glucose", "HbA1c", "BMI", "Waist_Circumference")

for (num_var_alcohol in num_alcohol) {
  
  datos_alcohol <- df[, c(cat_alcohol, num_var_alcohol)]
  datos_alcohol <- datos_alcohol[complete.cases(datos_alcohol), ]
  datos_alcohol[[cat_alcohol]] <- as.factor(datos_alcohol[[cat_alcohol]])
  
  # Calcular estadísticas por grupo
  stats_alcohol <- datos_alcohol %>%
    group_by(Alcohol_Consumption) %>%
    summarise(
      Media = mean(.data[[num_var_alcohol]], na.rm = TRUE),
      Q1 = quantile(.data[[num_var_alcohol]], 0.25, na.rm = TRUE),
      Q3 = quantile(.data[[num_var_alcohol]], 0.75, na.rm = TRUE),
      .groups = "drop"
    ) %>%
    mutate(
      label_media = paste0("bar(x) == ", round(Media, 1)),
      label_q1 = paste0("Q[1] == ", round(Q1, 1)),
      label_q3 = paste0("Q[3] == ", round(Q3, 1))
    )

  g <- ggplot(datos_alcohol, aes(x = .data[[cat_alcohol]], y = .data[[num_var_alcohol]], fill = .data[[cat_alcohol]])) +
    geom_violin(trim = FALSE, alpha = 0.4, color = NA) +
    geom_boxplot(width = 0.12, outlier.shape = NA, fill = "white", color = "black", alpha = 0.6) +
    geom_jitter(color = "steelblue", width = 0.15, alpha = 0.2, size = 0.8) +
    stat_summary(fun = mean, geom = "point", shape = 23, size = 3, fill = "white", color = "black") +
    geom_text(data = stats_alcohol, aes(x = Alcohol_Consumption, y = Media, label = label_media),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = -1.2) +
    geom_text(data = stats_alcohol, aes(x = Alcohol_Consumption, y = Q1, label = label_q1),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = 1.8) +
    geom_text(data = stats_alcohol, aes(x = Alcohol_Consumption, y = Q3, label = label_q3),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = -1.2) +
    scale_fill_brewer(palette = "Set2") +
    labs(
      title = paste("Distribución de", num_var_alcohol, "según nivel de consumo de alcohol"),
      x = "Alcohol_Consumption", y = num_var_alcohol
    ) +
    theme_classic(base_size = 13) +
    theme(
      axis.text.x = element_text(angle = 30, hjust = 1),
      plot.title = element_text(face = "bold", size = 14),
      legend.position = "none"
    )
  
  print(g)
}

Un mayor consumo de alcohol muestra con un aumento leve en los valores medios de GGT y una mayor dispersión (Q1–Q3) en el grupo de consumo elevado, lo que indica posibles alteraciones hepáticas. Además, las personas que no consumen alcohol presentan un IMC ligeramente inferior. En cambio, no se observan diferencias clínicas relevantes en colesterol total ni alteraciones en el colesterol HDL. En conjunto, estos resultados apoyan la hipótesis de que el consumo excesivo de alcohol puede impactar negativamente en la función hepática y contribuir a un perfil metabólico menos favorable.

cat_sex <- "Sex"
num_vars_sex <- c("BMI", "Waist_Circumference", "Fasting_Blood_Glucose", "HbA1c",
                  "Blood_Pressure_Systolic", "Blood_Pressure_Diastolic", "Dietary_Intake_Calories")

for (num_var_sex in num_vars_sex) {
  
  datos_sex <- df[, c(cat_sex, num_var_sex)]
  datos_sex <- datos_sex[complete.cases(datos_sex), ]
  datos_sex[[cat_sex]] <- as.factor(datos_sex[[cat_sex]])
  
  # Calcular estadísticas por grupo
  stats_sex <- datos_sex %>%
    group_by(Sex) %>%
    summarise(
      Media = mean(.data[[num_var_sex]], na.rm = TRUE),
      Q1 = quantile(.data[[num_var_sex]], 0.25, na.rm = TRUE),
      Q3 = quantile(.data[[num_var_sex]], 0.75, na.rm = TRUE),
      .groups = "drop"
    ) %>%
    mutate(
      label_media = paste0("bar(x) == ", round(Media, 1)),
      label_q1 = paste0("Q[1] == ", round(Q1, 1)),
      label_q3 = paste0("Q[3] == ", round(Q3, 1))
    )

  g <- ggplot(datos_sex, aes(x = .data[[cat_sex]], y = .data[[num_var_sex]], fill = .data[[cat_sex]])) +
    geom_violin(trim = FALSE, alpha = 0.4, color = NA) +
    geom_boxplot(width = 0.12, outlier.shape = NA, fill = "white", color = "black", alpha = 0.6) +
    geom_jitter(color = "steelblue", width = 0.15, alpha = 0.2, size = 0.8) +
    stat_summary(fun = mean, geom = "point", shape = 23, size = 3, fill = "white", color = "black") +
    geom_text(data = stats_sex, aes(x = Sex, y = Media, label = label_media),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = -1.2) +
    geom_text(data = stats_sex, aes(x = Sex, y = Q1, label = label_q1),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = 1.8) +
    geom_text(data = stats_sex, aes(x = Sex, y = Q3, label = label_q3),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = -1.2) +
    scale_fill_brewer(palette = "Set2") +
    labs(
      title = paste("Distribución de", num_var_sex, "según sexo"),
      x = "Sexo", y = num_var_sex
    ) +
    theme_classic(base_size = 13) +
    theme(
      axis.text.x = element_text(angle = 30, hjust = 1),
      plot.title = element_text(face = "bold", size = 14),
      legend.position = "none"
    )
  
  print(g)
}

No se aprecian diferencias clínicamente relevantes entre hombres y mujeres en los principales marcadores. Las medias de glucosa en ayunas (135.3 vs. 134.3), HbA1c (9.5 ambos), presión arterial sistólica (133.9 vs. 134.4), presión diastólica (89.6 vs. 89.5), BMI (29.5 vs. 29.3) y la ingesta calórica (2741.8 vs. 2743.2) son prácticamente idénticas entre sexos. También los rangos intercuartílicos (Q1–Q3) son muy similares. Por tanto, no se identifican patrones de riesgo diferenciados por sexo en esta muestra, lo cual sugiere que otras variables pueden ser más determinantes en la identificación de perfiles con riesgo a padecer diabetes.

cat_ethnicity <- "Ethnicity"
num_vars_ethnicity <- c("Fasting_Blood_Glucose", "HbA1c", "BMI", "Waist_Circumference",
                        "Cholesterol_Total", "Cholesterol_HDL", "Cholesterol_LDL")

for (num_var_eth in num_vars_ethnicity) {
  
  datos_eth <- df[, c(cat_ethnicity, num_var_eth)]
  datos_eth <- datos_eth[complete.cases(datos_eth), ]
  datos_eth[[cat_ethnicity]] <- as.factor(datos_eth[[cat_ethnicity]])
  
  # Calcular estadísticas por grupo
  stats_eth <- datos_eth %>%
    group_by(Ethnicity) %>%
    summarise(
      Media = mean(.data[[num_var_eth]], na.rm = TRUE),
      Q1 = quantile(.data[[num_var_eth]], 0.25, na.rm = TRUE),
      Q3 = quantile(.data[[num_var_eth]], 0.75, na.rm = TRUE),
      .groups = "drop"
    ) %>%
    mutate(
      label_media = paste0("bar(x) == ", round(Media, 1)),
      label_q1 = paste0("Q[1] == ", round(Q1, 1)),
      label_q3 = paste0("Q[3] == ", round(Q3, 1))
    )

  g <- ggplot(datos_eth, aes(x = .data[[cat_ethnicity]], y = .data[[num_var_eth]], fill = .data[[cat_ethnicity]])) +
    geom_violin(trim = FALSE, alpha = 0.4, color = NA) +
    geom_boxplot(width = 0.12, outlier.shape = NA, fill = "white", color = "black", alpha = 0.6) +
    geom_jitter(color = "steelblue", width = 0.15, alpha = 0.2, size = 0.8) +
    stat_summary(fun = mean, geom = "point", shape = 23, size = 3, fill = "white", color = "black") +
    geom_text(data = stats_eth, aes(x = Ethnicity, y = Media, label = label_media),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = -1.2) +
    geom_text(data = stats_eth, aes(x = Ethnicity, y = Q1, label = label_q1),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = 1.8) +
    geom_text(data = stats_eth, aes(x = Ethnicity, y = Q3, label = label_q3),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = -1.2) +
    scale_fill_brewer(palette = "Set2") +
    labs(
      title = paste("Distribución de", num_var_eth, "según grupo étnico"),
      x = "Grupo étnico", y = num_var_eth
    ) +
    theme_classic(base_size = 13) +
    theme(
      axis.text.x = element_text(angle = 30, hjust = 1),
      plot.title = element_text(face = "bold", size = 14),
      legend.position = "none"
    )
  
  print(g)
}

Se muestran leves variaciones en los marcadores entre los grupos étnicos. Los asiáticos presentan mayor glucemia en ayunas pero menor IMC y circunferencia de cintura, lo que podría reflejar una mayor predisposición a diabetes con menor adiposidad. Los grupos Black e Hispanic tienen valores glucémicos peores (HbA1c más alto) y niveles de colesterol total algo mayores. Las diferencias en colesterol LDL e IMC son mínimas entre ellos.

cat_family <- "Family_History_of_Diabetes"
num_vars_family <- c("Fasting_Blood_Glucose", "HbA1c", "BMI", 
                     "Cholesterol_Total", "GGT", "Blood_Pressure_Systolic")

for (num_var_family in num_vars_family) {
  
  datos_family <- df[, c(cat_family, num_var_family)]
  datos_family <- datos_family[complete.cases(datos_family), ]
  datos_family[[cat_family]] <- as.factor(datos_family[[cat_family]])
  
  # Calcular estadísticas por grupo
  stats_family <- datos_family %>%
    group_by(Family_History_of_Diabetes) %>%
    summarise(
      Media = mean(.data[[num_var_family]], na.rm = TRUE),
      Q1 = quantile(.data[[num_var_family]], 0.25, na.rm = TRUE),
      Q3 = quantile(.data[[num_var_family]], 0.75, na.rm = TRUE),
      .groups = "drop"
    ) %>%
    mutate(
      label_media = paste0("bar(x) == ", round(Media, 1)),
      label_q1 = paste0("Q[1] == ", round(Q1, 1)),
      label_q3 = paste0("Q[3] == ", round(Q3, 1))
    )
  
  g <- ggplot(datos_family, aes(x = .data[[cat_family]], y = .data[[num_var_family]], fill = .data[[cat_family]])) +
    geom_violin(trim = FALSE, alpha = 0.4, color = NA) +
    geom_boxplot(width = 0.12, outlier.shape = NA, fill = "white", color = "black", alpha = 0.6) +
    geom_jitter(color = "steelblue", width = 0.15, alpha = 0.2, size = 0.8) +
    stat_summary(fun = mean, geom = "point", shape = 23, size = 3, fill = "white", color = "black") +
    geom_text(data = stats_family, aes(x = Family_History_of_Diabetes, y = Media, label = label_media),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = -1.2) +
    geom_text(data = stats_family, aes(x = Family_History_of_Diabetes, y = Q1, label = label_q1),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = 1.8) +
    geom_text(data = stats_family, aes(x = Family_History_of_Diabetes, y = Q3, label = label_q3),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = -1.2) +
    scale_fill_brewer(palette = "Set2") +
    labs(
      title = paste("Distribución de", num_var_family, "según antecedentes familiares de diabetes"),
      x = "Antecedentes familiares", y = num_var_family
    ) +
    theme_classic(base_size = 13) +
    theme(
      axis.text.x = element_text(angle = 30, hjust = 1),
      plot.title = element_text(face = "bold", size = 14),
      legend.position = "none"
    )
  
  print(g)
}

A partir de las personas con y sin antecedentes familiares de diabetes vemos que presentan valores medios prácticamente idénticos. En concreto, la glucosa en ayunas tiene una media de 134.8 mg/dL en ambos grupos, al igual que la HbA1c, con una media de 9.5 %. El IMC también es prácticamente el mismo (29.5 vs. 29.4), y las distribuciones de colesterol total, GGT y presión arterial sistólica muestran valores medios similares y una dispersión comparable entre ambos grupos. En colesterol total y GGT, las personas con antecedentes familiares muestran una leve disminución en los primeros cuartiles (Q1), lo que podría indicar una ligera variabilidad. No obstante, en términos generales no se aprecian diferencias claras entre quienes tienen antecedentes y quienes no.

cat_gestational <- "Previous_Gestational_Diabetes"
num_vars_gestational <- c("Fasting_Blood_Glucose", "HbA1c", "BMI", "Waist_Circumference")

for (num_var in num_vars_gestational) {
  
  datos_gest <- df[, c(cat_gestational, num_var)]
  datos_gest <- datos_gest[complete.cases(datos_gest), ]
  datos_gest[[cat_gestational]] <- as.factor(datos_gest[[cat_gestational]])
  
  # Calcular estadísticas por grupo
  stats_gest <- datos_gest %>%
    group_by(Previous_Gestational_Diabetes) %>%
    summarise(
      Media = mean(.data[[num_var]], na.rm = TRUE),
      Q1 = quantile(.data[[num_var]], 0.25, na.rm = TRUE),
      Q3 = quantile(.data[[num_var]], 0.75, na.rm = TRUE),
      .groups = "drop"
    ) %>%
    mutate(
      label_media = paste0("bar(x) == ", round(Media, 1)),
      label_q1 = paste0("Q[1] == ", round(Q1, 1)),
      label_q3 = paste0("Q[3] == ", round(Q3, 1))
    )
  
  g <- ggplot(datos_gest, aes(x = .data[[cat_gestational]], y = .data[[num_var]], fill = .data[[cat_gestational]])) +
    geom_violin(trim = FALSE, alpha = 0.4, color = NA) +
    geom_boxplot(width = 0.12, outlier.shape = NA, fill = "white", color = "black", alpha = 0.6) +
    geom_jitter(color = "steelblue", width = 0.15, alpha = 0.2, size = 0.8) +
    stat_summary(fun = mean, geom = "point", shape = 23, size = 3, fill = "white", color = "black") +
    geom_text(data = stats_gest, aes(x = Previous_Gestational_Diabetes, y = Media, label = label_media),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = -1.2) +
    geom_text(data = stats_gest, aes(x = Previous_Gestational_Diabetes, y = Q1, label = label_q1),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = 1.8) +
    geom_text(data = stats_gest, aes(x = Previous_Gestational_Diabetes, y = Q3, label = label_q3),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = -1.2) +
    scale_fill_brewer(palette = "Set2") +
    labs(
      title = paste("Distribución de", num_var, "según diagnóstico previo de diabetes gestacional"),
      x = "Diabetes gestacional previa", y = num_var
    ) +
    theme_classic(base_size = 13) +
    theme(
      axis.text.x = element_text(angle = 30, hjust = 1),
      plot.title = element_text(face = "bold", size = 14),
      legend.position = "none"
    )
  
  print(g)
}

Las mujeres con antecedente de diabetes gestacional no presentan diferencias marcadas en los valores medios de glucosa en ayunas (135.7 vs. 133.9 mg/dL) ni en HbA1c (9.5% en ambos grupos). El índice de masa corporal (IMC) también se mantiene prácticamente idéntico (29.4 en ambos casos), y lo mismo sucede con la circunferencia de cintura, cuyo promedio apenas varía (95.0 cm vs. 94.6 cm). Las distribuciones son muy similares, tanto en valores centrales como en los cuartiles Q1 y Q3.

# Physical_Activity_Level
cat_activity <- "Physical_Activity_Level"
num_vars_activity <- c("Fasting_Blood_Glucose", "HbA1c", "BMI", 
                       "Waist_Circumference", "Cholesterol_Total", 
                       "GGT", "Dietary_Intake_Calories")

for (num_var in num_vars_activity) {
  
  datos_activity <- df[, c(cat_activity, num_var)]
  datos_activity <- datos_activity[complete.cases(datos_activity), ]
  datos_activity[[cat_activity]] <- as.factor(datos_activity[[cat_activity]])
  
  # Calcular estadísticas por grupo
  stats_activity <- datos_activity %>%
    group_by(Physical_Activity_Level) %>%
    summarise(
      Media = mean(.data[[num_var]], na.rm = TRUE),
      Q1 = quantile(.data[[num_var]], 0.25, na.rm = TRUE),
      Q3 = quantile(.data[[num_var]], 0.75, na.rm = TRUE),
      .groups = "drop"
    ) %>%
    mutate(
      label_media = paste0("bar(x) == ", round(Media, 1)),
      label_q1 = paste0("Q[1] == ", round(Q1, 1)),
      label_q3 = paste0("Q[3] == ", round(Q3, 1))
    )
  
  g <- ggplot(datos_activity, aes(x = .data[[cat_activity]], y = .data[[num_var]], fill = .data[[cat_activity]])) +
    geom_violin(trim = FALSE, alpha = 0.4, color = NA) +
    geom_boxplot(width = 0.12, outlier.shape = NA, fill = "white", color = "black", alpha = 0.6) +
    geom_jitter(color = "steelblue", width = 0.15, alpha = 0.2, size = 0.8) +
    stat_summary(fun = mean, geom = "point", shape = 23, size = 3, fill = "white", color = "black") +
    geom_text(data = stats_activity, aes(x = Physical_Activity_Level, y = Media, label = label_media),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = -1.2) +
    geom_text(data = stats_activity, aes(x = Physical_Activity_Level, y = Q1, label = label_q1),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = 1.8) +
    geom_text(data = stats_activity, aes(x = Physical_Activity_Level, y = Q3, label = label_q3),
              parse = TRUE, inherit.aes = FALSE, size = 3.5, vjust = -1.2) +
    scale_fill_brewer(palette = "Set2") +
    labs(
      title = paste("Distribución de", num_var, "según nivel de actividad física"),
      x = "Nivel de actividad física", y = num_var
    ) +
    theme_classic(base_size = 13) +
    theme(
      axis.text.x = element_text(angle = 30, hjust = 1),
      plot.title = element_text(face = "bold", size = 14),
      legend.position = "none"
    )
  
  print(g)
}

En cuanto a la glucosa en ayunas, los valores medios son prácticamente iguales en los tres grupos (134.4–135.1 mg/dL), sin diferencias significativas, aunque el grupo con actividad moderada presenta un Q1 levemente más alto. Para la hemoglobina glicosilada (HbA1c), se observan también medias muy similares (9.5–9.6%), con un patrón de distribución homogéneo, sin indicios de mejor control glucémico asociado a mayor actividad.

Respecto al IMC y la circunferencia de cintura, los valores medios son casi idénticos (IMC ≈ 29.4–29.5, cintura ≈ 94.6–95), lo cual indica que el nivel de actividad física no parece asociarse con una reducción de grasa corporal en esta muestra. El colesterol total, por su parte, muestra una media levemente superior en el grupo de actividad moderada (226.5 mg/dL) frente a los grupos bajo y alto (224.4–224.6 mg/dL), pero sin diferencias destacables. Lo mismo ocurre con la GGT, cuyos valores medios rondan los 55 U/L para todos los grupos, sin variación significativas. Por último, en cuanto a la ingesta calórica, el promedio se mantiene entre 2737 y 2748 kcal diarias, también con poca variabilidad según el nivel de actividad, lo que sugiere que la actividad física no se acompaña de un ajuste en la dieta energética en esta muestra. En resumen, Las variables HbA1c, IMC, cintura e ingesta calórica no presentan patrones de mejora con el aumento de actividad. Esto podría deberse a la homogeneidad de la muestra.

Segmentación e identificación de perfiles de riesgo

El objetivo de la segmentaciones es clasificar a los individuos en grupos de riesgo bajo=Normal, medio=Pre-diabetes y alto=diabetes según sus valores de glucosa, HbA1c e IMC, para identificar subpoblaciones con mayor o menor probabilidad de desarrollar diabetes tipo 2.

Esperamos obtener: + Perfiles de riesgo según estandares internacionales. + Análisis cruzado entre estos perfiles y factores como actividad física, dieta y antecedentes familiares. + Aproximación al riesgo metabólico sin necesidad de modelado predictivo.

Para ello, clasificamos las variables clínicas según estándares internacionales:
# Creamos la variable binaria 'Diabetes' usando los criterios clínicos: Glucosa en ayunas ≥ 126 mg/dL o HbA1c ≥ 6.5%
df$Diabetes <- ifelse(
  df$Fasting_Blood_Glucose >= 126 | df$HbA1c >= 6.5,
  1,  # Tiene diabetes
  0   # No tiene diabetes
)

# Verificamos
cat("Estructura de la nueva variable:\n")
## Estructura de la nueva variable:
str(df$Diabetes)
##  num [1:10000] 1 1 1 1 1 1 1 1 1 1 ...
# Tabla de frecuencias absolutas
cat("\nDistribución absoluta:\n")
## 
## Distribución absoluta:
print(table(df$Diabetes))
## 
##    0    1 
##  953 9047
# Tabla de proporciones (porcentaje sobre el total)
cat("\nDistribución porcentual:\n")
## 
## Distribución porcentual:
print(round(prop.table(table(df$Diabetes)), 3))
## 
##     0     1 
## 0.095 0.905
df$Glucose_Category <- cut(df$Fasting_Blood_Glucose,
                           breaks = c(-Inf, 99, 125, Inf),
                           labels = c("Normal", "Pre-diabetes", "Diabetes"))

df$HbA1c_Category <- cut(df$HbA1c,
                         breaks = c(-Inf, 5.6, 6.4, Inf),
                         labels = c("Normal", "Pre-diabetes", "Diabetes"))

df$BMI_Category <- cut(df$BMI,
                       breaks = c(-Inf, 24.9, 29.9, Inf),
                       labels = c("Normal", "Overweight", "Obese"))

Estudio de diferencias significativas entre las variables numéricas continuas como glucosa en ayunas (Fasting_Blood_Glucose), HbA1c e IMC (BMI) y el diagnóstico de diabetes (variable binaria 0/1).

Seleccionamos las Categorías clínicas: BMI_Category, Glucose_Category para detectar la presencia o no de diabetes tipo 2, según organismos como la Organización Mundial de la Salud (OMS) y *Nurses’ Health Study** han demostrado que el exceso de peso, especialmente el IMC ≥ 30, incrementa significativamente el riesgo de desarrollar diabetes tipo 2 debido a mecanismos como la resistencia a la insulina y la inflamación crónica.

. Recuperado de:

Hu, F. B., Manson, J. E., Stampfer, M. J., Colditz, G., Liu, S., Solomon, C. G., & Willett, W. C. (2001). Diet, lifestyle, and the risk of type 2 diabetes mellitus in women. , (11), 790–797.

# Tablas cruzadas entre la variable binaria Diabetes y las categorías clínicas
cat("Tabla: Glucose_Category vs Diabetes\n")
## Tabla: Glucose_Category vs Diabetes
print(table(df$Glucose_Category, df$Diabetes))
##               
##                   0    1
##   Normal        512 1741
##   Pre-diabetes  420 1581
##   Diabetes       21 5725
print(round(prop.table(table(df$Glucose_Category, df$Diabetes), 1), 3))
##               
##                    0     1
##   Normal       0.227 0.773
##   Pre-diabetes 0.210 0.790
##   Diabetes     0.004 0.996
cat("\nTabla: HbA1c_Category vs Diabetes\n")
## 
## Tabla: HbA1c_Category vs Diabetes
print(table(df$HbA1c_Category, df$Diabetes))
##               
##                   0    1
##   Normal        625  846
##   Pre-diabetes  328  417
##   Diabetes        0 7784
print(round(prop.table(table(df$HbA1c_Category, df$Diabetes), 1), 3))
##               
##                    0     1
##   Normal       0.425 0.575
##   Pre-diabetes 0.440 0.560
##   Diabetes     0.000 1.000
cat("\nTabla: BMI_Category vs Diabetes\n")
## 
## Tabla: BMI_Category vs Diabetes
print(table(df$BMI_Category, df$Diabetes))
##             
##                 0    1
##   Normal      264 2592
##   Overweight  224 2135
##   Obese       465 4320
print(round(prop.table(table(df$BMI_Category, df$Diabetes), 1), 3))
##             
##                  0     1
##   Normal     0.092 0.908
##   Overweight 0.095 0.905
##   Obese      0.097 0.903

El objetivo de este análisis es validar si las categorías clínicas definidas por los valores de glucosa en ayunas (), hemoglobina glicosilada () e índice de masa corporal () se asocian significativamente con la presencia de diabetes, definida como una variable binaria (\(Diabetes = 1\) si \(Glucosa \geq 126\) o \(HbA1c \geq 6.5\%\)). Esta validación permite evaluar si los puntos de corte clínicos son útiles para clasificar perfiles de riesgo metabólico.

Las variables clínicas categorizadas según estándares internacionales (glucosa y HbA1c) se relacionan fuertemente con la variable , validando su uso como marcadores de riesgo.

Este análisis confirma que las variables clínicas clasificadas por puntos de corte internacionales pueden utilizarse como perfiles de riesgo y sirven como base para modelado predictivo supervisado.

# Selección de variables
vars_radar <- c("Fasting_Blood_Glucose", "HbA1c", "BMI", 
                "Waist_Circumference", "Cholesterol_Total", "Blood_Pressure_Systolic")

# Media por grupo de categoría de glucosa
df_radar <- aggregate(df[, vars_radar], by = list(df$Glucose_Category), mean, na.rm = TRUE)
rownames(df_radar) <- df_radar$Group.1
df_radar <- df_radar[, -1]

# Filas de máximos y mínimos para escalar
df_radar <- rbind(
  max = apply(df_radar, 2, max) * 1.1,
  min = apply(df_radar, 2, min) * 0.9,
  df_radar
)

# Colores por grupo
colors_border <- c("forestgreen", "orange", "firebrick")
colors_fill <- adjustcolor(colors_border, alpha.f = 0.25)

# Aumentamos el área del gráfico
par(mar = c(2, 3, 4, 3))

# Gráfico radar
radarchart(df_radar,
           pcol = colors_border,
           pfcol = colors_fill,
           plwd = 2,
           plty = 1,
           cglcol = "grey", 
           cglty = 1,
           axislabcol = "black", 
           caxislabels = seq(0, max(df_radar), length.out = 5),
           calcex = 0.7,
           vlcex = 0.9,
           title = "Perfil clínico promedio por categoría de glucosa"
)

legend("topright", legend = rownames(df_radar)[-c(1, 2)], 
       bty = "n", pch = 20 , col = colors_border, text.col = "black", cex = 0.8)

vars_radar_hba1c <- c("Fasting_Blood_Glucose", "HbA1c", "BMI", 
                      "Waist_Circumference", "Cholesterol_Total", "Blood_Pressure_Systolic")

# Calcular medias por categoría HbA1c
df_radar_hba1c <- aggregate(df[, vars_radar_hba1c], 
                            by = list(df$HbA1c_Category), FUN = mean, na.rm = TRUE)
rownames(df_radar_hba1c) <- df_radar_hba1c$Group.1
df_radar_hba1c <- df_radar_hba1c[, -1]

# Añadir límites para escalar el gráfico
df_radar_hba1c <- rbind(
  max = apply(df_radar_hba1c, 2, max) * 1.1,
  min = apply(df_radar_hba1c, 2, min) * 0.9,
  df_radar_hba1c
)

# Colores para cada grupo
colors_border_hba1c <- c("darkgreen", "darkorange", "darkred")
colors_fill_hba1c <- adjustcolor(colors_border_hba1c, alpha.f = 0.3)

# Aumentamos el área del gráfico
par(mar = c(2, 3, 4, 3))

# Graficar radar
radarchart(df_radar_hba1c,
           pcol = colors_border_hba1c,
           pfcol = colors_fill_hba1c,
           plwd = 2,
           plty = 1,
           cglcol = "grey", 
           cglty = 1,
           axislabcol = "black", 
           caxislabels = round(seq(0, max(df_radar_hba1c), length.out = 5), 0),
           calcex = 0.9,
           vlcex = 1,
           title = "Perfil clínico medio por categoría de HbA1c"
)

legend("topright", legend = rownames(df_radar_hba1c)[-c(1, 2)],
       bty = "n", pch = 20, col = colors_border_hba1c, text.col = "black", cex = 0.9)

cat("\nMedias por categoría de HbA1c:\n")
## 
## Medias por categoría de HbA1c:
print(aggregate(df[, vars_radar_hba1c], by = list(HbA1c = df$HbA1c_Category), FUN = mean, na.rm = TRUE))
##          HbA1c Fasting_Blood_Glucose     HbA1c      BMI Waist_Circumference
## 1       Normal              135.5586  4.799048 29.54663            94.58035
## 2 Pre-diabetes              133.8933  6.044698 29.52658            94.65477
## 3     Diabetes              134.7129 10.728726 29.38349            94.85164
##   Cholesterol_Total Blood_Pressure_Systolic
## 1          226.1056                134.7961
## 2          223.7942                133.1195
## 3          225.1191                134.1441
vars_radar_bmi <- c("Fasting_Blood_Glucose", "HbA1c", "BMI", 
                    "Waist_Circumference", "Cholesterol_Total", "Blood_Pressure_Systolic")

# Calcular la media por grupo de BMI
df_radar_bmi <- aggregate(df[, vars_radar_bmi],
                          by = list(df$BMI_Category), FUN = mean, na.rm = TRUE)
rownames(df_radar_bmi) <- df_radar_bmi$Group.1
df_radar_bmi <- df_radar_bmi[, -1]

# Añadir máximos y mínimos para escalar correctamente
df_radar_bmi <- rbind(
  max = apply(df_radar_bmi, 2, max) * 1.1,
  min = apply(df_radar_bmi, 2, min) * 0.9,
  df_radar_bmi
)

# Definir colores por categoría
colors_border_bmi <- c("steelblue", "orange", "firebrick")
colors_fill_bmi <- adjustcolor(colors_border_bmi, alpha.f = 0.25)

# Aumentar área del gráfico
par(mar = c(3, 3, 4, 3))

# Crear el gráfico radar
radarchart(df_radar_bmi,
           pcol = colors_border_bmi,
           pfcol = colors_fill_bmi,
           plwd = 2,
           plty = 1,
           cglcol = "grey", 
           cglty = 1,
           axislabcol = "black", 
           caxislabels = round(seq(0, max(df_radar_bmi), length.out = 5), 0),
           calcex = 1,
           vlcex = 1.1,
           title = "Perfil clínico medio según categoría de IMC"
)

# Leyenda
legend("topright", legend = rownames(df_radar_bmi)[-c(1, 2)], 
       bty = "n", pch = 20, col = colors_border_bmi, text.col = "black", cex = 1)

El análisis comparativo de los perfiles clínicos según las categorías de glucosa en ayunas y HbA1c y permite evaluar si los puntos de corte clínicos reflejan diferencias reales en los individuos. Se parte del supuesto de que, a medida que se avanza de Normal a Diabetes, los valores clínicos como presión arterial, colesterol, IMC y glucosa tienden a deteriorarse. Los gráficos radar muestran que los individuos clasificados como Diabetes presentan valores más elevados en glucosa, HbA1c y presión sistólica, con leves aumentos en IMC, colesterol y circunferencia de cintura. Las diferencias son consistentes tanto para la clasificación por glucosa como por HbA1c. La categoría clínica del IMC normal, sobrepeso y obesidad, se confirma que a mayor peso corporal, también tienden a elevarse progresivamente los valores medios de glucosa, HbA1c, presión arterial y circunferencia de cintura, aunque las diferencias no son tan marcadas como en glucosa o HbA1c.


7 EJERCICIO 4: Proceso de Modelado


En este apartado se prepara los datos para que puedan ser utilizados en técnicas de modelado no supervisado como PCA (Análisis de Componentes Principales) o SVD (Descomposición en Valores Singulares). Esto implica garantizar que las variables estén limpias, estandarizadas y estructuradas de forma que puedan identificarse patrones.

Se han eliminado las columnas inecesarias que no aporten valor a nuestro estudio, además, se ha verificado que no existen valores null ni vacios NA y se ha creado la variable binaria Diabetes y las variables categorizadas Glucose_Category, HbA1c_Category, BMI_Category. Y, por supuesto, se ha explorado y validado los outliers mediante boxplot e histogramas. Aunque no fue necesario aplicar la winsorización ya que no se identificaron valores extremos ni distribucioes anómalas en el dataset original.

Proseguimos con la discretización de la variable age para posteriormente normalizar las variables numéricas.


8 EJERCICIO 5: Método de Discretización


Discretizamos la variable continua Age que implica dividir su rango en intervalos o categorías: + Se agrupa a los individuos por tramos de edad: jóvenes, adultos, mayores. + Se analiza los perfiles o patrones por grupos de edad. + se visualiza gráficas con la variable edad convertida en categórica y analizamos cada subgrupo.

# Creamos una copia del dataset df para discretizar la v.age
df2 <- df

# Discretización de la variable Age en rangos
df2$Age_Group <- cut(df2$Age,
                     breaks = c(-Inf, 29, 44, 59, Inf),
                     labels = c("18-29", "30-44", "45-59", "60+"),
                     right = TRUE)

# Conversión en factor
df2$Age_Group <- as.factor(df2$Age_Group)

# Verificamos la distribución
table(df2$Age_Group)
## 
## 18-29 30-44 45-59   60+ 
##  1922  3068  3023  1987

El objetivo de este análisis es explorar cómo se distribuyen las principales variables clínicas según el grupo de edad y el diagnóstico de diabetes. Para ello, se seleccionaron tres ejes clave:

Las variables se han seleccionado por su relevancia directa en el diagnóstico y seguimiento de la diabetes tipo 2, tal como establecen las guías clínicas internacionales. Además, la inclusión de la edad como variable categórica permite detectar patrones diferenciales de riesgo metabólico en distintos rangos de edad.

# Seleccionamos las variables numéricas que nos interesan
num_vars <- c("Fasting_Blood_Glucose", "HbA1c", "BMI", "Cholesterol_Total", "Blood_Pressure_Systolic")

# Loop para graficar cada variable numérica por grupo de edad y color por Diabetes
for (var in num_vars) {
  g <- ggplot(df2, aes_string(x = "Age_Group", y = var, fill = "as.factor(Diabetes)")) +
    geom_violin(trim = FALSE, alpha = 0.5, color = NA, position = position_dodge(1)) +
    geom_boxplot(width = 0.1, outlier.shape = NA, color = "black", position = position_dodge(1), alpha = 0.7) +
    scale_fill_manual(values = c("skyblue", "tomato"), 
                      name = "Diabetes",
                      labels = c("No", "Sí")) +
    labs(
      title = paste("Distribución de", var, "por grupo de edad y diagnóstico de diabetes"),
      x = "Grupo de edad",
      y = var
    ) +
    theme_minimal(base_size = 13) +
    theme(
      axis.text.x = element_text(angle = 30, hjust = 1),
      plot.title = element_text(face = "bold", size = 14)
    )
  
  print(g)
}

Al comparar la distribución de cada variable clínica según los grupos de edad y el estado de diabetes, vemos:
# Tabla resumen con proporciones y frecuencias relativas
df2 %>%
  group_by(Age_Group, Diabetes) %>%
  summarise(Frecuencia = n(), .groups = "drop") %>%
  group_by(Age_Group) %>%
  mutate(Proporcion = Frecuencia / sum(Frecuencia),
         Porcentaje = paste0(round(Proporcion * 100, 1), "%")) %>%
  ggplot(aes(x = Age_Group, y = Proporcion, color = as.factor(Diabetes), shape = as.factor(Diabetes))) +
  geom_point(size = 4) +
  geom_line(aes(group = Diabetes), linewidth = 1) +
  geom_text(aes(label = Porcentaje), vjust = 1.5, size = 3.8, show.legend = FALSE) +
  scale_color_manual(values = c("0" = "steelblue", "1" = "firebrick"),
                     labels = c("No", "Sí")) +
  scale_shape_manual(values = c("0" = 16, "1" = 17),
                     labels = c("No", "Sí")) +
  labs(
    title = "Proporción de diabetes por grupo de edad",
    x = "Grupo de edad",
    y = "Proporción",
    color = "Diabetes",
    shape = "Diabetes"
  ) +
  theme_minimal(base_size = 12)

+ Observaciones:

La gráfica muestra la proporción de personas diagnosticadas con diabetes y sin diagnóstico en función del grupo de edad. En todos los rangos etarios, la proporción de individuos con diabetes es alta, oscilando entre el 89{,}7,% y el 92{,}1,%, mientras que la proporción sin diagnóstico de diabetes permanece por debajo del 10{,}5,%. En el grupo más joven (18–29 años), la proporción de personas con diabetes alcanza el 90{,}5,%. En particular, el grupo de mayores de 60 años presenta la proporción más elevada de casos (92{,}1,%), mientras que el grupo de 30 a 44 años muestra la más baja (89{,}7,%). Paralelamente, la proporción de personas sin diagnóstico disminuye con la edad, pasando de un 10{,}3,% en el grupo de 30–44 años a un 7{,}9,% en el de 60 o más. Estos resultados sugieren que el riesgo de desarrollar diabetes tipo 2 se incrementa ligeramente con la edad, lo cual es coherente con la literatura médica, que vincula el envejecimiento con un deterioro progresivo del metabolismo de la glucosa.

Normalizamos las variables numéricas con el objetivo de preparar los datos para técnicas de análisis multivariado como el Análisis de Componentes Principales (PCA) o la Descomposición en Valores Singulares (SVD). Normalizar significa transformar los valores de cada variable para que tengan una media de cero y una desviación estándar de uno, cuando las variables originales están en distintas escalas o unidades, como sucede en nuestro caso con medidas clínicas como la glucosa en ayunas, HbA1c, presión arterial o colesterol.

La normalización garantiza que todas las variables contribuyan de forma equilibrada al análisis y evita que aquellas con valores absolutos mayores dominen los resultados. En este contexto, nos permitirá explorar relaciones entre variables, identificar patrones ocultos y reducir la dimensionalidad del conjunto de datos sin perder información relevante. Además, dará más precisión en la segmentación no supervisada de perfiles clínicos y la interpretación de estructuras en la población.

# Eliminamos la variable Age y trabajaremos con el rango de edad en el df normalizado
df_norm <- df2[, -which(names(df2) == "Age")]

# Variables numéricas
numericas <- df_norm %>% select(where(is.numeric))

# Variables binarias
binarias <- numericas %>%
  select(where(~ all(. %in% c(0, 1)))) %>%
  names()

# Excluimos todas las binarias
vars_norm <- numericas %>%
  select( -all_of(binarias)) %>%
  names()

# Normalización (media = 0, sd = 1)
numericas_norm <- as.data.frame(scale(df_norm[, vars_norm]))

# Eliminamos columnas originales normalizadas y añadimos las nuevas
df_norm <- df_norm %>%
  select(-all_of(vars_norm)) %>%
  bind_cols(numericas_norm)

# Verificación de los datos
str(df_norm)
## 'data.frame':    10000 obs. of  24 variables:
##  $ Sex                          : chr  "Female" "Male" "Female" "Male" ...
##  $ Ethnicity                    : chr  "White" "Asian" "Black" "Asian" ...
##  $ Physical_Activity_Level      : chr  "Moderate" "Moderate" "Low" "Low" ...
##  $ Alcohol_Consumption          : chr  "Moderate" "Moderate" "Heavy" "Moderate" ...
##  $ Smoking_Status               : chr  "Never" "Current" "Former" "Never" ...
##  $ Family_History_of_Diabetes   : int  0 0 1 1 0 1 0 0 1 1 ...
##  $ Previous_Gestational_Diabetes: int  1 1 0 0 0 1 0 1 0 0 ...
##  $ Diabetes                     : num  1 1 1 1 1 1 1 1 1 1 ...
##  $ Glucose_Category             : Factor w/ 3 levels "Normal","Pre-diabetes",..: 2 3 3 3 3 1 1 1 1 1 ...
##  $ HbA1c_Category               : Factor w/ 3 levels "Normal","Pre-diabetes",..: 3 3 3 3 3 3 3 3 3 3 ...
##  $ BMI_Category                 : Factor w/ 3 levels "Normal","Overweight",..: 3 1 2 3 3 3 3 2 2 1 ...
##  $ Age_Group                    : Factor w/ 4 levels "18-29","30-44",..: 3 3 2 4 1 2 3 2 2 2 ...
##  $ BMI                          : num  1.034 -0.862 -0.716 0.532 0.661 ...
##  $ Waist_Circumference          : num  -0.792 -1.627 1.321 0.39 1.113 ...
##  $ Fasting_Blood_Glucose        : num  -0.289 1.3 0.192 0.867 0.309 ...
##  $ HbA1c                        : num  0.438 1.037 1.572 -0.223 -0.758 ...
##  $ Blood_Pressure_Systolic      : num  0.683 -1.194 1.717 1.602 -0.466 ...
##  $ Blood_Pressure_Diastolic     : num  1.4179 0.0836 0.8378 1.6499 0.4317 ...
##  $ Cholesterol_Total            : num  -0.637 0.848 0.834 -0.972 -0.511 ...
##  $ Cholesterol_HDL              : num  -0.332 0.48 -1.577 -0.957 -0.077 ...
##  $ Cholesterol_LDL              : num  -0.937 0.321 0.793 -1.343 -1.108 ...
##  $ GGT                          : num  -0.6827 1.2878 0.0399 -0.8024 1.0328 ...
##  $ Serum_Urate                  : num  1.166 0.41 0.9598 -0.0711 1.3034 ...
##  $ Dietary_Intake_Calories      : num  -1.681 -0.125 -1.477 1.47 0.584 ...
cat("\n\n")
summary(numericas_norm)
##       BMI           Waist_Circumference Fasting_Blood_Glucose
##  Min.   :-1.76931   Min.   :-1.724019   Min.   :-1.721236    
##  1st Qu.:-0.86182   1st Qu.:-0.875813   1st Qu.:-0.866281    
##  Median : 0.01326   Median : 0.007156   Median :-0.007339    
##  Mean   : 0.00000   Mean   : 0.000000   Mean   : 0.000000    
##  3rd Qu.: 0.85593   3rd Qu.: 0.848410   3rd Qu.: 0.877509    
##  Max.   : 1.71481   Max.   : 1.752237   Max.   : 1.733129    
##      HbA1c           Blood_Pressure_Systolic Blood_Pressure_Diastolic
##  Min.   :-1.733873   Min.   :-1.69143        Min.   :-1.71476        
##  1st Qu.:-0.852378   1st Qu.:-0.84885        1st Qu.:-0.84458        
##  Median :-0.002364   Median :-0.00627        Median :-0.03241        
##  Mean   : 0.000000   Mean   : 0.00000        Mean   : 0.00000        
##  3rd Qu.: 0.879131   3rd Qu.: 0.87461        3rd Qu.: 0.89578        
##  Max.   : 1.729145   Max.   : 1.71719        Max.   : 1.70795        
##  Cholesterol_Total   Cholesterol_HDL    Cholesterol_LDL          GGT          
##  Min.   :-1.749511   Min.   :-1.72104   Min.   :-1.715992   Min.   :-1.74517  
##  1st Qu.:-0.867954   1st Qu.:-0.87494   1st Qu.:-0.871379   1st Qu.:-0.87197  
##  Median : 0.007785   Median : 0.01243   Median : 0.001233   Median : 0.01089  
##  Mean   : 0.000000   Mean   : 0.00000   Mean   : 0.000000   Mean   : 0.00000  
##  3rd Qu.: 0.866648   3rd Qu.: 0.88604   3rd Qu.: 0.854512   3rd Qu.: 0.86284  
##  Max.   : 1.741805   Max.   : 1.71838   Max.   : 1.750455   Max.   : 1.73217  
##   Serum_Urate        Dietary_Intake_Calories
##  Min.   :-1.720462   Min.   :-1.7338        
##  1st Qu.:-0.895772   1st Qu.:-0.8560        
##  Median :-0.002357   Median :-0.0216        
##  Mean   : 0.000000   Mean   : 0.0000        
##  3rd Qu.: 0.891057   3rd Qu.: 0.8728        
##  Max.   : 1.715748   Max.   : 1.7533
cat("\n\n")
head(df_norm)
##      Sex Ethnicity Physical_Activity_Level Alcohol_Consumption Smoking_Status
## 1 Female     White                Moderate            Moderate          Never
## 2   Male     Asian                Moderate            Moderate        Current
## 3 Female     Black                     Low               Heavy         Former
## 4   Male     Asian                     Low            Moderate          Never
## 5 Female     Asian                Moderate               Heavy        Current
## 6 Female     Asian                     Low                None          Never
##   Family_History_of_Diabetes Previous_Gestational_Diabetes Diabetes
## 1                          0                             1        1
## 2                          0                             1        1
## 3                          1                             0        1
## 4                          1                             0        1
## 5                          0                             0        1
## 6                          1                             1        1
##   Glucose_Category HbA1c_Category BMI_Category Age_Group        BMI
## 1     Pre-diabetes       Diabetes        Obese     45-59  1.0341904
## 2         Diabetes       Diabetes       Normal     45-59 -0.8618158
## 3         Diabetes       Diabetes   Overweight     30-44 -0.7159692
## 4         Diabetes       Diabetes        Obese       60+  0.5318297
## 5         Diabetes       Diabetes        Obese     18-29  0.6614712
## 6           Normal       Diabetes        Obese     30-44  0.6776764
##   Waist_Circumference Fasting_Blood_Glucose      HbA1c Blood_Pressure_Systolic
## 1         -0.79238268            -0.2890031  0.4383834               0.6831131
## 2         -1.62668414             1.3000049  1.0365410              -1.1935397
## 3          1.32118102             0.1919508  1.5717346               1.7171871
## 4          0.38954439             0.8668806 -0.2227381               1.6022900
## 5          1.11260566             0.3088678 -0.7579318              -0.4658580
## 6          0.09058637            -1.5883758  1.2569148               1.3724958
##   Blood_Pressure_Diastolic Cholesterol_Total Cholesterol_HDL Cholesterol_LDL
## 1               1.41789042        -0.6369450     -0.33151386      -0.9373744
## 2               0.08361280         0.8480280      0.48018724       0.3212124
## 3               0.83776972         0.8340628     -1.57658082       0.7931825
## 4               1.64993871        -0.9721113     -0.95748675      -1.3426820
## 5               0.43168522        -0.5112576     -0.07699742      -1.1080302
## 6               0.02560073        -1.6959774     -0.72360677       1.4838053
##           GGT Serum_Urate Dietary_Intake_Calories
## 1 -0.68265037  1.16595425              -1.6807260
## 2  1.28784631  0.40998799              -0.1248624
## 3  0.03986508  0.95978164              -1.4769986
## 4 -0.80242566 -0.07108145               1.4700722
## 5  1.03284086  1.30340266               0.5839974
## 6  0.86283722  0.61616061               1.0012200

9 EJERCICIO 6: Estudio PCA (Análisis de Componentes Principales)


En el contexto de nuestro análisis clínico, donde se manejan múltiples variables como glucosa, HbA1c, colesterol y presión arterial, el PCA nos permite identificar patrones subyacentes y relaciones entre estas variables. Al reducir la dimensionalidad, podemos visualizar y comprender mejor los factores que contribuyen al riesgo de diabetes tipo 2, facilitando la identificación de perfiles clínicos y la toma de decisiones informadas.

# Creamos una copia del dataset normalizado
df_norm2 <- df_norm

# Eliminamos las variables derivadas que son redundantes
vars_delete <- c("Diabetes", "Glucose_Category", "HbA1c_Category", "BMI_Category")

df_norm2 <- df_norm2[, !(names(df_norm2) %in% vars_delete)]


head(df_norm2)
##      Sex Ethnicity Physical_Activity_Level Alcohol_Consumption Smoking_Status
## 1 Female     White                Moderate            Moderate          Never
## 2   Male     Asian                Moderate            Moderate        Current
## 3 Female     Black                     Low               Heavy         Former
## 4   Male     Asian                     Low            Moderate          Never
## 5 Female     Asian                Moderate               Heavy        Current
## 6 Female     Asian                     Low                None          Never
##   Family_History_of_Diabetes Previous_Gestational_Diabetes Age_Group        BMI
## 1                          0                             1     45-59  1.0341904
## 2                          0                             1     45-59 -0.8618158
## 3                          1                             0     30-44 -0.7159692
## 4                          1                             0       60+  0.5318297
## 5                          0                             0     18-29  0.6614712
## 6                          1                             1     30-44  0.6776764
##   Waist_Circumference Fasting_Blood_Glucose      HbA1c Blood_Pressure_Systolic
## 1         -0.79238268            -0.2890031  0.4383834               0.6831131
## 2         -1.62668414             1.3000049  1.0365410              -1.1935397
## 3          1.32118102             0.1919508  1.5717346               1.7171871
## 4          0.38954439             0.8668806 -0.2227381               1.6022900
## 5          1.11260566             0.3088678 -0.7579318              -0.4658580
## 6          0.09058637            -1.5883758  1.2569148               1.3724958
##   Blood_Pressure_Diastolic Cholesterol_Total Cholesterol_HDL Cholesterol_LDL
## 1               1.41789042        -0.6369450     -0.33151386      -0.9373744
## 2               0.08361280         0.8480280      0.48018724       0.3212124
## 3               0.83776972         0.8340628     -1.57658082       0.7931825
## 4               1.64993871        -0.9721113     -0.95748675      -1.3426820
## 5               0.43168522        -0.5112576     -0.07699742      -1.1080302
## 6               0.02560073        -1.6959774     -0.72360677       1.4838053
##           GGT Serum_Urate Dietary_Intake_Calories
## 1 -0.68265037  1.16595425              -1.6807260
## 2  1.28784631  0.40998799              -0.1248624
## 3  0.03986508  0.95978164              -1.4769986
## 4 -0.80242566 -0.07108145               1.4700722
## 5  1.03284086  1.30340266               0.5839974
## 6  0.86283722  0.61616061               1.0012200
df_pca <- df_norm2[, sapply(df_norm2, is.numeric)]

# Filtramos las columnas que no son binarias
df_pca <- df_pca[, sapply(df_pca, function(col) length(unique(col)) > 2)]

# Aplicamos PCA sobre df_norm (sin variables categóricas ni binarias)
pca_result <- prcomp(df_pca, center = TRUE, scale. = TRUE)

# Resumen del PCA: proporción de varianza explicada
summary(pca_result)
## Importance of components:
##                            PC1    PC2     PC3     PC4     PC5     PC6     PC7
## Standard deviation     1.02121 1.0194 1.01473 1.01308 1.00554 1.00297 0.99637
## Proportion of Variance 0.08691 0.0866 0.08581 0.08553 0.08426 0.08383 0.08273
## Cumulative Proportion  0.08691 0.1735 0.25931 0.34484 0.42910 0.51293 0.59566
##                            PC8     PC9    PC10    PC11   PC12
## Standard deviation     0.99358 0.99315 0.98724 0.97894 0.9724
## Proportion of Variance 0.08227 0.08219 0.08122 0.07986 0.0788
## Cumulative Proportion  0.67792 0.76012 0.84134 0.92120 1.0000

El análisis de componentes principales (PCA) permite identificar combinaciones lineales que explican la mayor parte de la variabilidad en los datos. En este caso, se extrajeron 12 componentes principales correspondientes a las 12 variables numéricas no binarias del conjunto de datos.

El primer componente explica aproximadamente un 8.7,% de la varianza total, y de forma acumulada los tres primeros componentes explican ya un 25.9,%. Al considerar los ocho primeros componentes, se alcanza un 67.8,% de la varianza total explicada, lo que indica que una gran parte de la información puede ser representada en un espacio de menor dimensión sin perder mucha información.

El valor de la desviación estándar de cada componente es cercano a 1, lo que sugiere que no hay componentes dominantes. Esto refuerza la idea de que la información se encuentra distribuida entre varios componentes, y será necesario interpretar al menos los primeros 6 u 8 para capturar las principales tendencias del conjunto de datos.

# Scree plot: porcentaje de varianza explicada por cada componente
fviz_eig(pca_result, addlabels = TRUE, ylim = c(0, 60))

# Biplot de los dos primeros componentes principales
fviz_pca_biplot(pca_result, 
                repel = TRUE,
                col.var = "steelblue", # color de las variables
                col.ind = "gray",      # color de los individuos
                title = "Biplot PCA - Primeros dos componentes")

Gráfica 1: En este caso particular, los primeros componentes aportan porcentajes similares de varianza, aproximadamente entre un 8.7,% y un 8.1,%. En conjunto, los dos primeros componentes (PC1 y PC2) explican alrededor del 17.4,% de la varianza total. No se observa un ``codo’’ pronunciado en la gráfica, lo que sugiere que no hay un componente dominante que concentre gran parte de la información. Por tanto, la varianza está distribuida de manera bastante uniforme entre los diferentes componentes. Al no existir un componente claramente predominante, será necesario considerar al menos los primeros seis u ocho componentes para conservar una proporción significativa.

Grafica 2: La gráfica presentada es un . En este gráfico, cada flecha representa una variable. La dirección y longitud de estas flechas (vectores) indican la magnitud y orientación de la contribución de cada variable en la construcción de los componentes. Las variables con vectores más largos, como , , y , tienen una fuerte influencia en las dos primeras dimensiones. Por otro lado, variables como o presentan vectores más cortos, lo que indica que su contribución a Dim1 y Dim2 es más limitada, aunque podrían aportar mayor información en componentes posteriores (PC3, PC4, etc.). Además, la relación entre las variables puede deducirse del ángulo entre los vectores: aquellos que apuntan en la misma dirección están positivamente correlacionados, los que están en direcciones opuestas están negativamente correlacionados, y los que son perpendiculares no presentan correlación. En este contexto, por ejemplo, y muestran orientación contraria a otras variables relevantes, lo que sugiere una relación inversa. En el análisis del riesgo de diabetes, se identifica qué factores clínicos como la glucosa, el colesterol o la presión arterial son más determinantes en la variabilidad del perfil metabólico de los pacientes.

El conjunto de datos ha sido transformado y normalizado para estar listo para aplicar técnicas de aprendizaje no supervisado, como el algoritmo . Esta transformación incluyó la normalización de todas las variables numéricas, así como las variables categóricas mediante v.dummies, de modo que todas las variables del conjunto resultante sean numéricas para aplicar métodos basados en distancia, ya que asegura que las variables estén en la misma escala y ninguna domine el cálculo de distancias euclidianas por tener una magnitud mayor.

El objetivo principal de este preprocesamiento es identificar patrones ocultos y estructuras subyacentes en los datos clínicos, sin utilizar etiquetas previamente conocidas como la variable binaria . A través de técnicas de clustering, se espera agrupar individuos según similitudes en su perfil metabólico y estilo de vida, lo cual puede revelar perfiles de riesgo diferenciados. Esto permite aproximarse al análisis de riesgo metabólico de forma exploratoria, sin depender de una variable objetivo, y valorar si los grupos formados coinciden con categorías clínicas conocidas como , o .

# Convertimos las categóricas a dummies. El -1 elimina la columna intercepto
df_dummies <- model.matrix(~ . - 1, data = df_norm2)  

# Convertimos a data.frame para compatibilidad
df_dummies <- as.data.frame(df_dummies)

# Verificamos dimensiones y preview
str(df_dummies)
## 'data.frame':    10000 obs. of  28 variables:
##  $ SexFemale                      : num  1 0 1 0 1 1 0 1 0 0 ...
##  $ SexMale                        : num  0 1 0 1 0 0 1 0 1 1 ...
##  $ EthnicityBlack                 : num  0 0 1 0 0 0 1 0 0 0 ...
##  $ EthnicityHispanic              : num  0 0 0 0 0 0 0 1 0 0 ...
##  $ EthnicityWhite                 : num  1 0 0 0 0 0 0 0 1 1 ...
##  $ Physical_Activity_LevelLow     : num  0 0 1 1 0 1 0 0 1 0 ...
##  $ Physical_Activity_LevelModerate: num  1 1 0 0 1 0 0 1 0 0 ...
##  $ Alcohol_ConsumptionModerate    : num  1 1 0 1 0 0 1 0 0 1 ...
##  $ Alcohol_ConsumptionNone        : num  0 0 0 0 0 1 0 0 1 0 ...
##  $ Smoking_StatusFormer           : num  0 0 1 0 0 0 0 0 1 1 ...
##  $ Smoking_StatusNever            : num  1 0 0 1 0 1 1 0 0 0 ...
##  $ Family_History_of_Diabetes     : num  0 0 1 1 0 1 0 0 1 1 ...
##  $ Previous_Gestational_Diabetes  : num  1 1 0 0 0 1 0 1 0 0 ...
##  $ Age_Group30-44                 : num  0 0 1 0 0 1 0 1 1 1 ...
##  $ Age_Group45-59                 : num  1 1 0 0 0 0 1 0 0 0 ...
##  $ Age_Group60+                   : num  0 0 0 1 0 0 0 0 0 0 ...
##  $ BMI                            : num  1.034 -0.862 -0.716 0.532 0.661 ...
##  $ Waist_Circumference            : num  -0.792 -1.627 1.321 0.39 1.113 ...
##  $ Fasting_Blood_Glucose          : num  -0.289 1.3 0.192 0.867 0.309 ...
##  $ HbA1c                          : num  0.438 1.037 1.572 -0.223 -0.758 ...
##  $ Blood_Pressure_Systolic        : num  0.683 -1.194 1.717 1.602 -0.466 ...
##  $ Blood_Pressure_Diastolic       : num  1.4179 0.0836 0.8378 1.6499 0.4317 ...
##  $ Cholesterol_Total              : num  -0.637 0.848 0.834 -0.972 -0.511 ...
##  $ Cholesterol_HDL                : num  -0.332 0.48 -1.577 -0.957 -0.077 ...
##  $ Cholesterol_LDL                : num  -0.937 0.321 0.793 -1.343 -1.108 ...
##  $ GGT                            : num  -0.6827 1.2878 0.0399 -0.8024 1.0328 ...
##  $ Serum_Urate                    : num  1.166 0.41 0.9598 -0.0711 1.3034 ...
##  $ Dietary_Intake_Calories        : num  -1.681 -0.125 -1.477 1.47 0.584 ...
# Creamos una lista para guardar la inercia (suma total de cuadrados intra-cluster)
inertia <- numeric()

# Rango de k a evaluar
K <- 1:10

# Calculamos la inercia para cada k
for (k in K) {
  kmeans_result <- kmeans(df_dummies, centers = k, nstart = 10)
  inertia[k] <- kmeans_result$tot.withinss
}

# Data frame para graficar
df_inertia <- data.frame(K = K, Inertia = inertia)

# Graficar el método del codo
ggplot(df_inertia, aes(x = K, y = Inertia)) +
  geom_line(color = "steelblue") +
  geom_point(size = 2) +
  labs(title = "Método del codo para determinar k óptimo",
       x = "Número de clusters (k)",
       y = "Inercia (total within-cluster SS)") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 1))

La gráfica obtenida representa la aplicación del método del codo para determinar el número óptimo de clusters \(k\) en el análisis de agrupamiento mediante K-means. En el eje horizontal se muestra el número de clusters considerados, mientras que en el eje vertical se representa la inercia intra-cluster total (también conocida como ). A medida que aumenta el número de clusters, la inercia disminuye progresivamente, es esperado, ya que un mayor número de grupos reduce la distancia media entre los datos y sus centroides. Sin embargo, este descenso no es lineal: entre \(k = 1\) y \(k = 3\) la reducción es notable, mientras que a partir de \(k = 4\) la ganancia en compactación disminuye. Este comportamiento indica que el punto óptimo de la curva— podría situarse entre \(k = 3\) y \(k = 4\), ya que a partir de estos valores el beneficio de añadir más clusters es marginal. Por tanto, se concluye que un modelo con 3 o 4 clusters podría ser adecuado para representar la estructura latente en los datos sin incurrir en sobreajuste.

# Calculamos silhouette promedio para k de 2 a 10
silhouette_avg <- numeric()
K <- 2:10

for (k in K) {
  km_res <- kmeans(df_dummies, centers = k, nstart = 10)
  sil <- silhouette(km_res$cluster, dist(df_dummies))
  silhouette_avg[k - 1] <- mean(sil[, 3])
}

# Data frame resultante
df_sil <- data.frame(K = K, Silhouette = silhouette_avg)

# Graficamos
ggplot(df_sil, aes(x = K, y = Silhouette)) +
  geom_line(color = "darkgreen") +
  geom_point(size = 2) +
  labs(title = "Método del Silhouette para determinar k óptimo",
       x = "Número de clusters (k)",
       y = "Coeficiente promedio de Silhouette") +
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5))

El análisis del coeficiente promedio de Silhouette revela que el valor más alto se alcanza cuando \(k = 2\), con un valor superior a 0{,}062. A partir de \(k = 3\), el coeficiente disminuye gradualmente sin repuntes significativos, lo cual indica que los datos no presentan agrupaciones claramente separadas. Sin embargo, el caso con dos grupos muestra la mayor cohesión interna y separación relativa entre clusters.

Aunque el valor absoluto del coeficiente está por debajo de 0{,}07), lo cual sugiere que las agrupaciones no están bien definidas, \(k = 2\) sigue siendo la mejort opción dado nuestro objetivo de reducción de dimensiones. Esta conclusión está en consonancia con los resultados obtenidos previamente mediante el método del codo, que también apuntaban a un punto de inflexión leve alrededor de \(k = 2\) o \(k = 3\).

En el contexto del análisis de datos clínicos normalizados para la predicción o segmentación de perfiles relacionados con la diabetes tipo 2, el valor óptimo estimado de \(k = 2\) sugiere la existencia de dos grandes grupos con características diferenciadas aunque la separación no está muy marcada

Dado que tanto el método del codo como el coeficiente de Silhouette coinciden en el número óptimo de clusters \(k = 2\), procederemos ahora a aplicar el algoritmo con \(k = 2\) sobre el conjunto de datos normalizado (), y posteriormente analizaremos los perfiles clínicos resultantes.

# K-means k = 2
kmeans_final <- kmeans(df_dummies, centers = 2, nstart = 25)

# Resultado de los clusters
df_clusters <- df_dummies
df_clusters$Cluster <- as.factor(kmeans_final$cluster)

# Resumen del número de individuos por cluster
table(df_clusters$Cluster)
## 
##    1    2 
## 4951 5049
# Medias por cluster para interpretar perfiles
cluster_summary <- df_clusters %>%
  group_by(Cluster) %>%
  summarise(across(everything(), mean))

print(cluster_summary)
## # A tibble: 2 × 29
##   Cluster SexFemale SexMale EthnicityBlack EthnicityHispanic EthnicityWhite
##   <fct>       <dbl>   <dbl>          <dbl>             <dbl>          <dbl>
## 1 1           0.493   0.507          0.246             0.240          0.260
## 2 2           0.508   0.492          0.261             0.255          0.236
## # ℹ 23 more variables: Physical_Activity_LevelLow <dbl>,
## #   Physical_Activity_LevelModerate <dbl>, Alcohol_ConsumptionModerate <dbl>,
## #   Alcohol_ConsumptionNone <dbl>, Smoking_StatusFormer <dbl>,
## #   Smoking_StatusNever <dbl>, Family_History_of_Diabetes <dbl>,
## #   Previous_Gestational_Diabetes <dbl>, `Age_Group30-44` <dbl>,
## #   `Age_Group45-59` <dbl>, `Age_Group60+` <dbl>, BMI <dbl>,
## #   Waist_Circumference <dbl>, Fasting_Blood_Glucose <dbl>, HbA1c <dbl>, …

En el análisis de agrupación realizado mediante con \(k = 2\), se identificaron dos clústeres con perfiles diferenciados. Aunque las diferencias son mínimas, identificamos algunos patrones relevantes. En términos demográficos, el presenta una mayor proporción de mujeres (0.5076 frente a 0.4932) y una mayor representación de personas de etnia negra e hispana. En cuanto al comportamiento relacionado con la salud, este grupo también muestra una ligeramente más prevalente y un consumo de alcohol más moderado.

Desde el punto de vista clínico, el se caracteriza por presentar valores más elevados en , , y , así como un mayor índice de masa corporal (IMC), lo que sugiere una mayor asociación con . Por el contrario, el muestra valores más altos en , , y una mayor ingesta calórica promedio.

Estas diferencias, aunque pequeñas debido a la normalización previa de los datos, indican la existencia de dentro de la población. El Clúster 1 podría estar más vinculado a un perfil de , mientras que el Clúster 2 refleja tendencias más marcadas en . Esta información proporciona información para desarrollar , como programas de control de peso, recomendaciones nutricionales o seguimiento glucémico adaptado al perfil del paciente.

# Visualización clústers con clusplot (PCA)
clusplot(df_dummies, 
         kmeans_final$cluster, 
         color = TRUE, 
         shade = TRUE, 
         labels = 2, 
         lines = 0, 
         main = "Visualización de Clústers con k = 2 (K-means)")

El gráfico generado mediante la función representa la distribución bidimensional de los individuos proyectados sobre los dos primeros componentes principales tras aplicar el algoritmo de con \(k = 2\). Cada punto corresponde a un individuo y las elipses coloreadas indican las agrupaciones. Los componentes 1 y 2 explican conjuntamente el 12.6,% de la variabilidad total del conjunto de datos. A pesar de que este porcentaje no es elevado, se observa una separación clara entre ambos clústers, sin solapamientos. Esto confirma visualmente \(k = 2\) como número óptimo de agrupaciones, tal y como sugirieron anteriormente los métodos del codo y del coeficiente de Silhouette. La distribución sugiere la existencia de dos perfiles clínicos diferenciados, lo cual es relevante en el contexto del análisis de datos clínicos relacionados con la diabetes tipo 2.

El método de (SVD) es una técnica matemática utilizada para descomponer una matriz de datos numéricos en tres componentes fundamentales: una matriz de vectores ortogonales de los individuos (\(\mathbf{U}\)), una matriz diagonal de valores singulares (\(\mathbf{D}\)), y una matriz de vectores ortogonales de las variables (\(\mathbf{V}^T\)). En el análisis de datos clínicos normalizados, aplicamos SVD con el objetivo de reducir la dimensionalidad del conjunto de datos, identificar patrones y evaluar la redundancia entre variables clínicas.

El principal propósito es reducidir dimensiones sin pérdida significativa de varianza. Dado que en este caso los datos han sido previamente centrados y escalados, el resultado de la SVD será matemáticamente equivalente al obtenido mediante PCA. Sin embargo, SVD ofrece ventajas computacionales y mayor generalidad en aplicaciones donde las matrices no necesariamente están centradas.

Aplicar SVD nos permite descubrir combinaciones lineales de variables que capturan los patrones y facilita la exploración de agrupaciones naturales o subperfiles clínicos.

# Aplicamos SVD con df_pca (solo numéricas, sin binarias)
svd_result <- svd(scale(df_pca))

# Dimensiones de los componentes
str(svd_result)
## List of 3
##  $ d: num [1:12] 102 102 101 101 101 ...
##  $ u: num [1:10000, 1:12] 0.00584 0.00461 0.00523 -0.00639 -0.00573 ...
##  $ v: num [1:12, 1:12] -0.3112 -0.0657 0.4148 -0.0704 -0.1255 ...
# Varianza explicada por cada componente (equivalente al PCA)
var_explained <- svd_result$d^2 / sum(svd_result$d^2)

# Gráfico de varianza explicada
plot(var_explained, type = "b",
     xlab = "Componentes (SVD)", ylab = "Proporción de varianza explicada",
     main = "Varianza explicada por componentes - SVD",
     col = "darkgreen", pch = 16)

La gráfica muestra la proporción de varianza explicada por cada uno de los doce componentes obtenidos mediante (SVD) aplicada a las variables numéricas no binarias del conjunto de datos clínico. Cada punto representa el porcentaje de información que captura un componente individual, y en conjunto se puede observar que la varianza explicada está distribuida de forma bastante homogénea entre los componentes, comenzando en torno al 8.7,% y descendiendo suavemente hasta el 8.0,%.

Este patrón coincide con los resultados obtenidos mediante el análisis de (PCA), donde también se observó una distribución similar, sin componentes dominantes ni puntos de inflexión. Lo que valida el hecho de que ambos métodos —PCA y SVD— son matemáticamente equivalentes cuando se aplican a datos centrados y escalados, como en este caso.

La principal diferencia radica en la formulación matemática. PCA se basa en la descomposición de la matriz de covarianzas, mientras que SVD opera directamente sobre la matriz de datos original. Sin embargo, el resultado es idéntico, como hemos visto en la varianza explicada y en la ausencia de un componente claramente predominante.


10 Bibiografía


Computational model helps with diabetes drug design. (2023, September 20). MIT News | Massachusetts Institute of Technology. https://news.mit.edu/2023/computational-model-helps-diabetes-drug-design-0920