library(pacman)
p_load(rio, cluster, factoextra, tidyverse, ggrepel, scatterplot3d)
library(tidyclust)
library(tidymodels)
9 Clustering jerárquico
9.1 Ejemplo introductorio
9.1.1 Base de datos
Vamos a explorar un ejemplo con una data pequeña:
<- c("A", "B", "C", "D", "E", "F")
Caso <- c(1, 2, 4, 6, 1, 4)
Var1 <- c(2, 5, 1, 4, 3, 6)
Var2 <- data.frame(Caso, Var1, Var2)
datarow.names(data) <- data$Caso
$Caso<- NULL data
9.1.2 Exploración y Estandarización
Antes de realizar un análisis de clustering, es crucial estandarizar los datos porque las variables pueden tener diferentes escalas y unidades de medida. Si no se estandarizan, las variables con valores más grandes pueden dominar las distancias calculadas, sesgando los resultados del clustering.
La estandarización coloca todas las variables en una escala común, normalmente con media cero y desviación estándar uno, asegurando que cada variable contribuya de manera equitativa al cálculo de distancias y permitiendo obtener grupos que reflejen patrones verdaderos en los datos.
#data.est <- as.data.frame(scale(data))
#str(data)
9.1.3 Medidas de distancia
La distancia entre puntos de datos cuantifica cuán cercanos o lejanos están unos de otros en el espacio multidimensional, influyendo directamente en la formación de los clusters. La matriz de distancias, que recopila todas las distancias entre cada par de puntos, sirve como la base para algoritmos de clustering como el de k-means o jerárquico, guiando el proceso de agrupamiento al identificar conjuntos de puntos que son más cercanos entre sí.
9.1.3.1 Tipos de distancia
Existen varios métodos de distancia utilizados en análisis de datos, siendo la distancia Euclídea la más común debido a su simplicidad y a que proporciona una medida directa de la separación entre puntos en un espacio multidimensional, ideal para datos continuos y normalmente distribuidos.
Sin embargo, en escenarios donde los datos son mixtos (es decir, contienen variables continuas, categóricas y binarias), es más apropiado utilizar otras métricas como la distancia de Gower, que permite calcular una similitud generalizada combinando diferentes tipos de variables. Este enfoque es crucial cuando se trabaja con conjuntos de datos heterogéneos, como encuestas que contienen respuestas numéricas y categóricas, asegurando así que todas las variables sean consideradas adecuadamente en el análisis.
9.1.3.2 Calculando la distancia
Las funciones usadas con mayor frecuencia para calcular las distancias son: dist(), get_dist() y daisy(), de los paquetes stats, factoextra y cluster:
Distancia euclidiana con dist()
<- dist(data, method = "euclidean")
dist.eucl
round(as.matrix(dist.eucl), 1)
A B C D E F
A 0.0 3.2 3.2 5.4 1.0 5.0
B 3.2 0.0 4.5 4.1 2.2 2.2
C 3.2 4.5 0.0 3.6 3.6 5.0
D 5.4 4.1 3.6 0.0 5.1 2.8
E 1.0 2.2 3.6 5.1 0.0 4.2
F 5.0 2.2 5.0 2.8 4.2 0.0
Otras distancias: “maximum”, “manhattan”, “minkowski”
Distancia euclidiana con daisy()
library(cluster)
<- daisy(data, metric= "euclidean")
dist.eucl2 round(as.matrix(dist.eucl2), 1)
A B C D E F
A 0.0 3.2 3.2 5.4 1.0 5.0
B 3.2 0.0 4.5 4.1 2.2 2.2
C 3.2 4.5 0.0 3.6 3.6 5.0
D 5.4 4.1 3.6 0.0 5.1 2.8
E 1.0 2.2 3.6 5.1 0.0 4.2
F 5.0 2.2 5.0 2.8 4.2 0.0
Otras distancias: “gower”, “manhattan”.
Distancia euclidiana con get_dist()
library(factoextra)
<- get_dist(data,
res.dist stand = TRUE, # En este caso estamos estandarizando al mismo tiempo!
method = "euclidean")
round(as.matrix(res.dist), 1)
A B C D E F
A 0.0 1.7 1.6 2.7 0.5 2.6
B 1.7 0.0 2.4 2.1 1.2 1.1
C 1.6 2.4 0.0 1.9 1.8 2.7
D 2.7 2.1 1.9 0.0 2.6 1.5
E 0.5 1.2 1.8 2.6 0.0 2.2
F 2.6 1.1 2.7 1.5 2.2 0.0
Otras distancias: “pearson”, “kendall”, “spearman”
9.1.3.3 Visualizando
Ver un gráfico de matriz de distancias como un heatmap en lugar de una matriz numérica es útil porque proporciona una visualización más intuitiva y rápida de las relaciones de similitud o disimilitud entre las observaciones. A diferencia de una matriz numérica, que puede ser difícil de interpretar a simple vista, el heatmap utiliza colores para representar distancias, facilitando la identificación de patrones, clusters, o grupos de datos que son similares entre sí.
Podemos visualizar la matriz de distancia con fviz_dist():
fviz_dist(res.dist)
fviz_dist(res.dist,
gradient = list(low = "white",
high = "#FC4E07"))
Interpretación del segundo heatmap:
Colores: La intensidad del color representa la magnitud de la distancia. En este gráfico, los colores más oscuros (rojo intenso) indican distancias mayores, mientras que los colores más claros (blanco o tonos claros) representan distancias menores.
Valores: Los valores en la barra de color a la derecha (de 0 a 2) indican la escala de la distancia. Por ejemplo, una celda de color blanco tiene un valor cercano a 0, lo que sugiere que las observaciones comparadas son muy similares o cercanas entre sí. Una celda de color más oscuro indica una mayor diferencia o distancia entre las observaciones.
9.1.4 Ejemplo con cluster jerárquico aglomerativo
Aplicamos el algoritmo que vimos en clase sobre las distancias calculadas anteriormente.
= hcut(dist.eucl, # MATRÍZ DE DISTANCIAS!!!
aglomerativo k = 3, # Extrae tres cluster
hc_func='agnes', # Método aglomerativo
hc_method = "single") # Método de agrup. sea single
NO SE CALCULA SOBRE LAS VARIABLES ORIGINALES.
Para obtener un número específico de clústeres a partir del dendrograma, debes “cortar” el dendrograma en un punto donde se divida en el número deseado de clústeres. El parámetro k le dice a la función cuántos clústeres deseas obtener después de hacer este corte.
hc_func=‘agnes’ especifica el uso del método de clustering jerárquico aglomerativo (AGNES).
hc_method = “single” indica el uso del método de enlace simple (single linkage) para calcular las distancias entre clústeres.
9.1.5 Visualización del clúster
Un dendrograma es una representación gráfica que muestra cómo se agrupan las observaciones (o datos) en un análisis de clustering jerárquico. Es una herramienta visual que ayuda a entender la estructura de los datos y cómo se forman los grupos o clusters de manera jerárquica.
En un dendrograma, las observaciones individuales se fusionan en clusters más grandes a medida que nos movemos hacia arriba en el gráfico, con la altura en el eje vertical representando la distancia o disimilitud entre los clusters.
plot(aglomerativo)
Eje vertical (Height): La altura indica la disimilitud o distancia entre los clusters que se fusionan. Cuanto mayor sea la altura de la unión, más diferentes son los clusters.
Fusiones de Clusters:
Las observaciones A y E se fusionan primero a una altura baja, lo que indica que son muy similares entre sí.
Luego, el cluster formado por A y E se une con el siguiente cluster (B) a una altura mayor, indicando una similitud moderada.
A continuación, la observación F se une al cluster previo de (A, E, B).
Más arriba, las observaciones D y F se combinan, y finalmente se une la observación C al cluster completo a una altura relativamente alta, lo que sugiere que C es la más disímil respecto a las demás observaciones.
Por otro lado, también el gráfico brinda el coeficiente Agregativo (Agglomerative Coefficient = 0.34). Este coeficiente es una medida de la estructura del clustering. Un valor cercano a 1 indica que los clusters son compactos y bien definidos, mientras que un valor cercano a 0 indica que los clusters son menos claros. En este caso, un valor de 0.34 sugiere que la estructura del clustering no es muy fuerte o bien definida.
En nuestro objeto creado podemos ver los elementos que tenemos.
names(aglomerativo)
[1] "order" "height" "ac" "merge" "diss" "call"
[7] "method" "order.lab" "cluster" "nbclust" "silinfo" "size"
[13] "data"
Lo más importante es extraer los cluster en los que se encuentran cada uno de nuestros casos:
$cluster aglomerativo
[1] 1 1 2 3 1 1
El número del cluster obtenido por el algoritmo sólo debe ser interpretado como una variable nominal, es decir no necesiamente está relacionado a un orden de magnitud de las variables.
Para saber las características que están dentro del cluster, debes de realizar un análisis exploratorio (lo veremos más abajo!).
9.2 Clúster jerárquico: USArrest
Data criminológica USArrest
Debemos:
Obtener data original.
Omitir perdidos: Esto es muy importante. No vamos a poder aplicar un algoritmo de clusterización si tenemos valores perdidos en nuestra variable.
Colocar nombre a las filas. Esto se realiza para identificar los casos en un gráfico. Si el número de datos es muy grande, no será necesario (las etiquetas se van a superponer y será ilegible).
Guardar la data editada con otro nombre (“subdata”, en este caso).
USArrests
Murder Assault UrbanPop Rape
Alabama 13.2 236 58 21.2
Alaska 10.0 263 48 44.5
Arizona 8.1 294 80 31.0
Arkansas 8.8 190 50 19.5
California 9.0 276 91 40.6
Colorado 7.9 204 78 38.7
Connecticut 3.3 110 77 11.1
Delaware 5.9 238 72 15.8
Florida 15.4 335 80 31.9
Georgia 17.4 211 60 25.8
Hawaii 5.3 46 83 20.2
Idaho 2.6 120 54 14.2
Illinois 10.4 249 83 24.0
Indiana 7.2 113 65 21.0
Iowa 2.2 56 57 11.3
Kansas 6.0 115 66 18.0
Kentucky 9.7 109 52 16.3
Louisiana 15.4 249 66 22.2
Maine 2.1 83 51 7.8
Maryland 11.3 300 67 27.8
Massachusetts 4.4 149 85 16.3
Michigan 12.1 255 74 35.1
Minnesota 2.7 72 66 14.9
Mississippi 16.1 259 44 17.1
Missouri 9.0 178 70 28.2
Montana 6.0 109 53 16.4
Nebraska 4.3 102 62 16.5
Nevada 12.2 252 81 46.0
New Hampshire 2.1 57 56 9.5
New Jersey 7.4 159 89 18.8
New Mexico 11.4 285 70 32.1
New York 11.1 254 86 26.1
North Carolina 13.0 337 45 16.1
North Dakota 0.8 45 44 7.3
Ohio 7.3 120 75 21.4
Oklahoma 6.6 151 68 20.0
Oregon 4.9 159 67 29.3
Pennsylvania 6.3 106 72 14.9
Rhode Island 3.4 174 87 8.3
South Carolina 14.4 279 48 22.5
South Dakota 3.8 86 45 12.8
Tennessee 13.2 188 59 26.9
Texas 12.7 201 80 25.5
Utah 3.2 120 80 22.9
Vermont 2.2 48 32 11.2
Virginia 8.5 156 63 20.7
Washington 4.0 145 73 26.2
West Virginia 5.7 81 39 9.3
Wisconsin 2.6 53 66 10.8
Wyoming 6.8 161 60 15.6
<-USArrests[1:30, # Con fines didácticos sólo utilizaremos los primeros 30 estados
subdatac(1,2,4)]
Paso 0: Análisis exploratorio
Podemos iniciar explorando la relación entre dos variables.
%>%
subdata ggplot(aes(x=Murder, y=Assault)) +
geom_point() +
geom_text(aes(label=rownames(subdata)))
Podríamos hacer lo mismo incorporando la variable “Rape”
%>%
subdata ggplot(aes(x=Murder, y=Rape)) +
geom_point() +
geom_text(aes(label=rownames(subdata)))
Pero, podríamos ver:
Alguna potencial agrupación en nuestras tres variables?
Identificas algún caso fronterizo?
library(plotly)
Adjuntando el paquete: 'plotly'
The following object is masked from 'package:ggplot2':
last_plot
The following object is masked from 'package:rio':
export
The following object is masked from 'package:stats':
filter
The following object is masked from 'package:graphics':
layout
plot_ly(x = subdata$Murder,
y = subdata$Assault,
z = subdata$Rape,
text=rownames(subdata), # Etiquetas
type = "scatter3d", # Tipo de gráfico
mode = "markers") |> # Puntos
layout(scene = list(
xaxis = list(title = "Murder"),
yaxis = list(title = "Assault"),
zaxis = list(title = "Rape")
))
O también podemos usar:
# library(scatterplot3d)
# library(rgl)
# with(subdata,plot3d(subdata[,1:3], type = "s", size=0.5)) +
# with(subdata,text3d(subdata[,1:3], texts=rownames(subdata), pos=4))
Paso 1: Cálculo de distancias
Calculan las distancias:
= daisy(subdata,
distanciasmetric="gower")
# Con la métrica de gower ya no es necesario la estandarización
Paso 2: Determinación del número de clusters
Antes de realizar el clustering, es fundamental determinar cuántos grupos son apropiados para nuestros datos. Existen diversos enfoques para abordar esta decisión, que pueden clasificarse en métodos gráficos y métodos estadísticos.
Métodos gráficos:
Utilizaremos el método del codo (elbow method) mediante la función fviz_nbclust() del paquete factoextra. El WSS mide qué tan compactos son los clusters: valores más bajos indican agrupamientos más cohesionados. El método busca identificar el “codo” en la curva, es decir, el punto donde agregar más clusters ya no produce una mejora sustancial en la compactación. Este punto representa un balance óptimo entre simplicidad del modelo y calidad del agrupamiento.
fviz_nbclust(subdata[,1:3],
# Método de clusterización elegido
hcut, diss=distancias, # Distancias
method = "wss", # Método
k.max = 10, # Probó con 10 cluster
verbose = F)
¿CÓMO LEER EL GRÁFICO?
La curva siempre decrece: A medida que aumentas el número de clusters, el WSS siempre disminuye (los grupos son más pequeños y compactos).
Buscar el “codo”: El punto donde la curva cambia de pendiente pronunciada a más suave. Es donde agregar más clusters ya no mejora sustancialmente la compactación.
Conclusión: El “codo” está en k=2 o k=3. Después de k=3, la mejora es marginal.
Métodos más robusto:
Complementaremos el análisis visual con la función NbClust() del paquete NbClust, que aplica simultáneamente hasta 30 índices diferentes para determinar el número óptimo de clusters. Esta función proporciona un “voto mayoritario” entre múltiples criterios estadísticos, ofreciendo una recomendación más robusta y objetiva.
library(NbClust)
set.seed(2023)
<- NbClust(subdata[,1:3], # Data
res.nbclust distance = "euclidean", # Distancia utilizada
min.nc = 2, # Mínimo
max.nc = 5, # Máximo
method = "average", # Método
index ="all") # Índices considerados
# *** : The Hubert index is a graphical method of determining the number of clusters.
# In the plot of Hubert index, we seek a significant knee that corresponds to a
# significant increase of the value of the measure i.e the significant peak in Hubert
# index second differences plot.
#
# *** : The D index is a graphical method of determining the number of clusters.
# In the plot of D index, we seek a significant knee (the significant peak in Dindex
# second differences plot) that corresponds to a significant increase of the value of
# the measure.
#
# *******************************************************************
# * Among all indices:
# * 12 proposed 2 as the best number of clusters
# * 5 proposed 3 as the best number of clusters
# * 1 proposed 4 as the best number of clusters
# * 6 proposed 5 as the best number of clusters
#
# ***** Conclusion *****
#
# * According to the majority rule, the best number of clusters is 2
#
#
# *******************************************************************
Paso 3: Calculamos el cluster
En R existen diversos paquetes para realizar análisis de clustering, tales como stats (con funciones como hclust() y kmeans()), cluster (que incluye pam() y agnes()), factoextra (con hcut() y eclust()), y tidyclust (que ofrece una interfaz unificada al estilo tidymodels). En este caso, utilizaremos la función hcut() del paquete factoextra, que simplifica el proceso de clustering jerárquico y facilita la visualización de resultados.
= hcut(x = distancias, # Matriz de distancias
aglomerativo k = 2, # Se visualizarán 2
hc_func='agnes', # Aglomerativo
hc_method = "ward.D") # Criterio para aglomerar
El método de enlace de Ward (o método de Ward) es un enfoque para el clustering jerárquico que minimiza la varianza total dentro de los clústeres. Cuando se combinan dos clústeres, el método Ward busca la fusión que resulta en el menor aumento en la suma de las varianzas dentro de los clústeres combinados.
Paso 4: Exploración de clúster creados
Una vez generados los clusters, el primer paso es explorar visualmente la estructura de agrupamiento obtenida. La exploración nos permite comprender cómo se han formado los grupos, identificar qué observaciones pertenecen a cada cluster, y visualizar las relaciones entre ellos mediante dendrogramas y gráficos de dispersión. Esta etapa es fundamentalmente descriptiva y nos ayuda a interpretar la solución de clustering en el contexto de nuestros datos.
Primero, veamos la asignación de cluster:
fviz_dend(aglomerativo, # Nuestro objeto cluster creado
rect = TRUE, # Resalta los cluster solicitados
cex = 0.5) # Tamaño de etiquetas
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>.
Esto quiere decir que a cada observación, en el objeto creado se ha creado también una variable que indica el cluster:
$cluster aglomerativo
[1] 1 1 1 2 1 1 2 2 1 1 2 2 1 2 2 2 2 1 2 1 2 1 2 1 2 2 2 1 2 2
Esta variable (aglomerativo$cluster) nos puede servir como variable de agrupación en nuestra data original a fin de poder describir cuáles son las características de los cluster creados.
Por ejemplo, podemos:
<-subdata %>%
subdata2mutate(aglomerativo=aglomerativo$cluster)
%>%
subdata2 group_by(aglomerativo) %>%
summarise(Murder = mean(Murder),
Assault = mean(Assault),
Rape=mean(Rape))
# A tibble: 2 × 4
aglomerativo Murder Assault Rape
<int> <dbl> <dbl> <dbl>
1 1 12.2 260. 31.2
2 2 5.24 118 16.2
Podemos ver que:
Cluster 1: Presenta, en promedio, mayores niveles de violencia.
Cluster 2: Presenta, en promedio, menores niveles de violencia.
Por otro lado, también podemos utilizar la función fviz_cluster para crear un gráfico de dispersión bidimensional que muestra cómo se distribuyen los clusters en el espacio.
fviz_cluster(object = list(data=subdata2[,1:3],
cluster = subdata2$aglomerativo),
geom = c("text"),
ellipse.type = "convex")
¿CÓMO LEER EL GRÁFICO?
Puntos: Cada observación, coloreada según su cluster asignado
Elipses: Delimitan el área que ocupa cada cluster (opcional)
Ejes (Dim1 y Dim2): Son variables (componentes principales) que resumen la variabilidad de tus datos. El porcentaje indica cuánta información captura cada eje.
Paso 5: Validación de clúster creados
Después de explorar los clusters formados, es necesario evaluar la calidad del agrupamiento obtenido. La validación utiliza métricas cuantitativas que nos indican qué tan coherentes son los clusters internamente (compactos) y qué tan separados están entre sí.
Herramientas como el coeficiente de silueta nos permiten determinar si la solución de clustering es adecuada y comparar diferentes configuraciones para seleccionar el número óptimo de grupos.
El gráfico de silueta es una herramienta visual que nos indica qué tan bien están agrupadas las observaciones en sus respectivos clusters. Para cada observación, el gráfico muestra un valor (coeficiente de silueta) que va de -1 a 1:
Cercano a 1: La observación está muy bien ubicada en su cluster, muy similar a sus compañeros de grupo y alejada de otros clusters.
Cercano a 0: La observación está en la frontera, podría pertenecer tanto a su cluster actual como al vecino.
Negativo (cercano a -1): La observación probablemente está mal clasificada, sería más similar a las observaciones de otro cluster.
fviz_silhouette(aglomerativo, label=TRUE)
cluster size ave.sil.width
1 1 13 0.49
2 2 17 0.55
Puedes ver más sobre esta gráfico de silueta en este video:
https://www.youtube.com/watch?v=_jg1UFoef1c
¿CÓMO LEER EL GRÁFICO?
Barras largas y positivas: Indican clusters bien definidos
Barras cortas o negativas: Señalan observaciones mal asignadas o clusters poco coherentes
Clusters de tamaño similar con siluetas altas: Sugieren una buena solución de clustering
Asimismo, podemos preguntar, qué observaciones han sido mal clusterizadas? Seguro será el caso que se encontraban entre dos grupos.
$silinfo$widths %>% data.frame() %>% filter(sil_width<0) aglomerativo
cluster neighbor sil_width
Missouri 2 1 -0.1576505
Esta identificación te puede servir para:
Detectar outliers o anomalías: Casos que no encajan bien en ningún grupo pueden ser valores atípicos importantes.
Refinar el clustering: Permite probar con diferente número de clusters, cambiar el método o ajustar parámetros para mejorar la solución.
Mejorar la interpretación: Examinar estos casos revela características híbridas o variables adicionales que deberías considerar.
Paso 6: Creación de variables
Ya habíamos visto en el paso 4 los clúster creados y habíamos explorado las características que tenían. ¿Cómo podríamos utilizarlo para generar una variable de grupo? ¿Cómo llamaríamos a esas categorías?
Como un grupo indicaba alta delincuencia y otros bajo delincuencia, podríamos utilizar esa etiqueta:
$seguridad = factor(subdata2$aglomerativo,
subdata2levels = c(1,2),
labels = c("Alta delincuencia", "Baja delincuencia"),
ordered = TRUE)
|> count(seguridad) subdata2
seguridad n
1 Alta delincuencia 13
2 Baja delincuencia 17
library(plotly)
plot_ly(x = subdata2$Murder,
y = subdata2$Assault,
z = subdata2$Rape,
text=rownames(subdata2),
color=subdata2$seguridad,
type = "scatter3d",
mode = "markers") |>
layout(scene = list(
xaxis = list(title = "Murder"),
yaxis = list(title = "Assault"),
zaxis = list(title = "Rape")
))
9.3 Método Divisivo
El clustering jerárquico divisivo sigue la misma estructura metodológica que el aglomerativo, con la diferencia fundamental en la dirección del proceso de agrupamiento. Mientras que el método aglomerativo comienza con cada observación como un cluster individual y las va uniendo progresivamente (enfoque bottom-up), el método divisivo parte de un único cluster que contiene todas las observaciones y las va dividiendo sucesivamente en grupos más pequeños (enfoque top-down).
Como ya tenemos las distancias calculadas podemos pasar de frente al cálculo de clúster jerárquico divisivo.
# Calculan las distancias
#distancias= daisy(subdata, metric="gower")
# Identificar el número recomendado de clusters. Aplica tanto para aglomerativo como divisivo
#fviz_nbclust(subdata, hcut,diss=g.dist,method = "gap_stat",k.max = 10,verbose = F) #Ya lo habíamos calculado
# Calculamos el cluster
= hcut(distancias,
divisivo k = 2, # Indicamos 2 por el resultado anterior
hc_func='diana') #
Validación e identificación de casos mal clasificados:
# Gráfico de silueta
fviz_silhouette(divisivo, label=TRUE)
cluster size ave.sil.width
1 1 14 0.48
2 2 16 0.60
$silinfo$widths %>% data.frame() %>% filter(sil_width<0) divisivo
[1] cluster neighbor sil_width
<0 rows> (o 0- extensión row.names)
Visualizamos:
fviz_dend(divisivo,
rect = TRUE,
cex = 0.5)
%>%
subdata mutate(divisivo=divisivo$cluster) %>%
group_by(divisivo) %>%
summarise(Murder = mean(Murder),
Assault = mean(Assault),
Rape=mean(Rape))
# A tibble: 2 × 4
divisivo Murder Assault Rape
<int> <dbl> <dbl> <dbl>
1 1 12.0 254. 31.0
2 2 5 114. 15.5
$divisivo = divisivo$cluster
subdata$divisivo = as.factor(subdata$divisivo)
subdatalevels(subdata$divisivo) = c("Alta delincuencia", "Baja delincuencia")
table(subdata$divisivo)
Alta delincuencia Baja delincuencia
14 16
fviz_cluster(object = list(data=subdata[,1:3], cluster = subdata$divisivo),
geom = c("text"),
ellipse.type = "convex")
# with(subdata,plot3d(subdata[,1:3], type = "s", size=0.8, col=as.numeric(divisivo)))
# with(subdata,text3d(subdata[,1:3], texts=rownames(subdata), pos=4))