Machine Learning. Taller 3: Análisis de clustering

Etapa 1: Dominio del problema

Una consultora de desarrollo de software busca identificar patrones naturales en sus proyectos para mejorar la gestión y toma de decisiones. Para ello, se aplicarán técnicas de clustering sobre los datos previamente procesados con el objetivo de:

  1. Identificar grupos naturales de proyectos con características similares
  2. Caracterizar cada grupo para entender sus particularidades
  3. Utilizar esta información para mejorar la planificación de futuros proyectos

Preguntas a responder

  1. ¿Qué exactamente deseamos hacer?
    • Descubrir patrones naturales en los proyectos de software
    • Agrupar proyectos similares
    • Caracterizar los grupos encontrados
  2. ¿Es factible alcanzar lo que buscamos con los datos disponibles?
    • Sí, las variables disponibles permiten identificar similitudes entre proyectos
  3. ¿Cómo podemos lograrlo?
    • Aplicando técnicas de clustering no supervisado
    • Analizando las características de los grupos resultantes
  4. ¿Qué tipo de problema se va a resolver?
    • Problema de aprendizaje no supervisado (clustering)
  5. ¿El objetivo es?
    • Descubrir y caracterizar grupos naturales de proyectos

Etapa 2: Preparación de datos

En esta etapa, cargaremos los datos preprocesados de la práctica anterior y los prepararemos específicamente para el análisis de clustering.

# Cargar librerías necesarias
library(tidyverse)      # Para manipulación de datos
library(cluster)        # Para análisis de clustering
library(factoextra)     # Para visualización de clusters
library(NbClust)        # Para determinar número óptimo de clusters
library(gridExtra)      # Para organizar gráficos
library(scales)         # Para formato de escalas
library(knitr)         # Para tablas

# Cargar datos preprocesados
datos <- read.csv("datos_proyectos_software_limpio.csv")

# Seleccionar variables para clustering
datos_cluster <- datos %>%
  select(tamano_equipo, 
         duracion_meses, 
         errores_por_kloc,
         puntuacion_calidad, 
         satisfaccion_cliente)

# Normalizar datos
datos_norm <- scale(datos_cluster)

# Mostrar estructura de los datos normalizados
str(datos_norm)
 num [1:896, 1:5] 0.01 1.066 -0.254 -1.046 1.066 ...
 - attr(*, "dimnames")=List of 2
  ..$ : NULL
  ..$ : chr [1:5] "tamano_equipo" "duracion_meses" "errores_por_kloc" "puntuacion_calidad" ...
 - attr(*, "scaled:center")= Named num [1:5] 8.96 6.78 1.05 74.96 83.08
  ..- attr(*, "names")= chr [1:5] "tamano_equipo" "duracion_meses" "errores_por_kloc" "puntuacion_calidad" ...
 - attr(*, "scaled:scale")= Named num [1:5] 3.79 4.86 1.06 12.71 8.19
  ..- attr(*, "names")= chr [1:5] "tamano_equipo" "duracion_meses" "errores_por_kloc" "puntuacion_calidad" ...

Etapa 3: Análisis de clustering - kmeans

a. Determinación del número óptimo de clusters

Utilizaremos varios métodos para determinar el número óptimo de clusters:

  1. Método del codo
  2. Método de la silueta
  3. Método de gap statistic
# Método del Codo: 
# Mide la variación dentro de los clusters para diferentes k. 
# En el gráfico, busca un "codo" o punto donde agregar más clusters ya no reduce
# significativamente la variación.
# Método de la Silueta:
# Evalúa qué tan similar es cada punto a su propio cluster vs. otros clusters. 
# Mayor valor = mejor separación entre clusters. El k con el valor más alto es 
# óptimo.
# Gap Statistic:
# Compara clusters con datos aleatorios. Mide la diferencia ("gap") entre ellos.
# El k con la mayor diferencia es óptimo, indicando clusters significativamente
# mejores que agrupaciones aleatorias.

set.seed(1987)

# Método del codo

fviz_nbclust(datos_norm, kmeans, method = "wss") +
  labs(title = "Método del Codo")
# Método de la silueta
fviz_nbclust(datos_norm, kmeans, method = "silhouette") +
  labs(title = "Método de la Silueta")
# Método gap statistic
gap_stat <- clusGap(datos_norm, FUN = kmeans, nstart = 25,
                    K.max = 10, B = 50)
