El análisis de clustering es una técnica estadística no supervisada utilizada para identificar grupos naturales dentro de un conjunto de datos. Cada subconjunto es un clúster, de modo que los objetos en un clúster son similares entre sí, pero diferentes a los objetos en otros clústeres. En el contexto de la biología, estos métodos permiten explorar patrones morfológicos, clasificar organismos según similitudes o ragos funcionales sin necesidad de etiquetas previas.
En este trabajo se emplea el algoritmo K-Means, uno de los métodos de clustering más utilizados debido a su eficiencia y facilidad de interpretación (Jain, 2010). El análisis se realiza sobre la base de datos de pingüinos del archipiélago Palmer (Horst et al., 2020), la cual contiene medidas morfométricas clave como longitud y profundidad del pico, longitud de la aleta y masa corporal.
El objetivo principal es determinar si las características morfológicas permiten agrupar a los individuos de manera coherente, evaluando si estos grupos coinciden o no con las especies conocidas.
library(palmerpenguins)
library(dplyr)
library(ggplot2)
library(factoextra)
library(table1)
library(GGally)
library(patchwork)
library(knitr)
data("penguins")
Los datos fueron recopilados y puestos a disposición por la Dra. Kristen Gorman y la Estación Palmer, Antártida LTER, miembro de Long Term Ecological Research Network (LTER). El paquete palmerpenguins contiene dos conjuntos de datos: Penguins_raw y Penguins. Ambos conjuntos contienen información sobre 344 pingüinos. Se incluyen tres especies de pingüinos del genero Pygoscelis (Adelidae: Pygoscelis adeliae; Gentoo; Pygoscelis papua y Chinstrap: Pygoscelis antarcticus), recopiladas en tres islas del archipiélago Palmer, Antártida, entre 2007 y 2009.
df <- penguins %>%
select(species, bill_length_mm, bill_depth_mm, flipper_length_mm, body_mass_g) %>%
na.omit() # Esto asegura que todas las filas sean completas
# Tabla resumen por especie
table1(~ bill_length_mm + bill_depth_mm + flipper_length_mm + body_mass_g | species, data = df)
| Adelie (N=151) |
Chinstrap (N=68) |
Gentoo (N=123) |
Overall (N=342) |
|
|---|---|---|---|---|
| bill_length_mm | ||||
| Mean (SD) | 38.8 (2.66) | 48.8 (3.34) | 47.5 (3.08) | 43.9 (5.46) |
| Median [Min, Max] | 38.8 [32.1, 46.0] | 49.6 [40.9, 58.0] | 47.3 [40.9, 59.6] | 44.5 [32.1, 59.6] |
| bill_depth_mm | ||||
| Mean (SD) | 18.3 (1.22) | 18.4 (1.14) | 15.0 (0.981) | 17.2 (1.97) |
| Median [Min, Max] | 18.4 [15.5, 21.5] | 18.5 [16.4, 20.8] | 15.0 [13.1, 17.3] | 17.3 [13.1, 21.5] |
| flipper_length_mm | ||||
| Mean (SD) | 190 (6.54) | 196 (7.13) | 217 (6.48) | 201 (14.1) |
| Median [Min, Max] | 190 [172, 210] | 196 [178, 212] | 216 [203, 231] | 197 [172, 231] |
| body_mass_g | ||||
| Mean (SD) | 3700 (459) | 3730 (384) | 5080 (504) | 4200 (802) |
| Median [Min, Max] | 3700 [2850, 4780] | 3700 [2700, 4800] | 5000 [3950, 6300] | 4050 [2700, 6300] |
# Boxplots por especie
p1 <- ggplot(df, aes(x = species, y = bill_length_mm, fill = species)) +
geom_boxplot() +
theme_minimal() +
labs(title = "Longitud del pico por especie",
x = "Especie", y = "Longitud del pico (mm)") +
theme(legend.position = "none")
p2 <- ggplot(df, aes(x = species, y = flipper_length_mm, fill = species)) +
geom_boxplot() +
theme_minimal() +
labs(title = "Longitud de la aleta por especie",
x = "Especie", y = "Longitud de la aleta (mm)") +
theme(legend.position = "none")
p3 <- ggplot(df, aes(x = species, y = bill_depth_mm, fill = species)) +
geom_boxplot() +
theme_minimal() +
labs(title = "Profundidad del pico por especie",
x = "Especie", y = "Profundidad del pico (mm)") +
theme(legend.position = "none")
p4 <- ggplot(df, aes(x = species, y = body_mass_g, fill = species)) +
geom_boxplot() +
theme_minimal() +
labs(title = "Masa corporal por especie",
x = "Especie", y = "Masa corporal (g)") +
theme(legend.position = "none")
# Combinar todos los gráficos en una cuadrícula 2x2
(p1 | p2) /
(p3 | p4)
# --- Gráfico de correlación entre variables ---
ggpairs(df[, 2:5], title = "Relaciones entre variables morfológicas")
Se estandarizan las variables (media = 0 y desviasion estandar = 1), para evitar que las variables con unidades grandes dominen el modelo
num_scaled <- scale(df[, 2:5])
fviz_nbclust(num_scaled, kmeans, method = "wss") +
ggtitle("Método del Codo - Número óptimo de clusters")
El primer gráfico utilizando la suma de cuadrados dentro del intervalo (WSS), se busca encontrar el punto donde la reducción del WSS deja de ser significantiva, lo que se puede observar cuando la curva forma un “codo”, indicando que los clústeres adicionales proporcionan rendimientos y valor decrecientes. Con este grafico se puede interpretar que k=3 es probablemente apropiado.
fviz_nbclust(num_scaled, kmeans, method = "silhouette") +
ggtitle("Método de la Silueta - Número óptimo de clusters")
En el segundo metodo dio k=2, pero teniendo en cuenta que ya se sabe que
son tres especies de pingüinos, se puede inferir que k=3 podría ser un
mejor ajuste.
Su objetivo es agrupar los datos en K grupos distintos, de forma que los elementos dentro de cada grupo sean similares entre sí, y diferentes de los de otros grupos, según las variables numéricas.
K-Means comienza con “semillas” aleatorias (puntos iniciales). Este parámetro hace que el algoritmo se ejecute 25 veces con distintos puntos iniciales, y elija el mejor resultado (el de menor variabilidad interna). Así se evitan soluciones locales subóptimas.
set.seed(123)
km <- kmeans(num_scaled, centers = 3, nstart = 25)
print(km)
## K-means clustering with 3 clusters of sizes 123, 132, 87
##
## Cluster means:
## bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
## 1 0.6562677 -1.0983711 1.1571696 1.0901639
## 2 -1.0465260 0.4858415 -0.8899121 -0.7694891
## 3 0.6600059 0.8157307 -0.2857869 -0.3737654
##
## Clustering vector:
## [1] 2 2 2 2 2 2 2 2 3 2 2 2 2 2 2 2 3 2 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
## [38] 2 2 2 2 2 3 2 2 2 2 2 3 2 2 2 3 2 2 2 2 2 2 2 3 2 2 2 2 2 2 2 3 2 2 2 3 2
## [75] 3 2 2 2 3 2 3 2 2 2 2 2 2 2 2 2 3 2 2 2 3 2 2 2 3 2 3 2 2 2 2 2 2 2 3 2 3
## [112] 2 3 2 3 2 2 2 2 2 2 2 3 2 2 2 2 2 3 2 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
## [149] 2 2 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [186] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [223] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [260] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 3
## [297] 2 3 3 3 3 3 3 3 2 3 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 3 3 3 3
## [334] 3 3 3 3 3 3 3 3 3
##
## Within cluster sum of squares by cluster:
## [1] 143.1502 122.1477 112.9852
## (between_SS / total_SS = 72.3 %)
##
## Available components:
##
## [1] "cluster" "centers" "totss" "withinss" "tot.withinss"
## [6] "betweenss" "size" "iter" "ifault"
El 72.3% hace referencia a que ese porcentaje de la variabilidad total de los datos está explicado por la diferencia entre clusters y explica la mayor parte de la variación de los datos (> 70% indica que los clusters están bien definidos y alejados entre sí.)
df$Cluster <- as.factor(km$cluster)
La función fviz_cluster() toma el resultado de un modelo de clustering (km) y lo proyecta visualmente en dos dimensiones. Esta función utiliza un Análisis de Componentes Principales (PCA) para reducir la dimensionalidad. En otras palabras, reduce el número de variables, conservando la mayor parte de la información y la varianza de los datos originales, facilitando la visualización y el análisis. De esta manera, acelera el procesamiento y minimiza la pérdida de información.
fviz_cluster(km, data = num_scaled,
geom = "point",
ellipse.type = "norm",
palette = "viridis",
ggtheme = theme_minimal()) +
ggtitle("Clustering de Pingüinos (K-Means, K=3)")
Las medidas morfológicas separan bien a los pingüinos en tres grupos. El cluster 1 agrupa individuos más grandes y los clusters 2 y 3 a grupos más pequeños.
df_summary <- df %>%
group_by(Cluster) %>%
summarise(across(c(bill_length_mm, bill_depth_mm, flipper_length_mm, body_mass_g),
mean, na.rm = TRUE))
kable(df_summary, align = "l")
| Cluster | bill_length_mm | bill_depth_mm | flipper_length_mm | body_mass_g |
|---|---|---|---|---|
| 1 | 47.50488 | 14.98211 | 217.1870 | 5076.016 |
| 2 | 38.20833 | 18.11061 | 188.4015 | 3584.659 |
| 3 | 47.52529 | 18.76207 | 196.8966 | 3902.011 |
| Cluster | Descripción |
|---|---|
| Cluster 1 | El cluster 1 es el de mayor tamaño corporal (5076 g) y las aletas más largas (217 mm). |
| Cluster 2 | El cluster 2 es el más pequeño (3585 g) con un pico corto (38.2 mm) y aletas cortas (188 mm). |
| Cluster 3 | El cluster 3 tiene el pico largo (47.5 mm) y profundo (18.8 mm), con un tamaño corporal intermedio (3902 g). |
table(df$Cluster, df$species)
##
## Adelie Chinstrap Gentoo
## 1 0 0 123
## 2 127 5 0
## 3 24 63 0
Esta tabla que muestra cuántos individuos de cada especie real cayeron dentro de cada cluster del modelo:
El primer clúster agrupó a los individuos de mayor tamaño corporal y aletas más largas (Gentoo), el segundo reunió a los individuos de menor tamaño y pico más corto (Adelie), mientras que el tercero representó una combinación intermedia con picos más largos y profundos (Chinstrap). El modelo agrupo el 100% de los datos para el primer cluster, el 96% para el segundo cluster y el 72% para el tercero.
El análisis de clustering mediante K-means permitió identificar tres grupos morfológicamente diferenciados dentro de la población de pingüinos analizada. Cada clúster reflejó combinaciones específicas de rasgos corporales, lo que evidencia patrones naturales de variación asociados, posiblemente, a diferencias entre especies.
Gorman, K. B., Williams, T. D., & Fraser, W. R. (2014). Ecological sexual dimorphism and environmental variability within a community of Antarctic penguins (genus Pygoscelis). PloS one, 9(3), e90081.
Jain, A. K. (2010). Data clustering: 50 years beyond K-means. Pattern recognition letters, 31(8), 651-666.