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:
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.
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:
Realizar un análisis exploratorio del juego de datos seleccionado.
Realizar tareas de limpieza y acondicionado para poder ser usado en procesos de modelado.
Realizar métodos de discretización.
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.
El presente documento describe un flujo analítico completo y contextualizado de un proyecto de Data Mining para su entrega como PRA1.
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:
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:
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.
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.
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.
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.
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.
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())
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.
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:
Sexo (Sex) ¿Presentan hombres y mujeres diferencias significativas en niveles de glucosa, lípidos, o IMC?
Etnia (Ethnicity) ¿Existen variaciones en marcadores de riesgo metabólico entre grupos étnicos?
Historial familiar de diabetes (Family_History_of_Diabetes) ¿Las personas con antecedentes familiares pueden presentar un mayor ratio de IMC, glucosa o HbA1c?
Diabetes gestacional previa (Previous_Gestational_Diabetes) ¿Muestran las mujeres embarazadas valores más alterados en glucosa o lípidos?
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?
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:
Diferencias significativas en glucosa, IMC y perfil lipídico según nivel de actividad física y antecedentes familiares.
Peor perfil metabólico en personas con baja actividad física o historial familiar de diabetes.
Valores más saludables en quienes reportan actividad física moderada/alta y mejor control dietético.
Consumo de alcohol asociado a alteraciones en GGT (hepática) y colesterol total.
Variables clave como HbA1c, cintura e ingesta calórica permiten afinar los perfiles de riesgo.
# 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.
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.
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)
}
# 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
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.
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