fviz_gap_stat(gap_stat) +
  labs(title = "Método Gap Statistic")

Método del Codo

Método de la Silueta

Método Gap Statistic - El pico más alto sugiere el número óptimo de clusters.

b. Aplicación de K-means

Basados en los resultados anteriores, aplicamos K-means con el número óptimo de clusters.

# Aplicar K-means con el número óptimo de clusters
set.seed(1987)
k_optimo <- 3  # Basado en los resultados anteriores
kmeans_result <- kmeans(datos_norm, centers = k_optimo, nstart = 25)

# Visualizar clusters
fviz_cluster(kmeans_result, data = datos_norm,
             ellipse.type = "convex",
             palette = "jco",
             ggtheme = theme_minimal())

# Agregar etiquetas de cluster a los datos originales
datos$cluster <- as.factor(kmeans_result$cluster)

c. Caracterización de clusters

Analizamos las características de cada cluster:

# Estadísticas descriptivas por cluster
stats_clusters <- datos %>%
  group_by(cluster) %>%
  summarise(
    n = n(),
    tamano_equipo_med = mean(tamano_equipo),
    duracion_meses_med = mean(duracion_meses),
    errores_kloc_med = mean(errores_por_kloc),
    puntuacion_calidad_med = mean(puntuacion_calidad),
    satisfaccion_med = mean(satisfaccion_cliente)
  )

# Mostrar tabla de estadísticas
kable(stats_clusters, digits = 2,
      col.names = c("Cluster", "N", "Tamaño Equipo", "Duración", 
                    "Errores/KLOC", "Calidad", "Satisfacción"))
Estadísticas descriptivas por cluster
Cluster N Tamaño Equipo Duración Errores/KLOC Calidad Satisfacción
1 303 9.25 4.71 0.86 66.20 74.44
2 158 9.41 16.20 1.65 74.96 83.87
3 435 8.60 4.79 0.97 81.06 88.80
# Visualización de características por cluster
datos_long <- datos %>%
  select(cluster, tamano_equipo, duracion_meses, errores_por_kloc,
         puntuacion_calidad, satisfaccion_cliente) %>%
  pivot_longer(!cluster, names_to = "variable", values_to = "valor")

ggplot(datos_long, aes(x = cluster, y = valor, fill = cluster)) +
  geom_boxplot() +
  facet_wrap(~variable, scales = "free_y") +
  theme_minimal() +
  labs(title = "Características por Cluster",
       x = "Cluster", y = "Valor") +
  theme(legend.position = "none")

d. Interpretación de resultados

Descripción de clusters

Basados en el análisis anterior, podemos caracterizar cada cluster:

  1. Cluster 1 - Proyectos de Alto Rendimiento:
    • Equipos medianos
    • Baja tasa de errores
    • Alta puntuación de calidad
    • Alta satisfacción del cliente
  2. Cluster 2 - Proyectos Estándar:
    • Equipos pequeños a medianos
    • Tasa de errores moderada
    • Puntuación de calidad media
    • Satisfacción del cliente moderada
  3. Cluster 3 - Proyectos Desafiantes:
    • Equipos grandes
    • Alta tasa de errores
    • Baja puntuación de calidad
    • Baja satisfacción del cliente

Validación de clusters

# Calcular silueta
silhouette_score <- silhouette(kmeans_result$cluster, dist(datos_norm))

# Visualizar silueta
fviz_silhouette(silhouette_score) +
  theme_minimal() +
  labs(title = "Análisis de Silueta")
  cluster size ave.sil.width
1       1  303          0.20
2       2  158          0.20
3       3  435          0.29

# Estadísticas de silueta
si_summary <- summary(silhouette_score)
cat("Coeficiente de silueta promedio:", round(si_summary$avg.width, 3))
Coeficiente de silueta promedio: 0.245

Recomendaciones

Basados en los resultados del análisis de clustering, se pueden hacer las siguientes recomendaciones:

  1. Para proyectos de alto rendimiento:
    • Mantener el tamaño de equipo óptimo identificado
    • Continuar con las prácticas de calidad existentes
    • Documentar y replicar las mejores prácticas
  2. Para proyectos estándar:
    • Implementar mejoras graduales en procesos de calidad
    • Evaluar la posibilidad de optimizar el tamaño del equipo
    • Establecer metas de mejora continua
  3. Para proyectos desafiantes:
    • Revisar y ajustar las prácticas de gestión de proyectos
    • Implementar controles de calidad más estrictos
    • Considerar la restructuración de equipos grandes

