library(ggplot2)
library(ggdendro)
library(cluster)
library(factoextra)
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(clustMixType)
library(dendextend) 
## 
## ---------------------
## Welcome to dendextend version 1.17.1
## Type citation('dendextend') for how to cite the package.
## 
## Type browseVignettes(package = 'dendextend') for the package vignette.
## The github page is: https://github.com/talgalili/dendextend/
## 
## Suggestions and bug-reports can be submitted at: https://github.com/talgalili/dendextend/issues
## You may ask questions at stackoverflow, use the r and dendextend tags: 
##   https://stackoverflow.com/questions/tagged/dendextend
## 
##  To suppress this message use:  suppressPackageStartupMessages(library(dendextend))
## ---------------------
## 
## Attaching package: 'dendextend'
## The following object is masked from 'package:ggdendro':
## 
##     theme_dendro
## The following object is masked from 'package:stats':
## 
##     cutree
library(fpc)

Traemos nuestro dataset para trabajar con él

data_obesidad = read.csv("/Users/macbookair/Desktop/R (MASTER)/ObesityDataSet_raw_and_data_sinthetic.csv", header = T)

Truncamos valores con decimales

columnas_a_truncar = c('Age','NCP', 'FCVC','CH2O','FAF', 'TUE')  # Lista de columnas a truncar

# Creamos un nuevo dataframe con valores truncados
tabla_truncada = data_obesidad
tabla_truncada[, columnas_a_truncar] = lapply(tabla_truncada[, columnas_a_truncar], trunc)

Variables a factor

# Seleccionamos las variables a convertir a factor
variables_a_factor = c('FCVC', "NCP", "CH2O", 'FAF',"Gender", 'TUE','FAVC','CAEC','SMOKE','CALC','MTRANS','NObeyesdad') 

# Utilizamos lapply para convertir las variables a factor
tabla_truncada[, variables_a_factor] = lapply(tabla_truncada[, variables_a_factor], as.factor)

Depuramos dataset

# Depuramos el dataset eliminando algunas variables que consideramos son menos relevantes
columnas_a_eliminar= c('Height','family_history_with_overweight','NCP','CH2O','SCC','Weight', "SMOKE", "FAVC", "TUE") # eliminamos también las variable que habíamos descubierto que no tienen mucho peso y nos generan ruido 
t_buena = tabla_truncada %>%
        select(-one_of(columnas_a_eliminar))

Exploración

Antes de iniciar con el clustering, haremos una nueva exploración de nuestros datos pues a lo largo del proceso el dataframe ha ido cambiando, así que consideramos que es una buena idea echar de nuevo un vistazo de forma breve para ver si encontramos algo interesante con esta nueva configuración de nuestros datos. Nos enfocaremos en analizar principalmente la relación entre la variable Age y el nivel de obesidad, ya que de acuerdo al índice de Gini, estas fueron las variables más estrechamente relacionadas.

str(t_buena)
## 'data.frame':    2111 obs. of  8 variables:
##  $ Gender    : Factor w/ 2 levels "Female","Male": 1 1 2 2 2 2 1 2 2 2 ...
##  $ Age       : num  21 21 23 27 22 29 23 22 24 22 ...
##  $ FCVC      : Factor w/ 3 levels "1","2","3": 2 3 2 3 2 2 3 2 3 2 ...
##  $ CAEC      : Factor w/ 4 levels "Always","Frequently",..: 4 4 4 4 4 4 4 4 4 4 ...
##  $ FAF       : Factor w/ 4 levels "0","1","2","3": 1 4 3 3 1 1 2 4 2 2 ...
##  $ CALC      : Factor w/ 4 levels "Always","Frequently",..: 3 4 2 2 4 4 4 4 2 3 ...
##  $ MTRANS    : Factor w/ 5 levels "Automobile","Bike",..: 4 4 4 5 4 1 3 4 4 4 ...
##  $ NObeyesdad: Factor w/ 7 levels "Insufficient_Weight",..: 2 2 2 6 7 2 2 2 2 2 ...
# Graficamos el nivel de obesidad en relación a la edad, para esto hay que hacer una tabla nueva que agrupe a la edad en clases, pues son demasiados valores


# Definimos los límites y el número de intervalos
edades_bins = cut(t_buena$Age, breaks = c(14, 20, 30, 40, 50, 61), labels = c("14-20", "21-30", "31-40", "41-50", "51-61"), include.lowest = TRUE)

# Creamos un nuevo data frame con las edades agrupadas
t_buena_agrupado <- data.frame(Gender = t_buena$Gender, EdadesAgrupadas = edades_bins, NObeyesdad = t_buena$NObeyesdad)

