Curso de R:

Capitulo 11: Análisis de conglomerados (cluster) II.

En este capítulo vamos a ver un par de ejemplos más de análisis de conglomerados con R. En el ejemplo del capítulo 10 partíamos de un conjunto de una sola variable que era la primera componente principal de un conjunto de datos que recogía características técnias de una serie de coches que se vendían es Estados Unidos, ahora vamos a ver dos ejemplo más donde tabajaremos con otras posibilidades de R.

Ejemplo 11.1:

En este ejemplo vamos a trabajar con los datos de inflación de países de la Unión Europea. Los datos obtenidos a través de la página web del Instituto Nacional de Estadística de España vienen recogidos en un archivo excell, desconozco si existe algún paquete que me permita importar datos de excel a R (si lo hay para importar archivos SAS y SPSS), podemos trabajar con ficheros de texto pero al tratarse de 15 países y 3 variables (país, índice e incremento interanual) me parece más adecuado el introducirlos directamente en R creando tres vectores y uniéndolos de la forma más apropiada:

> nombres<-c("Alemania","Austria","Bélgica","España","Finlandia","Francia","Grecia","Holanda""Irlanda","Italia","Luxemburgo","Portugal","Dinamarca","Reino Unido","Suecia")    
> indices<-scan()                       #indice de precios al consumo
1: 109 108.9 111.3 117.3 112.4 108.7
7: 125.8 119.5 122.4 114.3 113 119.1
13: 114.1 108.7 110.2
16:
Read 15 items > tasa<-scan() #Incremento de la tasa interanual
1: 1 1.6 1.2 3.5 1.4 1.8 3.8 3.7 4.5
10: 1.8 2.2 3.8 2.5 1 1.2
16:
Read 15 items

En este ejemplo vamos a separar los países inflacionistas de los no inflacionistas, ese va a ser nuestro objetivo, por ello partimos de un número predeterminado de cluster en este caso 2 (inflacionistas y no inflacionistas, insisto) y asignamos a cada individuo a un cluster mediante una técnica de ubicación iterativa a esta técnica se la conoce como el algoritmo de las k-medias. Pretendemos mejorar las clasificaciones minimizando las distancias utilizadas en la formación de los conglomerados. Sería siempre interesante empezar por la aglomeración jerárquica pero en este caso el objetivo de nuestro agrupamiento parece bastante claro y no es necesario comprobar cuantos cluster se pueden formar.

El siguiente paso con R es unir los dos vectores que contienen datos numéricos para formar el conjunto de datos sobre el que vamos a calcular la matriz de distancias con la que posteriormente realizaremos el análisis de agrupamiento. En este caso el método de cálculo de la matriz de distancias es el método Manhattan que se utiliza habitualmente en variables dicotómbicas pero para que sigais conociendo posibilidades vamos a trabajar con ella:

> datos.inflacion<-cbind(indices,tasa)
> matriz11.1<-dist(datos.inflacion,method="manhattan")

Veamos el gráfico de dispersión para identificar visualmente algún cluster:

Si se identifican los cluster, arriba a la derecha tenemos los países con mayor tasa y abajo a la izquierda los de menor tasa de acumulación, ahora sólo nos queda plasmar esta idea inicial en R y ver la forma de agrupamiento de los países. Para realizar el algoritmo de las k-medias R dispone de la función kmeans(<matriz_distancias>,<num_cluster>), donde ambos argumentos son obligatorios ya que el número de cluster está prefijado:

> kmeans(matriz11.1,2)
$cluster (1)
[1] 1 1 1 2 1 1 2 2 2 1 1 2 1 1 1
$centers                                                                          (2)
1 2 3 4 5 6 7 8
1 2.356353 2.364722 2.035462 6.551598 2.266588 2.530643 14.912252 8.715068
2 12.174428 12.140668 9.906339 3.547262 8.798775 12.300583 4.995479 2.386100
9 10 11 12 13 14 15
1 11.720213 3.325348 2.554850 8.355977 3.273403 2.530059 2.024244
2 3.010035 6.879040 8.009260 2.462113 6.872028 12.465291 10.963804
$withinss
         [1] 545.6604 591.0740
$size                                                                            (3)
         [1] 10 5

Esto es lo que no ofrece R. en (1) tenemos a que cluster se une cada observación, en (2) las distancias de cada observación al cluster y en (3) el tamaño de cada cluster. También aparece una función withinss que no sé para que sirve pero que ahí está, si alguien lo averigua que por favor me mande un correo. Ahora nos queda ver a que cluster pertenece cada país para ello unimos el vector nombres que creamos con anterioridad con la variable $cluster de el análisis, también unimos tasa e indices para ver mejor como funciona:

> kmedias<-kmeans(matriz11.1,2)
> cbind(nombres,kmedias$cluster,tasa,indices)
nombres tasa indices
[1,] "Alemania" "1" "1" "109"
[2,] "Austria" "1" "1.6" "108.9"
[3,] "Bélgica" "1" "1.2" "111.3"
[4,] "España" "2" "3.5" "117.3"
[5,] "Finlandia" "1" "1.4" "112.4"
[6,] "Francia" "1" "1.8" "108.7"
[7,] "Grecia" "2" "3.8" "125.8"
[8,] "Holanda" "2" "3.7" "119.5"
[9,] "Irlanda" "2" "4.5" "122.4"
[10,] "Italia" "1" "1.8" "114.3"
[11,] "Luxemburgo" "1" "2.2" "113"
[12,] "Portugal" "2" "3.8" "119.1"
[13,] "Dinamarca" "1" "2.5" "114.1"
[14,] "Reino Unido" "1" "1" "108.7"
[15,] "Suecia" "1" "1.2" "110.2"

Los menos inflacionistas los ha clasificado en el cluster 1 y los más inflcionistas como Holanda, Portugal,... y por supuesto mi querida España en el cluster 2. Si quisieramos ver este análisis de forma gráfica emplearíamos la función plot a la que la incluiremos unos argumentos para reconocer los cluster visualmente:

> plot(datos.inflacion,col=kmedias$cluster,pch=17,main="Agrupamiento")

En plot los argumentos que he añadido son los colores que van unidos al cluster asignado, el tipo de de símbolo que se cambia con pch (de 1 a 18) y el título con main, por lo demás comprobar que observaciones se unen con los cluster, si se quieren identificar sólo hay que emplear la función identify que ya vimos en un capítulo anterior.

Ejemplo 11.2:

En este ejemplo vamos a agrupar distintos tipos de alimentos según sus aportes en calorías, proteínas, grasas y calcio. El conjunto de datos está en mi disco duro como un archivo de texto en el directorio datos (haz click aquí para ver el archivo) hemos de emplear la función read.table para importar el archivo a R:

> alimentos<-read.table("c:\\datos\\alimentos.txt",header=TRUE)
> alimentos
Alimentos Calorias Proteinas Grasas Calcio
1 Hamburguesas 245 21 27 9.0
2 Buey 420 15 39 7.0
3 Pollo 115 20 3 8.0
4 Cordero 280 18 22 9.0
5 JamónAhumado 340 20 28 9.0
6 Cerdo 340 19 29 9.0
7 ChuletaCerdo 355 19 30 9.0
8 PescadoAzul 135 22 4 25.0
9 Sardinas 180 22 9 16.7
10 AtúnLata 170 25 7 12.3
11 MejillonesLata 155 16 9 15.7
12 MejillonesHervidos 200 19 13 15.0

Hay que reseñar el hecho de que para leer variables alfanuméricas no podemos tener espacios en blanco ya que la función read.table podía darnos problemas, para poder poner espacios en blanco en las variables alfanuméricas hay que dar un formato de entrada que todavía no hemos visto, por eso recomiendo siempre un vistazo previo al conjunto de entrada para evitar problemas. Como podéis ver en mi caso las observaciones de la variable Alimentos no tienen espacios en blanco y he juntado palabras para evitar estos problemas. es posible leer datos en blanco, leer archivos con diferentes delimitadores, cambiar el punto decimal por coma,... pero esto lo veremos en posteriores capítulos.

Ya tenemos el conjunto de datos con el que vamos a trabajar, como siempre tenemos la pega de que este conjunto tiene una variable caracter y a la hora de realizar cálculos sobre él nos puede plantear trastornos por eso hemos de separar la variable Alimentos o como suelo hacer yo unir el resto de variables (Calorías, Proteínas, Grasas y Calcio) en una matriz con la función cbind:

> attach(alimentos)
> alimentos.2<-as.matrix(cbind(Calorías, Proteínas, Grasas, Calcio))
> alimentos.2
Calorias Proteinas Grasas Calcio
[1,] 245 21 27 9.0
[2,] 420 15 39 7.0
[3,] 115 20 3 8.0
[4,] 280 18 22 9.0
[5,] 340 20 28 9.0
[6,] 340 19 29 9.0
[7,] 355 19 30 9.0
[8,] 135 22 4 25.0
[9,] 180 22 9 16.7
[10,] 170 25 7 12.3
[11,] 155 16 9 15.7
[12,] 200 19 13 15.0
> detach()