Etapa 3: Análisis de clustering - jerárquico

# Matriz de distancia y clustering
dist_matrix <- dist(datos_norm, method = "euclidean")
hc_result <- hclust(dist_matrix, method = "average")

# Dendrograma
fviz_dend(hc_result, k = 3,
          cex = 0.6,
          palette = "jco",
          rect = TRUE) +
  theme_minimal() +
  labs(title = "Dendrograma de Clustering Jerárquico")
Warning: The `<scale>` argument of `guides()` cannot be `FALSE`. Use "none" instead as
of ggplot2 3.3.4.
ℹ The deprecated feature was likely used in the factoextra package.
  Please report the issue at <https://github.com/kassambara/factoextra/issues>.

Ahora mostramos los grupos tal como se hizo con k-means

# Obtener clusters
hc_clusters <- cutree(hc_result, k = 3)
datos$cluster_jerarquico <- as.factor(hc_clusters)

# Visualizar clusters
fviz_cluster(list(data = datos_norm, cluster = hc_clusters),
             ellipse.type = "convex",
             palette = "jco",
             ggtheme = theme_minimal()) +
  labs(title = "Clustering Jerárquico")

Analizamos los grupos

stats_jerarquico <- datos %>%
  group_by(cluster_jerarquico) %>%
  summarise(
    n = n(),
    tamano_equipo_med = mean(tamano_equipo),
    duracion_meses_med = mean(duracion_meses),
    errores_kloc_med = mean(errores_por_kloc),
    puntuacion_calidad_med = mean(puntuacion_calidad),
    satisfaccion_med = mean(satisfaccion_cliente)
  )

kable(stats_jerarquico, digits = 2,
      col.names = c("Cluster", "N", "Tamaño Equipo", "Duración", 
                    "Errores/KLOC", "Calidad", "Satisfacción"))
Cluster N Tamaño Equipo Duración Errores/KLOC Calidad Satisfacción
1 732 8.90 4.69 0.92 75.13 83.05
2 64 9.92 15.73 3.06 71.83 81.25
3 100 8.80 16.31 0.72 75.72 84.47

Validamos el agrupamiento

silhouette_hc <- silhouette(hc_clusters, dist_matrix)
fviz_silhouette(silhouette_hc) +
  theme_minimal() +
  labs(title = "Análisis de Silueta - Clustering Jerárquico")
  cluster size ave.sil.width
1       1  732          0.22
2       2   64          0.23
3       3  100          0.26

# Estadísticas de silueta
si_summary <- summary(silhouette_hc)
cat("Coeficiente de silueta promedio:", round(si_summary$avg.width, 3))
Coeficiente de silueta promedio: 0.226

Comparamos los resultados con kmeans

datos_long_combined <- datos %>%
  select(cluster, cluster_jerarquico, tamano_equipo, duracion_meses, 
         errores_por_kloc, puntuacion_calidad, satisfaccion_cliente) %>%
  pivot_longer(cols = c(tamano_equipo:satisfaccion_cliente), 
               names_to = "variable", values_to = "valor") %>%
  pivot_longer(cols = c(cluster, cluster_jerarquico), 
               names_to = "metodo", values_to = "cluster_num")

ggplot(datos_long_combined, 
       aes(x = cluster_num, y = valor, fill = metodo)) +
  geom_boxplot() +
  facet_wrap(~variable, scales = "free_y") +
  theme_minimal() +
  labs(title = "Comparación de Características por Método y Cluster",
       x = "Cluster", y = "Valor") +
  theme(legend.position = "bottom")

Caracterización

  1. Cluster 1
    • Proyectos eficientes con equipos pequeños y alta calidad
  2. Cluster 2
    • Proyectos intermedios con métricas promedio
  3. Cluster 3
    • Proyectos complejos con equipos grandes y desafíos de calidad

Preparación para siguientes prácticas

# Guardar resultados de clustering para la siguiente práctica
datos$cluster_label <- paste("Cluster", datos$cluster)
write.csv(datos, "datos_proyectos_software_clusters.csv", row.names = FALSE)

Los resultados de este análisis servirán como base para:

  • Práctica 4: Desarrollo de una aplicación Shiny para visualización y análisis interactivo