# Grafica el nivel de obesidad en función de las edades agrupadas
ggplot(t_buena_agrupado, aes(x = EdadesAgrupadas, fill = NObeyesdad)) +
  geom_bar(position = "dodge", stat = "count") +
  ggtitle("Nivel de obesidad y Edad agrupada") +
  xlab("Edades Agrupadas") +
  ylab("Conteo") +
  theme_minimal()

Aquí miramos las frecuencias absolutas del nivel de obesidad en relación a las edades.(ojo, los tipos de obesidad están en orden alfabético) Anteriormente ya habíamos notado que la edad influye significativamente sobre el nivel de obesidad. Una gran cantidad de personas con Obesidad tipo III se encuentran en el rango de edades de 21 a 30, mientras que la mayoría de las personas que tienen peso insuficiente están en el rango de edad de entre 14 y 20.

# Calcula las frecuencias relativas
frecuencias_relativas <- table(t_buena_agrupado$EdadesAgrupadas, t_buena_agrupado$NObeyesdad) / rowSums(table(t_buena_agrupado$EdadesAgrupadas, t_buena_agrupado$NObeyesdad))

# Convierte las frecuencias relativas en un data frame
df_frecuencias_relativas <- as.data.frame(frecuencias_relativas)

# Biblioteca necesaria
library(ggplot2)

# Grafica el nivel de obesidad en función de las edades agrupadas (frecuencias relativas)
ggplot(df_frecuencias_relativas, aes(x = Var1, y = Freq, fill = Var2)) +
  geom_bar(stat = "identity", position = "dodge") +
  ggtitle("Nivel de obesidad y Edad agrupada (Frecuencias Relativas)") +
  xlab("Edades Agrupadas") +
  ylab("Frecuencia Relativa") +
  scale_fill_manual(values = c("Insufficient_Weight" = "lightblue", "Normal_Weight" = "blue", "Overweight_Level_I" = "red", "Overweight_Level_II" = "orange", "Obesity_Type_I" = "lightgreen", "Obesity_Type_II" = "black", "Obesity_Type_III" = "green"), name = "NObeyesdad", labels = c("no" = "No Obesidad", "yes" = "Obesidad")) +  # Personaliza los colores y la leyenda
  theme_minimal()

En las frecuencias relativas hay información interesante también, vemos que la mayoría de las personas en el rango de 51 a 61 tienen sobrepeso nivel II, la mayoría de las personas en el rango de edad de 41 a 50 tienen obesidad tipo II y como habíamos observado en el gráfico anterior, la mayoría de las personas de 14 a 20 tienen peso insuficiente. Otra observación es que en el rango de edad entre 51 y 61 prácticamente no tenemos personas con Obesidad tipo II o tipo III, esto puede sugerir dos cosas, o que la expectativa de vida de las personas con este tipo de obesidad es menor o que cuando alcanzan esa edad pierden peso.

# Crea un nuevo data frame con las edades agrupadas
t_buenascatter <- data.frame(Gender = t_buena$Gender, Age= t_buena$Age , NObeyesdad = t_buena$NObeyesdad)

# Grafica el nivel de obesidad en función de las edades agrupadas como puntos (clusters)
ggplot(t_buenascatter, aes(x = Age, y = Gender, color = NObeyesdad)) +
  geom_jitter(position = position_jitter(width = 0.2, height = 0), size = 3) +
  ggtitle("Nivel de obesidad | Edad | Género") +
  xlab("Edades Agrupadas") +
  ylab("Género") +
  theme_minimal()

Haremos otro gráfico similar pero agrupando las edades en rangos

# Define los límites y el número de intervalos
edades <- cut(t_buena$Age, breaks = c(14, 20, 30, 40, 50, 61), labels = c("14-20", "21-30", "31-40", "41-50", "51-61"), include.lowest = TRUE)

# Crea un nuevo data frame con las edades agrupadas
t_buenascatter <- data.frame(Gender = t_buena$Gender, EdadesAgrupadas = edades, NObeyesdad = t_buena$NObeyesdad)

# Grafica el nivel de obesidad en función de las edades agrupadas como puntos (clusters)
ggplot(t_buenascatter, aes(x = EdadesAgrupadas, y = Gender, color = NObeyesdad)) +
  geom_jitter(position = position_jitter(width = 0.2, height = 0), size = 3) +
  ggtitle("Nivel de obesidad y Edad agrupada") +
  xlab("Edades Agrupadas") +
  ylab("Género") +
  theme_minimal()