Ya podemos empezar a realizar nuestra labor matemática que en este caso vamos a emplear para el agrupamiento las medidas de disimilitud con base en la distancia euclidea pero que son todo lo contrario a las medidas de similitud que empleamos para los anteriores ejemplos, para hacer este ejemplo hemos de cargar la librería cluster:

> library(cluster)
> disimilar<-daisy(alimentos.2)
> disimilar
Dissimilarities :
[1] 175.524927 132.204387 35.482390 95.010526 95.042096 110.059075
[7] 113.516519 67.891752 77.793894 92.162302 47.549974 307.159568
[13] 141.074448 80.932070 80.746517 65.772335 287.789854 242.163354
[19] 252.293658 266.836448 221.711524 166.105388 226.386837 226.501656
[25] 241.518115 26.343880 65.883913 55.538185 41.367741 85.877820
[31] 60.332413 60.415230 75.432089 147.040811 101.214080 111.287421
[37] 125.868543 80.734132 1.414214 15.165751 207.028983 161.320457
[43] 171.396879 186.136751 140.932608 15.033296 207.159359 161.456774
[49] 171.554335 186.222689 141.039002 222.128341 176.449114 186.549966
[55] 201.233422 156.044865 46.031402 37.473858 23.398504 66.445466
[61] 11.504782 25.729361 20.685502 17.932094 31.293610 45.282337
Metric : euclidean 
         Number of objects : 12 

Esa es la matriz de disimilaridades entre los valores de las observaciones que calcula R, a partir de ella vamos a hacer el agrupamiento, vamos a ver gráficamente el comprotamiento de ese agrupamiento y posteriormente determinaremos el número de cluster necesarios para llevar a buen puerto nuestro estudio:

> cluster11.2<-hclust(disimilar)
> plot.hclust(cluster11.2)
> data.frame(alimentos$Alimentos,cluster11.2$order)
alimentos.Alimentos cluster11.2.order
1 Hamburguesas 2
2 Buey 7
3 Pollo 5
4 Cordero 6
5 JamónAhumado 1
6 Cerdo 4
7 ChuletaCerdo 12
8 PescadoAzul 9
9 Sardinas 10
10 AtúnLata 3
11 MejillonesLata 8
12 MejillonesHervidos 11

Es bastante claro que tenemos dos cluster, por un lado están las observaciones 2, 7, 5, 6 y por el otro las restantes, para ver que alimentos son esas observaciones he unido la variable order (orden del dendograma) de cluster11.2 con el nombre de los alimentos para poder realizar una correspondencia visual entre los grupos y los nombres de los alimentos así el primer cluster sería buey, chuleta de cerdo, jamón ahumado y cerdo, es un extraño cluster pero continuemos con el estudio para ver porque se produce esta asociación.. ¿Qué ocurriría si tuvieramos una gran cantidad de observaciones y una referencia visual planterara problemas? En ese caso tras identificar el número de cluster emplearíamos una técnica por medios divisorios (Partitioning methods en terminología anglosajona) que situaría cada observación en los alguno de los cluster de forma que se minimizara la suma de las disimilaridades. Esto se hace con R del siguiente modo:

> cluster11.2b<-pam(alimentos.2,2)
> cluster11.2b
Medoids:
Calorias Proteinas Grasas Calcio
[1,] 170 25 7 12.3
[2,] 340 19 29 9.0
Clustering vector:
[1] 1 2 1 2 2 2 2 1 1 1 1 1
Objective function:
build swap
37.89533 32.42881
Available components:
         [1] "medoids" "clustering" "objective" "isolation"          "clusinfo" 
         [6] "silinfo" "diss" "call" "data"        

La función pam permite hacer lo que señalabamos antes, pero en este caso nos encontramos con que los cluster formados son distintos a los que formábamos con los métodos jerárquicos ya que el tamaño de los cluster es distinto que el tamaño anterior, como siempre veámoslo gráficamente que es como mejor se puede ver como trabajan las distintas funciones de R:

> par(mfrow=c(2,2))
> plot(cluster11.2b)
> plot(cluster11.2)

Hemos dicho a R que nos ofrezca los gráficos en una pantalla dividida en 4 celdas con la función par y después graficamos los análisis con plot, vemos que la función pam ofrece 2 gráficos: uno de componentes principales y otro de distancias entre los centroides de los cluster. Vemos que cada método ha establecido distintos grupos, ahora comparemos las dos formas de agrupamiento:

> data.frame(alimentos$Alimentos,cluster11.2$order,cluster11.2b$clustering)
alimentos.Alimentos cluster11.2.order cluster11.2b.clustering
1 Hamburguesas 2 1
2 Buey 7 2
3 Pollo 5 1
4 Cordero 6 2
5 JamónAhumado 1 2
6 Cerdo 4 2
7 ChuletaCerdo 12 2
8 PescadoAzul 9 1
9 Sardinas 10 1
10 AtúnLata 3 1
11 MejillonesLata 8 1
12 MejillonesHervidos 11 1
Alimento
Jerárquicos
Métodos divisorios
Hamburguesa
1
1
Buey
2
2
Pollo
1
1
Cordero
1
2(*)
Jamón ahumado
2
2
Cerdo
2
2
Chuleta de cerdo
2
2
Pescado azul
1
1
Sardinas
1
1
Atún en lata
1
1
Mejillones en lata
1
1
Mejillones hervidos
1
1

El problema lo hemos encontrado con el cordero que se ha unido al cluster 2 y en el análisis jerárquico se había unido al cluster 1. Como siguiente paso en nuestro ejemplo vamos a buscar una regla de funcionamiento para la agrupación de observaciones y esto lo hacemos sumarizando el objeto cluster11.2b que creamos con anterioridad:

> summary(cluster11.2b)
Medoids:
Calorias Proteinas Grasas Calcio (1)
[1,] 170 25 7 12.3
[2,] 340 19 29 9.0
Clustering vector: (2)
[1] 1 2 1 2 2 2 2 1 1 1 1 1
Objective function:
build swap
37.89533 32.42881
Numerical information per cluster:                        (3)     
         size max_diss av_diss diameter separation
         [1,] 7 77.79389 33.07663 132.2044 35.48239
         [2,] 5 80.74652 31.52185 141.0744 35.48239
Isolated clusters:
         L-clusters: character(0)
         L*-clusters: character(0)
Silhouette plot information:                  (4)
         cluster neighbor sil_width
         11 1 2 0.7879602
         10 1 2 0.7839538
         9 1 2 0.7648888
         8 1 2 0.7563286
         3 1 2 0.7093818
         12 1 2 0.6655977
         1 1 2 0.1340588
         7 2 1 0.7681916
         6 2 1 0.7680220
         5 2 1 0.7675267
         2 2 1 0.6322059
         4 2 1 0.2312497
         Average silhouette width per cluster:
         [1] 0.6574528 0.6334392
         Average silhouette width of total data set:
         [1] 0.6474471
Dissimilarities :
         [1] 175.524927 132.204387 35.482390 95.010526 95.042096 110.059075
         [7] 113.516519 67.891752 77.793894 92.162302 47.549974 307.159568
         [13] 141.074448 80.932070 80.746517 65.772335 287.789854 242.163354
         [19] 252.293658 266.836448 221.711524 166.105388 226.386837 226.501656
         [25] 241.518115 26.343880 65.883913 55.538185 41.367741 85.877820
         [31] 60.332413 60.415230 75.432089 147.040811 101.214080 111.287421
         [37] 125.868543 80.734132 1.414214 15.165751 207.028983 161.320457
         [43] 171.396879 186.136751 140.932608 15.033296 207.159359 161.456774
         [49] 171.554335 186.222689 141.039002 222.128341 176.449114 186.549966
         [55] 201.233422 156.044865 46.031402 37.473858 23.398504 66.445466
         [61] 11.504782 25.729361 20.685502 17.932094 31.293610 45.282337
Metric : euclidean 
         Number of objects : 12 
Available components:
         [1] "medoids" "clustering" "objective" "isolation"          "clusinfo" 
         [6] "silinfo" "diss" "call" "data"

En (1) tenemos la media de cada variable dentro del cluster, los alimentos "sanos" con menor aporte calórico, menos grasas y más calcio forman el cluster 1 y los "menos sanos" forman el cluster 2. En (2) tenemos el vector que coloca cada observación en un cluster. En (3) tenemos información numérica sobre cada cluster como el tamaño, distancias medias y máximas,... En (4) tenemos como funciona el algoritmo que clasifica las observaciones, le funcionamiento es análogo a la función hclust. Este ha sido el estudio que al final nos ha ofrecido una buena función clasificatoria de alimentos aunque en un principio planteara algunas dudas, de todas formas yo sigo sin entender como ha clasificado a las hamburguesas como un alimento "sano"...

Volver a la página principal.

Menú del curso

Capítulo 12: Introducción al Análisis de la Varianza.

 

  

 

1