En general estos scatterplots no son muy buenos en términos visuales, ya que al ser casi todas nuestras variables categóricas los puntos se ordenan en líneas y esto dificulta el que podamos observar una distribución de los puntos que nos permita agruparlos en posibles clusters.

Aquí podemos ver más o menos cómo se agrupan los niveles de sobrepeso con relación a su genero y edad. Aunque no estámos muy seguros de si este gráfico sirve para interpretar pues los puntos podrían estar encimados.

Vamos a hacer un primer clustering basado en prototipos y usando las variables de mayor relevancia

Clustering

Hacemos un primer clustering con kproto y 3 clusters seleccionando las columnas que parecen ser más relevantes.

# Seleccionamos las columnas relevantes para el clustering (variables numéricas y categóricas)
t_buena_clustering = t_buena[, c("Age", "FCVC", "CAEC", "FAF", "CALC")]

# Especificamos el número de clusters 
num_clusters = 3

# Realizamos el clustering con k-prototypes
resultados_clustering = kproto(t_buena_clustering, num_clusters, iter.max = 20, nstart = 10)
## # NAs in variables:
##  Age FCVC CAEC  FAF CALC 
##    0    0    0    0    0 
## 0 observation(s) with NAs.
## 
## Estimated lambda: 82.66924 
## 
## 0 observation(s) with NAs.
## 
## # NAs in variables:
##  Age FCVC CAEC  FAF CALC 
##    0    0    0    0    0 
## 0 observation(s) with NAs.
## 
## 0 observation(s) with NAs.
## 
## # NAs in variables:
##  Age FCVC CAEC  FAF CALC 
##    0    0    0    0    0 
## 0 observation(s) with NAs.
## 
## 0 observation(s) with NAs.
## 
## # NAs in variables:
##  Age FCVC CAEC  FAF CALC 
##    0    0    0    0    0 
## 0 observation(s) with NAs.
## 
## 0 observation(s) with NAs.
## 
## # NAs in variables:
##  Age FCVC CAEC  FAF CALC 
##    0    0    0    0    0 
## 0 observation(s) with NAs.
## 
## 0 observation(s) with NAs.
## 
## # NAs in variables:
##  Age FCVC CAEC  FAF CALC 
##    0    0    0    0    0 
## 0 observation(s) with NAs.
## 
## 0 observation(s) with NAs.
## 
## # NAs in variables:
##  Age FCVC CAEC  FAF CALC 
##    0    0    0    0    0 
## 0 observation(s) with NAs.
## 
## 0 observation(s) with NAs.
## 
## # NAs in variables:
##  Age FCVC CAEC  FAF CALC 
##    0    0    0    0    0 
## 0 observation(s) with NAs.
## 
## 0 observation(s) with NAs.
## 
## # NAs in variables:
##  Age FCVC CAEC  FAF CALC 
##    0    0    0    0    0 
## 0 observation(s) with NAs.
## 
## 0 observation(s) with NAs.
## 
## # NAs in variables:
##  Age FCVC CAEC  FAF CALC 
##    0    0    0    0    0 
## 0 observation(s) with NAs.
## 
## 0 observation(s) with NAs.
# Visualizamos los resultados
print(resultados_clustering)
## Distance type: standard 
## 
## Numeric predictors: 1 
## Categorical predictors: 4 
## Lambda: 82.66924 
## 
## Number of Clusters: 3 
## Cluster sizes: 399 716 996 
## Within cluster error: 42461.28 68391.16 108915.3 
## 
## Cluster prototypes:
##        Age FCVC      CAEC FAF      CALC
## 1 34.44110    2 Sometimes   0 Sometimes
## 2 22.83380    3 Sometimes   0 Sometimes
## 3 20.59739    2 Sometimes   1 Sometimes
# Añadimos la información de los clusters al conjunto de datos original
t_buena_con_clusters = cbind(t_buena, Cluster = resultados_clustering$cluster)

# Visualiza el conjunto de datos con la asignación de clusters
head(t_buena_con_clusters)
##   Gender Age FCVC      CAEC FAF       CALC                MTRANS
## 1 Female  21    2 Sometimes   0         no Public_Transportation
## 2 Female  21    3 Sometimes   3  Sometimes Public_Transportation
## 3   Male  23    2 Sometimes   2 Frequently Public_Transportation
## 4   Male  27    3 Sometimes   2 Frequently               Walking
## 5   Male  22    2 Sometimes   0  Sometimes Public_Transportation
## 6   Male  29    2 Sometimes   0  Sometimes            Automobile
##            NObeyesdad Cluster
## 1       Normal_Weight       3
## 2       Normal_Weight       2
## 3       Normal_Weight       3
## 4  Overweight_Level_I       2
## 5 Overweight_Level_II       2
## 6       Normal_Weight       1
clusplot(t_buena_con_clusters, resultados_clustering$cluster, color=T, shade=T, labels=0, lines=0, main="CLUSTERING k Prototipos")

Estos componentes explican únicamente el 42.33 % de la variabilidad

Ahora intentaremos otro modelo con K-means para variables mixtas (categóricas y numéricas)

# Calculamos la matriz de distancia de Gower
distancia_gower = daisy(t_buena, metric = "gower")

# Realizamos el clustering con k-means
k = 4  # Lo intentamos con varios pero finalmente 4 pareció la mejor opción
kmeans_result = kmeans(distancia_gower, centers = k, nstart = 20)

# Visualizamos los resultados
table(kmeans_result$cluster)
## 
##   1   2   3   4 
## 381 885 414 431
# Calculamos las métricas utilizando cluster.stats
cluster_metrics <- cluster.stats(distancia_gower, kmeans_result$cluster)

# Imprimimos las métricas
print(cluster_metrics)
## $n
## [1] 2111
## 
## $cluster.number
## [1] 4
## 
## $cluster.size
## [1] 381 885 414 431
## 
## $min.cluster.size
## [1] 381
## 
## $noisen
## [1] 0
## 
## $diameter
## [1] 0.9920213 0.7659574 0.8111702 0.5638298
## 
## $average.distance
## [1] 0.5261678 0.3407161 0.3662156 0.1634715
## 
## $median.distance
## [1] 0.5132979 0.3776596 0.3882979 0.1382979
## 
## $separation
## [1] 0.007978723 0.007978723 0.125000000 0.015957447
## 
## $average.toother
## [1] 0.5735146 0.5039418 0.5531966 0.5175171
## 
## $separation.matrix
##             [,1]        [,2]  [,3]       [,4]
## [1,] 0.000000000 0.007978723 0.125 0.01595745
## [2,] 0.007978723 0.000000000 0.125 0.12500000
## [3,] 0.125000000 0.125000000 0.000 0.12500000
## [4,] 0.015957447 0.125000000 0.125 0.00000000
## 
## $ave.between.matrix
##           [,1]      [,2]      [,3]      [,4]
## [1,] 0.0000000 0.5611551 0.6516913 0.5238001
## [2,] 0.5611551 0.0000000 0.4840711 0.4724527
## [3,] 0.6516913 0.4840711 0.0000000 0.6080682
## [4,] 0.5238001 0.4724527 0.6080682 0.0000000
## 
## $average.between
## [1] 0.5324179
## 
## $average.within
## [1] 0.3430001
## 
## $n.between
## [1] 1585389
## 
## $n.within
## [1] 641716
## 
## $max.diameter
## [1] 0.9920213
## 
## $min.separation
## [1] 0.007978723
## 
## $within.cluster.ss
## [1] 159.4532
## 
## $clus.avg.silwidths
##           1           2           3           4 
## -0.07418574  0.20657420  0.23387155  0.64441072 
## 
## $avg.silwidth
## [1] 0.2506477
## 
## $g2
## NULL
## 
## $g3
## NULL
## 
## $pearsongamma
## [1] 0.5032102
## 
## $dunn
## [1] 0.008042895
## 
## $dunn2
## [1] 0.8979127
## 
## $entropy
## [1] 1.317327
## 
## $wb.ratio
## [1] 0.6442309
## 
## $ch
## [1] 494.3193
## 
## $cwidegap
## [1] 0.3829787 0.1303191 0.2606383 0.1303191
## 
## $widestgap
## [1] 0.3829787
## 
## $sindex
## [1] 0.1104039
## 
## $corrected.rand
## NULL
## 
## $vi
## NULL

Corrimos este modelo de clustering primero con 3, 8 y 5 clusters, sin embargo, con la intención de no saturar el cuaderno y ser repetitivos, dejamos aquí unicamente el de 4 clusters pues tras realizar nuestro elbow chart descubrimos que después de 4 clusters la mejoría era poco significativa y que en 8 clusters el modelo empezaba a sobreajustar.

El tamaño de cada cluster es de 605, 263, 811, y 432, respectivamente. El diámetro o distancia máxima entre dos puntos dentro de un cluster varía para cada cluster entre 0.60 y 0.88. La distancia promedio entre todos los pares de puntos en de cada cluster varía entre 0.23 y 0.46.

clusplot(t_buena, kmeans_result$cluster, color=T, shade=T, labels=0, lines=0, main="CLUSTERING K-means")

Estos componentes explican únicamente el 41.44 % de la variabilidad. Ligeramente menor que el de k prototipos. Esto puede significar que el modelo no representa a los datos con tanta consistencia.

Visualizaremos algunas matrices para ver como se agrupan algunas variables en cada uno de los 4 clusters

# Visualizamos Genero
table(kmeans_result$cluster,t_buena$Gender)
##    
##     Female Male
##   1    268  113
##   2    230  655
##   3    114  300
##   4    431    0
# Visualizamos Nivel de obesidad 
table(kmeans_result$cluster,t_buena$NObeyesdad)
##    
##     Insufficient_Weight Normal_Weight Obesity_Type_I Obesity_Type_II
##   1                 127           148             21               2
##   2                  67            92            200             197
##   3                  41            29            110              97
##   4                  37            18             20               1
##    
##     Obesity_Type_III Overweight_Level_I Overweight_Level_II
##   1                2                 45                  36
##   2                0                161                 168
##   3                0                 57                  80
##   4              322                 27                   6
# Visualizamos frecuencia en consumo de vegetales 
table(kmeans_result$cluster,t_buena$FCVC)
##    
##       1   2   3
##   1  65 137 179
##   2  76 753  56
##   3  27 367  20
##   4  34   0 397

Visualizamos con un scatterplot el cluster contemplando Edad y Frecuencia de actividad física

# Agregamos la asignación de cluster al dataset original
t_buena$Cluster = as.factor(kmeans_result$cluster)

# Creamos un scatterplot
ggplot(t_buena, aes(x = Age, y = FAF, color = Cluster)) +
  geom_point() +
  ggtitle("Clustering Scatterplot") +
  xlab("Age") +
  ylab("FAF") +
  theme_minimal()

Aquí podemos percibir algunos agrupamientos interesantes, por ejemplo el cluster 1 nos dice que las personas que hacen ejercicio de 0 a 2 días por semana se concentran más bien en el rango de edad de 15 a 25 años. El cluster 3 agrupa a personas de 25 a 35 años que hacen ejercicio de 0 a 4 días y en este mismo cluster se cuelan también algunos de edades menores. Los clusters 2 y 4 parecen estar menos agrupados o ser menos consistentes.

Realizamos un Elbow Chart

# Nos arrojaba un error sobre presencia de nulos, así que los eliminamos aquí
t_buena_sin_na = na.omit(t_buena[, 1:8])

# Calcular la matriz de distancia de Gower
distancia_gower = daisy(t_buena_sin_na, metric = "gower")

# Inicializamos un vector para almacenar el la suma total de cuadrados
tot.withinss = numeric(10)

# Hacemos iteraciones sobre 10 clusters
for (i in 1:10) {
  # Intentaremos realizar k-means
  try({
    kmeans_result = kmeans(distancia_gower, centers = i, nstart = 20)
    tot.withinss[i] = kmeans_result$tot.withinss
  }, silent = TRUE)
}

# Graficamos el Elbow Chart
plot(1:10, tot.withinss, type = "b", pch = 19, main = "ELBOW Chart", xlab = "Número de Clusters", ylab = "Total suma de cuadrados")

Este Elbow chart es bastante interesante, pues además de mostrar que no hay gran mejora en el rango de 4 a 7 clusters de pronto tiene un desenso repentino hasta 8 clusters, esto parece indicar que a partir de 8 clusters el modelo deja de funcionar y sobreajusta.

Hacemos un dendograma para graficar clustering por gerarquias

dendobes = hclust(
  dist(t_buena[,1:9], method="euclidian"),
  method="complete"
  )
## Warning in dist(t_buena[, 1:9], method = "euclidian"): NAs introduced by
## coercion
plot(dendobes, hang = -5, cex = 0.7, main ="DENDOGRAMA")

Es este dendograma hacemos un clusterin por gerarquias y podemos observar que si pasáramos una horizontal aproximadamente por el 25 o 30 obtendríamos los 4 grupos o clusters de nuestro modelo de k-means. Se ve algo apretado sin embargo intentamos con distintos métodos y “complete” era el que mas separado quedaba.

En conclusión, los clusters que obtuvimos no están muy bien definidos o delimitados y existe mucho solapamiento de datos, tal vez reduciendo el número de variables o haciendo una reducción de dimensionalidad (la cual intentaos hacer pero nos topamos con el inconveniente de que casi todas nuestras variables son categóricas y eso dificulta el prosedimiento, motivo por el cual desistimos de la odisea) podríamos obtener algunos clusters mejor definidos y explicados por sus variables.